@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.
- package/README.md +63 -0
- package/dist/client.d.ts +25 -0
- package/dist/client.js +83 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +15 -0
- package/dist/migrations.d.ts +19 -0
- package/dist/migrations.js +74 -0
- package/dist/templates.d.ts +33 -0
- package/dist/templates.js +89 -0
- package/dist/tools.d.ts +36 -0
- package/dist/tools.js +22 -0
- package/dist/vite.d.ts +29 -0
- package/dist/vite.js +77 -0
- package/package.json +47 -0
- package/templates/ai-app/KAZZLE.md +21 -0
- package/templates/ai-app/bun.lock +290 -0
- package/templates/ai-app/components/server/index.ts +238 -0
- package/templates/ai-app/components/server/package.json +20 -0
- package/templates/ai-app/components/server/tsconfig.json +11 -0
- package/templates/ai-app/components/ui/index.html +12 -0
- package/templates/ai-app/components/ui/package.json +28 -0
- package/templates/ai-app/components/ui/public/favicon.svg +4 -0
- package/templates/ai-app/components/ui/src/App.tsx +52 -0
- package/templates/ai-app/components/ui/src/api.ts +85 -0
- package/templates/ai-app/components/ui/src/main.tsx +11 -0
- package/templates/ai-app/components/ui/src/styles/app.css +210 -0
- package/templates/ai-app/components/ui/src/styles/theme.css +181 -0
- package/templates/ai-app/components/ui/src/tabs/chat-tab.tsx +75 -0
- package/templates/ai-app/components/ui/src/tabs/extract-tab.tsx +92 -0
- package/templates/ai-app/components/ui/src/tabs/image-tab.tsx +51 -0
- package/templates/ai-app/components/ui/src/tabs/speech-tab.tsx +47 -0
- package/templates/ai-app/components/ui/src/tabs/transcribe-tab.tsx +42 -0
- package/templates/ai-app/components/ui/src/tabs/video-tab.tsx +76 -0
- package/templates/ai-app/components/ui/src/types.ts +53 -0
- package/templates/ai-app/components/ui/src/vite-env.d.ts +1 -0
- package/templates/ai-app/components/ui/tsconfig.json +21 -0
- package/templates/ai-app/components/ui/vite.config.ts +22 -0
- package/templates/ai-app/kazzle.config.ts +9 -0
- package/templates/ai-app/package.json +17 -0
- package/templates/empty-app/KAZZLE.md +10 -0
- package/templates/empty-app/kazzle.config.ts +8 -0
- package/templates/empty-app/package.json +15 -0
- package/templates/manifest.json +10 -0
- package/templates/process-app/KAZZLE.md +10 -0
- package/templates/process-app/components/process/index.ts +155 -0
- package/templates/process-app/components/process/package.json +20 -0
- package/templates/process-app/components/process/tsconfig.json +12 -0
- package/templates/process-app/kazzle.config.ts +32 -0
- package/templates/process-app/package.json +18 -0
- package/templates/process-app/skills/tools/SKILL.md +37 -0
- package/templates/process-app/skills/tools/tools.ts +19 -0
- package/templates/realtime-app/KAZZLE.md +11 -0
- package/templates/realtime-app/bun.lock +887 -0
- package/templates/realtime-app/components/server/deploy.ts +105 -0
- package/templates/realtime-app/components/server/index.ts +40 -0
- package/templates/realtime-app/components/server/migrations/001_items.sql +6 -0
- package/templates/realtime-app/components/server/package.json +20 -0
- package/templates/realtime-app/components/server/routes.ts +137 -0
- package/templates/realtime-app/components/server/tsconfig.json +15 -0
- package/templates/realtime-app/components/ui/index.html +13 -0
- package/templates/realtime-app/components/ui/package.json +36 -0
- package/templates/realtime-app/components/ui/public/favicon.svg +4 -0
- package/templates/realtime-app/components/ui/src/App.tsx +77 -0
- package/templates/realtime-app/components/ui/src/lib/connector.ts +80 -0
- package/templates/realtime-app/components/ui/src/lib/schema.ts +35 -0
- package/templates/realtime-app/components/ui/src/lib/sync.ts +24 -0
- package/templates/realtime-app/components/ui/src/main.tsx +49 -0
- package/templates/realtime-app/components/ui/src/styles/theme.css +181 -0
- package/templates/realtime-app/components/ui/src/vite-env.d.ts +2 -0
- package/templates/realtime-app/components/ui/tsconfig.json +14 -0
- package/templates/realtime-app/components/ui/vite.config.ts +86 -0
- package/templates/realtime-app/kazzle.config.ts +11 -0
- package/templates/realtime-app/package.json +17 -0
- package/templates/shared/icon.svg +4 -0
- package/templates/ui-app/KAZZLE.md +16 -0
- package/templates/ui-app/components/ui/bun.lock +894 -0
- package/templates/ui-app/components/ui/index.html +13 -0
- package/templates/ui-app/components/ui/package.json +30 -0
- package/templates/ui-app/components/ui/public/favicon.svg +4 -0
- package/templates/ui-app/components/ui/src/App.tsx +41 -0
- package/templates/ui-app/components/ui/src/main.tsx +24 -0
- package/templates/ui-app/components/ui/src/styles/theme.css +181 -0
- package/templates/ui-app/components/ui/tsconfig.json +21 -0
- package/templates/ui-app/components/ui/vite.config.ts +97 -0
- package/templates/ui-app/kazzle.config.ts +10 -0
- package/templates/ui-app/package.json +16 -0
- package/templates/ui-db-app/KAZZLE.md +12 -0
- package/templates/ui-db-app/components/server/index.ts +218 -0
- package/templates/ui-db-app/components/server/package.json +21 -0
- package/templates/ui-db-app/components/server/tsconfig.json +11 -0
- package/templates/ui-db-app/components/ui/index.html +12 -0
- package/templates/ui-db-app/components/ui/package.json +28 -0
- package/templates/ui-db-app/components/ui/public/favicon.svg +4 -0
- package/templates/ui-db-app/components/ui/src/App.tsx +221 -0
- package/templates/ui-db-app/components/ui/src/api.ts +79 -0
- package/templates/ui-db-app/components/ui/src/main.tsx +10 -0
- package/templates/ui-db-app/components/ui/src/styles/theme.css +181 -0
- package/templates/ui-db-app/components/ui/src/types.ts +17 -0
- package/templates/ui-db-app/components/ui/src/vite-env.d.ts +1 -0
- package/templates/ui-db-app/components/ui/tsconfig.json +21 -0
- package/templates/ui-db-app/components/ui/vite.config.ts +19 -0
- package/templates/ui-db-app/kazzle.config.ts +9 -0
- 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,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.
|