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.
| 6 |
| 7 | ```bash |
| 8 | npm install @colineapp/sdk |
| 9 | ``` |
| 10 |
| 11 | ## Build a Coline App in 5 Minutes |
| 12 |
| 13 | ### 1. Define your app |
| 14 |
| 15 | ```ts |
| 16 | import { ColineApp, ui, actions, createHandler } from "@colineapp/sdk"; |
| 17 |
| 18 | const app = new ColineApp({ |
| 19 | key: "com.acme.todos", |
| 20 | name: "Acme Todos", |
| 21 | description: "Track todos inside Coline.", |
| 22 | permissions: ["app.home.read", "files.read", "files.write"], |
| 23 | hosting: { mode: "external", baseUrl: "https://your-app.example.com" }, |
| 24 | }); |
| 25 |
| 26 | // Define a file type your app owns |
| 27 | app.defineFileType({ |
| 28 | typeKey: "todo", |
| 29 | name: "Todo", |
| 30 | description: "A todo item", |
| 31 | storage: "coline_document", |
| 32 | indexable: true, |
| 33 | homeRenderMode: "cached", |
| 34 | }); |
| 35 |
| 36 | // Render the app's home screen |
| 37 | app.onHomeRender((ctx) => |
| 38 | ui.stack([ |
| 39 | ui.heading("Todos"), |
| 40 | ui.text(`Workspace: ${ctx.workspace.name}`), |
| 41 | ui.button("New Todo", { |
| 42 | action: actions.createFile({ |
| 43 | name: "Untitled Todo", |
| 44 | typeKey: "todo", |
| 45 | document: { status: "open" }, |
| 46 | }), |
| 47 | }), |
| 48 | ]), |
| 49 | ); |
| 50 |
| 51 | // Render an individual file |
| 52 | app.onFileRender((ctx) => |
| 53 | ui.stack([ |
| 54 | ui.heading(ctx.file.title ?? "Todo"), |
| 55 | ui.badge(String(ctx.document["status"] ?? "open")), |
| 56 | ]), |
| 57 | ); |
| 58 | ``` |
| 59 |
| 60 | ### 2. Serve it |
| 61 |
| 62 | The SDK gives you one handler that routes all Coline traffic: |
| 63 |
| 64 | ```ts |
| 65 | const handler = createHandler({ |
| 66 | app, |
| 67 | secret: process.env.COLINE_DELIVERY_SECRET!, |
| 68 | onWebhook: (event) => { |
| 69 | console.log("Webhook:", event.type); |
| 70 | }, |
| 71 | }); |
| 72 | ``` |
| 73 |
| 74 | Mount it on any framework: |
| 75 |
| 76 | ```ts |
| 77 | // Hono |
| 78 | const server = new Hono(); |
| 79 | server.all("/coline/*", (c) => handler(c.req.raw)); |
| 80 |
| 81 | // Next.js App Router (app/coline/[...path]/route.ts) |
| 82 | export const GET = handler; |
| 83 | export const POST = handler; |
| 84 |
| 85 | // Cloudflare Workers / Bun / Deno |
| 86 | export default { fetch: handler }; |
| 87 |
| 88 | // Express (with raw body parsing) |
| 89 | app.all("/coline/*", async (req, res) => { |
| 90 | const response = await handler(toWebRequest(req)); |
| 91 | res.status(response.status).json(await response.json()); |
| 92 | }); |
| 93 | ``` |
| 94 |
| 95 | ### 3. Register on Coline |
| 96 |
| 97 | Start your app, then go to [coline.app/developers/apps/new](https://coline.app/developers/apps/new). |
| 98 |
| 99 | 1. Enter your app's base URL (e.g. `http://localhost:3100`) |
| 100 | 2. Click **Import from app** — the console fetches your manifest from `GET /coline/manifest` |
| 101 | 3. Review everything and submit |
| 102 |
| 103 | That's it. The console pulls your manifest, permissions, file types, and notification channels directly from the SDK — no copy-pasting JSON. |
| 104 |
| 105 | For store-listed apps, submit for review from the app detail page. For private apps, install directly into your workspace. |
| 106 |
| 107 | --- |
| 108 |
| 109 | ## Routes Your App Exposes |
| 110 |
| 111 | `createHandler()` handles these automatically: |
| 112 |
| 113 | | Route | Method | Purpose | |
| 114 | |-------|--------|---------| |
| 115 | | `/coline/manifest` | GET | Returns your app manifest (used by the developer console) | |
| 116 | | `/coline/render/home` | POST | Renders your app's home screen | |
| 117 | | `/coline/render/file` | POST | Renders a file owned by your app | |
| 118 | | `/coline/actions` | POST | Handles UI action callbacks (button clicks, etc.) | |
| 119 | | `/coline/events` | POST | Receives webhook events | |
| 120 |
| 121 | All POST routes are verified using your delivery secret. |
| 122 |
| 123 | --- |
| 124 |
| 125 | ## API Client |
| 126 |
| 127 | For server-to-server calls (creating files, sending notifications, etc.): |
| 128 |
| 129 | ```ts |
| 130 | import { ColineApiClient } from "@colineapp/sdk"; |
| 131 |
| 132 | const client = new ColineApiClient({ |
| 133 | baseUrl: "https://coline.app", |
| 134 | apiKey: process.env.COLINE_API_KEY!, |
| 135 | }); |
| 136 |
| 137 | // Fluent scoped handles |
| 138 | const ws = client.workspace("acme"); |
| 139 | const app = ws.app("com.acme.todos"); |
| 140 |
| 141 | // Create a file |
| 142 | const { file } = await app.createFile({ |
| 143 | name: "Buy milk", |
| 144 | typeKey: "todo", |
| 145 | document: { status: "open" }, |
| 146 | }); |
| 147 |
| 148 | // Update it |
| 149 | await app.file(file.id).updateDocument({ status: "done" }); |
| 150 |
| 151 | // Send a notification |
| 152 | await app.sendNotification({ |
| 153 | channelKey: "updates", |
| 154 | title: "Todo completed", |
| 155 | body: "Buy milk is done!", |
| 156 | recipients: [{ userId: "user_123" }], |
| 157 | }); |
| 158 | ``` |
| 159 |
| 160 | ## Webhooks |
| 161 |
| 162 | Verify and handle webhook events with typed payloads: |
| 163 |
| 164 | ```ts |
| 165 | import { webhooks } from "@colineapp/sdk"; |
| 166 |
| 167 | // In your webhook handler: |
| 168 | const event = await webhooks.constructEvent(rawBody, request.headers, secret); |
| 169 |
| 170 | switch (event.type) { |
| 171 | case "message.created": |
| 172 | console.log(event.data.plaintext); |
| 173 | break; |
| 174 | case "task.created": |
| 175 | console.log(event.data.title, event.data.priority); |
| 176 | break; |
| 177 | } |
| 178 | ``` |
| 179 |
| 180 | Or use `createHandler({ onWebhook })` for automatic verification. |
| 181 |
| 182 | ## UI Components |
| 183 |
| 184 | Build native Coline UI with the `ui` helper. Components render directly inside Coline's workspace — no iframe, no custom CSS. |
| 185 |
| 186 | ### Text & Typography |
| 187 |
| 188 | ```ts |
| 189 | import { ui, actions } from "@colineapp/sdk"; |
| 190 |
| 191 | ui.heading("Title", { level: 1 }); // h1–h4 |
| 192 | ui.text("Body text"); |
| 193 | ui.text("Error occurred", { tone: "danger" }); |
| 194 | ui.badge("Active", { tone: "positive" }); // default, muted, positive, warning, danger |
| 195 | ui.link("Read more", "https://docs.example.com", { external: true }); |
| 196 | ``` |
| 197 |
| 198 | ### Layout |
| 199 |
| 200 | ```ts |
| 201 | ui.stack([child1, child2], { gap: "md" }); // vertical |
| 202 | ui.row([badge1, badge2], { gap: "sm" }); // horizontal |
| 203 | ui.divider(); |
| 204 | ``` |
| 205 |
| 206 | ### Interactive |
| 207 |
| 208 | ```ts |
| 209 | ui.button("Save", { action: actions.custom("my-app.save") }); |
| 210 | ui.button("Open", { action: actions.openFile("file_123") }); |
| 211 | ui.button("Create", { |
| 212 | action: actions.createFile({ name: "New Todo", typeKey: "todo" }), |
| 213 | }); |
| 214 | ``` |
| 215 |
| 216 | ### Form Inputs |
| 217 |
| 218 | ```ts |
| 219 | ui.input({ name: "title", label: "Title", placeholder: "Enter a title…" }); |
| 220 | ui.input({ name: "notes", label: "Notes", type: "textarea" }); |
| 221 | ui.input({ name: "amount", label: "Amount", type: "number", required: true }); |
| 222 | ui.select({ |
| 223 | name: "priority", |
| 224 | label: "Priority", |
| 225 | options: [ |
| 226 | { value: "low", label: "Low" }, |
| 227 | { value: "medium", label: "Medium" }, |
| 228 | { value: "high", label: "High" }, |
| 229 | ], |
| 230 | defaultValue: "medium", |
| 231 | }); |
| 232 | ``` |
| 233 |
| 234 | ### Data Display |
| 235 |
| 236 | ```ts |
| 237 | ui.table({ |
| 238 | columns: [ |
| 239 | { key: "name", label: "Name" }, |
| 240 | { key: "status", label: "Status" }, |
| 241 | { key: "date", label: "Date", align: "right" }, |
| 242 | ], |
| 243 | rows: [ |
| 244 | { name: "Sprint planning", status: "Done", date: "Jan 15" }, |
| 245 | { name: "Design review", status: "Open", date: "Jan 20" }, |
| 246 | ], |
| 247 | }); |
| 248 |
| 249 | ui.codeBlock("const x = 42;", { language: "typescript" }); |
| 250 | ``` |
| 251 |
| 252 | ### Media & References |
| 253 |
| 254 | ```ts |
| 255 | ui.image({ src: "https://...", alt: "Screenshot", width: 800, height: 400 }); |
| 256 | ui.fileCard({ title: "Q4 Report", subtitle: "Updated yesterday", fileId: "file_123" }); |
| 257 | ui.userChip({ label: "Alice", userId: "user_123" }); |
| 258 | ui.emptyState({ |
| 259 | title: "No items yet", |
| 260 | description: "Create your first item.", |
| 261 | action: actions.custom("my-app.create"), |
| 262 | }); |
| 263 | ``` |
| 264 |
| 265 | ## Raw OpenAPI Client |
| 266 |
| 267 | For direct spec-driven access: |
| 268 |
| 269 | ```ts |
| 270 | import { createAppPlatformClient } from "@colineapp/sdk"; |
| 271 |
| 272 | const client = createAppPlatformClient({ |
| 273 | baseUrl: "https://coline.app", |
| 274 | apiKey: process.env.COLINE_API_KEY!, |
| 275 | }); |
| 276 |
| 277 | const { data } = await client.GET("/api/v1/apps"); |
| 278 | ``` |
| 279 |
| 280 | ## Regenerate OpenAPI Types |
| 281 |
| 282 | ```bash |
| 283 | pnpm sdk:generate:openapi |
| 284 | ``` |
| 285 |