@kazzle/app 0.1.693

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 (103) hide show
  1. package/README.md +63 -0
  2. package/dist/client.d.ts +25 -0
  3. package/dist/client.js +83 -0
  4. package/dist/index.d.ts +86 -0
  5. package/dist/index.js +15 -0
  6. package/dist/migrations.d.ts +19 -0
  7. package/dist/migrations.js +74 -0
  8. package/dist/templates.d.ts +33 -0
  9. package/dist/templates.js +89 -0
  10. package/dist/tools.d.ts +36 -0
  11. package/dist/tools.js +22 -0
  12. package/dist/vite.d.ts +29 -0
  13. package/dist/vite.js +77 -0
  14. package/package.json +47 -0
  15. package/templates/ai-app/KAZZLE.md +21 -0
  16. package/templates/ai-app/bun.lock +290 -0
  17. package/templates/ai-app/components/server/index.ts +238 -0
  18. package/templates/ai-app/components/server/package.json +20 -0
  19. package/templates/ai-app/components/server/tsconfig.json +11 -0
  20. package/templates/ai-app/components/ui/index.html +12 -0
  21. package/templates/ai-app/components/ui/package.json +28 -0
  22. package/templates/ai-app/components/ui/public/favicon.svg +4 -0
  23. package/templates/ai-app/components/ui/src/App.tsx +52 -0
  24. package/templates/ai-app/components/ui/src/api.ts +85 -0
  25. package/templates/ai-app/components/ui/src/main.tsx +11 -0
  26. package/templates/ai-app/components/ui/src/styles/app.css +210 -0
  27. package/templates/ai-app/components/ui/src/styles/theme.css +181 -0
  28. package/templates/ai-app/components/ui/src/tabs/chat-tab.tsx +75 -0
  29. package/templates/ai-app/components/ui/src/tabs/extract-tab.tsx +92 -0
  30. package/templates/ai-app/components/ui/src/tabs/image-tab.tsx +51 -0
  31. package/templates/ai-app/components/ui/src/tabs/speech-tab.tsx +47 -0
  32. package/templates/ai-app/components/ui/src/tabs/transcribe-tab.tsx +42 -0
  33. package/templates/ai-app/components/ui/src/tabs/video-tab.tsx +76 -0
  34. package/templates/ai-app/components/ui/src/types.ts +53 -0
  35. package/templates/ai-app/components/ui/src/vite-env.d.ts +1 -0
  36. package/templates/ai-app/components/ui/tsconfig.json +21 -0
  37. package/templates/ai-app/components/ui/vite.config.ts +22 -0
  38. package/templates/ai-app/kazzle.config.ts +9 -0
  39. package/templates/ai-app/package.json +17 -0
  40. package/templates/empty-app/KAZZLE.md +10 -0
  41. package/templates/empty-app/kazzle.config.ts +8 -0
  42. package/templates/empty-app/package.json +15 -0
  43. package/templates/manifest.json +10 -0
  44. package/templates/process-app/KAZZLE.md +10 -0
  45. package/templates/process-app/components/process/index.ts +155 -0
  46. package/templates/process-app/components/process/package.json +20 -0
  47. package/templates/process-app/components/process/tsconfig.json +12 -0
  48. package/templates/process-app/kazzle.config.ts +32 -0
  49. package/templates/process-app/package.json +18 -0
  50. package/templates/process-app/skills/tools/SKILL.md +37 -0
  51. package/templates/process-app/skills/tools/tools.ts +19 -0
  52. package/templates/realtime-app/KAZZLE.md +11 -0
  53. package/templates/realtime-app/bun.lock +887 -0
  54. package/templates/realtime-app/components/server/deploy.ts +105 -0
  55. package/templates/realtime-app/components/server/index.ts +40 -0
  56. package/templates/realtime-app/components/server/migrations/001_items.sql +6 -0
  57. package/templates/realtime-app/components/server/package.json +20 -0
  58. package/templates/realtime-app/components/server/routes.ts +137 -0
  59. package/templates/realtime-app/components/server/tsconfig.json +15 -0
  60. package/templates/realtime-app/components/ui/index.html +13 -0
  61. package/templates/realtime-app/components/ui/package.json +36 -0
  62. package/templates/realtime-app/components/ui/public/favicon.svg +4 -0
  63. package/templates/realtime-app/components/ui/src/App.tsx +77 -0
  64. package/templates/realtime-app/components/ui/src/lib/connector.ts +80 -0
  65. package/templates/realtime-app/components/ui/src/lib/schema.ts +35 -0
  66. package/templates/realtime-app/components/ui/src/lib/sync.ts +24 -0
  67. package/templates/realtime-app/components/ui/src/main.tsx +49 -0
  68. package/templates/realtime-app/components/ui/src/styles/theme.css +181 -0
  69. package/templates/realtime-app/components/ui/src/vite-env.d.ts +2 -0
  70. package/templates/realtime-app/components/ui/tsconfig.json +14 -0
  71. package/templates/realtime-app/components/ui/vite.config.ts +86 -0
  72. package/templates/realtime-app/kazzle.config.ts +11 -0
  73. package/templates/realtime-app/package.json +17 -0
  74. package/templates/shared/icon.svg +4 -0
  75. package/templates/ui-app/KAZZLE.md +16 -0
  76. package/templates/ui-app/components/ui/bun.lock +894 -0
  77. package/templates/ui-app/components/ui/index.html +13 -0
  78. package/templates/ui-app/components/ui/package.json +30 -0
  79. package/templates/ui-app/components/ui/public/favicon.svg +4 -0
  80. package/templates/ui-app/components/ui/src/App.tsx +41 -0
  81. package/templates/ui-app/components/ui/src/main.tsx +24 -0
  82. package/templates/ui-app/components/ui/src/styles/theme.css +181 -0
  83. package/templates/ui-app/components/ui/tsconfig.json +21 -0
  84. package/templates/ui-app/components/ui/vite.config.ts +97 -0
  85. package/templates/ui-app/kazzle.config.ts +10 -0
  86. package/templates/ui-app/package.json +16 -0
  87. package/templates/ui-db-app/KAZZLE.md +12 -0
  88. package/templates/ui-db-app/components/server/index.ts +218 -0
  89. package/templates/ui-db-app/components/server/package.json +21 -0
  90. package/templates/ui-db-app/components/server/tsconfig.json +11 -0
  91. package/templates/ui-db-app/components/ui/index.html +12 -0
  92. package/templates/ui-db-app/components/ui/package.json +28 -0
  93. package/templates/ui-db-app/components/ui/public/favicon.svg +4 -0
  94. package/templates/ui-db-app/components/ui/src/App.tsx +221 -0
  95. package/templates/ui-db-app/components/ui/src/api.ts +79 -0
  96. package/templates/ui-db-app/components/ui/src/main.tsx +10 -0
  97. package/templates/ui-db-app/components/ui/src/styles/theme.css +181 -0
  98. package/templates/ui-db-app/components/ui/src/types.ts +17 -0
  99. package/templates/ui-db-app/components/ui/src/vite-env.d.ts +1 -0
  100. package/templates/ui-db-app/components/ui/tsconfig.json +21 -0
  101. package/templates/ui-db-app/components/ui/vite.config.ts +19 -0
  102. package/templates/ui-db-app/kazzle.config.ts +9 -0
  103. package/templates/ui-db-app/package.json +17 -0
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "{{APP_SLUG}}",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "workspaces": [
7
+ "components/*"
8
+ ],
9
+ "scripts": {
10
+ "dev": "bun run --cwd components/ui dev",
11
+ "start": "bun run --cwd components/server start",
12
+ "check": "bun run --filter='./components/*' check"
13
+ },
14
+ "dependencies": {
15
+ "@kazzle/app": "{{KAZZLE_APP_VERSION}}"
16
+ }
17
+ }
@@ -0,0 +1,10 @@
1
+ # Kazzle template context
2
+
3
+ This app was created from the `empty` template. It intentionally starts with no components so the app structure can be designed from the user's requirements.
4
+
5
+ Safe next steps:
6
+ - Add components to `kazzle.config.ts` only after choosing the app type.
7
+ - Add files that match those components.
8
+ - Add `package.json` scripts, including `check`, before calling the app done.
9
+
10
+ If the user wants a common app shape, prefer a purpose-built template such as `ui`, `process`, `ui-db`, or `realtime` instead of rebuilding that structure manually here.
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from '@kazzle/app';
2
+
3
+ export default defineConfig({
4
+ /** Path to the app icon relative to the repo root. Uploaded to CDN on publish. */
5
+ icon: 'icon.svg',
6
+ /** App components — each entry defines a deployable unit. */
7
+ components: [],
8
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "{{APP_SLUG}}",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "workspaces": [
7
+ "components/*"
8
+ ],
9
+ "scripts": {
10
+ "check": "echo 'No components yet'"
11
+ },
12
+ "dependencies": {
13
+ "@kazzle/app": "{{KAZZLE_APP_VERSION}}"
14
+ }
15
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "entries": [
3
+ { "id": "empty", "label": "Empty", "description": "Minimal Kazzle app scaffold with config and skills only.", "tags": ["starter"] },
4
+ { "id": "ui", "label": "UI", "description": "React + Vite UI component with the Kazzle preview Vite helper.", "tags": ["ui", "frontend"] },
5
+ { "id": "process", "label": "Process", "description": "Background/triggered process component plus AI tool skill.", "tags": ["process", "tools"] },
6
+ { "id": "ui-db", "label": "UI + Postgres", "description": "React UI plus Hono API backed by a plain Postgres database.", "tags": ["ui", "database"] },
7
+ { "id": "realtime", "label": "Realtime", "description": "Offline-first UI with PowerSync sync over a Postgres backend.", "tags": ["ui", "database", "sync"] },
8
+ { "id": "ai", "label": "AI", "description": "React UI plus Hono server exposing Kazzle AI (chat, image, speech, extract).", "tags": ["ui", "ai"] }
9
+ ]
10
+ }
@@ -0,0 +1,10 @@
1
+ # Kazzle template context
2
+
3
+ This app was created from the `process` template. It is a server-side process app, not a browser UI.
4
+
5
+ Template-owned setup:
6
+ - `kazzle.config.ts` declares one process component.
7
+ - `components/process/index.ts` is the process entrypoint.
8
+ - `components/process/package.json` already contains start, dev, typecheck, and check scripts.
9
+
10
+ Keep runtime-only logic in the process entrypoint. Do not add frontend files, browser preview requirements, or database wiring unless the user's requirements need them.
@@ -0,0 +1,155 @@
1
+ /**
2
+ * {{APP_NAME}} — process component entry point (persistent mode).
3
+ *
4
+ * This is a long-running HTTP server. The Kazzle platform routes triggers
5
+ * (schedule and webhook) into this server at the paths declared in
6
+ * `kazzle.config.ts` → `triggers[]`. Each request carries:
7
+ *
8
+ * - `Authorization: Bearer ${KAZZLE_TRIGGER_SECRET}` — validate this
9
+ * before doing any work. Calls without the header are external traffic
10
+ * and should be handled with your own auth (or rejected).
11
+ * - `x-kazzle-trigger-name` — the trigger's `name` from the manifest.
12
+ * - `x-kazzle-trigger-run-id` — opaque ID for log correlation.
13
+ * - `x-kazzle-triggered-by` — 'cron' | 'webhook' | 'manual'.
14
+ *
15
+ * `PORT` and `HOST` are injected by `kazzle run`. Missing values throw so
16
+ * the platform sees a fast, readable failure instead of binding to the
17
+ * wrong port.
18
+ *
19
+ * If you want one-off ephemeral runs per trigger instead (Vercel cron
20
+ * style), set `processMode: 'triggered'` in kazzle.config.ts and read
21
+ * `TRIGGER_NAME` + `WEBHOOK_PAYLOAD` from process.env on startup; the
22
+ * platform will spawn this file fresh per trigger and the body below
23
+ * (`Bun.serve`) is never reached.
24
+ */
25
+
26
+ import { SaveBookmarkInput } from '../../skills/tools/tools';
27
+
28
+ // ─── Triggered mode (one-off ephemeral run) ─────────────────────────────
29
+ //
30
+ // If TRIGGERED_BY is set, the platform launched us for a single trigger
31
+ // (cron or webhook) and expects the process to do its job and exit. This
32
+ // mirrors the `processMode: 'triggered'` lifecycle described above.
33
+ if (process.env.TRIGGERED_BY) {
34
+ const trigger = process.env.TRIGGERED_BY;
35
+ const runId = process.env.RUN_ID ?? 'unknown';
36
+ const triggerName = process.env.TRIGGER_NAME ?? '';
37
+ const payload = process.env.WEBHOOK_PAYLOAD ?? '';
38
+ console.log(`[{{APP_NAME}}] Triggered by: ${trigger} (runId=${runId}, trigger=${triggerName})`);
39
+ if (payload) console.log(`[{{APP_NAME}}] Payload: ${payload}`);
40
+ console.log(`[{{APP_NAME}}] Job complete`);
41
+ process.exit(0);
42
+ }
43
+
44
+ // ─────────────────────────────────────────────────────────────────────────
45
+ // DO NOT CHANGE THE NEXT 4 LINES. DO NOT ADD FALLBACKS. DO NOT HARDCODE.
46
+ // PORT and HOST are injected by `kazzle run`. Missing values must throw so
47
+ // the platform sees a fast, readable failure instead of binding to the
48
+ // wrong port.
49
+ // ─────────────────────────────────────────────────────────────────────────
50
+ if (!process.env.PORT || !process.env.HOST) {
51
+ throw new Error('PORT and HOST must be set by "kazzle run" — never hardcode them.');
52
+ }
53
+ const PORT = Number(process.env.PORT);
54
+ const HOST = process.env.HOST;
55
+
56
+ // KAZZLE_TRIGGER_SECRET is injected into the env when the migration that
57
+ // adds `drive_items.trigger_secret` lands. Until then the platform skips
58
+ // the Authorization header; treat a missing secret here as "auth not yet
59
+ // available" and accept platform-tagged requests by header presence only.
60
+ const TRIGGER_SECRET = process.env.KAZZLE_TRIGGER_SECRET ?? '';
61
+
62
+ function isPlatformTrigger(req: Request): boolean {
63
+ if (!req.headers.get('x-kazzle-trigger-name')) return false;
64
+ if (!TRIGGER_SECRET) return true;
65
+ const auth = req.headers.get('authorization') ?? '';
66
+ return auth === `Bearer ${TRIGGER_SECRET}`;
67
+ }
68
+
69
+ const server = Bun.serve({
70
+ hostname: HOST,
71
+ port: PORT,
72
+ async fetch(req) {
73
+ const url = new URL(req.url);
74
+
75
+ // /health is the liveness probe. Keep it dependency-free so a flaky
76
+ // database or downstream API doesn't take the whole process down.
77
+ if (url.pathname === '/health') {
78
+ return Response.json({
79
+ status: 'ok',
80
+ pid: process.pid,
81
+ uptime: process.uptime(),
82
+ });
83
+ }
84
+
85
+ // ── Platform-routed triggers ─────────────────────────────────────────
86
+ //
87
+ // Each trigger declared in kazzle.config.ts → triggers[] arrives here
88
+ // at its declared `path`. The block below shows the canonical handler
89
+ // shape — extend it as you add triggers.
90
+
91
+ // Example: handle a daily cron declared as
92
+ // { name: 'hourly-sync', kind: 'schedule', schedule: '0 * * * *', path: '/cron/hourly-sync' }
93
+ if (req.method === 'POST' && url.pathname === '/cron/hourly-sync') {
94
+ if (!isPlatformTrigger(req)) return new Response('Unauthorized', { status: 401 });
95
+ // Do the cron work here.
96
+ console.log(`[{{APP_NAME}}] hourly sync fired (runId=${req.headers.get('x-kazzle-trigger-run-id')})`);
97
+ return Response.json({ ok: true });
98
+ }
99
+
100
+ // Example: handle a webhook declared as
101
+ // { name: 'incoming', kind: 'webhook', path: '/webhook/incoming' }
102
+ if (req.method === 'POST' && url.pathname === '/webhook/incoming') {
103
+ if (!isPlatformTrigger(req)) return new Response('Unauthorized', { status: 401 });
104
+ const body = await req.json().catch(() => ({})) as Record<string, unknown>;
105
+ console.log(`[{{APP_NAME}}] webhook fired:`, JSON.stringify(body).slice(0, 256));
106
+ return Response.json({ ok: true, received: true });
107
+ }
108
+
109
+ // ── AI tool handlers (the "kitchen") ─────────────────────────────────
110
+ //
111
+ // Each tool declared in skills/tools/tools.ts is backed by a route here.
112
+ // When the AI calls `save_bookmark`, Kazzle POSTs { input } to this path
113
+ // with the app identity as `Authorization: Bearer <token>`. Return either
114
+ // plain text or JSON { content } — that becomes the tool result the AI sees.
115
+ //
116
+ // To add a tool: add it to tools.ts (name + Zod input + path), then add
117
+ // its matching `POST <path>` route below.
118
+ if (req.method === 'POST' && url.pathname === '/tools/save-bookmark') {
119
+ const { input } = (await req.json().catch(() => ({}))) as { input?: unknown };
120
+ const parsed = SaveBookmarkInput.safeParse(input);
121
+ if (!parsed.success) {
122
+ return Response.json({ content: parsed.error.issues.map(issue => issue.message).join('; ') }, { status: 400 });
123
+ }
124
+ const toolInput = parsed.data;
125
+ // Do the real work here (write to your DB, call an API, etc.).
126
+ //
127
+ // Per-install runtime secrets (e.g. a connected user's OAuth token) are
128
+ // saved/read by calling Kazzle back with the SAME identity header this
129
+ // tool call arrived with — Kazzle scopes them to this install. Use a
130
+ // fixed name; never key by user yourself:
131
+ //
132
+ // const auth = req.headers.get('authorization')!;
133
+ // const api = process.env.KAZZLE_API_URL;
134
+ // await fetch(`${api}/apps/secrets/gmail_token`, {
135
+ // method: 'PUT', headers: { authorization: auth, 'content-type': 'application/json' },
136
+ // body: JSON.stringify({ value: accessToken }),
137
+ // });
138
+ // const res = await fetch(`${api}/apps/secrets/gmail_token`, { headers: { authorization: auth } });
139
+ // const token = res.ok ? (await res.json()).value : null; // 404 → not connected yet
140
+ console.log(`[{{APP_NAME}}] save_bookmark:`, toolInput.title ?? '(untitled)', toolInput.url);
141
+ return Response.json({ content: `Saved bookmark: ${toolInput.title ?? toolInput.url}` });
142
+ }
143
+
144
+ // ── Your own HTTP endpoints ──────────────────────────────────────────
145
+
146
+ if (req.method === 'POST' && url.pathname === '/process') {
147
+ const body = await req.json() as Record<string, unknown>;
148
+ return Response.json({ received: true, echo: body });
149
+ }
150
+
151
+ return new Response('Hello from {{APP_NAME}}!');
152
+ },
153
+ });
154
+
155
+ console.log(`[{{APP_NAME}}] Server listening on port ${server.port}`);
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "{{APP_SLUG}}",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "start": "bun run index.ts",
7
+ "dev": "kazzle run -- bun --watch index.ts",
8
+ "typecheck": "tsc --noEmit --allowImportingTsExtensions",
9
+ "check": "bun run typecheck"
10
+ },
11
+ "dependencies": {
12
+ "@kazzle/app": "{{KAZZLE_APP_VERSION}}",
13
+ "hono": "^4.12.16",
14
+ "zod": "^4.3.6"
15
+ },
16
+ "devDependencies": {
17
+ "@types/bun": "^1.3.6",
18
+ "typescript": "^5.8.3"
19
+ }
20
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "types": ["bun"]
10
+ },
11
+ "include": ["index.ts"]
12
+ }
@@ -0,0 +1,32 @@
1
+ import { defineConfig } from '@kazzle/app';
2
+
3
+ export default defineConfig({
4
+ /** Path to the app icon relative to the repo root. Uploaded to CDN on publish. */
5
+ icon: 'icon.svg',
6
+ /** App components — each entry defines a deployable unit. */
7
+ components: [
8
+ {
9
+ name: '{{APP_NAME}}',
10
+ type: 'process',
11
+ path: './components/process/index.ts',
12
+ runtime: { dev: 'bun run dev', run: 'bun run start' },
13
+ // Persistent processes are long-running servers. The platform posts
14
+ // triggers into the running server at each trigger's `path` with
15
+ // `Authorization: Bearer ${KAZZLE_TRIGGER_SECRET}`. Customer code
16
+ // validates the header before doing work.
17
+ processMode: 'persistent',
18
+ triggers: [
19
+ // Example cron — uncomment and adjust. 5-field, minute resolution.
20
+ // { name: 'hourly-sync', kind: 'schedule', schedule: '0 * * * *', path: '/cron/hourly-sync' },
21
+ // Example webhook — POSTs to /webhooks/{spaceId}/{appId}/{{APP_NAME}}/incoming
22
+ // { name: 'incoming', kind: 'webhook', path: '/webhook/incoming' },
23
+ ],
24
+ },
25
+ ],
26
+ // Skills = what the AI knows + can do with this app. Each skill is a folder
27
+ // (SKILL.md + optional tools.ts). The tools in tools.ts are backed by
28
+ // `POST /tools/<name>` routes in the process component above (the "kitchen").
29
+ skills: [
30
+ { name: '{{APP_NAME}}', path: './skills/tools/SKILL.md' },
31
+ ],
32
+ });
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "{{APP_SLUG}}",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "workspaces": [
7
+ "components/*"
8
+ ],
9
+ "scripts": {
10
+ "dev": "bun run --cwd components/process dev",
11
+ "start": "bun run --cwd components/process start",
12
+ "check": "bun run --filter='./components/*' check"
13
+ },
14
+ "dependencies": {
15
+ "@kazzle/app": "{{KAZZLE_APP_VERSION}}",
16
+ "zod": "^4.3.6"
17
+ }
18
+ }
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: {{APP_NAME}}
3
+ description: What {{APP_NAME}} can do and the tools it exposes.
4
+ alwaysActive: true
5
+ ---
6
+
7
+ # {{APP_NAME}}
8
+
9
+ This skill is the **menu** for {{APP_NAME}}. It tells the AI what this app can do
10
+ and lists the tools it can call. The **kitchen** — the code that actually runs
11
+ each tool — lives in the process component (`components/process/index.ts`) as one
12
+ `POST /tools/<name>` route per tool.
13
+
14
+ ## How a tool call works
15
+
16
+ 1. The AI calls a tool listed in `tools.ts`, e.g. `save_bookmark(url, title)`.
17
+ 2. Kazzle POSTs `{ "input": { ... } }` to that tool's handler route
18
+ (`POST /tools/save-bookmark`) on this app's process component, with the app
19
+ identity as `Authorization: Bearer <token>`.
20
+ 3. The handler does the work and returns the result — either plain text or JSON
21
+ `{ "content": "..." }`. The AI sees that as the tool result.
22
+
23
+ ## Tools
24
+
25
+ - **`save_bookmark(url, title)`** — save a bookmark and return a confirmation.
26
+
27
+ ## Adding a tool (do both, always)
28
+
29
+ 1. Add the tool to `tools.ts` with `type: "app"`, its provider-safe `name`,
30
+ friendly `displayName`, AI-facing `description`, Zod `input` schema, the
31
+ `component` that serves it (a component name from `kazzle.config.ts`), and the
32
+ `path` of its handler route.
33
+ 2. Add the matching `POST <path>` route to that component (`components/process/index.ts`).
34
+
35
+ A tool listed here with no matching route does nothing; a route with no menu entry
36
+ is never called. Never hardcode a URL or use `{{ }}` placeholders — Kazzle joins
37
+ the component's live URL and `path` at call time.
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ import type { KazzleTool } from '@kazzle/app/tools';
3
+
4
+ export const SaveBookmarkInput = z.object({
5
+ url: z.string().url().describe('URL to save.'),
6
+ title: z.string().optional().describe('Optional human-readable title for the bookmark.'),
7
+ }).strict();
8
+
9
+ export const tools = [
10
+ {
11
+ type: 'app',
12
+ name: 'save_bookmark',
13
+ displayName: 'Save bookmark',
14
+ description: 'Save a bookmark URL and return a confirmation.',
15
+ component: '{{APP_NAME}}',
16
+ path: '/tools/save-bookmark',
17
+ input: SaveBookmarkInput,
18
+ },
19
+ ] as const satisfies readonly KazzleTool[];
@@ -0,0 +1,11 @@
1
+ # Kazzle template context
2
+
3
+ This app was created from the `realtime` template. It already includes React UI, a server process, and realtime database wiring.
4
+
5
+ Template-owned setup:
6
+ - `kazzle.config.ts` declares the UI and server components. Realtime env vars are injected by `app.create`.
7
+ - `components/server/index.ts` owns the server process and sync-aware API behavior.
8
+ - `components/ui/src/` owns the realtime UI and client logic.
9
+ - `components/ui/package.json` and `components/server/package.json` already contain install, build, and check scripts.
10
+
11
+ Do not rerun `db.connect`, rewrite manifest env refs, or replace realtime setup after `app.create` reports template setup complete. Change template-owned files only when requirements actually change app behavior.