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 open the Developer Console in your workspace settings. |
| 98 |
| 99 | 1. Go to **Apps → Add App** |
| 100 | 2. Enter your app's base URL (e.g. `http://localhost:3100`) |
| 101 | 3. Click **Import from app** — the console fetches your manifest from `GET /coline/manifest` |
| 102 | 4. Review permissions, file types, and notification channels, then submit |
| 103 |
| 104 | The console pulls everything directly from the SDK — no copy-pasting JSON. |
| 105 |
| 106 | As the app owner, you can install your own unpublished versions directly into your workspace for development. For store-listed apps, submit the version for review from the app detail page in the console. |
| 107 |
| 108 | --- |
| 109 |
| 110 | ## Routes Your App Exposes |
| 111 |
| 112 | `createHandler()` handles these automatically: |
| 113 |
| 114 | | Route | Method | Purpose | |
| 115 | |-------|--------|---------| |
| 116 | | `/coline/manifest` | GET | Returns your app manifest (used by the developer console) | |
| 117 | | `/coline/render/home` | POST | Renders your app's home screen | |
| 118 | | `/coline/render/file` | POST | Renders a file owned by your app | |
| 119 | | `/coline/actions` | POST | Handles UI action callbacks (button clicks, etc.) | |
| 120 | | `/coline/events` | POST | Receives webhook events | |
| 121 |
| 122 | All POST routes are verified using your delivery secret. |
| 123 |
| 124 | --- |
| 125 |
| 126 | ## API Client |
| 127 |
| 128 | For server-to-server calls (creating files, sending notifications, etc.): |
| 129 |
| 130 | ```ts |
| 131 | import { ColineApiClient } from "@colineapp/sdk"; |
| 132 |
| 133 | const client = new ColineApiClient({ |
| 134 | baseUrl: "https://coline.app", |
| 135 | apiKey: process.env.COLINE_API_KEY!, |
| 136 | }); |
| 137 |
| 138 | // Fluent scoped handles |
| 139 | const ws = client.workspace("acme"); |
| 140 | const app = ws.app("com.acme.todos"); |
| 141 |
| 142 | // Create a file |
| 143 | const { file } = await app.createFile({ |
| 144 | name: "Buy milk", |
| 145 | typeKey: "todo", |
| 146 | document: { status: "open" }, |
| 147 | }); |
| 148 |
| 149 | // Update it |
| 150 | await app.file(file.id).updateDocument({ status: "done" }); |
| 151 |
| 152 | // Send a notification |
| 153 | await app.createNotification({ |
| 154 | channelKey: "updates", |
| 155 | typeKey: "todo.completed", |
| 156 | title: "Todo completed", |
| 157 | body: "Buy milk is done!", |
| 158 | recipients: [{ userId: "user_123" }], |
| 159 | }); |
| 160 | ``` |
| 161 |
| 162 | ## Webhooks |
| 163 |
| 164 | Verify and handle webhook events with typed payloads: |
| 165 |
| 166 | ```ts |
| 167 | import { webhooks } from "@colineapp/sdk"; |
| 168 |
| 169 | // In your webhook handler: |
| 170 | const event = await webhooks.constructEvent(rawBody, request.headers, secret); |
| 171 |
| 172 | switch (event.type) { |
| 173 | case "message.created": |
| 174 | console.log(event.data.plaintext); |
| 175 | break; |
| 176 | case "task.created": |
| 177 | console.log(event.data.title, event.data.priority); |
| 178 | break; |
| 179 | } |
| 180 | ``` |
| 181 |
| 182 | Or use `createHandler({ onWebhook })` for automatic verification. |
| 183 |
| 184 | ## UI Components |
| 185 |
| 186 | Build native Coline UI with the `ui` helper. Components render directly inside Coline's workspace — no iframe, no custom CSS. |
| 187 |
| 188 | ### Text & Typography |
| 189 |
| 190 | ```ts |
| 191 | import { ui, actions } from "@colineapp/sdk"; |
| 192 |
| 193 | ui.heading("Title", { level: 1 }); // h1–h4 |
| 194 | ui.text("Body text"); |
| 195 | ui.text("Error occurred", { tone: "danger" }); |
| 196 | ui.badge("Active", { tone: "positive" }); // default, muted, positive, warning, danger |
| 197 | ui.link("Read more", "https://docs.example.com", { external: true }); |
| 198 | ``` |
| 199 |
| 200 | ### Layout |
| 201 |
| 202 | ```ts |
| 203 | ui.stack([child1, child2], { gap: "md" }); // vertical |
| 204 | ui.row([badge1, badge2], { gap: "sm" }); // horizontal |
| 205 | ui.divider(); |
| 206 | ``` |
| 207 |
| 208 | ### Interactive |
| 209 |
| 210 | ```ts |
| 211 | ui.button("Save", { action: actions.custom("my-app.save") }); |
| 212 | ui.button("Open", { action: actions.openFile("file_123") }); |
| 213 | ui.button("Create", { |
| 214 | action: actions.createFile({ name: "New Todo", typeKey: "todo" }), |
| 215 | }); |
| 216 | ``` |
| 217 |
| 218 | ### Form Inputs |
| 219 |
| 220 | ```ts |
| 221 | ui.input({ name: "title", label: "Title", placeholder: "Enter a title…" }); |
| 222 | ui.input({ name: "notes", label: "Notes", type: "textarea" }); |
| 223 | ui.input({ name: "amount", label: "Amount", type: "number", required: true }); |
| 224 | ui.select({ |
| 225 | name: "priority", |
| 226 | label: "Priority", |
| 227 | options: [ |
| 228 | { value: "low", label: "Low" }, |
| 229 | { value: "medium", label: "Medium" }, |
| 230 | { value: "high", label: "High" }, |
| 231 | ], |
| 232 | defaultValue: "medium", |
| 233 | }); |
| 234 | ``` |
| 235 |
| 236 | ### Data Display |
| 237 |
| 238 | ```ts |
| 239 | ui.table({ |
| 240 | columns: [ |
| 241 | { key: "name", label: "Name" }, |
| 242 | { key: "status", label: "Status" }, |
| 243 | { key: "date", label: "Date", align: "right" }, |
| 244 | ], |
| 245 | rows: [ |
| 246 | { name: "Sprint planning", status: "Done", date: "Jan 15" }, |
| 247 | { name: "Design review", status: "Open", date: "Jan 20" }, |
| 248 | ], |
| 249 | }); |
| 250 |
| 251 | ui.codeBlock("const x = 42;", { language: "typescript" }); |
| 252 | ``` |
| 253 |
| 254 | ### Media & References |
| 255 |
| 256 | ```ts |
| 257 | ui.image({ src: "https://...", alt: "Screenshot", width: 800, height: 400 }); |
| 258 | ui.fileCard({ title: "Q4 Report", subtitle: "Updated yesterday", fileId: "file_123" }); |
| 259 | ui.userChip({ label: "Alice", userId: "user_123" }); |
| 260 | ui.emptyState({ |
| 261 | title: "No items yet", |
| 262 | description: "Create your first item.", |
| 263 | action: actions.custom("my-app.create"), |
| 264 | }); |
| 265 | ``` |
| 266 |
| 267 | ## Tab Autocomplete API |
| 268 |
| 269 | The Tab API provides OpenAI-compatible completions for inline autocomplete. Authenticate with a workspace API key that has the `ai.invoke` scope. |
| 270 |
| 271 | ```ts |
| 272 | import { ColineApiClient } from "@colineapp/sdk"; |
| 273 |
| 274 | const client = new ColineApiClient({ |
| 275 | baseUrl: "https://coline.app", |
| 276 | apiKey: process.env.COLINE_API_KEY!, |
| 277 | }); |
| 278 |
| 279 | // Stream text completions |
| 280 | for await (const text of client.streamTabText({ |
| 281 | tab_context: { |
| 282 | surface: "docs", |
| 283 | workspace_slug: "acme", |
| 284 | entity_id: "doc_123", |
| 285 | active_text_before_cursor: "The quick brown ", |
| 286 | }, |
| 287 | })) { |
| 288 | process.stdout.write(text); |
| 289 | } |
| 290 | ``` |
| 291 |
| 292 | Supported surfaces: `notes`, `docs`, `messages`, `tasks`, `calendar`. |
| 293 |
| 294 | ## Login with Coline (OAuth) |
| 295 |
| 296 | Add "Login with Coline" to your app using standard OAuth 2.0 Authorization Code + PKCE. |
| 297 |
| 298 | ```ts |
| 299 | import { ColineApiClient, createPkcePair } from "@colineapp/sdk"; |
| 300 |
| 301 | const client = new ColineApiClient({ baseUrl: "https://coline.app" }); |
| 302 |
| 303 | // 1. Build the authorize URL |
| 304 | const pkce = await createPkcePair(); |
| 305 | const authorizeUrl = client.buildLoginWithColineAuthorizeUrl({ |
| 306 | clientId: "col_client_...", |
| 307 | redirectUri: "https://your-app.com/callback", |
| 308 | scope: "openid profile email", |
| 309 | codeChallenge: pkce.challenge, |
| 310 | codeChallengeMethod: pkce.method, |
| 311 | }); |
| 312 | // Redirect the user to authorizeUrl |
| 313 |
| 314 | // 2. Exchange the code (in your callback handler) |
| 315 | const token = await client.exchangeOAuthCode({ |
| 316 | clientId: "col_client_...", |
| 317 | clientSecret: "col_secret_...", |
| 318 | code: callbackCode, |
| 319 | redirectUri: "https://your-app.com/callback", |
| 320 | codeVerifier: pkce.verifier, |
| 321 | }); |
| 322 |
| 323 | // 3. Get user info |
| 324 | const user = await client.getOAuthUserInfo(token.access_token); |
| 325 | console.log(user.email, user.name); |
| 326 | ``` |
| 327 |
| 328 | Create OAuth clients from the Developer Console under **Login with Coline**. |
| 329 |
| 330 | ## Raw OpenAPI Client |
| 331 |
| 332 | For direct spec-driven access: |
| 333 |
| 334 | ```ts |
| 335 | import { createAppPlatformClient } from "@colineapp/sdk"; |
| 336 |
| 337 | const client = createAppPlatformClient({ |
| 338 | baseUrl: "https://coline.app", |
| 339 | apiKey: process.env.COLINE_API_KEY!, |
| 340 | }); |
| 341 |
| 342 | const { data } = await client.GET("/api/v1/apps"); |
| 343 | ``` |
| 344 |
| 345 | ## Regenerate OpenAPI Types |
| 346 |
| 347 | ```bash |
| 348 | pnpm sdk:generate:openapi |
| 349 | ``` |
| 350 |