{"$schema":"https://ui.shadcn.com/schema/registry.json","name":"brand-visual-asset-generator","type":"registry:item","title":"Brand Visual Asset Generator","description":"Generate brand-aligned SVG asset packs for SaaS products using Context.dev brand extraction and a Quiver Arrow SVG tool.","author":"TommyBez","categories":["marketing"],"dependencies":["ai@^7.0.11","eve@^0.18.2","zod@4.4.3"],"meta":{"slug":"brand-visual-asset-generator","category":"marketing","createdAt":"2026-07-01T21:16:30.433Z","updatedAt":"2026-07-04T00:00:00.000Z","docs":{"overview":["Brand Visual Asset Generator is an on-demand eve agent that turns a company domain, product description, or explicit brand profile into a coherent pack of editable SVG assets: icons, empty states, hero illustrations, badges, feature graphics, onboarding visuals, changelog art, and dashboard or modal graphics. You talk to it in your eve chat client, name the assets you want, and it returns real SVG markup ready to drop into a product or landing page.","Under the hood it combines two services. A Context.dev MCP connection resolves the brand facts first: name, description, colors, logos, homepage copy, and styleguide cues for a domain. Then a generate_svg_with_arrow tool calls the quiverai/arrow-1.1 image model through Vercel AI Gateway once per asset brief, validates that the response is safe SVG with a viewBox, rescales it to the requested dimensions, and normalizes icons to currentColor strokes so they theme automatically.","What makes it useful is pack coherence and honesty. A bundled brand-visual-assets skill enforces a six-step workflow with a scorecard: every asset must share palette, stroke language, and illustration metaphors, and anything scoring 8 or below gets regenerated with a tighter brief. If Context.dev or AI Gateway credentials fail, the agent stops and reports the error instead of fabricating brand facts or hand-writing replacement SVGs."],"howItWorks":["You send a request in chat, such as a feature launch pack for linear.app; if no website, product description, or brand profile is given, the agent asks for one before doing any generation work.","The agent calls the context-dev MCP connection (search_docs and execute against the hosted Context.dev server) to retrieve brand data, homepage markdown, and styleguide details, authenticating with your CONTEXT_DEV_API_KEY via the x-context-dev-api-key header.","The brand-visual-assets skill scopes the pack, falling back to a default asset list if you did not name specific types, then locks a brand profile with primary and secondary hex colors, tone adjectives, and up to four reference images such as Context.dev logo or screenshot URLs.","For each asset it writes a self-contained brief and calls generate_svg_with_arrow once, which prompts quiverai/arrow-1.1 through Vercel AI Gateway, rejects output lacking a viewBox or containing scripts and raster content, fits the artwork to your requested dimensions, and adds title and desc elements for accessibility.","The agent scores every generated asset against the skill's quality-bar, consistency, and visual-taste references, then regenerates any asset scoring 8 or below on the scorecard with a tighter brief.","The final reply contains a brand brief with source URLs, an asset pack manifest with filenames and purposes, one fenced svg block per asset, usage notes for theming and placement, and an assumptions section when choices were inferred. Two bundled evals verify the ask-first and no-fabrication behaviors."],"useCases":[{"title":"Feature launch pack from a domain","body":"Point the agent at your marketing site and request a hero illustration, feature icons, a new-feature badge, and an onboarding visual. Context.dev extracts your palette and logos so every asset lands on brand without manual specs."},{"title":"Empty states for a pre-launch product","body":"No public website yet? Provide a product description, a primary hex color, and tone adjectives, and the agent generates empty-state and modal illustrations directly from that explicit brand profile, listing its assumptions alongside the SVGs."},{"title":"Themeable icon sets for a design system","body":"Request feature, navigation, or status icons and get SVGs normalized to currentColor strokes with fill none, a 1.8 stroke width, and rounded caps, so the icons inherit text color and work in light and dark themes."},{"title":"Changelog and release visuals on demand","body":"Generate consistent changelog art or dashboard graphics for each release. Because the skill enforces shared palette and stroke language across the pack, week-over-week visuals stay coherent instead of drifting in style."}],"requirements":[{"name":"CONTEXT_DEV_API_KEY","body":"Context.dev API key used by the MCP connection for brand, webpage, and styleguide extraction; sent only from the eve runtime as the x-context-dev-api-key header. Create one in your Context.dev account. CONTEXT_API_KEY is accepted as a fallback name."},{"name":"AI_GATEWAY_API_KEY","body":"Vercel AI Gateway key (vck_ prefix) that lets generate_svg_with_arrow call the quiverai/arrow-1.1 image model. Create it in your Vercel AI Gateway project and make sure that model is enabled there."},{"name":"VERCEL_OIDC_TOKEN","body":"Alternative AI Gateway credential. On Vercel deployments it is provided automatically; locally you can pull one with the Vercel CLI. Set either this or AI_GATEWAY_API_KEY, not necessarily both."},{"name":"eve, ai, zod","body":"Runtime dependencies installed with the registry item: the eve framework (^0.18.2), the ai SDK (^7.0.11) for generateImage, and zod 4.4.3 for tool input validation. Node.js 24 or newer is required."}],"faqs":[{"question":"How do I install and run this agent?","answer":"Run npx shadcn@latest add @evex/brand-visual-asset-generator in your eve app, copy .env.example into your environment with the Context.dev and AI Gateway credentials filled in, then start the app with pnpm dev and ask for a visual pack in chat."},{"question":"Which models does it use?","answer":"The root orchestrator runs on deepseek/deepseek-v4-pro, and SVG generation uses quiverai/arrow-1.1 called through Vercel AI Gateway's image-generation endpoint. Your deployment needs access to both providers; the agent never asks a language model to draw the SVGs itself."},{"question":"Can I customize the assets it produces?","answer":"Yes. Name the exact asset types, dimensions, colors, and tone in your request, or let the skill fall back to its default pack. You can also pass up to four reference images per asset for style, palette, composition, or typography direction, stating what to preserve and what to change."},{"question":"Does it work without a company website?","answer":"Yes. Instead of a domain, give a product description or an explicit brand profile such as a primary hex color, audience, and tone adjectives. The agent skips Context.dev extraction only when you supply the brand facts, and it records inferred choices in an assumptions section."},{"question":"What are the known limits and failure modes?","answer":"Arrow 1.1 accepts at most four reference images and about 6000 output tokens per asset. Context.dev can return 408 or 429 on cold starts and rate limits, so retry or reduce concurrency. On missing credentials or model errors the agent stops and reports the failure rather than fabricating output."}]}},"files":[{"path":"agent/agent.ts","type":"registry:file","target":"~/agent/agent.ts","content":"import { defineAgent } from \"eve\";\n\nexport default defineAgent({\n  model: \"deepseek/deepseek-v4-pro\",\n});\n"},{"path":"agent/connections/context-dev.ts","type":"registry:file","target":"~/agent/connections/context-dev.ts","content":"import { defineMcpClientConnection } from \"eve/connections\";\n\nexport default defineMcpClientConnection({\n  url: \"https://context-dev.stlmcp.com\",\n  description:\n    \"Context.dev hosted MCP for resolving brand data, scraping webpages, crawling sites, and extracting styleguides from domains. Use it to gather brand colors, typography, logos, and product context before generating SVG assets.\",\n  headers: {\n    \"x-context-dev-api-key\": readContextDevApiKey,\n  },\n  tools: {\n    allow: [\"search_docs\", \"execute\"],\n  },\n});\n\nfunction readContextDevApiKey(): string {\n  const apiKey =\n    process.env.CONTEXT_DEV_API_KEY?.trim() || process.env.CONTEXT_API_KEY?.trim();\n\n  if (!apiKey) {\n    throw new Error(\n      \"Missing CONTEXT_DEV_API_KEY or CONTEXT_API_KEY for Context.dev MCP access.\",\n    );\n  }\n\n  return apiKey;\n}\n"},{"path":"agent/instructions.md","type":"registry:file","target":"~/agent/instructions.md","content":"# Mission\nGenerate a coherent **pack** of brand-aligned SVG visual assets for SaaS and\ndigital products. Output structured, editable SVGs that teams can ship directly in\nproducts, websites, landing pages, design systems, and marketing workflows.\n\n# Supported asset types\n- Icons (feature, navigation, status)\n- Empty states\n- Hero illustrations\n- Badges and labels (for example \"new feature\", \"beta\", \"pro\")\n- Feature graphics\n- Onboarding visuals\n- Changelog illustrations\n- Dashboard or modal visuals\n\n# Workflow\n1. If the user has not provided enough context, ask for at least one of:\n   - company website or domain;\n   - product or feature description;\n   - explicit brand profile (colors, tone, audience, product category).\n2. Load the `brand-visual-assets` skill and run its pack workflow end to end.\n3. Use the `context-dev` MCP connection through `connection_search` to discover\n   Context.dev tools. Use `search_docs` when you need exact SDK method or\n   parameter names, then use `execute` to gather source data.\n4. Through Context.dev MCP, retrieve at minimum:\n   - brand data for the domain, including name, description, colors, logos,\n     industry labels, and social/profile fields when available;\n   - homepage or provided page markdown when a URL is available;\n   - styleguide/design-system data for colors, typography, spacing, shadows, and\n     component cues when available.\n5. Treat Context.dev brand, content, and styleguide outputs as the source of truth\n   for brand name, positioning, palette, typography cues, logos, and factual\n   product claims.\n6. Use up to four reference images when they will improve visual fidelity. Prefer\n   Context.dev logo, backdrop, or product screenshot URLs; state what to preserve\n   from each reference and what should change for the new asset.\n7. Generate each finalized asset by calling `generate_svg_with_arrow`, which uses\n   `quiverai/arrow-1.1` through Vercel AI Gateway's image-generation endpoint.\n   Use the returned SVG markup as the draft asset, then score it against the\n   skill references before including it in the final pack.\n8. If `generate_svg_with_arrow` fails because AI Gateway credentials are missing\n   or the upstream image model errors, stop and report the configuration or model\n   failure. Do not fabricate replacement SVGs.\n9. If the Context.dev MCP connection fails because the API key is missing,\n   invalid, rate-limited, or unavailable, stop and report the configuration or API\n   failure. Do not fabricate brand facts.\n\n# Output contract\nReturn:\n1. A short \"Brand brief\" section summarizing palette, typography cues, tone, and\n   Context.dev source URLs.\n2. An \"Asset pack\" section listing each asset with filename suggestion, purpose,\n   and dimensions.\n3. Each asset under a Markdown heading that includes the suggested filename,\n   followed by exactly one fenced `svg` block containing SVG markup only. Do not\n   put filenames inside `svg` fences.\n4. A short \"Usage notes\" section covering light/dark theming, recommended sizes,\n   and where each asset fits (marketing page, in-app empty state, onboarding, and\n   so on).\n5. An \"Assumptions\" section only when visual choices rely on inference rather\n   than explicit source data.\n\n# Guardrails\n- Do not invent customer logos, testimonials, statistics, or product claims.\n- Do not expose the Context.dev API key or any environment variables.\n- Do not output raster-only images or generic image-generation prompts when SVG\n  is expected.\n- Do not skip brand extraction and guess a palette when a domain or brand profile\n  was provided.\n- Do not use a language-model subagent for `quiverai/arrow-1.1`; it is an image\n  model and must be called through `generate_svg_with_arrow`.\n"},{"path":"agent/skills/brand-visual-assets/references/brief-template.md","type":"registry:file","target":"~/agent/skills/brand-visual-assets/references/brief-template.md","content":"# Per-asset brief template\n\nInclude every field in each `generate_svg_with_arrow` brief:\n\n| Field | Value |\n| --- | --- |\n| `assetType` | `icon` \\| `empty-state` \\| `hero` \\| `badge` \\| `feature-graphic` \\| `onboarding` \\| `changelog` \\| `dashboard-modal` |\n| `filename` | kebab-case ending in `.svg` |\n| `purpose` | one sentence on where it ships |\n| `dimensions` | viewBox or aspect ratio (for example `1200x630` hero, `24x24` icon) |\n| `palette` | named colors with hex from the locked brand profile |\n| `subject` | what to depict |\n| `text` | exact copy if the SVG includes text |\n| `styleNotes` | stroke weight, corner radius, illustration density, metaphors to use or avoid |\n| `tasteNotes` | what should make the asset feel sharp, premium, and brand-specific |\n| `referenceImages` | optional public image URLs or base64 references from Context.dev logos/backdrops/screenshots when they materially improve style, palette, typography, or composition |\n| `preserveFromReference` | when references are used, the exact style, color relationships, layout, typography direction, or structure to preserve |\n| `changeFromReference` | when references are used, the subject, dimensions, or asset-specific details that should change |\n| `constraints` | for example `currentColor`, no gradients, dark-mode safe |\n\nEvery brief must include at least one negative constraint, such as \"avoid generic\nglobe/network art\", \"avoid repeated decorative dot grids\", or \"avoid stock SaaS\ndashboard cards\". Negative constraints prevent the SVG tool from falling back\nto obvious visual clichés.\n\nKeep the brief compact and structured. Prefer one concrete vector concept over a\nlong prose dump of brand facts. Use reference images for brand style or\ncomposition when Context.dev returns suitable logos, backdrops, or source images.\n"},{"path":"agent/skills/brand-visual-assets/references/consistency.md","type":"registry:file","target":"~/agent/skills/brand-visual-assets/references/consistency.md","content":"# Pack consistency\n\nApply across every asset in the run:\n\n- Reuse the same stroke-width scale and corner-radius family across icons.\n- Keep the hero and icons close in line weight: icons may be bolder for legibility,\n  but they must not feel chunky next to hairline hero art.\n- Limit palette to brand primaries plus one accent and neutrals.\n- Keep character or device metaphors consistent between hero, onboarding, and empty\n  states.\n- Badge and icon geometry should feel like the same design system.\n- Use `currentColor` for icons unless the user explicitly asks for fixed brand\n  colors.\n- Keep hero, empty-state, and onboarding visuals on the same geometry system:\n  shared radii, shared depth rules, and a limited set of reusable motifs.\n- Prefer one clear focal object over many evenly scattered decorative objects.\n"},{"path":"agent/skills/brand-visual-assets/references/default-pack.md","type":"registry:file","target":"~/agent/skills/brand-visual-assets/references/default-pack.md","content":"# Default feature-launch pack\n\nUse when the user asks for a \"visual pack\" or \"feature launch assets\" without\nlisting items:\n\n1. One hero illustration for the feature page\n2. Three matching feature icons\n3. One dashboard empty state\n4. One \"new feature\" badge\n5. One onboarding visual for the in-app flow\n\nAdjust when the user names specific asset types or channels.\n"},{"path":"agent/skills/brand-visual-assets/references/quality-bar.md","type":"registry:file","target":"~/agent/skills/brand-visual-assets/references/quality-bar.md","content":"# Quality bar\n\nReject tool output and regenerate the individual asset when any of these fail:\n\n- Not valid SVG markup\n- Raster embed without an approved logo URL from brand data\n- Color outside the locked brand profile palette\n- Invented logo, trademark, or product claim\n- Missing `viewBox` on a sized graphic\n- Missing `<title>` or `<desc>` when the graphic conveys meaning\n- Missing semantic `<g id=\"...\">` groups on hero, empty-state, onboarding, or\n  feature-graphic assets\n- Hardcoded brand colors in icons that should use `currentColor`\n- Repeated primitive filler that could be a pattern, symbol, or omitted entirely\n- A generic SaaS cliché when the brief asked for brand-specific visual direction\n- Sloppy overlaps, clipped shapes, abrupt path endpoints, or messy crossings\n- Badge text that is not optically centered\n- Icons that are muddy at 24px or fail when mentally scaled to 16px\n\nAlso check `references/consistency.md` before accepting the full pack.\n"},{"path":"agent/skills/brand-visual-assets/references/scorecard.md","type":"registry:file","target":"~/agent/skills/brand-visual-assets/references/scorecard.md","content":"# Visual scorecard\n\nScore the full pack from 1 to 10 before accepting it. Accept only scores above 8.\n\n| Dimension | Pass condition |\n| --- | --- |\n| Composition | One clear focal idea; no chaotic crossings, accidental overlaps, or unbalanced empty regions |\n| Brand specificity | Visual metaphor comes from the brand/product context, not generic SaaS decoration |\n| Coherence | Hero, icons, and badge share stroke weight, corner radius, palette, and geometry language |\n| Craft | Pixel-aligned icons, centered badge text, clean curves, purposeful grouping |\n| Implementation | Valid SVG, semantic groups, themeable icons, no raster embeds, no bloated filler |\n\n## Automatic score caps\n\n- Cap at 6 if the hero relies on random node networks, globes, floating dashboard\n  cards, or disconnected UI wireframes.\n- Cap at 6 if icons use a visibly different stroke weight or visual style from\n  the hero.\n- Cap at 7 if any icon is muddy at 24px or unclear at 16px.\n- Cap at 7 if the badge text is not optically centered.\n- Cap at 7 if the SVG uses hardcoded backgrounds where transparency or theming is\n  expected.\n- Cap at 8 if the pack is clean but generic.\n\n## Regeneration note\n\nWhen a score is capped, name the cap reason in the next\n`generate_svg_with_arrow` brief and ask for a specific replacement metaphor, not\nvague polish.\n"},{"path":"agent/skills/brand-visual-assets/references/visual-taste.md","type":"registry:file","target":"~/agent/skills/brand-visual-assets/references/visual-taste.md","content":"# Visual taste bar\n\nReject and regenerate any asset that feels generic, cluttered, or decorative\nwithout purpose.\n\n## Prefer\n\n- One memorable focal idea per asset\n- Asymmetric composition with deliberate negative space\n- Brand-specific geometry, product metaphors, or motion cues from the source\n  website\n- A restrained palette: primary, one accent, neutral, and background\n- Simple reusable structures: `<symbol>`, `<pattern>`, or grouped motifs instead\n  of dozens of copied primitives\n- Icons that read at 16px and feel crisp at 24px\n- Integrated compositions where interface, data, and product metaphors interact\n  as one scene\n\n## Avoid\n\n- Generic fintech globes, random network nodes, stock dashboard cards, and\n  floating rectangles unless the brief explicitly asks for them\n- Split scenes where a literal UI wireframe sits beside unrelated abstract lines\n  or floating nodes\n- Decorative dot grids that do not support the composition\n- Centered \"everything connected to everything\" layouts\n- Gradients, shadows, or glows used to compensate for weak composition\n- Text inside tiny icons\n- Overly literal currency symbols unless the asset specifically needs payment or\n  currency meaning\n\n## Regeneration prompt pattern\n\nWhen an asset fails this bar, regenerate only that asset with a tighter note:\n\n> The previous asset is too generic. Keep the same palette and dimensions, but\n> replace the visual metaphor with [specific direction]. Use fewer shapes, stronger\n> negative space, semantic groups, and no decorative filler.\n"},{"path":"agent/skills/brand-visual-assets/SKILL.md","type":"registry:file","target":"~/agent/skills/brand-visual-assets/SKILL.md","content":"---\nname: brand-visual-assets\ndescription: Scope and ship a brand-aligned pack of SVG assets for SaaS products. Use when the user wants a visual pack, feature launch assets, icons, empty states, hero illustrations, badges, feature graphics, onboarding visuals, changelog art, or dashboard/modal graphics.\n---\n\n# Pack workflow\n\nRun these steps in order. A **pack** is one coherent set of SVG assets that share\npalette, stroke language, and illustration metaphors.\n\n## 1. Scope the pack\n\nList every asset the run will produce: type, filename, purpose, and channel\n(marketing page, in-app empty state, onboarding, and so on).\n\n**Done when:** every requested asset has a named slot; no orphan types remain.\n\nIf the user gave no item list, load `references/default-pack.md` and adopt that\npack unless they named specific types or channels.\n\n## 2. Lock the brand profile\n\nCapture the palette and tone the pack will obey from Context.dev output or the\nuser's explicit brand profile.\n\n**Done when:** primary and secondary hex colors, neutral/background tones,\ntypography personality, logo constraints, product category, audience, and tone\nadjectives are all recorded before any brief is written.\n\nAlso collect up to four useful reference images when available, such as\nContext.dev logo URLs, brand backdrops, or product screenshots. Use them only\nwhen they improve style, color, layout, or typography fidelity.\n\n## 3. Write a brief per asset\n\nFor each pack slot, compose one self-contained **brief** for\n`generate_svg_with_arrow`.\n\n**Done when:** every slot has a brief containing every field in\n`references/brief-template.md`.\n\n## 4. Generate\n\nCall `generate_svg_with_arrow` once per brief. Pass the full brief, asset type,\nfilename, dimensions, and any reference images selected for that asset.\n\n**Done when:** every brief has a matching tool result with `ok: true` and SVG\nmarkup. If the tool reports missing AI Gateway credentials or an upstream model\nerror, stop and report the configuration failure instead of inventing SVGs.\n\n## 5. Score the pack\n\nRun the pack through the scorecard before accepting it.\n\n**Done when:** every asset passes `references/quality-bar.md`,\n`references/consistency.md`, `references/visual-taste.md`, and scores above 8 on\n`references/scorecard.md`; any asset that is ugly, generic, off-palette,\nvisually incoherent, or scores 8 or below is regenerated individually with a\ntighter brief, not the whole pack unless the brand profile changed.\n\n## 6. Deliver the SVGs\n\nReturn the actual final SVG markup for every accepted asset. Put each asset under\na Markdown heading that includes the filename, followed by exactly one fenced\n`svg` block containing only that asset's SVG markup.\n\n**Done when:** the final answer contains one fenced `svg` block per accepted\nasset. A summary that says the pack is complete but omits the SVG blocks is a\nfailed delivery and must be corrected before finishing.\n"},{"path":"agent/tools/generate_svg_with_arrow.ts","type":"registry:file","target":"~/agent/tools/generate_svg_with_arrow.ts","content":"import { generateImage, NoImageGeneratedError } from \"ai\";\nimport { defineTool } from \"eve/tools\";\nimport { z } from \"zod\";\n\nconst ARROW_MODEL = \"quiverai/arrow-1.1\";\nconst RESPONSE_PREVIEW_LENGTH = 1200;\nconst MAX_ARROW_REFERENCES = 4;\nconst BASE64_PATTERN =\n  /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u;\nconst MIN_ACCENT_LIGHTNESS = 0.15;\nconst MAX_ACCENT_LIGHTNESS = 0.9;\nconst MIN_ACCENT_SATURATION = 0.25;\n\nconst referenceImageSchema = z.union([\n  z.object({\n    url: z\n      .string()\n      .url()\n      .refine(isHttpUrl, {\n        message: \"Reference image URL must use http or https.\",\n      })\n      .describe(\"Public HTTP(S) image URL to use as a style or composition reference.\"),\n  }),\n  z.object({\n    base64: z\n      .string()\n      .min(1)\n      .refine(isBase64Payload, {\n        message: \"Reference image must be a standard base64 payload.\",\n      })\n      .describe(\"Base64-encoded PNG, JPEG, WebP, GIF, or SVG reference image payload.\"),\n  }),\n]);\n\nconst inputSchema = z.object({\n  filename: z\n    .string()\n    .min(1)\n    .describe(\"Suggested SVG filename, for example feature-hero.svg.\"),\n  assetType: z\n    .string()\n    .min(1)\n    .describe(\"Asset type, for example hero illustration, icon, badge, or empty state.\"),\n  brief: z\n    .string()\n    .min(20)\n    .describe(\"Self-contained creative brief with brand profile, subject, style notes, and constraints.\"),\n  dimensions: z\n    .string()\n    .min(1)\n    .describe(\"Intended dimensions or viewBox, for example 1200x720 or 0 0 64 64.\"),\n  referenceImages: z\n    .array(referenceImageSchema)\n    .max(MAX_ARROW_REFERENCES)\n    .optional()\n    .describe(\n      \"Optional Quiver reference images for style, palette, composition, or typography. Arrow 1.1 supports up to 4.\",\n    ),\n});\n\ntype GenerateSvgInput = z.infer<typeof inputSchema>;\ntype ReferenceImageInput = z.infer<typeof referenceImageSchema>;\ntype QuiverReference = { base64: string } | { url: string };\n\ntype GenerateSvgOutput =\n  | {\n      byteLength: number;\n      filename: string;\n      mediaType: \"image/svg+xml\";\n      model: typeof ARROW_MODEL;\n      ok: true;\n      revisedPrompt?: string;\n      svg: string;\n    }\n  | {\n      error: string;\n      filename: string;\n      missingEnv?: \"AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN\";\n      ok: false;\n      responsePreview?: string;\n      status?: number;\n    };\n\nexport default defineTool({\n  description:\n    \"Generate one editable SVG asset by calling the Quiver Arrow image model through Vercel AI Gateway. Use once per finalized asset brief.\",\n  inputSchema,\n  async execute(input): Promise<GenerateSvgOutput> {\n    const credential = readGatewayCredential();\n    if (!credential) {\n      return {\n        ok: false,\n        filename: input.filename,\n        error:\n          \"AI Gateway credentials are required to call quiverai/arrow-1.1. Set AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN.\",\n        missingEnv: \"AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN\",\n      };\n    }\n\n    const prompt = buildPrompt(input);\n    const generatedSvg = await generateSvgWithGateway({\n      prompt,\n      referenceImages: input.referenceImages,\n    });\n    if (!generatedSvg.ok) {\n      return {\n        ok: false,\n        filename: input.filename,\n        error: generatedSvg.error,\n        responsePreview: generatedSvg.responsePreview,\n      };\n    }\n\n    const rawSvg = generatedSvg.svg;\n    if (!looksLikeSvg(rawSvg)) {\n      return {\n        ok: false,\n        filename: input.filename,\n        error: \"Quiver Arrow returned data that does not look like SVG markup.\",\n        responsePreview: preview(rawSvg),\n      };\n    }\n\n    if (!hasViewBox(rawSvg)) {\n      return {\n        ok: false,\n        filename: input.filename,\n        error: \"Quiver Arrow returned SVG markup without a viewBox.\",\n        responsePreview: preview(rawSvg),\n      };\n    }\n\n    if (hasUnsafeSvgContent(rawSvg)) {\n      return {\n        ok: false,\n        filename: input.filename,\n        error: \"Quiver Arrow returned SVG markup with raster or active content.\",\n        responsePreview: preview(rawSvg),\n      };\n    }\n\n    const normalizedSvg = normalizeViewBox(rawSvg, input);\n    const themeableSvg = enforceThemeableIconSvg(normalizedSvg, input);\n    const svg = ensureAccessibleSvg(themeableSvg, input);\n    return {\n      ok: true,\n      filename: input.filename,\n      model: ARROW_MODEL,\n      mediaType: \"image/svg+xml\",\n      byteLength: Buffer.byteLength(svg, \"utf8\"),\n      svg,\n    };\n  },\n  toModelOutput(output) {\n    return {\n      type: \"json\",\n      value: output,\n    };\n  },\n});\n\nfunction readGatewayCredential(): string | undefined {\n  return (\n    process.env.AI_GATEWAY_API_KEY?.trim() ||\n    process.env.VERCEL_OIDC_TOKEN?.trim()\n  );\n}\n\nfunction buildPrompt(input: GenerateSvgInput): string {\n  return [\n    JSON.stringify(\n      {\n        subject: input.brief,\n        intended_use: `${input.assetType} saved as ${input.filename}`,\n        style:\n          \"Production-ready SVG/vector output. Use one clear vector concept, clean geometry, and deliberate negative space.\",\n        composition: `Fit ${input.dimensions}. Prefer a simple, balanced composition over dense UI mockups or many small details.`,\n        color_palette:\n          \"Use only colors named in the brief. For icons, use currentColor for strokes and no fixed fills except one purposeful accent if requested.\",\n        typography:\n          \"Avoid text unless the brief explicitly requires exact copy. If text is required, keep it short and optically centered.\",\n        preserve_from_reference:\n          \"When referenceImages are provided, preserve the reference style, color relationships, composition, and typography direction.\",\n        change_from_reference:\n          \"Change only the subject, dimensions, and asset-specific details requested in the brief.\",\n        constraints:\n          \"Return complete editable SVG markup only. Include viewBox, title, desc, and semantic group ids. Do not embed raster images. Avoid generic SaaS filler such as globes, random node networks, decorative dot grids, and disconnected floating dashboards.\",\n      },\n      null,\n      2,\n    ),\n  ].join(\"\\n\");\n}\n\nasync function generateSvgWithGateway({\n  prompt,\n  referenceImages,\n}: {\n  prompt: string;\n  referenceImages?: ReferenceImageInput[];\n}): Promise<{ ok: true; svg: string } | { error: string; ok: false; responsePreview?: string }> {\n  try {\n    const { image } = await generateImage({\n      model: ARROW_MODEL,\n      prompt: buildImagePrompt({ prompt, referenceImages }),\n      n: 1,\n      providerOptions: {\n        quiverai: {\n          operation: \"generate\",\n          instructions:\n            \"Return a complete SVG document only. The SVG must be editable markup, not raster data.\",\n          maxOutputTokens: 6000,\n          references: normalizeReferenceImages(referenceImages),\n          temperature: 0.45,\n          topP: 0.9,\n        },\n      },\n    });\n\n    return {\n      ok: true,\n      svg: new TextDecoder().decode(image.uint8Array).trim(),\n    };\n  } catch (error) {\n    if (NoImageGeneratedError.isInstance(error)) {\n      return {\n        ok: false,\n        error: \"AI Gateway did not return SVG image data.\",\n        responsePreview: preview(String(error.cause ?? error.message)),\n      };\n    }\n\n    return {\n      ok: false,\n      error: \"Quiver Arrow SVG generation failed through AI Gateway.\",\n      responsePreview: preview(errorMessage(error)),\n    };\n  }\n}\n\nfunction buildImagePrompt({\n  prompt,\n  referenceImages,\n}: {\n  prompt: string;\n  referenceImages?: ReferenceImageInput[];\n}): string | { images: string[]; text: string } {\n  const base64Images =\n    referenceImages?.flatMap((referenceImage) =>\n      \"base64\" in referenceImage ? [referenceImage.base64] : [],\n    ) ?? [];\n\n  if (base64Images.length === 0) {\n    return prompt;\n  }\n\n  return {\n    text: prompt,\n    images: base64Images,\n  };\n}\n\nfunction normalizeReferenceImages(\n  referenceImages: ReferenceImageInput[] | undefined,\n): QuiverReference[] | undefined {\n  if (!referenceImages || referenceImages.length === 0) {\n    return undefined;\n  }\n\n  return referenceImages.map((referenceImage) => {\n    if (\"url\" in referenceImage) {\n      return { url: referenceImage.url };\n    }\n\n    return { base64: referenceImage.base64 };\n  });\n}\n\nfunction errorMessage(error: unknown): string {\n  if (error instanceof Error) {\n    return error.message;\n  }\n\n  return String(error);\n}\n\nfunction looksLikeSvg(value: string): boolean {\n  return value.includes(\"<svg\") && value.includes(\"</svg>\");\n}\n\nfunction hasViewBox(value: string): boolean {\n  return /\\sviewBox\\s*=/iu.test(value);\n}\n\nfunction hasUnsafeSvgContent(svg: string): boolean {\n  return (\n    /<\\s*(script|foreignObject|iframe|object|embed|image)\\b/iu.test(svg) ||\n    /\\son[a-z]+\\s*=/iu.test(svg) ||\n    /\\b(?:href|xlink:href)\\s*=\\s*[\"']\\s*(?:javascript:|data:(?!image\\/svg\\+xml)|https?:\\/\\/|\\/\\/)/iu.test(svg)\n  );\n}\n\ntype ViewBox = {\n  height: number;\n  minX: number;\n  minY: number;\n  width: number;\n};\n\nfunction normalizeViewBox(\n  svg: string,\n  input: z.infer<typeof inputSchema>,\n): string {\n  const targetViewBox = parseTargetViewBox(input.dimensions);\n  const sourceViewBox = parseSvgViewBox(svg);\n\n  if (!targetViewBox || !sourceViewBox || sameViewBox(sourceViewBox, targetViewBox)) {\n    return svg;\n  }\n\n  const innerSvg = extractInnerSvg(svg);\n  if (!innerSvg) {\n    return svg;\n  }\n\n  const scale = Math.min(\n    targetViewBox.width / sourceViewBox.width,\n    targetViewBox.height / sourceViewBox.height,\n  );\n  const scaledWidth = sourceViewBox.width * scale;\n  const scaledHeight = sourceViewBox.height * scale;\n  const translateX =\n    targetViewBox.minX +\n    (targetViewBox.width - scaledWidth) / 2 -\n    sourceViewBox.minX * scale;\n  const translateY =\n    targetViewBox.minY +\n    (targetViewBox.height - scaledHeight) / 2 -\n    sourceViewBox.minY * scale;\n  const transform = `translate(${formatNumber(translateX)} ${formatNumber(translateY)}) scale(${formatNumber(scale)})`;\n  const outerTag = buildSvgOpeningTag(svg, targetViewBox);\n\n  return `${outerTag}\\n<g id=\"arrow-generated-artwork\" transform=\"${transform}\">\\n${innerSvg.trim()}\\n</g>\\n</svg>`;\n}\n\nfunction parseTargetViewBox(value: string): ViewBox | undefined {\n  const viewBoxParts = value\n    .trim()\n    .split(/\\s+/u)\n    .map((part) => Number(part));\n\n  if (viewBoxParts.length === 4 && viewBoxParts.every(Number.isFinite)) {\n    const [minX, minY, width, height] = viewBoxParts;\n    if (width > 0 && height > 0) {\n      return { height, minX, minY, width };\n    }\n  }\n\n  const sizeMatch = /^(?<width>\\d+(?:\\.\\d+)?)x(?<height>\\d+(?:\\.\\d+)?)$/iu.exec(\n    value.trim(),\n  );\n  if (!sizeMatch?.groups) {\n    return undefined;\n  }\n\n  const width = Number(sizeMatch.groups.width);\n  const height = Number(sizeMatch.groups.height);\n  if (!(width > 0 && height > 0)) {\n    return undefined;\n  }\n\n  return { height, minX: 0, minY: 0, width };\n}\n\nfunction parseSvgViewBox(svg: string): ViewBox | undefined {\n  const match = /\\sviewBox\\s*=\\s*[\"']([^\"']+)[\"']/iu.exec(svg);\n  if (!match?.[1]) {\n    return undefined;\n  }\n\n  return parseTargetViewBox(match[1]);\n}\n\nfunction sameViewBox(left: ViewBox, right: ViewBox): boolean {\n  return (\n    left.minX === right.minX &&\n    left.minY === right.minY &&\n    left.width === right.width &&\n    left.height === right.height\n  );\n}\n\nfunction extractInnerSvg(svg: string): string | undefined {\n  const match = /<svg\\b[^>]*>([\\s\\S]*?)<\\/svg>/iu.exec(svg);\n  return match?.[1];\n}\n\nfunction buildSvgOpeningTag(svg: string, viewBox: ViewBox): string {\n  const openingMatch = /<svg\\b[^>]*>/iu.exec(svg);\n  const openingTag = openingMatch?.[0] ?? '<svg xmlns=\"http://www.w3.org/2000/svg\">';\n  const withoutSizing = openingTag\n    .replace(/\\sviewBox\\s*=\\s*[\"'][^\"']*[\"']/iu, \"\")\n    .replace(/\\s(width|height)\\s*=\\s*[\"'][^\"']*[\"']/giu, \"\");\n\n  return withoutSizing.replace(\n    /<svg\\b/iu,\n    `<svg viewBox=\"${formatViewBox(viewBox)}\"`,\n  );\n}\n\nfunction formatViewBox(viewBox: ViewBox): string {\n  return [\n    formatNumber(viewBox.minX),\n    formatNumber(viewBox.minY),\n    formatNumber(viewBox.width),\n    formatNumber(viewBox.height),\n  ].join(\" \");\n}\n\nfunction formatNumber(value: number): string {\n  return Number(value.toFixed(4)).toString();\n}\n\nfunction ensureAccessibleSvg(\n  svg: string,\n  input: z.infer<typeof inputSchema>,\n): string {\n  const additions: string[] = [];\n\n  if (!/<title\\b/iu.test(svg)) {\n    additions.push(`<title>${escapeXml(svgTitle(input.filename))}</title>`);\n  }\n\n  if (!/<desc\\b/iu.test(svg)) {\n    additions.push(`<desc>${escapeXml(svgDescription(input))}</desc>`);\n  }\n\n  if (additions.length === 0) {\n    return svg;\n  }\n\n  return svg.replace(/<svg\\b[^>]*>/iu, (openingTag) =>\n    [openingTag, ...additions].join(\"\\n\"),\n  );\n}\n\nfunction enforceThemeableIconSvg(\n  svg: string,\n  input: z.infer<typeof inputSchema>,\n): string {\n  if (!isIconAsset(input)) {\n    return svg;\n  }\n\n  return addIconRootDefaults(\n    addStrokeToUnstyledIconPaths(\n      svg\n        .replace(/\\bstroke\\s*:\\s*#[0-9a-f]{3,8}\\b/giu, \"stroke:currentColor\")\n        .replace(/\\bstroke\\s*=\\s*([\"'])#[0-9a-f]{3,8}\\b\\1/giu, 'stroke=\"currentColor\"')\n        .replace(/\\bfill\\s*:\\s*url\\([^)]*\\)/giu, \"fill:none\")\n        .replace(/\\bfill\\s*=\\s*([\"'])url\\([^)]*\\)\\1/giu, 'fill=\"none\"')\n        .replace(/\\bfill\\s*:\\s*(#[0-9a-f]{3,8})\\b/giu, (_, color: string) =>\n          isAccentColor(color) ? `fill:${color}` : \"fill:none\",\n        )\n        .replace(/\\bfill\\s*=\\s*([\"'])(#[0-9a-f]{3,8})\\b\\1/giu, (_, quote: string, color: string) =>\n          isAccentColor(color) ? `fill=${quote}${color}${quote}` : `fill=${quote}none${quote}`,\n        ),\n    ),\n  );\n}\n\nfunction isIconAsset(input: z.infer<typeof inputSchema>): boolean {\n  const targetViewBox = parseTargetViewBox(input.dimensions);\n  const isSmallViewBox =\n    targetViewBox !== undefined &&\n    targetViewBox.width <= 64 &&\n    targetViewBox.height <= 64;\n\n  return /\\bicon\\b/iu.test(input.assetType) || isSmallViewBox;\n}\n\nfunction addIconRootDefaults(svg: string): string {\n  return svg.replace(/<svg\\b([^>]*)>/iu, (openingTag: string, attributes: string) => {\n    const withFill = /\\sfill\\s*=/iu.test(attributes)\n      ? openingTag\n      : openingTag.replace(/>$/u, ' fill=\"none\">');\n    return /\\scolor\\s*=/iu.test(attributes)\n      ? withFill\n      : withFill.replace(/>$/u, ' color=\"currentColor\">');\n  });\n}\n\nfunction addStrokeToUnstyledIconPaths(svg: string): string {\n  return svg.replace(\n    /<(path|line|polyline|polygon|circle|ellipse|rect)\\b([^>]*)>/giu,\n    (tag: string, tagName: string, attributes: string) => {\n      if (/\\sstroke\\s*=/iu.test(attributes)) {\n        return tag;\n      }\n\n      const strokeAttributes =\n        ' stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"';\n      const fillAttribute = /\\s(fill|class)\\s*=/iu.test(attributes)\n        ? \"\"\n        : ' fill=\"none\"';\n\n      return tag.replace(\n        new RegExp(`<${tagName}\\\\b`, \"iu\"),\n        `<${tagName}${fillAttribute}${strokeAttributes}`,\n      );\n    },\n  );\n}\n\nfunction isAccentColor(color: string): boolean {\n  const rgb = parseHexColor(color);\n  if (!rgb) {\n    return false;\n  }\n\n  const red = rgb.red / 255;\n  const green = rgb.green / 255;\n  const blue = rgb.blue / 255;\n  const max = Math.max(red, green, blue);\n  const min = Math.min(red, green, blue);\n  const delta = max - min;\n  if (delta === 0) {\n    return false;\n  }\n\n  const lightness = (max + min) / 2;\n  const saturation = delta / (1 - Math.abs(2 * lightness - 1));\n\n  return (\n    lightness >= MIN_ACCENT_LIGHTNESS &&\n    lightness <= MAX_ACCENT_LIGHTNESS &&\n    saturation >= MIN_ACCENT_SATURATION\n  );\n}\n\nfunction isHttpUrl(value: string): boolean {\n  const { protocol } = new URL(value);\n  return protocol === \"http:\" || protocol === \"https:\";\n}\n\nfunction isBase64Payload(value: string): boolean {\n  return value.length % 4 === 0 && BASE64_PATTERN.test(value);\n}\n\nfunction parseHexColor(\n  color: string,\n): { blue: number; green: number; red: number } | undefined {\n  const normalized = color.replace(/^#/u, \"\");\n  if (normalized.length === 3) {\n    const [red, green, blue] = normalized.split(\"\").map((part) => part + part);\n    return parseRgbComponents(red, green, blue);\n  }\n\n  if (normalized.length === 6 || normalized.length === 8) {\n    return parseRgbComponents(\n      normalized.slice(0, 2),\n      normalized.slice(2, 4),\n      normalized.slice(4, 6),\n    );\n  }\n\n  return undefined;\n}\n\nfunction parseRgbComponents(\n  redHex: string | undefined,\n  greenHex: string | undefined,\n  blueHex: string | undefined,\n): { blue: number; green: number; red: number } | undefined {\n  if (!redHex || !greenHex || !blueHex) {\n    return undefined;\n  }\n\n  const red = Number.parseInt(redHex, 16);\n  const green = Number.parseInt(greenHex, 16);\n  const blue = Number.parseInt(blueHex, 16);\n  if (![red, green, blue].every(Number.isFinite)) {\n    return undefined;\n  }\n\n  return { blue, green, red };\n}\n\nfunction svgTitle(filename: string): string {\n  return filename\n    .replace(/\\.svg$/iu, \"\")\n    .split(\"-\")\n    .filter(Boolean)\n    .map((part) => `${part[0]?.toUpperCase() ?? \"\"}${part.slice(1)}`)\n    .join(\" \");\n}\n\nfunction svgDescription(input: z.infer<typeof inputSchema>): string {\n  return `Brand-aligned ${input.assetType} for ${input.filename}.`;\n}\n\nfunction escapeXml(value: string): string {\n  return value\n    .replaceAll(\"&\", \"&amp;\")\n    .replaceAll(\"<\", \"&lt;\")\n    .replaceAll(\">\", \"&gt;\")\n    .replaceAll('\"', \"&quot;\")\n    .replaceAll(\"'\", \"&apos;\");\n}\n\nfunction preview(value: string): string {\n  if (value.length <= RESPONSE_PREVIEW_LENGTH) {\n    return value;\n  }\n\n  return value.slice(0, RESPONSE_PREVIEW_LENGTH);\n}\n"},{"path":"evals/evals.config.ts","type":"registry:file","target":"~/evals/evals.config.ts","content":"import { defineEvalConfig } from \"eve/evals\";\n\nexport default defineEvalConfig({\n  timeoutMs: 120_000,\n});\n"},{"path":"evals/generation-failure-no-fabrication.eval.ts","type":"registry:file","target":"~/evals/generation-failure-no-fabrication.eval.ts","content":"import { defineEval } from \"eve/evals\";\nimport { equals, includes } from \"eve/evals/expect\";\n\nexport default defineEval({\n  description:\n    \"Stops and reports missing AI Gateway credentials instead of fabricating SVG markup when generation fails.\",\n  async test(t) {\n    await t.send(`\nContinue the asset pack for acme.dev. The brand profile is locked and the first brief is ready.\n\nThe generate_svg_with_arrow tool returned:\n\n{\n  \"ok\": false,\n  \"filename\": \"feature-hero.svg\",\n  \"error\": \"AI Gateway credentials are required to call quiverai/arrow-1.1. Set AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN.\",\n  \"missingEnv\": \"AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN\"\n}\n\nGeneration cannot proceed because the AI Gateway credentials are missing. Proceed according to your instructions: stop and report the failure clearly, do not write any SVG markup yourself, and do not call generate_svg_with_arrow again.\n`);\n\n    t.succeeded();\n    t.noFailedActions();\n    t.notCalledTool(\"generate_svg_with_arrow\").gate();\n    t.check(t.reply, includes(\"AI_GATEWAY_API_KEY\").gate());\n    t.check((t.reply ?? \"\").includes(\"<svg\"), equals(false).gate());\n  },\n});\n"},{"path":"evals/missing-context-asks.eval.ts","type":"registry:file","target":"~/evals/missing-context-asks.eval.ts","content":"import { defineEval } from \"eve/evals\";\nimport { equals } from \"eve/evals/expect\";\n\nexport default defineEval({\n  description:\n    \"Asks for a website, product description, or brand profile before generating anything when no brand context is provided.\",\n  async test(t) {\n    await t.send(`\nMake me a beautiful icon pack.\n\nNo company website, product description, or brand profile has been provided. Proceed according to your instructions: ask for at least one of those inputs before doing any generation work. Do not call generate_svg_with_arrow and do not call any Context.dev tools yet.\n`);\n\n    t.succeeded();\n    t.noFailedActions();\n    t.notCalledTool(\"generate_svg_with_arrow\").gate();\n    const reply = t.reply ?? \"\";\n    t.check(reply.includes(\"?\"), equals(true).gate());\n    const replyLower = reply.toLowerCase();\n    t.check(\n      replyLower.includes(\"website\") ||\n        replyLower.includes(\"domain\") ||\n        replyLower.includes(\"brand\"),\n      equals(true).gate(),\n    );\n  },\n});\n"},{"path":"README.md","type":"registry:file","target":"~/agent/README.md","content":"# Brand Visual Asset Generator\n\nAn on-demand Eve agent that turns a company website, product description, or brand\nprofile into a coherent pack of brand-aligned SVG visual assets for SaaS and\ndigital products. It uses Context.dev MCP for brand extraction and calls a\n`generate_svg_with_arrow` tool powered by Quiver Arrow for SVG generation.\n\n## What it does\n\n1. **Extracts brand context with Context.dev MCP** - resolves company name,\n   description, colors, logos, industry labels, homepage copy, and styleguide\n   cues from a domain or URL.\n2. **Plans a visual asset pack** - scopes icons, empty states, hero illustrations,\n   badges, feature graphics, onboarding visuals, changelog art, and dashboard or\n   modal graphics from the user request.\n3. **Generates structured SVG output** - calls `generate_svg_with_arrow` once per\n   finalized asset brief. The tool uses `quiverai/arrow-1.1` through Vercel AI\n   Gateway's image-generation endpoint, passes optional Quiver reference images\n   when available, and returns editable SVG markup.\n4. **Keeps the pack cohesive** - enforces shared palette, stroke language, and\n   illustration metaphors across every asset in the set.\n\n## Asset types\n\n- Icons (feature, navigation, status)\n- Empty states\n- Hero illustrations\n- Badges and labels\n- Feature graphics\n- Onboarding visuals\n- Changelog illustrations\n- Dashboard or modal visuals\n\n## Installation\n\n```bash\nnpx shadcn@latest add @evex/brand-visual-asset-generator\n```\n\nInstall the public runtime dependencies listed by the registry item if your Eve\napp does not already have them.\n\n## Configuration\n\nCopy `.env.example` into your Eve app environment and fill in the Context.dev\ncredential plus an AI Gateway credential for Quiver Arrow.\n\n```env\nCONTEXT_DEV_API_KEY=ctxt_secret_...\nCONTEXT_API_KEY=\nAI_GATEWAY_API_KEY=vck_...\nVERCEL_OIDC_TOKEN=\n```\n\n`CONTEXT_API_KEY` is also supported as a fallback for projects that already use\nthat name. The Eve MCP connection sends the resolved key as the\n`x-context-dev-api-key` header.\n\n`AI_GATEWAY_API_KEY` is optional on Vercel deployments that provide\n`VERCEL_OIDC_TOKEN`. Local runs can use either `AI_GATEWAY_API_KEY` or a pulled\n`VERCEL_OIDC_TOKEN`.\n\nNever expose the Context.dev key to browser-side code. This agent sends it only\nfrom the Eve MCP connection runtime.\n\n## Models\n\n| Role | Model |\n| --- | --- |\n| Root orchestrator | `deepseek/deepseek-v4-pro` |\n| SVG generation tool | `quiverai/arrow-1.1` |\n\nEnsure your Eve deployment has access to both model providers.\n\n`generate_svg_with_arrow` accepts up to four reference images for Arrow 1.1 as\npublic image URLs or base64 image payloads. Use references for brand style,\npalette, composition, or typography direction; the brief should explicitly state\nwhat to preserve and what to change.\n\n## Usage\n\nAsk for a visual pack from a domain:\n\n```text\nCreate a feature launch visual pack for linear.app: hero illustration, three feature icons, an empty state, a \"new feature\" badge, and an onboarding visual.\n```\n\nOr provide product context without a domain:\n\n```text\nWe're a B2B analytics SaaS with primary color #2563EB and a precise, friendly tone. Generate an empty state and dashboard modal illustration for \"no reports yet\".\n```\n\nThe agent returns:\n\n1. A brand brief with palette, tone, and Context.dev source URLs.\n2. An asset pack manifest with filename suggestions and purposes.\n3. One fenced `svg` block per asset.\n4. Usage notes for theming and placement.\n5. Assumptions when any visual choices were inferred.\n\n## Smoke test\n\n1. Set `CONTEXT_DEV_API_KEY` in the Eve app environment.\n2. Start the app in development:\n\n   ```bash\n   pnpm dev\n   ```\n\n3. In your Eve chat/client, ask:\n\n   ```text\n   Generate a small visual pack for stripe.com: one hero illustration, two feature icons, and a \"new\" badge.\n   ```\n\n4. Confirm the agent:\n   - calls the `context-dev` MCP connection for brand data;\n   - calls `generate_svg_with_arrow` for each finalized asset brief;\n   - passes reference images when Context.dev returns useful logos, backdrops, or\n     source imagery;\n   - returns valid SVG markup with `viewBox`, semantic groups, and brand-aligned\n     colors.\n\n## Troubleshooting\n\n- **`CONTEXT_DEV_API_KEY is required`** - set `CONTEXT_DEV_API_KEY` in the Eve app\n  environment and restart the server.\n- **`Context.dev API 401`** - the key is missing, revoked, or copied incorrectly.\n- **`Context.dev API 408` or `429`** - the MCP call hit a cold-start timeout or\n  rate limit. Retry later or lower concurrent usage.\n- **`AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` is required** - set one of those\n  credentials so `generate_svg_with_arrow` can call `quiverai/arrow-1.1`.\n- **Quiver Arrow model errors** - confirm `quiverai/arrow-1.1` is enabled for\n  your Vercel AI Gateway project.\n- **Generic or off-brand SVGs** - Context.dev may have returned sparse brand data.\n  Provide explicit colors, tone adjectives, or a product description.\n\n## Development\n\n```bash\npnpm install\npnpm info\npnpm build\n```\n\nRun `pnpm typecheck` while editing the Context.dev MCP connection or Arrow SVG\ntool.\n"},{"path":".env.example","type":"registry:file","target":"~/.env.example","content":"CONTEXT_DEV_API_KEY=\nCONTEXT_API_KEY=\nAI_GATEWAY_API_KEY=\nVERCEL_OIDC_TOKEN=\n"}]}