Cheela Labs
START

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.
01

Scaffold the project

Run the initializer in an empty directory.

Terminal
npx cheela init

It writes five things and overwrites none of them if they exist:

  • .cheela/runtime.ts — where capabilities are registered
  • cheela.config.ts — your API key, endpoint, and product description
  • package.json — with @cheela/cli, @cheela/runtime, @cheela/sdk
  • .env.example
  • .gitignore entries for .env and the generator cache
Terminal
npm install   # or pnpm install / yarn
02

Create 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 is CHEELA_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.
.env
CHEELA_API_KEY=ch_sk_...
CHEELA_RUNTIME_SECRET=...
CHEELA_RUNTIME_ID=rt_...
03

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.

.cheela/runtime.ts
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;
Names allow letters, digits and hyphens

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:

Terminal
npx cheela dev
04

Serve 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:

app/cheela/execute/route.ts
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.

05

Make it reachable

Start your app, then open a tunnel to it and put the public URL in your config.

Terminal
ngrok http 3000
# → https://your-subdomain.ngrok-free.app
cheela.config.ts
import { 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.

06

Deploy

See what would be sent before sending it:

Terminal
npx cheela deploy --dry-run

Then publish:

Terminal
npx cheela deploy
Output
Cheela 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:

Terminal
npx cheela status
07

Call it

Send a message the model can only answer by calling your capability. Use the public key here.

Terminal
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.

A failed execution still returns 200

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 curl for a real widget.