@agent-native/core 0.135.0 → 0.135.2

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.
Files changed (50) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/docs/content/getting-started-actions.mdx +235 -0
  4. package/corpus/core/docs/content/getting-started-database.mdx +253 -0
  5. package/corpus/core/docs/content/getting-started-pages.mdx +190 -0
  6. package/corpus/core/docs/content/getting-started.mdx +57 -613
  7. package/corpus/core/package.json +1 -1
  8. package/corpus/core/src/sharing/access.ts +44 -2
  9. package/corpus/templates/clips/actions/add-comment.ts +6 -2
  10. package/corpus/templates/clips/app/components/player/comments-panel.tsx +9 -2
  11. package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +44 -27
  12. package/corpus/templates/clips/app/components/player/scrubber.tsx +10 -2
  13. package/corpus/templates/clips/app/components/player/video-player.tsx +4 -0
  14. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +22 -15
  15. package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -0
  16. package/corpus/templates/content/actions/_database-utils.ts +113 -4
  17. package/corpus/templates/content/actions/get-document.ts +84 -3
  18. package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +9 -2
  19. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +29 -13
  20. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +5 -12
  21. package/corpus/templates/content/app/components/editor/body-hydration.ts +12 -6
  22. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2 -3
  23. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -12
  24. package/corpus/templates/content/app/hooks/use-content-database.ts +16 -27
  25. package/corpus/templates/content/app/hooks/use-create-page.ts +3 -6
  26. package/corpus/templates/content/app/hooks/use-document-properties.ts +9 -16
  27. package/corpus/templates/content/app/hooks/use-document-versions.ts +3 -3
  28. package/corpus/templates/content/app/hooks/use-documents.ts +85 -29
  29. package/corpus/templates/content/app/hooks/use-notion.ts +15 -13
  30. package/corpus/templates/content/app/i18n-data.ts +32 -1
  31. package/corpus/templates/content/app/lib/document-query.ts +36 -0
  32. package/corpus/templates/content/changelog/2026-08-02-pages-opened-from-a-database-now-keep-that-database-s-fields.md +6 -0
  33. package/corpus/templates/content/server/lib/document-context.ts +11 -6
  34. package/corpus/templates/content/shared/api.ts +8 -0
  35. package/dist/collab/struct-routes.d.ts +1 -1
  36. package/dist/notifications/routes.d.ts +3 -3
  37. package/dist/observability/routes.d.ts +1 -1
  38. package/dist/progress/routes.d.ts +1 -1
  39. package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
  40. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  41. package/dist/secrets/routes.d.ts +9 -9
  42. package/dist/sharing/access.d.ts.map +1 -1
  43. package/dist/sharing/access.js +32 -2
  44. package/dist/sharing/access.js.map +1 -1
  45. package/docs/content/getting-started-actions.mdx +235 -0
  46. package/docs/content/getting-started-database.mdx +253 -0
  47. package/docs/content/getting-started-pages.mdx +190 -0
  48. package/docs/content/getting-started.mdx +57 -613
  49. package/package.json +1 -1
  50. package/src/sharing/access.ts +44 -2
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: "Getting Started"
3
- description: "Create a chat-first agentic app, add an action, render structured results inline, then grow into a persistent page the agent can open."
3
+ description: "Create a Chat app and connect an AI engine so your agent can respond."
4
4
  ---
5
5
 
6
6
  # Getting Started
@@ -14,23 +14,34 @@ hands-on build path.
14
14
 
15
15
  The quickest way in is the **Chat template**: a minimal app that gives you a
16
16
  working AI chat interface, durable threads, auth, and an `actions/` directory
17
- ready to extend. It's the foundation most Agent-Native apps grow from.
17
+ ready to extend. This guide is split into four short pages so you can stop at
18
+ any point with a working app.
18
19
 
19
- The first useful path is:
20
+ <Steps>
20
21
 
21
- 1. Create a chat app.
22
- 2. Connect an AI engine so the agent can respond.
23
- 3. Add one action.
24
- 4. Render the action result inline in chat.
25
- 5. Persist data in SQL.
26
- 6. Add a page the agent can open when visual inspection is better than another
27
- paragraph in the transcript.
22
+ ### Create a chat app and connect AI
28
23
 
29
- Want a complete domain app instead? Clone a rich template such as
30
- [Mail](/docs/template-mail), [Calendar](/docs/template-calendar),
31
- [Forms](/docs/template-forms), [Analytics](/docs/template-analytics), or
32
- [Plan](/docs/template-plan). Want no browser UI yet? See
33
- [Automation-First Apps](/docs/pure-agent-apps) after this tutorial.
24
+ Scaffold the Chat template, install dependencies, start the dev server, and
25
+ connect an AI engine. You'll have a working agent chat in under five minutes.
26
+ **You are here.**
27
+
28
+ ### Add an action
29
+
30
+ Define your first typed action — a text analyzer — and register a React
31
+ renderer so the result appears as a bar chart directly inside the chat
32
+ transcript.
33
+
34
+ ### Persist data in SQL
35
+
36
+ Wire up a database plugin, define a schema, and add read/write actions so the
37
+ agent can save and recall results across conversations.
38
+
39
+ ### Add a page
40
+
41
+ Build a React route that lists saved analyses, then extend the sidebar so users
42
+ and the agent can navigate to it.
43
+
44
+ </Steps>
34
45
 
35
46
  ## 1. Create a chat app {#create-your-app}
36
47
 
@@ -55,13 +66,16 @@ unavailable`. These are a normal race condition during the initial boot and
55
66
  resolve on their own. The app is ready once **VITE ready** displays in the
56
67
  terminal output.
57
68
 
58
- When the app starts, you might find yourself on the login screen rather than in the app. In this case, just sign up with a made up login to satisfy the login screen.
59
- For local development, no email verification is actually required.
69
+ When the app starts, you might find yourself on the login screen rather than in the app. In this case, just sign up with a made-up login to satisfy the login screen. For local development, no email verification is actually required.
70
+
71
+ <Callout tone="info">
60
72
 
61
- > **Want to attach a real debugger?** Run `agent-native dev --inspect` (or
62
- > `--inspect-brk[=<port>]`) instead of `pnpm dev` to attach the Node inspector
63
- > to the dev server, on port `9229` by default. Override the dev runner with
64
- > the `NITRO_DEV_RUNNER` env var if your setup needs a different one.
73
+ **Want to attach a real debugger?** Run `agent-native dev --inspect` (or
74
+ `--inspect-brk[=<port>]`) instead of `pnpm dev` to attach the Node inspector
75
+ to the dev server, on port `9229` by default. Override the dev runner with
76
+ the `NITRO_DEV_RUNNER` env var if your setup needs a different one.
77
+
78
+ </Callout>
65
79
 
66
80
  ### If you want a different type of app
67
81
 
@@ -98,610 +112,40 @@ echo "ANTHROPIC_API_KEY=sk-ant-..." >> .env
98
112
  Restart the dev server. Once an AI engine is connected, the Setup panel
99
113
  hides itself and the agent is ready to chat.
100
114
 
101
- > **Blank screen?** Create a `.env` file in your `my-app/` directory with
102
- > `ANTHROPIC_API_KEY=sk-ant-...`, then restart `pnpm dev`. The in-app Setup
103
- > panel only appears once the app has loaded, so a missing key that prevents
104
- > the app from rendering needs to be fixed via the environment variable rather than
105
- > the UI.
106
-
107
- ## 3. Add an action {#add-an-action}
108
-
109
- An action is a typed operation that both your agent and your UI can call. It's
110
- how the agent does things in your app. Actions live in the `actions/` directory
111
- and can be triggered from chat, from React components, from the CLI, or on a
112
- schedule. You define them once and call them from anywhere.
113
-
114
- ### Try the starter action
115
-
116
- The Chat template includes a `hello` action at `actions/hello.ts`:
117
-
118
- ```ts filename="actions/hello.ts"
119
- import { defineAction } from "@agent-native/core/action";
120
- import { z } from "zod";
121
-
122
- export default defineAction({
123
- description: "Return a friendly greeting.",
124
- schema: z.object({
125
- name: z.string().default("world").describe("Name to greet"),
126
- }),
127
- http: { method: "GET" },
128
- run: async ({ name }) => {
129
- return { message: `Hello, ${name}!` };
130
- },
131
- });
132
- ```
133
-
134
- Run it from the terminal (inside your `my-app/` directory):
135
-
136
- ```bash
137
- pnpm action hello --name Alice
138
- ```
139
-
140
- Or open your app at `http://localhost:8080` and ask the agent in the chat there:
141
-
142
- > Use the hello action with the name Alice.
143
-
144
- ### Add your own action
145
-
146
- Replace the starter action with the first real operation in your domain. This example
147
- counts words, sentences, and paragraphs in any text you pass it. It computes
148
- everything locally, so there's nothing to configure and no external service to connect.
149
-
150
- Create a new file called `analyze-text.ts` in your `actions/` directory:
151
-
152
- ```ts filename="actions/analyze-text.ts"
153
- import { defineAction } from "@agent-native/core/action";
154
- import { z } from "zod";
155
-
156
- const textStatsSchema = z.object({
157
- title: z.string(),
158
- points: z.array(z.object({ label: z.string(), value: z.number() })),
159
- });
160
-
161
- export default defineAction({
162
- description: "Count words, sentences, and paragraphs in a block of text.",
163
- schema: z.object({
164
- text: z
165
- .string()
166
- .default(
167
- "The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.",
168
- ),
169
- }),
170
- outputSchema: textStatsSchema,
171
- chatUI: {
172
- renderer: "text.stats-chart",
173
- title: "Text stats",
174
- },
175
- readOnly: true,
176
- run: async ({ text }) => ({
177
- title: "Text statistics",
178
- points: [
179
- { label: "Characters", value: text.length },
180
- { label: "Words", value: text.split(/\s+/).filter(Boolean).length },
181
- {
182
- label: "Sentences",
183
- value: text.split(/[.!?]+/).filter(Boolean).length,
184
- },
185
- {
186
- label: "Paragraphs",
187
- value: text.split(/\n\n+/).filter(Boolean).length,
188
- },
189
- ],
190
- }),
191
- });
192
- ```
193
-
194
- Try it from the terminal:
195
-
196
- ```bash
197
- pnpm action analyze-text --text "Hello world. How are you today?"
198
- ```
199
-
200
- Or open your app at `http://localhost:8080` and ask the agent in the chat there:
201
-
202
- > Run the analyze-text action on "Hello world. How are you today?"
203
-
204
- #### Define once, call from anywhere
205
-
206
- This action is now reachable from chat, React hooks, CLI, HTTP, MCP, A2A,
207
- scheduled jobs, and webhooks.
208
-
209
- TIP: Any time you want the agent to call a specific action without ambiguity, phrasing it as "Run the `<action-name>` action" is most reliable. Natural-language prompts work well once the agent has enough context about your app's domain. For a brand-new app with no data or context yet, explicit is safer.
210
-
211
- ## 4. Render the result inline {#render-inline}
212
-
213
- When the agent runs `analyze-text`, it returns structured data: a title and an
214
- array of counts. By default the agent will describe that data in prose: "The
215
- text has 9 words, 2 sentences..." and so on. That works, but you can
216
- also render the result as a real UI component (a bar chart, a table, a card)
217
- directly inside the chat transcript, right where the agent responded.
218
-
219
- This is what `chatUI.renderer` in the action does. It's a label that says "when
220
- this action's result appears in chat, hand it to this React component instead of
221
- summarizing it in text." The component receives the validated action output as
222
- props and renders whatever you want.
223
-
224
- In the next step, you'll create `app/chat-renderers.tsx`, but first, add one import line
225
- to `app/root.tsx` so it runs on startup:
226
-
227
- ```ts filename="app/root.tsx"
228
- import "./chat-renderers";
229
- ```
230
-
231
- Add it alongside your other imports at the top of the file. That's the only
232
- change to `root.tsx`. The import just ensures the file runs and registers the
233
- renderer. Now create the renderer file:
234
-
235
- ```tsx filename="app/chat-renderers.tsx"
236
- import {
237
- registerActionChatRenderer,
238
- type ToolRendererProps,
239
- } from "@agent-native/core/client/chat";
240
-
241
- type TextStatsResult = {
242
- title: string;
243
- points: Array<{ label: string; value: number }>;
244
- };
245
-
246
- const MAX_BAR_PX = 80;
247
-
248
- function TextStatsChart({ context }: ToolRendererProps) {
249
- const result = context.resultJson as TextStatsResult;
250
- const max = Math.max(...result.points.map((point) => point.value), 1);
251
- return (
252
- <section className="rounded-lg border bg-card p-4">
253
- <h3 className="text-sm font-medium">{result.title}</h3>
254
- <div className="mt-4 flex items-end gap-2">
255
- {result.points.map((point) => (
256
- <div
257
- key={point.label}
258
- className="flex flex-1 flex-col items-center gap-2"
259
- >
260
- <div
261
- className="w-full rounded-t bg-blue-500"
262
- style={{
263
- height: `${Math.max(Math.round((point.value / max) * MAX_BAR_PX), 2)}px`,
264
- }}
265
- />
266
- <span className="text-xs text-muted-foreground">{point.label}</span>
267
- </div>
268
- ))}
269
- </div>
270
- </section>
271
- );
272
- }
273
-
274
- registerActionChatRenderer({
275
- id: "text.stats-chart",
276
- renderer: "text.stats-chart",
277
- Component: TextStatsChart,
278
- });
279
- ```
280
-
281
- Once the renderer is registered, the agent's response looks like this. Instead
282
- of a paragraph of text, your React component renders directly inside the chat
283
- transcript:
284
-
285
- <WireframeBlock id="doc-block-inline-result-wireframe">
286
- <Screen
287
- surface="desktop"
288
- html={
289
- "<div style='min-height:340px;box-sizing:border-box;padding:24px;display:flex;justify-content:center;align-items:center;background:var(--wf-bg)'><div style='width:min(640px,100%);display:flex;flex-direction:column;gap:14px'><div class='wf-card' data-rough style='align-self:flex-end;max-width:70%;padding:12px 14px'><strong>User</strong><p style='margin:6px 0 0'>Run the analyze-text action on \"Hello world. How are you today?\"</p></div><div class='wf-card' data-rough style='align-self:flex-start;width:min(480px,100%);padding:14px'><strong>Agent</strong><p class='wf-muted' style='margin:6px 0 12px'>Rendered with text.stats-chart.</p><section class='wf-card' data-rough style='padding:14px'><h3 style='margin:0 0 12px;font-size:14px'>Text statistics</h3><div data-rough='line:bottom' style='height:104px;display:flex;align-items:end;gap:8px;border-bottom:1.4px solid var(--wf-line);padding-bottom:4px'><div style='flex:1;display:flex;flex-direction:column;align-items:center;gap:6px'><div data-rough style='height:80px;width:100%;background:color-mix(in srgb, var(--wf-accent) 36%, transparent);border:1.4px solid var(--wf-accent);border-radius:8px 8px 3px 3px'></div><span class='wf-muted'>Characters</span></div><div style='flex:1;display:flex;flex-direction:column;align-items:center;gap:6px'><div data-rough style='height:18px;width:100%;background:color-mix(in srgb, var(--wf-accent) 30%, transparent);border:1.4px solid var(--wf-accent);border-radius:8px 8px 3px 3px'></div><span class='wf-muted'>Words</span></div><div style='flex:1;display:flex;flex-direction:column;align-items:center;gap:6px'><div data-rough style='height:2px;width:100%;background:color-mix(in srgb, var(--wf-accent) 24%, transparent);border:1.4px solid var(--wf-accent);border-radius:8px 8px 3px 3px'></div><span class='wf-muted'>Sentences</span></div><div style='flex:1;display:flex;flex-direction:column;align-items:center;gap:6px'><div data-rough style='height:2px;width:100%;background:color-mix(in srgb, var(--wf-accent) 24%, transparent);border:1.4px solid var(--wf-accent);border-radius:8px 8px 3px 3px'></div><span class='wf-muted'>Paragraphs</span></div></div></section></div></div></div>"
290
- }
291
- />
292
- </WireframeBlock>
293
-
294
- Use this step when the result belongs where the agent is speaking:
295
-
296
- - setup summaries
297
- - short reports
298
- - approvals
299
- - tables or charts small enough to inspect inline
300
- - links into durable app views
301
-
302
- For reusable generic outputs, the framework also ships built-in
303
- `data-chart` and `data-table` renderers, plus `data-insights` for combined
304
- summary/chart/table cards. See [Native Chat UI](/docs/native-chat-ui). For
305
- temporary controls the agent creates at runtime, see
306
- [Generative UI](/docs/generative-ui).
307
-
308
- ## 5. Persist data in SQL {#persist-data}
309
-
310
- Right now, every time the agent runs `analyze-text` the result appears in chat
311
- and then disappears. There's nothing to look back at, nothing the agent can
312
- reference later, and no way to build a page around the data. Persisting to SQL
313
- fixes that: the agent writes results to a table, and both the agent and your UI
314
- can read them back at any time.
315
-
316
- Agent-Native apps have a SQL database available by default: SQLite locally,
317
- and your configured provider (Postgres, Turso/libSQL, Cloudflare D1) in
318
- production.
319
-
320
- ### Wire up the database plugin
321
-
322
- The Chat template doesn't include a database plugin by default. Create
323
- `server/plugins/db.ts` to initialize it. This is what runs migrations and
324
- makes the database available to your actions:
325
-
326
- ```ts filename="server/plugins/db.ts"
327
- import { runMigrations } from "@agent-native/core/db";
328
-
329
- export default runMigrations(
330
- [
331
- {
332
- version: 1,
333
- sql: `CREATE TABLE IF NOT EXISTS text_analyses (
334
- id TEXT PRIMARY KEY,
335
- input TEXT NOT NULL,
336
- char_count INTEGER NOT NULL,
337
- word_count INTEGER NOT NULL,
338
- sentence_count INTEGER NOT NULL,
339
- created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
340
- )`,
341
- },
342
- ],
343
- { table: "text_analyses_migrations" },
344
- );
345
- ```
346
-
347
- Each entry in the array is an additive migration. When you add new columns or
348
- tables later, append a new version object. Never edit existing ones.
349
-
350
- ### Define the schema
351
-
352
- Create `server/db/schema.ts`. The `server/db/` directory may not exist yet,
353
- so create it if needed. This file describes your tables using typed helpers so
354
- your actions get full TypeScript autocomplete:
355
-
356
- ```ts filename="server/db/schema.ts"
357
- import { integer, now, table, text } from "@agent-native/core/db/schema";
358
-
359
- export const textAnalyses = table("text_analyses", {
360
- id: text("id").primaryKey(),
361
- input: text("input").notNull(),
362
- charCount: integer("char_count").notNull(),
363
- wordCount: integer("word_count").notNull(),
364
- sentenceCount: integer("sentence_count").notNull(),
365
- createdAt: text("created_at").notNull().default(now()),
366
- });
367
- ```
368
-
369
- Use the framework schema helpers (`table`, `text`, `integer`, `now`) rather than
370
- `sqliteTable`, `pgTable`, or dialect-specific imports. They pick the configured
371
- SQL backend automatically, so the same schema runs locally on SQLite and in
372
- production on any supported provider.
373
-
374
- After adding both files, restart the dev server so the migration runs:
375
-
376
- ```bash
377
- pnpm dev
378
- ```
379
-
380
- Look for these two lines in the terminal output. They confirm the table was created:
381
-
382
- ```
383
- [db] Applying 1 migration(s) on SQLite/libsql…
384
- [db] Applied migration v1 (1 statement)
385
- ```
386
-
387
- The `NitroViteError` lines, `BETTER_AUTH_SECRET` warning, and
388
- `SECRETS_ENCRYPTION_KEY` warning that also appear are normal for local dev and
389
- can be ignored.
390
-
391
- ### Add actions for the table
392
-
393
- Now create the action files that read and write the table. These go in your
394
- `actions/` directory, the same place as `hello.ts` and `analyze-text.ts`. You
395
- create them yourself, one file per operation. The agent and your UI will call
396
- them the same way they call any other action.
397
-
398
- **`actions/save-text-analysis.ts`** writes a result row to the database.
399
- Call this after running `analyze-text` to make the result durable:
400
-
401
- ```ts filename="actions/save-text-analysis.ts"
402
- import { defineAction } from "@agent-native/core/action";
403
- import { getDbExec } from "@agent-native/core/db";
404
- import { z } from "zod";
405
-
406
- export default defineAction({
407
- description: "Save a text analysis result to the database.",
408
- schema: z.object({
409
- input: z.string(),
410
- charCount: z.number(),
411
- wordCount: z.number(),
412
- sentenceCount: z.number(),
413
- }),
414
- run: async ({ input, charCount, wordCount, sentenceCount }) => {
415
- const id = crypto.randomUUID();
416
- await getDbExec().execute({
417
- sql: `INSERT INTO text_analyses (id, input, char_count, word_count, sentence_count)
418
- VALUES (?, ?, ?, ?, ?)`,
419
- args: [id, input, charCount, wordCount, sentenceCount],
420
- });
421
- return { id };
422
- },
423
- });
424
- ```
425
-
426
- **`actions/list-text-analyses.ts`** reads all saved results. The agent can
427
- call this to summarize past analyses, and your UI can use it to populate a page:
428
-
429
- ```ts filename="actions/list-text-analyses.ts"
430
- import { defineAction } from "@agent-native/core/action";
431
- import { getDbExec } from "@agent-native/core/db";
432
- import { z } from "zod";
433
-
434
- export default defineAction({
435
- description: "List all saved text analyses, newest first.",
436
- schema: z.object({}),
437
- run: async () => {
438
- const result = await getDbExec().execute(
439
- `SELECT id, input, char_count, word_count, sentence_count, created_at
440
- FROM text_analyses
441
- ORDER BY created_at DESC`,
442
- );
443
- return result.rows;
444
- },
445
- });
446
- ```
447
-
448
- **`actions/delete-text-analysis.ts`** removes a row by id:
449
-
450
- ```ts filename="actions/delete-text-analysis.ts"
451
- import { defineAction } from "@agent-native/core/action";
452
- import { getDbExec } from "@agent-native/core/db";
453
- import { z } from "zod";
454
-
455
- export default defineAction({
456
- description: "Delete a saved text analysis by id.",
457
- schema: z.object({ id: z.string() }),
458
- run: async ({ id }) => {
459
- await getDbExec().execute({
460
- sql: `DELETE FROM text_analyses WHERE id = ?`,
461
- args: [id],
462
- });
463
- return { deleted: id };
464
- },
465
- });
466
- ```
467
-
468
- Once these files are saved the dev server picks them up automatically. No
469
- restart needed. Try listing analyses from the terminal:
470
-
471
- ```bash
472
- pnpm action list-text-analyses
473
- ```
474
-
475
- You should see an empty array. The table exists and the action works; there's
476
- just nothing saved yet:
477
-
478
- ```
479
- []
480
- ```
115
+ <Callout tone="warning">
481
116
 
482
- Data is saved to `data/app.db`, a SQLite file in your project directory that
483
- gets created automatically on first run. In production you'd point
484
- `DATABASE_URL` at a hosted database instead, but locally this file is all you
485
- need.
117
+ **Blank screen?** Create a `.env` file in your `my-app/` directory with
118
+ `ANTHROPIC_API_KEY=sk-ant-...`, then restart `pnpm dev`. The in-app Setup
119
+ panel only appears once the app has loaded, so a missing key that prevents
120
+ the app from rendering needs to be fixed via the environment variable rather than
121
+ the UI.
486
122
 
487
- To save something, first run `analyze-text` to get the counts:
123
+ </Callout>
488
124
 
489
- ```bash
490
- pnpm action analyze-text --text "Hello world"
491
- ```
492
-
493
- You'll see output like:
494
-
495
- ```
496
- {
497
- title: 'Text statistics',
498
- points: [
499
- { label: 'Characters', value: 11 },
500
- { label: 'Words', value: 2 },
501
- { label: 'Sentences', value: 1 },
502
- { label: 'Paragraphs', value: 1 }
503
- ]
504
- }
505
- ```
125
+ ## What's next {#next}
506
126
 
507
- Then pass those values to `save-text-analysis`:
127
+ Your app is running and the agent can respond. Continue the series to build it out:
508
128
 
509
- ```bash
510
- pnpm action save-text-analysis \
511
- --input "Hello world" \
512
- --charCount 11 \
513
- --wordCount 2 \
514
- --sentenceCount 1
515
- ```
129
+ <Cards>
516
130
 
517
- Now run `list-text-analyses` again and you'll see the saved row:
131
+ ### [Add an Action](/docs/getting-started-actions)
518
132
 
519
- ```bash
520
- pnpm action list-text-analyses
521
- ```
133
+ Define your first typed action and render its result as a chart directly inside
134
+ the chat transcript.
522
135
 
523
- Or ask the agent in the chat at `http://localhost:8080` to do both steps at once:
524
-
525
- > Run analyze-text on "Hello world", then save the result.
526
-
527
- ## 6. Add a page the agent can open {#add-a-page}
528
-
529
- Chat is great for conversational interaction, but some data is better inspected
530
- in a dedicated UI: a table you can scan, sort, or delete rows from. This step
531
- adds a React route that displays everything saved in `text_analyses`, using the
532
- same `list-text-analyses` and `delete-text-analysis` actions you already wrote.
533
- There's no second data layer. The page is just a view over the same SQL state
534
- the agent reads and writes.
535
-
536
- Create the route file at `app/routes/text-analyses.tsx`. Route files in
537
- `app/routes/` are automatically picked up by the framework. The filename
538
- becomes the URL path, so this page will be available at
539
- `http://localhost:8080/text-analyses`.
540
-
541
- ```tsx filename="app/routes/text-analyses.tsx"
542
- import {
543
- useActionMutation,
544
- useActionQuery,
545
- } from "@agent-native/core/client/hooks";
546
-
547
- export default function TextAnalysesRoute() {
548
- const analyses = useActionQuery("list-text-analyses", {});
549
- const deleteAnalysis = useActionMutation("delete-text-analysis");
550
-
551
- return (
552
- <main className="mx-auto flex max-w-3xl flex-col gap-6 p-6">
553
- <header>
554
- <h1 className="text-2xl font-semibold">Text analyses</h1>
555
- <p className="text-muted-foreground">
556
- Results saved by the agent or triggered manually.
557
- </p>
558
- </header>
559
- <section className="flex flex-col gap-3">
560
- {analyses.data?.length === 0 && (
561
- <p className="text-muted-foreground">No analyses saved yet.</p>
562
- )}
563
- {analyses.data?.map((row: any) => (
564
- <article
565
- key={row.id}
566
- className="flex items-start justify-between rounded-lg border p-4"
567
- >
568
- <div className="flex flex-col gap-1">
569
- <p className="text-sm font-medium">{row.input}</p>
570
- <p className="text-xs text-muted-foreground">
571
- {row.word_count} words · {row.char_count} characters ·{" "}
572
- {row.sentence_count} sentences
573
- </p>
574
- </div>
575
- <button
576
- className="text-xs text-destructive hover:underline"
577
- onClick={() => deleteAnalysis.mutate({ id: row.id })}
578
- >
579
- Delete
580
- </button>
581
- </article>
582
- ))}
583
- </section>
584
- </main>
585
- );
586
- }
587
- ```
136
+ ### [Persist Data in SQL](/docs/getting-started-database)
588
137
 
589
- `useActionQuery` calls `list-text-analyses` and keeps the result live. If the
590
- agent saves a new row while the page is open, it appears automatically.
591
- `useActionMutation` calls `delete-text-analysis` when the user clicks Delete,
592
- then invalidates the query so the list refreshes.
593
-
594
- Open `http://localhost:8080/text-analyses` in your browser. If you saved an
595
- analysis in the previous step you'll see it listed. Then ask the agent in chat:
596
-
597
- > Open the text analyses page.
598
-
599
- If you get a 404, try restarting your dev server.
600
-
601
- The agent calls the `navigate` action (already included in the Chat
602
- template) to send the browser to `/text-analyses`. This is what it looks like
603
- with a few saved rows:
604
-
605
- <WireframeBlock id="doc-block-response-insights-page-wireframe">
606
- <Screen
607
- surface="desktop"
608
- html={
609
- "<main style='min-height:400px;box-sizing:border-box;padding:28px;background:var(--wf-bg)'><div style='max-width:720px;margin:0 auto;display:flex;flex-direction:column;gap:20px'><header><h2 style='margin:0 0 4px;font-size:24px;font-weight:600'>Text analyses</h2><p class='wf-muted' style='margin:0;font-size:14px'>Results saved by the agent or triggered manually.</p></header><section style='display:flex;flex-direction:column;gap:10px'><article class='wf-card' data-rough style='display:flex;align-items:center;justify-content:space-between;padding:14px 16px'><div><p style='margin:0 0 4px;font-size:14px;font-weight:500'>Hello world</p><p class='wf-muted' style='margin:0;font-size:12px'>2 words · 11 characters · 1 sentence</p></div><span class='wf-muted' style='font-size:12px'>Delete</span></article><article class='wf-card' data-rough style='display:flex;align-items:center;justify-content:space-between;padding:14px 16px'><div><p style='margin:0 0 4px;font-size:14px;font-weight:500'>The quick brown fox jumps over the lazy dog.</p><p class='wf-muted' style='margin:0;font-size:12px'>9 words · 44 characters · 1 sentence</p></div><span class='wf-muted' style='font-size:12px'>Delete</span></article><article class='wf-card' data-rough style='display:flex;align-items:center;justify-content:space-between;padding:14px 16px'><div><p style='margin:0 0 4px;font-size:14px;font-weight:500'>Pack my box with five dozen liquor jugs.</p><p class='wf-muted' style='margin:0;font-size:12px'>8 words · 40 characters · 1 sentence</p></div><span class='wf-muted' style='font-size:12px'>Delete</span></article></section></div></main>"
610
- }
611
- />
612
- </WireframeBlock>
613
-
614
- ## 7. Extend the navigation {#extend-navigation}
615
-
616
- The sidebar's links are a plain array in `app/components/layout/Sidebar.tsx`,
617
- not a separate config file. Open it and add an entry for the Text analyses
618
- page next to the existing Chat entry:
619
-
620
- ```tsx filename="app/components/layout/Sidebar.tsx"
621
- import { IconList, IconMessageCircle } from "@tabler/icons-react";
622
-
623
- const navItems = [
624
- {
625
- icon: IconMessageCircle,
626
- labelKey: "navigation.chat",
627
- href: "/",
628
- view: "chat",
629
- },
630
- {
631
- icon: IconList,
632
- labelKey: "navigation.textAnalyses",
633
- href: "/text-analyses",
634
- view: "text-analyses",
635
- },
636
- ];
637
- ```
138
+ Save action results to a database so the agent can reference them across
139
+ conversations.
638
140
 
639
- `icon` takes an imported Tabler icon component, not a string name. `labelKey`
640
- looks up a string in the i18n catalog (`app/i18n/en-US.ts` and the other
641
- locale files); an unregistered key still renders — it falls back to a
642
- humanized version of the key (`navigation.textAnalyses` becomes "Text
643
- analyses") — but add it to the catalogs if you want the label translated. See
644
- [Internationalization](/docs/internationalization).
645
-
646
- Save the file. The dev server picks up the change automatically and the sidebar
647
- updates without a restart.
648
-
649
- ### Agent navigation
650
-
651
- The sidebar link lets users navigate manually. The agent can also open pages on
652
- its own using two built-in actions that ship with the Chat template:
653
-
654
- - **`view-screen`** reads the current route and returns a compact summary of
655
- what the user is looking at.
656
- - **`navigate`** writes a same-origin path to the browser's history.
657
-
658
- As you add more pages, keep `navigate` updated so the agent knows what
659
- destinations exist. Document available paths in `AGENTS.md` so the model can
660
- reason about them.
661
-
662
- When the app has both a full-page chat route and an app page, use the shared chat
663
- handoff helpers described in [Agent Surfaces](/docs/agent-surfaces#rich-chat):
664
- `AgentChatSurface`, `AgentSidebar`, `useAgentChatHomeHandoff`,
665
- `useAgentChatHomeHandoffLinks`, and `chatViewTransition`. That lets the full
666
- chat slide into the side panel as the page opens, keeping the same thread while
667
- the user inspects durable data.
668
-
669
- ## Project structure {#project-structure}
670
-
671
- ```text
672
- my-app/
673
- actions/ # Agent-callable and UI-callable operations
674
- app/ # React routes, pages, and chat surfaces
675
- server/ # Nitro server and SQL schema
676
- AGENTS.md # Always-on instructions for the app agent
677
- .agents/ # Skills the agent loads when relevant
678
- data/app.db # Local SQLite state when DATABASE_URL is unset
679
- ```
141
+ ### [Add a Page](/docs/getting-started-pages)
680
142
 
681
- ## Want a full analytics starting point? {#analytics-starting-point}
143
+ Build a React route that displays your saved data and wire it into the sidebar.
682
144
 
683
- The text-analyses example above is intentionally small so you can see the
684
- framework pieces. If you are building a real analytics product, start from
685
- [Analytics](/docs/template-analytics) instead. It is the robust starting point:
686
- connect your providers, use the existing dashboards and agent actions, then
687
- customize the app from there.
145
+ </Cards>
688
146
 
689
- ## What's next {#next}
147
+ Or jump to a specific topic:
690
148
 
691
- - **[What Is Agent-Native?](/docs/what-is-agent-native)**: the vision and the
692
- case for building this way.
693
- - **[Key Concepts](/docs/key-concepts)**: the architecture underneath this
694
- tutorial — SQL, actions, live sync, context awareness.
695
- - **[Actions](/docs/actions)**: schemas, auth, approvals, hooks, and transport.
696
- - **[Native Chat UI](/docs/native-chat-ui)**: render action results as tables,
697
- charts, and typed cards.
698
- - **[Chat Template](/docs/template-chat)**: the minimal chat-first app you just
699
- created.
700
- - **[Analytics Template](/docs/template-analytics)**: a robust analytics app
701
- starting point; connect providers and customize from there.
702
- - **[Context Awareness](/docs/context-awareness)**: `view-screen`, `navigate`,
703
- route state, and selected objects.
704
- - **[Agent Surfaces](/docs/agent-surfaces)**: chat, inline UI, app pages,
705
- embedded sidecars, automation, and external agents.
706
- - **[Deployment](/docs/deployment)**: put your app on your own domain.
149
+ - **[What Is Agent-Native?](/docs/what-is-agent-native)**: the vision and case for building this way.
150
+ - **[Chat Template](/docs/template-chat)**: the minimal chat-first app you just created.
707
151
  - **[FAQ](/docs/faq)**: quick answers on cost, hosting, models, and templates.