pleach

Install

Package-manager and framework matrix — one page that covers every way to bring `@pleach/core` into a project.

The soil layer before the trellis goes up. Install paths organized by what you already use: pick a package manager, pick a framework, pick a provider. The wizard wraps all three; the manual snippets are if you want to see what gets generated.

npx pleach init detects your framework, asks four questions (template, provider, plugin stub, schema scaffold), and writes a starter project. Per-framework template matrix lives at CLI → pleach init.

npx pleach init
pnpm dlx pleach init
yarn dlx pleach init
bunx pleach init

Run npm run dev. On Next.js App Router the scaffold runs a keyless demo with no key set — the real graph, base-tools, and audit ledger run with canned model text, and the bundled /audit view shows real rows from your first message. Set a provider key to go live. ~60 seconds wall-clock.

Manual: install @pleach/core

npm install @pleach/core
pnpm add @pleach/core
yarn add @pleach/core
bun add @pleach/core

Manual: set a provider key

export ANTHROPIC_API_KEY="..."
export OPENAI_API_KEY="..."
export GOOGLE_GENERATIVE_AI_API_KEY="..."
export OPENROUTER_API_KEY="..."
export DEEPSEEK_API_KEY="..."
export MISTRAL_API_KEY="..."
export MOONSHOT_API_KEY="..."

The runtime auto-detects whichever key is set. Detection priority matches the tab order above. To pin a provider explicitly (skipping detection), pass provider: "anthropic" to createPleachRoute().

Manual: wire a route handler

The same createPleachRoute() factory works in any fetch-based runtime — App Router, Pages Router, Astro, SvelteKit, Remix, Hono, Workers, Bun. Pick the framework you're on; the body is identical.

// app/api/chat/route.ts
import { createPleachRoute } from "@pleach/core/quickstart";

export const POST = createPleachRoute();
// pages/api/chat.ts
import { createPleachRoute } from "@pleach/core/quickstart";

const handler = createPleachRoute();

export const config = { api: { bodyParser: false } };

export default async function POST(req, res) {
  const response = await handler(new Request(`http://x${req.url}`, {
    method: "POST",
    body: req as unknown as BodyInit,
  }));
  res.status(response.status);
  response.headers.forEach((v, k) => res.setHeader(k, v));
  if (response.body) {
    const reader = response.body.getReader();
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;
      res.write(value);
    }
  }
  res.end();
}
// src/pages/api/chat.ts
import type { APIRoute } from "astro";
import { createPleachRoute } from "@pleach/core/quickstart";

const handler = createPleachRoute();

export const POST: APIRoute = ({ request }) => handler(request);
// src/routes/api/chat/+server.ts
import { createPleachRoute } from "@pleach/core/quickstart";

const handler = createPleachRoute();

export const POST = ({ request }) => handler(request);
// app/routes/api.chat.ts
import { createPleachRoute } from "@pleach/core/quickstart";

const handler = createPleachRoute();

export const action = ({ request }) => handler(request);
// src/index.ts
import { Hono } from "hono";
import { createPleachRoute } from "@pleach/core/quickstart";

const app = new Hono();
const handler = createPleachRoute();

app.post("/api/chat", (c) => handler(c.req.raw));

export default app;
// src/index.ts
import { createPleachRoute } from "@pleach/core/quickstart";

const handler = createPleachRoute();

export default {
  async fetch(req: Request): Promise<Response> {
    const url = new URL(req.url);
    if (url.pathname === "/api/chat" && req.method === "POST") {
      return handler(req);
    }
    return new Response("Not found", { status: 404 });
  },
};
// server.ts
import { createPleachRoute } from "@pleach/core/quickstart";

const handler = createPleachRoute();

Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);
    if (url.pathname === "/api/chat" && req.method === "POST") {
      return handler(req);
    }
    return new Response("Not found", { status: 404 });
  },
});

What this gives you

A streaming chat handler at POST /api/chat that accepts { sessionId?, message } JSON and streams StreamEvent NDJSON back. No persistence, no plugins, no domain customization — the runtime returns a substrate placeholder until you adopt a storage adapter and (optionally) a plugin.

For keyless local dev — no env var, no 503 — pass demo: process.env.NODE_ENV !== "production" to createPleachRoute(). With no key it drives a real graph turn over canned model text and writes real audit rows (response header x-pleach-mode: demo); a resolved key always wins, and it stays off in a production build. It's the mode the wizard scaffolds, with an /audit view to read the rows.

For the React side — useChat(), <ChatBox /> — see Getting started. For a production-shape handler with custom storage, plugins, and provider pinning, see Getting started → Custom storage, plugins, or provider pinning.

Where to go next

On this page