The Coline App SDK: apps, tools, file types and surfaces that run inside Coline workspaces, and public websites with Coline Sites. TypeScript, with an in-memory test workspace. MIT.
The Coline App SDK — define apps that live inside Coline workspaces: Kairo tools, file types with rich previews, home surfaces, app storage, scheduled triggers, and external API access — and public websites with Coline Sites. Coline hosts your logic; there are no servers to deploy unless you want them.
npx create-coline-app my-app # an app for a workspace
npx create-coline-app my-site --template site # a public website, no account needed
Docs: docs.coline.app · for coding agents: docs.coline.app/agents.md
import { z } from "zod/v4";
import { defineApp, defineTool, ui } from "@colineapp/sdk/v2";
const createTodo = defineTool({
name: "todos.create_todo",
description: "Create a todo with a title.",
input: z.object({ title: z.string().min(1) }),
effect: "write", // enforced by the runtime as a hard capability ceiling
execute: async (input, ctx) => {
const file = await ctx.coline.files.create({
typeKey: "todos.todo",
name: input.title,
document: { title: input.title, status: "open" },
});
return {
output: { fileId: file.fileId },
card: ui.reference({ kind: "file", id: file.fileId }),
};
},
});
export default defineApp({
key: "todos",
name: "Todos",
permissions: ["files.read", "files.write", "ai.tools"],
hosting: { default: "coline" },
surfaces: { home: { tier: "tree" } },
tools: [createTodo],
handlers: {
renderHome: async (ctx) => {
const { files } = await ctx.coline.files.list({ typeKey: "todos.todo" });
return ui.stack([
ui.heading("Todos"),
...files.map((f) => ui.fileCard({ title: f.name, fileId: f.fileId })),
]);
},
},
});
One definition produces two artifacts: a serializable manifest (what the app may do — reviewed and consented to) and a runtime bundle (what it does). Every capability call is checked against the workspace's grants, the acting user's access, and the declared tool effect.
The same coline.* object everywhere your code runs — tools, render
handlers, schedules, hosted or external:
coline.files — create/read/update/trash workspace filescoline.storage.kv / coline.storage.collection(name) — app-private
key-value state and queryable recordscoline.members, coline.notifications, coline.searchcoline.net.fetch — external HTTP through Coline's allowlist, with
{{secret:KEY}} server-side credential injectionimport { createTestWorkspace } from "@colineapp/sdk/testing";
import app from "./app.config";
const ws = createTestWorkspace(app);
await ws.invokeTool("todos.create_todo", { title: "Ship it" });
expect(ws.files.byType("todos.todo")).toHaveLength(1);
@colineapp/sdk/testing runs your app against an in-memory workspace with
the same capability contract as production.
| Entry | Contents |
|---|---|
@colineapp/sdk/v2 | defineApp, defineTool, defineFileType, ui, actions, manifest schemas, capability types, createColineAppHandler for external hosting |
@colineapp/sdk/testing | createTestWorkspace — the in-memory test harness |
@colineapp/sdk | The legacy v1 surface (frozen; do not use for new apps) |
Full guides at docs.coline.app: apps, sites, and every page as Markdown in llms.txt.
MIT © Coline
Run coline-app check before pushing: it checks types, the manifest, hosted logic,
and the React bundle without uploading. dev --internal updates one development
version; install it in the workspace and reload after changes. The CLI bundles
from the app's installed dependencies and preserves imported CSS and inline assets.
CLI 2.5 compiles app-specific Tailwind v4 and ordinary CSS automatically.
Import UI styles followed by app.css. The scaffold includes experimental UI
skills, screen examples, and a credential-free coline-app preview command.
The starter includes a React home and editor, tree mobile fallback, and a tool.
Use coline.tools.invoke or actions.invokeTool to run the same declared tool
from UI that Kairo runs. Save with expectedVersion to detect concurrent edits.
Homes/editors can write within grants; previews and server render handlers cannot.
createTestWorkspace(app).client() tests the interactive contract and
grantedPermissions simulates revoked consent.
New tasks, calendar, messages, and references capabilities require their
explicit manifest permissions and retain the invoking user's workspace access.
Use files.list({ scope: "workspace" }) for shared files; document writes stay
with the owning app. Collections are install-private. A native mobile React runtime is separate
work; Coline Code builds and publishes apps (see docs/sdk/full-stack.md).
createLiveCollection(coline, name, query) exposes a bounded live query with
synchronous optimistic updates, queued version-checked writes, errors, and
reconnect recovery. Subscribe via subscribe, call start, read getSnapshot,
and finish with dispose. React authors use useLiveCollection from
@colineapp/ui instead. A view holds at most 200 records.
Collection envelopes include version and access. insert accepts
{ access: { workspace: "none" | "read" | "write", readers: [], writers: [] } };
the default is workspace read/write. Only the creator can setAccess. Use
update(id, patch, { expectedVersion }) to reject stale writes. The same options
apply to delete/setAccess. Missing or inaccessible get returns null. Permissions
are filtered before pagination and also apply to reference resolution.
sync(query, revision?) returns a durable revision with an authorized snapshot;
records: null means unchanged. Live views use WebSocket invalidations and a
two-second fallback poll. They are structured-data views, not offline replicas
or character-level collaborative editors. Mutation errors must be caught; a
network error alone does not establish whether the server committed the write.
Coline provides the AI models and credentials. Discover configured models with
await coline.ai.models(). Start text/structured output, embedding, speech,
transcription, reranking or video work with coline.ai.run({ kind, input }).
Images use coline.ai.images.generate(input), including references, masks and
partial previews on supported models. Every job needs model, idempotencyKey
and maxCostMicrousd. Media outputs are normal Coline files.
import { watchAiJob } from "@colineapp/sdk/v2";
const model = (await coline.ai.models()).find((entry) => entry.kind === "language");
if (!model) throw new Error("No language model is configured");
const job = await coline.ai.run({
kind: "language",
input: {
model: model.id,
prompt: "Write a short welcome message",
idempotencyKey: crypto.randomUUID(),
maxCostMicrousd: model.priceMicrousd,
},
});
for await (const update of watchAiJob(coline.ai, job.id)) {
if (update.type === "event") console.log(update.event);
else console.log(update.job.status, update.job.output);
}
Persist the job ID and last event sequence to reconnect. Aborting observation
leaves the job running; ai.jobs.cancel(id) cancels it. Request ai.generate,
plus files.read/files.write for media access and ai.tools for app tool loops.
Use files.readUrl(fileId) to obtain a fresh signed URL for an output file.
The existing ai.generate(prompt, options) remains available.
See docs/sdk/ai.md in the Coline repository for all request families, provider
configuration, limits, billing and test fixtures. Models and their capabilities
are server-configured; not every provider supports every operation.
Collection orderBy should name a scalar field. Numbers sort numerically, so
priority 2 precedes 10 in ascending order. In a mixed field, numbers precede
other scalar values; remaining values use case-sensitive text order, with null
and missing values treated as empty text. Descending reverses that order; record
IDs break ties. The server, test workspace, and optimistic live view agree.
Live views clear cached rows and reject pending edits for both raw SDK denials and HTTP-prefixed authorization denials. A transport with no subscription support still starts the bounded polling fallback.
Events (triggers.webhooks + handlers.onEvent, coline.events.emit),
inbound endpoints (defineEndpoint, verified for Stripe, GitHub, Slack,
Shopify, Linear, HMAC, or a shared token), durable background jobs
(defineJob, coline.jobs.*, continueWith, checkpoints, progress), OAuth
connections held by Coline (connections, {{connection:KEY}} in
net.fetch), developer secrets, collection count/aggregate/batch,
uploads up to 5 GB, files.preview/saveAs/move, and new tree nodes (list, toolbar, searchInput,
filter, chart, confirm, stat, keyValue). createColineAppHandler
rejects replayed deliveries; pass seenDelivery to share that across
instances. See docs/sdk/full-stack.md in the Coline repository.
surfaces.site serves an app as a website on its own address,
{name}.coline.space or a custom domain, deployable without an account.
collections declares what visitors may do with each collection
(public, visitor, signedIn, owner, team) and an optional zod
schema enforced on every visitor write. An undeclared collection is
invisible to visitors.surfaces.site.visitors turns on sign-in with Google or Coline and sets
who may use the site: open, signedIn or approved.write; records they create belong
to the visitor. uploads accepts files, endpoints serves app endpoints
on the site's paths, agentTools offers tools to browser agents.@colineapp/sdk/site/react: createSiteHooks<typeof app>()
returns useCollection, usePaginatedCollection and useTool typed from
the app, plus useSession, SignInButton, useUpload and a router.
Without React, @colineapp/sdk/site has createSiteClient, site,
liveCollection, getSession, signIn and signOut.createTestWorkspace(app).asVisitor() and
invokeSiteTool.Start with npx create-coline-app my-site --template site. Guide:
docs.coline.app/sites.