one-prod
v0.1.0
Published
ONE production - chatbot + landing page (https://one.ie)
Readme
ONE Web Template
Astro 6 + React 19 + Cloudflare Workers chatbot template. Deploy an AI agent to the web in minutes. One codebase handles landing page, multi-agent chat, and optional Telegram/Discord webhooks.
Agents are markdown. One .md file describes the system prompt, journey, page sections, theme, and chat UI — see agent-authoring.md. Every published agent gets a complete studio page at /studio/<name> with zero TypeScript required.
Quick Start
# Install
npm install
# Set your OpenRouter key
wrangler secret put OPENROUTER_API_KEY
# Run locally — Vite dev server (port 4321, no CF bindings)
npm run dev
# Run locally — full CF stack (D1 + KV + R2 + Workers, port 8787)
one dev # build + wrangler dev with local bindings
one dev --skip-build # skip rebuild
one dev --remote # real D1/KV/R2 instead of local
# Deploy to Cloudflare Workers
npm run deployStructure
web/
src/
pages/
index.astro # Landing page
chat.astro # Full-page chat (Agent-aware via ?agent= URL param)
agents.astro # Agent selection gallery
api/
chat.ts # Chat streaming endpoint (AI SDK v6)
openrouter-models.ts # Model list
webhook/
telegram.ts # Telegram webhook (optional)
discord.ts # Discord webhook (optional)
components/
Chat.tsx # Chat UI — agent selection, streaming, voice, tools
ChatWidget.tsx # Floating chat widget
ChatLazy.tsx # Lazy-load wrapper
Hero.tsx # Landing page hero
Features.tsx # Feature grid
Pricing.tsx # Pricing cards
chat/
MessageList.tsx # Message rendering
VoiceMenu.tsx # Voice selection
AddMenu.tsx # Attachment/tool menu
pay/
PayPanel.tsx # Payment UI (optional)
showcase/ # Demo agents and workflows
lib/
agent.ts # Agent config loader (legacy, simplified)
agents.ts # Agent registry — loads agents/*.md + skills/*.md
agent-md.ts # YAML frontmatter parser for agents/skills
llm.ts # OpenRouter / Groq API wrapper
ui-signal.ts # emitClick() for telemetry
types.ts # TypeScript definitions
agents/ # Agent markdown definitions
agent.md # Default agent (via import)
support.md # Named agent (loaded via glob)
sales.md # Another agent
skills/ # Shared skill definitions
handle-complaint.md # Skills referenced by agents
escalate.md
qualify-lead.md
close-deal.md
wrangler.toml # Cloudflare Workers config
package.jsonStudio pages
Every agent published in web/agents/*.md automatically gets a studio page at /studio/<name> that renders:
- Hero — image + eyebrow + typewriter (from
ui.hero) - Funnel — 4–6 stage cards from
journey.stages - Quick prompts — pills from
journey.pills - Sections — page surfaces (
stat,card,grid,list,compare) fromsections[] - Skills — bound from
skills[]refs - Chat-on-right — full Chat surface, customizable via
ui(placeholder, streaming, labels, voice, avatar, quick-replies, animation, layout)
Reference implementations live in this repo:
/studio/marketing-strategist←web/agents/marketing-strategist.md/studio/hanoi-planner←web/agents/hanoi-planner.md
Both pages are 100% data-driven from their single .md file. No bespoke .astro per agent. The canonical authoring guide is agent-authoring.md.
Publishing flow
oneie auth login --key sk_xxx
oneie agent new my-agent --profile core # scaffolds my-agent/agent.md
$EDITOR my-agent/agent.md # author following agent-authoring.md
oneie agent validate my-agent/agent.md # kebab-case name, anchor→section ids
oneie agent lint my-agent/agent.md # chip labels ≤ 28, ≤ 4 continuations/stage
oneie agent publish my-agent/agent.md --slug acme
# → live at acme.one.ie/chat?agent=my-agent
# → and at one.ie/studio/my-agent
oneie agent pull my-agent --slug acme # round-trip the live version back
oneie agent unpublish my-agent --slug acme # remove (idempotent)The publish endpoint exposes three methods:
| Method | Path | What |
| --- | --- | --- |
| POST | /api/agents/publish | Body {slug, name, content} — writes {slug}/agents/{name}.md |
| GET | /api/agents/publish?slug=&name= | Returns {ok, content, bytes, key} |
| DELETE | /api/agents/publish?slug=&name= | Returns {ok, removed: bool, key} — idempotent |
All three accept Bearer auth with two shapes: server token, or <slug>:<token> for owner-scoped writes. The file lands in R2 at {slug}/agents/{name}.md and is loaded by api/chat.ts and /studio/[agent].astro on the next request.
The SDK exposes client.publishAgent | pullAgent | unpublishAgent; MCP exposes publish_agent | pull_agent | unpublish_agent.
Embedding on external sites
One <script> tag puts the chat on any third-party page — Chat.tsx runs unchanged inside an iframe served from one.ie.
<script async src="https://one.ie/c/chat.js"
data-agent="your-agent" data-view="icon"
data-position="right" data-primary="#1234ab" data-ws="acme"></script>| Surface | Path | What |
| --- | --- | --- |
| Loader | public/c/chat.js | vanilla IIFE, ~2.7 KB gz. Reads data-*, builds the launcher, lazy-injects the iframe. data-view: icon (deferred bubble) · half (docked panel) · full (viewport). |
| Route | /embed/[agent] | the iframe surface — Layout chat-only (sidebar=none, chat-full), ?primary= → ThemeOverride, frame-ancestors scoped to this route (studio/* stays unframeable). |
| Bridge | src/lib/embed-bridge.ts | typed, origin-checked, versioned postMessage envelope (parent ↔ iframe) + buildFrameAncestors per-workspace CSP allowlist. |
data-*: data-agent (required) · data-view · data-position (left/right) · data-primary (hex) · data-ws (analytics correlation) · data-greeting. Per-workspace framing is allowlisted from D1 domains (rows present → only those origins; none → public-open). The iframe is same-origin to one.ie, so /api/chat calls stay first-party — no CORS. No SRI on the snippet by design (patchable like one.js/Stripe.js). Spec: ../../text/chat-script-plan.md.
Agents
Agents are markdown files with YAML frontmatter + system prompt.
Default Agent
The root agent.md (outside agents/) is the default. When a user visits /chat, they see this agent unless ?agent=<name> is in the URL.
---
name: agent
title: ONE Demo
model: meta-llama/llama-4-scout-17b-16e-instruct
tools: [crawl, image]
skills: [handle-complaint, escalate]
starters:
- What is ONE?
- How do I sell a skill?
---
You are a helpful assistant...Named Agents
Files in agents/*.md are discoverable:
agents/
support.md # ?agent=support → Customer Support agent
sales.md # ?agent=sales → Sales agentVisit /agents to see a gallery of all agents. Click "Chat" on any agent to open /chat?agent=<name>.
Agent Frontmatter
| Field | Type | Notes |
|-------|------|-------|
| name | string | Agent ID (used in URL) |
| title | string | Display name |
| description | string | Short bio shown in gallery |
| model | string | LLM model (default: meta-llama/llama-4-maverick) |
| tools | string[] | Platform tool whitelist: crawl, image (omit = all available) |
| skills | string[] | Skill references: [handle-complaint, escalate] |
| starters | string[] | Example prompts shown in UI |
Models
Specify via model field in agent frontmatter or AGENT_MODEL in wrangler.toml.
| Model | Provider | Speed | Cost |
|-------|----------|-------|------|
| meta-llama/llama-4-maverick | OpenRouter | Fast | $0.15/1M tokens |
| groq/meta-llama/llama-4-scout-17b | Groq | Very fast | $0.10/1M tokens |
| anthropic/claude-haiku-4-5 | OpenRouter | Balanced | $0.80/1M tokens |
| anthropic/claude-sonnet-4-5 | OpenRouter | Slow | $3.00/1M tokens |
For Groq models, also set:
wrangler secret put GROQ_API_KEYSkills
Skills are markdown files in skills/ with YAML frontmatter (no prompt body needed).
---
name: handle-complaint
title: Handle Customer Complaint
description: Resolve customer issues step by step
price: 50
tags: [support, customer-service]
---Agents reference skills by name in their frontmatter:
skills: [handle-complaint, escalate]Skills are discovered at build time via import.meta.glob('../../skills/*.md') in agents.ts. They're metadata-only; the LLM instructions live in the agent's system prompt.
Configuration
wrangler.toml
[vars]
AGENT_ID = "one-demo"
AGENT_NAME = "ONE Demo"
AGENT_MODEL = "meta-llama/llama-4-maverick"
ONE_API_URL = "https://dev.one.ie" # Optional — for ONE substrate syncSecrets
wrangler secret put OPENROUTER_API_KEY # Required
wrangler secret put GROQ_API_KEY # Optional — if using Groq models
wrangler secret put TELEGRAM_TOKEN # Optional — for Telegram webhook
wrangler secret put DISCORD_TOKEN # Optional — for Discord webhookChat Endpoint
The /api/chat endpoint powers both the web UI and webhook channels.
POST /api/chat
Content-Type: application/json
{
"messages": [
{ "role": "user", "content": "..." }
],
"agentId": "support", # optional — defaults to agent.md
"model": "anthropic/claude-haiku-4-5" # optional — overrides agent frontmatter
}Response is streamed via ai SDK v6's createAgentUIStreamResponse.
Channels (Optional)
Telegram
- Create a bot with @BotFather, get token
wrangler secret put TELEGRAM_TOKEN- Deploy:
npm run deploy - Set webhook:
curl "https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://<your-worker>.workers.dev/api/webhook/telegram"Discord
- Create app at discord.com/developers
- Add bot to your server, copy token
wrangler secret put DISCORD_TOKEN- Set interactions endpoint URL in Discord Developer Portal to:
https://<your-worker>.workers.dev/api/webhook/discordDesign System
Uses 6 editable color tokens: background, foreground, font, primary, secondary, tertiary (defined in src/layouts/Layout.astro).
No Tailwind palette classes allowed — the build enforces this via --color-*: initial in @theme. Invalid colors silently emit no CSS, so always verify your styles work.
See .claude/rules/design.md for full token reference, depth levels, and patterns.
UI Signals
Every onClick handler calls emitClick() for telemetry:
import { emitClick } from '@/lib/ui-signal'
<button onClick={() => {
emitClick('ui:chat:copy')
handleCopy()
}}>Copy</button>Format: ui:<surface>:<action> (all lowercase, colon-delimited).
Compile & Deploy Agents
The @oneie/sdk/compile function (part of one-ie/one) can compile agents to multiple formats:
# From the root one-ie/one directory
npx tsx -e "
import { compile } from '@oneie/sdk/compile'
const agent = await compile('web/agents/support.md', 'uagents') # Python uAgents
console.log(agent)
"Supported formats: uagents (Python) · mcp (JSON) · skill (SKILL.md).
Deploy
npm run deployDeploys to Cloudflare Workers with the Astro adapter (@astrojs/cloudflare). SSR for pages, static assets cached globally.
Performance
- Lighthouse 100% on
/chat(from memory constraint) - <10ms gateway latency (Cloudflare)
- <100ms first token from LLM (streaming via AI SDK v6)
Uses:
client:loadfor above-fold interactive UIclient:idlefor non-critical componentsclient:visiblefor lazy-loaded galleries
Files of Note
| File | Purpose |
|------|---------|
| src/lib/agents.ts | Build-time agent registry + skill resolver |
| src/lib/agent-md.ts | YAML frontmatter parser (handles tools, skills, model, etc.) |
| src/pages/api/chat.ts | AI SDK streaming endpoint |
| wrangler.toml | Worker secrets, KV bindings, custom domain |
| .claude/rules/ | Design system, React 19, Astro rules for Claude Code |
See Also
agents/CLAUDE.md— agent markdown spec and templatesone/dictionary.md— canonical names and terminology- Root
CLAUDE.md— workspace overview and operating rules
License
MIT
