Quickstart
By the end of this page a model will call code running on your machine. Budget about ten minutes, most of it waiting on npm.
Before you start
- Node 22 or newer. Check with
node -v. - An account on the dashboard. The free tier is enough for this page.
- A way to expose a local port over HTTPS — ngrok, cloudflared, or anything equivalent. Cheela calls into your endpoint, so localhost alone will not do.
Scaffold the project
Run the initializer in an empty directory.
npx cheela initIt writes five things and overwrites none of them if they exist:
.cheela/runtime.ts— where capabilities are registeredcheela.config.ts— your API key, endpoint, and product descriptionpackage.json— with@cheela/cli,@cheela/runtime,@cheela/sdk.env.example.gitignoreentries for.envand the generator cache
npm install # or pnpm install / yarnCreate a runtime
A runtime is the identity that owns your capabilities. Create one in the dashboard. You get three credentials, and they are not interchangeable:
ch_sk_…— the deploy key. Secret. This isCHEELA_API_KEY.ch_pk_…— the public key. Safe to embed in a web page. It can execute, never deploy.- The runtime secret — used to sign requests to your endpoint. This is
CHEELA_RUNTIME_SECRET.
CHEELA_API_KEY=ch_sk_...
CHEELA_RUNTIME_SECRET=...
CHEELA_RUNTIME_ID=rt_...Write a capability
Two objects: what the capability is, and what it does. The first is published to Cheela; the second never leaves your machine.
import { Runtime } from "@cheela/runtime";
import { z } from "zod";
const runtime = new Runtime();
runtime.register(
{
name: "weather-now",
description: "Current conditions for a city",
// Required — the Agent Discovery Specification needs it, so deploy does too.
version: "1.0.0",
input: z.object({ city: z.string() }),
},
{
name: "lookup",
async handler(context, input) {
console.log("called for", input.city, "as", context.executionId);
return { city: input.city, tempC: 21, sky: "clear" };
},
},
);
export default runtime;weather-now is legal. weather.now and weather_now are not — tool-calling APIs reject the dot, and the discovery spec rejects the underscore. The runtime refuses the name at registration rather than letting it fail later as an opaque provider error.
Check what is registered without deploying anything:
npx cheela devServe the endpoint
Cheela calls you over HTTPS, and signs every request. createCheelaHandler verifies the signature and dispatches to your runtime. In a Next.js app:
import { createCheelaHandler } from "@cheela/runtime";
import runtime from "../../../.cheela/runtime";
export const POST = createCheelaHandler({
runtime,
secret: process.env.CHEELA_RUNTIME_SECRET!,
runtimeId: process.env.CHEELA_RUNTIME_ID,
});
// The signature covers the exact bytes received, so nothing may re-parse
// the body before verification.
export const dynamic = "force-dynamic";Express, Hono, Bun, Deno and Cloudflare Workers are covered too — see Serve capability calls.
Make it reachable
Start your app, then open a tunnel to it and put the public URL in your config.
ngrok http 3000
# → https://your-subdomain.ngrok-free.appimport { defineConfig } from "@cheela/cli";
export default defineConfig({
apiKey: process.env.CHEELA_API_KEY!,
endpoint: "https://your-subdomain.ngrok-free.app/cheela/execute",
website: {
name: "My Product",
description: "What this runtime does.",
url: "https://example.com",
},
adp: {
namespace: "com.example",
},
});There is no provider or model field. Both are Cheela’s, because Cheela pays for the tokens.
Deploy
See what would be sent before sending it:
npx cheela deploy --dry-runThen publish:
npx cheela deployCheela Deploy
✓ Config loaded
✓ Runtime loaded
✓ Found 1 capabilities
✓ Found 1 actions
✓ Deployment manifest valid
Generators
capability-manifest .cheela/generated/capability-manifest/capabilities.json (created)
runtime-manifest .cheela/generated/runtime-manifest/runtime.json (created)
openapi .cheela/generated/openapi/openapi.json (created)
adp .cheela/generated/adp/agent-discovery.json (created)
✓ Runtime authenticated
✓ Deployment created
Deployment 1 is active.Confirm the control plane agrees with your local runtime:
npx cheela statusCall it
Send a message the model can only answer by calling your capability. Use the public key here.
curl https://api.cheelalabs.com/v1/runtime/execute \
-H "Authorization: Bearer $CHEELA_PUBLIC_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "user", "parts": [{ "type": "text", "content": "What is the weather in Lisbon?" }] }
]
}'Your terminal should log called for Lisbon. The response carries the full transcript, including the tool_call and tool_result parts, plus token counts and a duration.
The HTTP request succeeded; the execution is what failed. Check status in the body, not the status line.
What to read next
- Architecture — the full request path, and why the signature exists.
- End-user identity — required reading before any capability touches a specific person’s data.
- Embed chat — swap that
curlfor a real widget.