Use otto's server and UI packages inside your own Bun/Node application.
@ottocode/server— embedded Hono app and built-in agent/tool metadata@ottocode/web-ui— prebuilt web UI assets andserveWebUI()@ottocode/api— generated client if you want to talk to an otto server externally@ottocode/sdk— lower-level runtime utilities
import { createEmbeddedApp } from '@ottocode/server';
const app = createEmbeddedApp({
provider: 'openai',
model: 'gpt-4o',
apiKey: process.env.OPENAI_API_KEY,
agent: 'build',
});
Bun.serve({
port: 3456,
idleTimeout: 240,
fetch: app.fetch,
});import { createEmbeddedApp } from '@ottocode/server';
import { serveWebUI } from '@ottocode/web-ui';
const api = createEmbeddedApp({
provider: 'anthropic',
model: 'claude-sonnet-4',
apiKey: process.env.ANTHROPIC_API_KEY,
agent: 'build',
});
const web = serveWebUI({ prefix: '/ui' });
Bun.serve({
port: 3456,
idleTimeout: 240,
async fetch(req) {
return (await web(req)) ?? api.fetch(req);
},
});That gives you:
- API routes on
/,/openapi.json, and/v1/* - browser UI on
/ui
@ottocode/server exports the current built-in presets:
import { BUILTIN_AGENTS, type BuiltinAgent } from '@ottocode/server';
const agent: BuiltinAgent = 'research';
console.log(Object.keys(BUILTIN_AGENTS));
// ['build', 'plan', 'general', 'research']Typical uses:
build— implementation and code changesplan— architecture/planninggeneral— mixed workflowsresearch— research across sessions/history/web
import { BUILTIN_TOOLS, type BuiltinTool } from '@ottocode/server';
const safeTools: BuiltinTool[] = BUILTIN_TOOLS.filter(
(tool) => !['shell', 'write', 'git_commit'].includes(tool),
);BUILTIN_TOOLS is the full exported built-in tool universe. A given agent does
not automatically receive all of them — use BUILTIN_AGENTS.<agent>.tools
to see the actual default tool list for that preset.
Current exported tool names include file, search, patch, git, terminal, control, and research helpers such as:
read,write,ls,tree,pwd,cd,globripgrep,websearchapply_patchshell,terminalgit_status,git_diff,git_commitupdate_todos,progress_update,finish,skillquery_sessions,query_messages,get_session_context,search_history,get_parent_session,present_action
Embedded otto still follows the normal resolution order:
- injected config (
createEmbeddedApp({...})) - environment variables
- project config (
.otto/...) - global config (
~/.config/otto/...) - built-in defaults
Auth secrets use secure OS-specific storage when they are not injected:
| Platform | Auth path |
|---|---|
| macOS | ~/Library/Application Support/otto/auth.json |
| Linux | $XDG_STATE_HOME/otto/auth.json or ~/.local/state/otto/auth.json |
| Windows | %APPDATA%/otto/auth.json |
import type { EmbeddedAppConfig } from '@ottocode/server';
const config: EmbeddedAppConfig = {
provider: 'openai',
model: 'gpt-4o',
apiKey: process.env.OPENAI_API_KEY,
agent: 'build',
defaults: {
provider: 'openai',
model: 'gpt-4o',
agent: 'build',
toolApproval: 'auto',
},
corsOrigins: ['https://myapp.example.com'],
};Useful fields:
provider,model,apiKeyagentauthfor multi-provider injected authagentsfor agent overridesdefaultsfor fallback defaultscorsOriginsfor reverse proxies, custom domains, or Tailscale-style deployments
import { BUILTIN_AGENTS, createEmbeddedApp } from '@ottocode/server';
const app = createEmbeddedApp({
provider: 'anthropic',
model: 'claude-sonnet-4',
apiKey: process.env.ANTHROPIC_API_KEY,
agent: 'general',
agents: {
general: {
...BUILTIN_AGENTS.general,
tools: ['read', 'ls', 'tree', 'ripgrep', 'update_todos', 'websearch'],
},
},
});If you do not inject everything explicitly, embedded otto can still read:
.otto/config.json.otto/agents.json.otto/agents/<name>.md~/.config/otto/config.json~/.config/otto/agents.json
Custom tools can still be discovered from:
.otto/tools/<tool-name>/tool.js.otto/tools/<tool-name>/tool.mjs~/.config/otto/tools/<tool-name>/tool.js~/.config/otto/tools/<tool-name>/tool.mjs
Your embedded server should expose the same route layout as the standalone runtime:
//openapi.json/v1/*
If you mount behind a reverse proxy, keep /v1/* reachable or rewrite consistently for your client.
- prefer
@ottocode/apiif you are calling an otto server from another process - use
serveWebUI()when you want the browser UI without rebuilding the frontend yourself - set
idleTimeouthigh enough for SSE streaming workloads