Skip to content

Commit c5ec8c8

Browse files
keugenekclaude
andcommitted
Add AppKit skill from llms.txt
Move AppKit documentation from appkit repo llms.txt to CLI skills. This establishes CLI as the single source of truth for agent guidance. Includes: - SKILL.md: Overview, hard rules, quick start - appkit-backend.md: Server, analytics, plugins, caching - appkit-frontend.md: Charts, hooks, UI components, SSE - appkit-scaffolding.md: Project setup, type generation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3e5581c commit c5ec8c8

5 files changed

Lines changed: 789 additions & 0 deletions

File tree

experimental/aitools/lib/skills/apps/.gitkeep

Whitespace-only changes.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
---
2+
name: appkit
3+
description: Build full-stack TypeScript Databricks Apps using @databricks/appkit (backend) and @databricks/appkit-ui (frontend). Use for analytics dashboards, data apps, SQL query visualization, SSE streaming, and Arrow data processing.
4+
---
5+
6+
# AppKit - Full-Stack TypeScript Databricks Apps
7+
8+
Build **full-stack TypeScript apps** on Databricks using:
9+
10+
- **Backend**: `@databricks/appkit`
11+
- **Frontend**: `@databricks/appkit-ui`
12+
- **Analytics**: SQL files in `config/queries/*.sql` executed via the AppKit analytics plugin
13+
14+
## Hard Rules (LLM Guardrails)
15+
16+
- **Do not invent APIs**. Stick to patterns shown here and documented exports only.
17+
- **`createApp()` is async**. Use **top-level `await createApp(...)`**.
18+
- **Always memoize query parameters** passed to `useAnalyticsQuery` / charts to avoid refetch loops.
19+
- **Always handle loading/error/empty states** in UI (use `Skeleton`, error text, empty state).
20+
- **Always use `sql.*` helpers** for query parameters (do not pass raw strings/numbers).
21+
- **Never construct SQL strings dynamically**. Use parameterized queries with `:paramName`.
22+
- **Never use `require()`**. Use ESM `import/export`.
23+
- **Charts do NOT accept children**. Use props (`xKey`, `yKey`, `colors`) NOT Recharts children.
24+
25+
## TypeScript Import Rules
26+
27+
With `verbatimModuleSyntax: true`, **always use `import type` for type-only imports**:
28+
29+
```ts
30+
import type { ReactNode } from "react";
31+
import { useMemo } from "react";
32+
```
33+
34+
## Detailed Documentation
35+
36+
- **Backend**: [appkit-backend.md](appkit-backend.md)
37+
- **Frontend**: [appkit-frontend.md](appkit-frontend.md)
38+
- **Project Setup**: [appkit-scaffolding.md](appkit-scaffolding.md)
39+
40+
## Quick Start
41+
42+
### Minimal Server
43+
44+
```ts
45+
// server/index.ts
46+
import { createApp, server, analytics } from "@databricks/appkit";
47+
48+
await createApp({
49+
plugins: [server(), analytics({})],
50+
});
51+
```
52+
53+
### Minimal Frontend
54+
55+
```tsx
56+
// client/src/App.tsx
57+
import { BarChart, Card, CardHeader, CardTitle, CardContent } from "@databricks/appkit-ui/react";
58+
import { useMemo } from "react";
59+
60+
export default function App() {
61+
const params = useMemo(() => ({}), []);
62+
63+
return (
64+
<Card>
65+
<CardHeader>
66+
<CardTitle>Sales by Region</CardTitle>
67+
</CardHeader>
68+
<CardContent>
69+
<BarChart queryKey="sales_by_region" parameters={params} />
70+
</CardContent>
71+
</Card>
72+
);
73+
}
74+
```
75+
76+
### SQL Query
77+
78+
```sql
79+
-- config/queries/sales_by_region.sql
80+
SELECT region, SUM(revenue) as revenue
81+
FROM sales
82+
GROUP BY region
83+
```
84+
85+
## Common Patterns
86+
87+
### useAnalyticsQuery (Custom UI Only)
88+
89+
Use only when you need custom UI (cards/KPIs/conditional rendering):
90+
91+
```tsx
92+
import { useMemo } from "react";
93+
import { useAnalyticsQuery, Skeleton } from "@databricks/appkit-ui/react";
94+
import { sql } from "@databricks/appkit-ui/js";
95+
96+
export function Users() {
97+
const params = useMemo(
98+
() => ({
99+
status: sql.string("active"),
100+
limit: sql.number(50),
101+
}),
102+
[],
103+
);
104+
105+
const { data, loading, error } = useAnalyticsQuery("users_list", params);
106+
107+
if (loading) return <Skeleton className="h-24 w-full" />;
108+
if (error) return <div className="text-destructive">Error: {error}</div>;
109+
if (!data || data.length === 0) return <div>No results</div>;
110+
111+
return <pre>{JSON.stringify(data, null, 2)}</pre>;
112+
}
113+
```
114+
115+
**Limitations:**
116+
- No `enabled` option. Use conditional rendering to mount/unmount.
117+
- No `refetch()`. Change `parameters` (memoized) or re-mount.
118+
119+
### Avoid Double-Fetching
120+
121+
```tsx
122+
// ❌ Wrong: fetches the same query twice
123+
const { data } = useAnalyticsQuery("spend_data", params);
124+
return <LineChart queryKey="spend_data" parameters={params} />;
125+
126+
// ✅ Correct: let the chart fetch
127+
return <LineChart queryKey="spend_data" parameters={params} />;
128+
```
129+
130+
## LLM Checklist
131+
132+
Before finalizing code:
133+
134+
- [ ] `package.json` has `"type": "module"`
135+
- [ ] `dev` script uses `NODE_ENV=development tsx watch server/index.ts`
136+
- [ ] `await createApp({ plugins: [...] })` is used
137+
- [ ] Charts use props NOT children
138+
- [ ] Query parameters are memoized with `useMemo`
139+
- [ ] Loading/error/empty states are explicit
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
# AppKit Backend (@databricks/appkit)
2+
3+
## Server Plugin (`server()`)
4+
5+
What it does:
6+
- Starts an Express server (default `host=0.0.0.0`, `port=8000`)
7+
- Mounts plugin routes under `/api/<pluginName>/...`
8+
- Adds `/health` (returns `{ status: "ok" }`)
9+
- Serves frontend:
10+
- **Development** (`NODE_ENV=development`): runs a Vite dev server in middleware mode
11+
- **Production**: auto-detects static frontend directory
12+
13+
```ts
14+
import { createApp, server } from "@databricks/appkit";
15+
16+
await createApp({
17+
plugins: [
18+
server({
19+
port: 8000, // default: Number(process.env.DATABRICKS_APP_PORT) || 8000
20+
host: "0.0.0.0", // default: process.env.FLASK_RUN_HOST || "0.0.0.0"
21+
autoStart: true, // default: true
22+
staticPath: "dist", // optional: force a specific static directory
23+
}),
24+
],
25+
});
26+
```
27+
28+
### Manual Server Start
29+
30+
When you need to extend Express:
31+
32+
```ts
33+
import { createApp, server } from "@databricks/appkit";
34+
35+
const appkit = await createApp({
36+
plugins: [server({ autoStart: false })],
37+
});
38+
39+
appkit.server.extend((app) => {
40+
app.get("/custom", (_req, res) => res.json({ ok: true }));
41+
});
42+
43+
await appkit.server.start();
44+
```
45+
46+
## Analytics Plugin (`analytics()`)
47+
48+
Add SQL query execution backed by Databricks SQL Warehouses:
49+
50+
```ts
51+
import { analytics, createApp, server } from "@databricks/appkit";
52+
53+
await createApp({
54+
plugins: [server(), analytics({})],
55+
});
56+
```
57+
58+
### SQL Queries
59+
60+
Put `.sql` files in `config/queries/`. Query key is filename without `.sql`:
61+
62+
```sql
63+
-- config/queries/spend_summary.sql
64+
-- @param startDate DATE
65+
-- @param endDate DATE
66+
-- @param limit NUMERIC
67+
SELECT *
68+
FROM usage
69+
WHERE usage_date BETWEEN :startDate AND :endDate
70+
LIMIT :limit
71+
```
72+
73+
**Supported `-- @param` types:** STRING, NUMERIC, BOOLEAN, DATE, TIMESTAMP, BINARY
74+
75+
**Server-injected params:**
76+
- `:workspaceId` is injected automatically and must NOT be annotated
77+
78+
### HTTP Endpoints
79+
80+
Mounted under `/api/analytics`:
81+
82+
- `POST /api/analytics/query/:query_key`
83+
- `POST /api/analytics/users/me/query/:query_key`
84+
- `GET /api/analytics/arrow-result/:jobId`
85+
86+
**Formats:**
87+
- `format: "JSON"` (default) returns JSON rows
88+
- `format: "ARROW"` returns Arrow statement_id over SSE
89+
90+
## Execution Context
91+
92+
### `asUser(req)` for User-Scoped Operations
93+
94+
```ts
95+
// Execute as the user (uses their Databricks permissions)
96+
router.post("/users/me/data", async (req, res) => {
97+
const result = await this.asUser(req).query("SELECT ...");
98+
res.json(result);
99+
});
100+
101+
// Service principal execution (default)
102+
router.post("/system/data", async (req, res) => {
103+
const result = await this.query("SELECT ...");
104+
res.json(result);
105+
});
106+
```
107+
108+
### Context Helper Functions
109+
110+
- `getExecutionContext()`: Returns current context (user or service)
111+
- `getCurrentUserId()`: Returns user ID in user context
112+
- `getWorkspaceClient()`: Returns appropriate WorkspaceClient
113+
- `getWarehouseId()`: `Promise<string>`
114+
- `getWorkspaceId()`: `Promise<string>`
115+
- `isInUserContext()`: Returns `true` if in user context
116+
117+
## Custom Plugins
118+
119+
```ts
120+
import { Plugin, toPlugin } from "@databricks/appkit";
121+
import type express from "express";
122+
123+
class MyPlugin extends Plugin {
124+
name = "my-plugin";
125+
envVars = [];
126+
127+
injectRoutes(router: express.Router) {
128+
this.route(router, {
129+
name: "hello",
130+
method: "get",
131+
path: "/hello",
132+
handler: async (_req, res) => {
133+
res.json({ ok: true });
134+
},
135+
});
136+
}
137+
}
138+
139+
export const myPlugin = toPlugin<typeof MyPlugin, Record<string, never>, "my-plugin">(
140+
MyPlugin,
141+
"my-plugin",
142+
);
143+
```
144+
145+
## Caching
146+
147+
### Global Cache
148+
149+
```ts
150+
await createApp({
151+
plugins: [server(), analytics({})],
152+
cache: {
153+
enabled: true,
154+
ttl: 3600, // seconds
155+
strictPersistence: false,
156+
},
157+
});
158+
```
159+
160+
### Plugin-Level Cache
161+
162+
```ts
163+
// inside a Plugin subclass:
164+
const value = await this.cache.getOrExecute(
165+
["my-plugin", "data", userId],
166+
async () => expensiveWork(),
167+
userKey,
168+
{ ttl: 300 },
169+
);
170+
```
171+
172+
## Environment Variables
173+
174+
### Required for Databricks Apps
175+
176+
| Variable | Description |
177+
|----------|-------------|
178+
| `DATABRICKS_HOST` | Workspace URL |
179+
| `DATABRICKS_APP_PORT` | Port to bind (default: 8000) |
180+
| `DATABRICKS_APP_NAME` | App name in Databricks |
181+
182+
### Required for SQL Queries
183+
184+
| Variable | How to Set |
185+
|----------|------------|
186+
| `DATABRICKS_WAREHOUSE_ID` | In `app.yaml`: `valueFrom: sql-warehouse` |
187+
188+
### Local Development Auth
189+
190+
**Option 1: Databricks CLI Auth (recommended)**
191+
192+
```bash
193+
databricks auth login --host [host] --profile [profile-name]
194+
DATABRICKS_CONFIG_PROFILE=my-profile npm run dev
195+
```
196+
197+
**Option 2: Environment variables**
198+
199+
```bash
200+
export DATABRICKS_HOST="https://xxx.cloud.databricks.com"
201+
export DATABRICKS_TOKEN="dapi..."
202+
export DATABRICKS_WAREHOUSE_ID="abc123..."
203+
npm run dev
204+
```
205+
206+
**Option 3: `.env` file**
207+
208+
```bash
209+
# .env (add to .gitignore!)
210+
DATABRICKS_HOST=https://xxx.cloud.databricks.com
211+
DATABRICKS_TOKEN=dapi...
212+
DATABRICKS_WAREHOUSE_ID=abc123...
213+
```

0 commit comments

Comments
 (0)