create-coline-app 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# AGENTS.md — building __APP_NAME__ (a Coline App)
|
|
2
|
+
|
|
3
|
+
You are working on a Coline App. Everything an agent needs is in this file —
|
|
4
|
+
no URLs required. The scaffold compiles, tests, and runs as-is; evolve it
|
|
5
|
+
rather than starting from a blank page.
|
|
6
|
+
|
|
7
|
+
## What a Coline App is
|
|
8
|
+
|
|
9
|
+
A Coline App runs INSIDE a Coline workspace on Coline's hosted runtime.
|
|
10
|
+
There are no servers, no database, no auth, no deploys — the platform
|
|
11
|
+
provides all of it through the `coline.*` capability API:
|
|
12
|
+
|
|
13
|
+
- `app.config.ts` — the ENTIRE backend: manifest + tools + file types +
|
|
14
|
+
UI handlers, bundled and executed server-side by Coline.
|
|
15
|
+
- `main.tsx` (optional) — a React UI running in a locked sandbox iframe.
|
|
16
|
+
Create it only if the manifest declares a `react` tier surface.
|
|
17
|
+
- `app.test.ts` — tests against an in-memory fake workspace.
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
- `npm test` — run tests (in-memory workspace; no Coline instance needed)
|
|
22
|
+
- `npm run typecheck` — strict TS; keep it clean
|
|
23
|
+
- `npx coline-app dev --internal` — watch mode; every save pushes a
|
|
24
|
+
`0.0.0-dev` version that hot-swaps the live app (reload the app page)
|
|
25
|
+
- `npx coline-app push --internal` — one-off push as a workspace-internal
|
|
26
|
+
app (auto-approved, private to the workspace)
|
|
27
|
+
- Needs `COLINE_API_KEY` (workspace API key with `apps.write` scope) and
|
|
28
|
+
`COLINE_BASE_URL` (the Coline instance) in the environment.
|
|
29
|
+
|
|
30
|
+
## The manifest (`defineApp`)
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
export default defineApp({
|
|
34
|
+
key: "__APP_KEY__", // immutable, lowercase, hyphens
|
|
35
|
+
name: "__APP_NAME__",
|
|
36
|
+
description: "…",
|
|
37
|
+
permissions: [...], // ONLY what you use — users see this list
|
|
38
|
+
hosting: { default: "coline" },// hosted runtime (no external server)
|
|
39
|
+
surfaces: { home: { tier: "tree" } }, // or { tier: "react" } with main.tsx
|
|
40
|
+
files: [...], // custom file types (optional)
|
|
41
|
+
tools: [...], // Kairo tools (optional)
|
|
42
|
+
handlers: { renderHome: async (context) => uiNode },
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Permissions (request the minimum)
|
|
47
|
+
|
|
48
|
+
| key | grants |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `storage.app` | app key-value + record collections |
|
|
51
|
+
| `files.read` / `files.write` | read / create workspace files |
|
|
52
|
+
| `drives.app` | a private drive owned by the app |
|
|
53
|
+
| `members.read` | list workspace members |
|
|
54
|
+
| `search.index` / `search.query` | add to / query workspace search |
|
|
55
|
+
| `notifications.write` | send notifications |
|
|
56
|
+
| `ai.generate` | call Coline's AI model (200/day) |
|
|
57
|
+
| `ai.tools` | expose tools to Kairo (the workspace AI) |
|
|
58
|
+
| `events.emit` | publish app events |
|
|
59
|
+
| `commands.register` | add command-palette commands |
|
|
60
|
+
| `realtime.subscribe` | live updates in the UI |
|
|
61
|
+
| `network.external` | HTTPS fetch to allowlisted hosts only |
|
|
62
|
+
|
|
63
|
+
## Tools (`defineTool`) — how Kairo operates your app
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const logDecision = defineTool({
|
|
67
|
+
name: "__APP_KEY__.log_decision", // MUST be prefixed with the app key
|
|
68
|
+
description: "…", // what Kairo reads to pick the tool
|
|
69
|
+
input: z.object({ title: z.string().min(1) }), // zod schema
|
|
70
|
+
effect: "write", // read | write | destructive | external
|
|
71
|
+
execute: async (input, context) => {
|
|
72
|
+
// context.coline — the capability client
|
|
73
|
+
// context.workspace — { id, slug, name }
|
|
74
|
+
// context.actor — who invoked it
|
|
75
|
+
return {
|
|
76
|
+
output: { id: "…" }, // structured result for the model
|
|
77
|
+
card: ui.stack([...]), // optional rich card shown in chat
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`effect` is ENFORCED by the runtime, not a hint: a `read` tool cannot
|
|
84
|
+
write. Reads auto-approve; `write` follows the session's permission mode;
|
|
85
|
+
`destructive`/`external` always prompt the user. Be honest.
|
|
86
|
+
|
|
87
|
+
## Storage — `coline.storage`
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
// Key-value
|
|
91
|
+
await context.coline.storage.kv.set("settings", { theme: "auto" });
|
|
92
|
+
const settings = await context.coline.storage.kv.get("settings");
|
|
93
|
+
|
|
94
|
+
// Typed record collections (the main data store)
|
|
95
|
+
interface Decision { title: string; status: "proposed" | "decided"; tags: string[] }
|
|
96
|
+
const decisions = context.coline.storage.collection<Decision>("decisions");
|
|
97
|
+
|
|
98
|
+
const rec = await decisions.insert({ title: "Use Postgres", status: "decided", tags: ["infra"] });
|
|
99
|
+
await decisions.update(rec.id, { status: "proposed" });
|
|
100
|
+
await decisions.get(rec.id); // envelope | null
|
|
101
|
+
await decisions.delete(rec.id);
|
|
102
|
+
const { records, nextCursor } = await decisions.query({
|
|
103
|
+
where: { status: "decided" }, // exact-match on top-level fields
|
|
104
|
+
orderBy: "title", order: "asc", // or omit for newest-first
|
|
105
|
+
limit: 50, cursor: undefined,
|
|
106
|
+
});
|
|
107
|
+
// Every record comes wrapped: { id, createdBy, createdAt, updatedAt, data }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Limits: 5,000 kv keys · 200,000 records per collection · 200 kB per value.
|
|
111
|
+
`query.where` is exact-match only — filter/search in memory after querying
|
|
112
|
+
when you need contains/ranges (keep result sets bounded with `limit`).
|
|
113
|
+
|
|
114
|
+
## UI — two tiers
|
|
115
|
+
|
|
116
|
+
### Tree tier (`ui.*` builders) — renders everywhere incl. mobile & chat cards
|
|
117
|
+
|
|
118
|
+
`ui.stack(children, { direction?, gap? })` · `ui.row(children)` ·
|
|
119
|
+
`ui.heading(text, { level? 1-4 })` · `ui.text(text, { tone? })` ·
|
|
120
|
+
`ui.badge(text, { tone? })` · `ui.card({ title?, description?, children?, footer? })` ·
|
|
121
|
+
`ui.button({ label, action, variant? })` · `ui.divider()` ·
|
|
122
|
+
`ui.emptyState({ title, description? })` · `ui.fileCard({ title, fileId, action? })` ·
|
|
123
|
+
`ui.table({ columns, rows })` · `ui.link({ label, href })` · `ui.image({ src, alt })`
|
|
124
|
+
|
|
125
|
+
Tones: `default | muted | positive | warning | danger`.
|
|
126
|
+
Actions: `actions.openFile(fileId)`, `actions.openAppHome()`,
|
|
127
|
+
`actions.navigate(path)`, `actions.createFile(...)`, `actions.custom(name, payload)`.
|
|
128
|
+
Chat cards (tool results) allow a validated subset — keep them simple
|
|
129
|
+
(stack/heading/text/badge/table/fileCard are all safe). Max 120 nodes.
|
|
130
|
+
|
|
131
|
+
### React tier (`main.tsx` + `@colineapp/ui`) — full React in a sandbox
|
|
132
|
+
|
|
133
|
+
Set `surfaces: { home: { tier: "react" } }` and create `main.tsx`:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { createRoot } from "react-dom/client";
|
|
137
|
+
import { ColineAppProvider, useColine, useColineQuery, useColineContext } from "@colineapp/ui";
|
|
138
|
+
|
|
139
|
+
function Home() {
|
|
140
|
+
const coline = useColine(); // capability client
|
|
141
|
+
const { data, isLoading, error, refetch } = // simple async hook
|
|
142
|
+
useColineQuery(() => coline.storage.collection("decisions").query({ limit: 100 }), []);
|
|
143
|
+
// …render
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const root = document.getElementById("root");
|
|
147
|
+
if (root) createRoot(root).render(<ColineAppProvider><Home /></ColineAppProvider>);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Sandbox rules (enforced, not conventions):
|
|
151
|
+
- NO network (`fetch` is blocked by CSP), no localStorage, no cookies.
|
|
152
|
+
ALL data flows through the `coline.*` client over the capability bridge.
|
|
153
|
+
- Theme tokens follow the host automatically (dark mode included) — use
|
|
154
|
+
the CSS variables / `@colineapp/ui` components, never hardcoded colors.
|
|
155
|
+
- One bundle per app: switch on `useColineContext().surface` if you have
|
|
156
|
+
multiple react surfaces.
|
|
157
|
+
|
|
158
|
+
## Rate limits & budgets
|
|
159
|
+
|
|
160
|
+
120 capability calls burst / 20 per second per install · `net.fetch`
|
|
161
|
+
20 burst / 2 per second · `ai.generate` 200/day · 10,000 executions/day.
|
|
162
|
+
Exceeding any returns a named error visible in the execution log.
|
|
163
|
+
|
|
164
|
+
## Testing (`app.test.ts` pattern)
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { describe, expect, it } from "vitest";
|
|
168
|
+
import { createTestWorkspace } from "@colineapp/sdk/testing";
|
|
169
|
+
import app from "./app.config";
|
|
170
|
+
|
|
171
|
+
it("logs a decision", async () => {
|
|
172
|
+
const workspace = createTestWorkspace(app);
|
|
173
|
+
const result = await workspace.invokeTool("__APP_KEY__.log_decision", {
|
|
174
|
+
title: "Use Postgres",
|
|
175
|
+
});
|
|
176
|
+
expect(result.card).not.toBeNull(); // result: { output, card }
|
|
177
|
+
// Also available: workspace.renderHome(), workspace.files.byType(...),
|
|
178
|
+
// workspace.renderFileSurface(...), workspace.fireSchedule(...)
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Debugging
|
|
183
|
+
|
|
184
|
+
- Tool throws → the error lands in the app's execution log (developer
|
|
185
|
+
console → your app → Executions) with the message redacted of secrets.
|
|
186
|
+
- Permission denied → the manifest doesn't declare it, the user didn't
|
|
187
|
+
grant it, or the tool's `effect` is too low for the operation.
|
|
188
|
+
- UI blank → check the browser console for the sandbox frame; the
|
|
189
|
+
capability bridge logs denials with reason codes.
|
|
190
|
+
|
|
191
|
+
## Style rules for the UI
|
|
192
|
+
|
|
193
|
+
- No eyebrow labels / kicker pills above headings. No monospace accent
|
|
194
|
+
fonts. Solid font weights (500–600).
|
|
195
|
+
- Explicit loading, empty, and error states for every async view.
|
|
196
|
+
- Dark mode must work — it's automatic if you use theme tokens.
|
|
@@ -65,27 +65,43 @@ export default defineApp({
|
|
|
65
65
|
files: [noteFileType],
|
|
66
66
|
tools: [createNote],
|
|
67
67
|
handlers: {
|
|
68
|
+
// The starter home screen — replace this with your app's real UI.
|
|
68
69
|
renderHome: async (context) => {
|
|
69
70
|
const { files } = await context.coline.files.list({
|
|
70
71
|
typeKey: "__APP_KEY__.note",
|
|
71
72
|
});
|
|
72
|
-
return ui.stack(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
73
|
+
return ui.stack(
|
|
74
|
+
[
|
|
75
|
+
ui.heading("__APP_NAME__ is running", { level: 1 }),
|
|
76
|
+
ui.text("Edit app.config.ts and save — dev mode hot-swaps this screen instantly.", {
|
|
77
|
+
tone: "muted",
|
|
78
|
+
}),
|
|
79
|
+
ui.card({
|
|
80
|
+
title: "Try the example tool",
|
|
81
|
+
description:
|
|
82
|
+
"This starter ships one Kairo tool and one file type. Ask Kairo: “create a note called Hello”.",
|
|
83
|
+
children:
|
|
84
|
+
files.length === 0
|
|
85
|
+
? [ui.text("Notes it creates will show up here.", { tone: "muted" })]
|
|
86
|
+
: files.map((file) =>
|
|
87
|
+
ui.fileCard({
|
|
88
|
+
title: file.name,
|
|
89
|
+
fileId: file.fileId,
|
|
90
|
+
action: actions.openFile(file.fileId),
|
|
91
|
+
}),
|
|
92
|
+
),
|
|
93
|
+
}),
|
|
94
|
+
ui.card({
|
|
95
|
+
title: "Where things live",
|
|
96
|
+
children: [
|
|
97
|
+
ui.text("app.config.ts — manifest, tools, file types, this screen"),
|
|
98
|
+
ui.text("app.test.ts — tests against an in-memory workspace"),
|
|
99
|
+
ui.text("Docs: /developers/docs · API reference: /developers/docs/reference"),
|
|
100
|
+
],
|
|
101
|
+
}),
|
|
102
|
+
],
|
|
103
|
+
{ gap: "lg" },
|
|
104
|
+
);
|
|
89
105
|
},
|
|
90
106
|
},
|
|
91
107
|
});
|