New project
This path starts with a Vite React app and adds the flue-eve Vite plugin. In
development, the browser talks to /eve/v1/* on the Vite origin while the Flue
server handles the compat routes.
1. Create the app
npm create vite@latest my-app -- --template react-ts
cd my-app2. Install
npm install flue-eve @flue/runtime hono
npm install -D @flue/cliflue-eve provides the adapter, @flue/runtime runs the agent, hono hosts
the route tree, and @flue/cli provides flue dev.
3. Add the Vite plugin
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { flueEve } from 'flue-eve/vite'
export default defineConfig({
plugins: [react(), flueEve()],
})The plugin can scaffold the sidecar that mounts the compat server and can alias
Eve imports to flue-eve imports during migration.
4. Initialize the Flue/Eve bridge
Run the CLI from the project root:
npx flue-eve initIt creates the Flue agent file, the Eve compat sidecar, and the app mount when
src/app.ts exists. The sidecar is the important piece: it mounts the Eve compat
server under /eve/v1.
// src/flue-eve-shim.ts
import { eveCompat, resolveAdmissionFromRuntime } from 'flue-eve/server'
import type { Hono } from 'hono'
export function mountEveCompat(app: Hono): void {
app.route(
'/eve/v1',
eveCompat({
agentName: 'assistant',
admission: resolveAdmissionFromRuntime('assistant', {
flueBaseUrl: process.env.FLUE_BASE_URL,
}),
}),
)
}// src/app.ts
import { flue } from '@flue/runtime/routing'
import { Hono } from 'hono'
import { mountEveCompat } from './flue-eve-shim.js'
const app = new Hono()
app.route('/', flue())
mountEveCompat(app)
export default appThis is the core bridge: browser requests keep the Eve route shape, while Flue handles agent execution behind the compat server. If you prefer to wire files manually, use the generated code above as the reference shape.
5. Render a chat UI
// src/App.tsx
import { useState } from 'react'
import { useEveAgent } from 'flue-eve/react'
export default function App() {
const agent = useEveAgent()
const [message, setMessage] = useState('')
const busy = agent.status === 'submitted' || agent.status === 'streaming'
return (
<form
onSubmit={(event) => {
event.preventDefault()
if (!message.trim() || busy) return
void agent.send({ message })
setMessage('')
}}
>
{agent.data.messages.map((item) => (
<article key={item.id}>
<strong>{item.role}</strong>
{item.parts.map((part, index) =>
part.type === 'text' ? <p key={index}>{part.text}</p> : null,
)}
</article>
))}
<input value={message} onChange={(event) => setMessage(event.target.value)} />
<button disabled={busy || !message.trim()}>Send</button>
{busy ? <button type="button" onClick={agent.stop}>Stop</button> : null}
</form>
)
}6. Run
npm run devOpen http://localhost:5173. With mock admission, the agent returns
deterministic responses without needing an LLM key.
You can also test the raw route:
curl -X POST http://localhost:5173/eve/v1/session \
-H 'content-type: application/json' \
-d '{"message":"Hello"}'To use real Flue execution, run flue dev and point the compat server at it:
FLUE_BASE_URL=http://127.0.0.1:3583 npm run devNext steps
- Authoring tools — define your agent's tools
- Authoring instructions — write system prompts
- Eve config — customize plugin options
- Deployment — deploy to production