create-theokit 1.0.14 → 1.0.16

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 (30) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cli.js +4 -13
  3. package/dist/cli.js.map +1 -1
  4. package/package.json +10 -8
  5. package/templates/default/CLAUDE.md +2 -2
  6. package/templates/default/README.md.tmpl +38 -50
  7. package/templates/default/agents/chat.ts +22 -0
  8. package/templates/default/app/layout.tsx +71 -3
  9. package/templates/default/app/page.tsx +296 -61
  10. package/templates/default/dot-claude/skills/theokit-agents/SKILL.md +78 -44
  11. package/templates/default/dot-claude/skills/theokit-frontend/SKILL.md +24 -26
  12. package/templates/default/dot-claude/skills/theokit-ui/SKILL.md +32 -28
  13. package/templates/default/eslint.config.mjs +1 -5
  14. package/templates/default/package.json.tmpl +16 -9
  15. package/templates/default/theo.config.ts +1 -3
  16. package/templates/default/tsconfig.json +7 -14
  17. package/templates/default/types/jobs.d.ts +25 -0
  18. package/templates/services/agent-node/Dockerfile.tmpl +20 -0
  19. package/templates/services/agent-node/README.md +38 -0
  20. package/templates/services/agent-node/package.json.tmpl +18 -0
  21. package/templates/services/agent-node/src/index.ts +58 -0
  22. package/templates/services/agent-node/tsconfig.json +13 -0
  23. package/templates/default/AGENTS.md +0 -106
  24. package/templates/default/app/globals.css +0 -377
  25. package/templates/default/app.ts +0 -15
  26. package/templates/default/drizzle.config.ts +0 -10
  27. package/templates/default/server/db/index.ts +0 -10
  28. package/templates/default/server/db/schema.ts +0 -13
  29. package/templates/default/server/index.ts +0 -2
  30. package/templates/default/tests/tasks.test.ts +0 -18
@@ -1,6 +1,10 @@
1
1
  # {{name}}
2
2
 
3
- Built with [TheoKit](https://theokit.dev) — Build the app your agent lives in.
3
+ Built with [TheoKit](https://theokit.dev) — **Build the app your agent lives in.**
4
+
5
+ This is the default template: a working **agent chat surface** wired to a mock
6
+ streaming agent, ready for you to plug in your LLM. Run it and you immediately
7
+ see your agent talking — that's the point.
4
8
 
5
9
  ## Getting Started
6
10
 
@@ -8,75 +12,60 @@ Built with [TheoKit](https://theokit.dev) — Build the app your agent lives in.
8
12
  # Install dependencies
9
13
  npm install
10
14
 
11
- # Seed the database with sample data
12
- npm run seed
13
-
14
- # Start development server
15
+ # Start development server (HMR)
15
16
  npm run dev
16
17
  ```
17
18
 
19
+ Open the app and chat with the agent. Edit the agent at `agents/chat.ts`
20
+ (pick your model / add tools) — the `@theokit/sdk` runtime resolves OpenRouter /
21
+ Anthropic / Ollama / OpenAI from env vars (see the file's header comment).
22
+
18
23
  ## Project Structure
19
24
 
20
25
  ```
21
26
  {{name}}/
22
27
  ├── app/
23
- │ ├── page.tsx # React frontend
24
- │ ├── layout.tsx # Root layout
25
- │ └── globals.css # Global styles
28
+ │ ├── page.tsx # Chat surface (@theokit/ui: ChatThread, ChatComposer, …)
29
+ │ └── layout.tsx # Root layout
26
30
  ├── server/
27
- │ ├── routes/
28
- │ │ ├── health.ts # GET /api/health
29
- │ │ └── tasks/
30
- │ │ ├── index.ts # GET /api/tasks + POST /api/tasks
31
- │ │ └── [id].ts # GET/PUT/DELETE /api/tasks/:id
32
- │ ├── db/
33
- │ │ ├── schema.ts # Drizzle ORM schema (SQLite)
34
- │ │ ├── index.ts # DB connection + auto-create tables
35
- │ │ └── seed.ts # Seed data
36
- │ └── index.ts # Server barrel (routes auto-discovered)
37
- ├── tests/
38
- │ └── tasks.test.ts # API smoke test
31
+ │ └── routes/
32
+ │ ├── chat.ts # Streaming agent endpoint (mock — replace with your LLM)
33
+ │ └── health.ts # GET /api/health
34
+ ├── tailwind.config.ts # TheoUI design tokens
39
35
  ├── theo.config.ts # TheoKit configuration
40
36
  └── .env.example # Environment variables
41
37
  ```
42
38
 
43
- ## API Endpoints
39
+ ## Key Concepts
44
40
 
45
- | Method | Path | Description |
46
- |--------|------|-------------|
47
- | GET | /api/tasks | List all tasks |
48
- | GET | /api/tasks/:id | Get task by ID |
49
- | POST | /api/tasks | Create task |
50
- | PUT | /api/tasks/:id | Update task |
51
- | DELETE | /api/tasks/:id | Delete task |
52
- | GET | /api/health | Health check |
41
+ - **Agent chat surface** — `app/page.tsx` renders `@theokit/ui` chat components
42
+ driven by the streaming endpoint.
43
+ - **`agents/chat.ts` (`defineAgent`)** — one file auto-served at `POST /api/agents/chat`,
44
+ streaming the ai-sdk `UIMessageStream` wire; `useAgent('/api/agents/chat')` on the client
45
+ reconstructs `UIMessage[]` you render via `message.parts`. `@theokit/sdk` owns
46
+ conversation persistence and provider resolution.
47
+ - **`defineAgentTool`** — declare typed tools (the template ships a
48
+ `current_time` example).
53
49
 
54
- ## Examples
50
+ ## About `@theokit/sdk`
55
51
 
56
- ```bash
57
- # List tasks
58
- curl http://localhost:3000/api/tasks
52
+ `@theokit/sdk` is the agent runtime this template depends on. Its publish to the
53
+ public npm registry is **operator-deferred** — if `npm install` cannot resolve
54
+ it yet, scaffold the registry-free variant instead (see Bare mode below) and add
55
+ the SDK once it is published.
59
56
 
60
- # Create a task
61
- curl -X POST http://localhost:3000/api/tasks \
62
- -H "Content-Type: application/json" \
63
- -d '{"title":"New task","priority":"high"}'
57
+ ## Bare mode (`--bare`)
64
58
 
65
- # Update a task
66
- curl -X PUT http://localhost:3000/api/tasks/1 \
67
- -H "Content-Type: application/json" \
68
- -d '{"done":true}'
59
+ Want the minimal "Hello Theo" scaffold with **no** agent UI and **no** unpublished
60
+ registry dependencies? Scaffold with `--bare`:
69
61
 
70
- # Delete a task
71
- curl -X DELETE http://localhost:3000/api/tasks/1
62
+ ```bash
63
+ npm create theokit my-app -- --template=default --bare
72
64
  ```
73
65
 
74
- ## Key Concepts
75
-
76
- - **defineRoute** — Typed API routes with Zod validation
77
- - **Drizzle + SQLite** — Type-safe database, zero config
78
- - **File-based routing** — `server/routes/tasks/[id].ts` → `/api/tasks/:id`
79
- - **Seeds** — `npm run seed` loads sample data
66
+ `--bare` strips `@theokit/ui`, `@theokit/sdk`, `lucide-react`, and the Tailwind
67
+ toolchain, replaces `app/page.tsx` with a plain "Hello Theo" page, and removes
68
+ the mock chat route — a scaffold that always installs without registry access.
80
69
 
81
70
  ## Commands
82
71
 
@@ -85,7 +74,6 @@ npm run dev # Start dev server with HMR
85
74
  npm run build # Build for production
86
75
  npm run start # Run production build
87
76
  npm run test # Run tests (vitest)
88
- npm run seed # Seed database
89
77
  npm run lint # ESLint check
90
78
  npm run typecheck # TypeScript type check
91
79
  ```
@@ -0,0 +1,22 @@
1
+ import { defineAgent } from '@theokit/agents'
2
+ import { z } from 'zod'
3
+
4
+ /**
5
+ * Chat agent — the zero-config `agents/*.ts` convention.
6
+ *
7
+ * This one file is auto-served at `POST /api/agents/chat` (dev + build), streaming the
8
+ * ai-sdk `UIMessageStream` that `useAgent('chat')` consumes on the client. No manual route,
9
+ * no manual client wiring. `@theokit/sdk` runs it; conversation turns auto-persist per
10
+ * session (the SDK owns storage).
11
+ *
12
+ * Provider: resolved from the environment — OPENROUTER_API_KEY (preferred — gateway to many
13
+ * models) OR ANTHROPIC_API_KEY / OPENAI_API_KEY. The `model` id is prefixed with the provider
14
+ * namespace so OpenRouter routes it upstream (see https://openrouter.ai/models).
15
+ *
16
+ * Add tools with `defineAgentTool` (from `theokit/server`) and pass them to `tools: [...]`.
17
+ */
18
+ export default defineAgent({
19
+ input: z.object({ message: z.string() }),
20
+ model: 'openai/gpt-4o-mini',
21
+ system: 'You are a helpful assistant.',
22
+ })
@@ -1,5 +1,73 @@
1
- import './globals.css'
1
+ import '@theokit/ui/styles.css'
2
2
 
3
- export default function Layout({ children }: { children: React.ReactNode }) {
4
- return <>{children}</>
3
+ import { Outlet } from 'react-router'
4
+ import { AgentProfile, ThemeSwitcher, CostMeter, type AgentProfileDescriptor } from '@theokit/ui'
5
+ import { Sidebar, TopNav, Tooltip, Badge } from '@usetheo/ui'
6
+ import { Bot, MessageSquare, History, Settings } from 'lucide-react'
7
+
8
+ const AGENTS: AgentProfileDescriptor[] = [
9
+ {
10
+ id: 'theo',
11
+ name: 'Theo',
12
+ description: 'Edit the agent at agents/chat.ts.',
13
+ tone: 'primary',
14
+ initials: 'TH',
15
+ badge: 'Online',
16
+ },
17
+ ]
18
+
19
+ export default function RootLayout() {
20
+ return (
21
+ <div className="grid h-screen w-screen grid-rows-[auto_1fr] bg-background text-foreground">
22
+ <TopNav className="border-border/60 border-b px-4 py-2">
23
+ <TopNav.Left>
24
+ <Tooltip label="TheoKit agent surface" side="bottom">
25
+ <span className="inline-flex items-center gap-2">
26
+ <Bot className="size-5 text-primary" aria-hidden />
27
+ <span className="font-semibold text-sm tracking-tight">Theo Agent</span>
28
+ <Badge variant="outline" size="sm">
29
+ v0.1
30
+ </Badge>
31
+ </span>
32
+ </Tooltip>
33
+ </TopNav.Left>
34
+ <TopNav.Right>
35
+ <Tooltip label="Toggle theme" side="bottom" align="end">
36
+ <span>
37
+ <ThemeSwitcher />
38
+ </span>
39
+ </Tooltip>
40
+ </TopNav.Right>
41
+ </TopNav>
42
+
43
+ <div className="grid h-full grid-cols-[260px_1fr] overflow-hidden">
44
+ <Sidebar className="flex flex-col border-border/60 border-r p-3">
45
+ <Sidebar.Header>
46
+ <AgentProfile agents={AGENTS} activeId="theo" />
47
+ </Sidebar.Header>
48
+
49
+ <Sidebar.Section title="Workspace">
50
+ <Sidebar.Item icon={MessageSquare} active>
51
+ New conversation
52
+ </Sidebar.Item>
53
+ <Sidebar.Item icon={History}>History</Sidebar.Item>
54
+ <Sidebar.Item icon={Settings}>Settings</Sidebar.Item>
55
+ </Sidebar.Section>
56
+
57
+ <Sidebar.Footer className="mt-auto">
58
+ <CostMeter
59
+ compact
60
+ title="This session"
61
+ cost={0.0023}
62
+ delta={{ value: 0.0023, period: 'now' }}
63
+ />
64
+ </Sidebar.Footer>
65
+ </Sidebar>
66
+
67
+ <main className="flex h-full flex-col overflow-hidden">
68
+ <Outlet />
69
+ </main>
70
+ </div>
71
+ </div>
72
+ )
5
73
  }
@@ -1,68 +1,303 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useMemo, useState } from 'react'
4
+ import {
5
+ ChatThread,
6
+ ChatMessage,
7
+ ChatComposer,
8
+ ToolCallCard,
9
+ AgentStreaming,
10
+ AgentErrorCard,
11
+ QuickActionChips,
12
+ ContextWindowBar,
13
+ type Message,
14
+ type QuickAction,
15
+ type ToolCallStatus,
16
+ } from '@theokit/ui'
17
+ import {
18
+ EmptyState,
19
+ CommandPalette,
20
+ Avatar,
21
+ Tooltip,
22
+ Button,
23
+ ScrollArea,
24
+ type CommandItem,
25
+ } from '@usetheo/ui'
26
+ import { Sparkles, Wrench, RotateCcw, Command } from 'lucide-react'
27
+ import { useAgent } from 'theokit/client'
28
+
29
+ /**
30
+ * Default scaffold — an Agent Surface, composed entirely from TheoUI.
31
+ *
32
+ * ChatThread / ChatMessage → conversation
33
+ * ToolCallCard → expandable tool invocations
34
+ * AgentStreaming → streaming indicator
35
+ * AgentErrorCard → error display
36
+ * ChatComposer → bottom input bar
37
+ * EmptyState → first-load screen
38
+ * ContextWindowBar → context usage at top
39
+ * CommandPalette → ⌘K quick actions
40
+ * Avatar → assistant face in messages
41
+ * Tooltip → hints on icons
42
+ *
43
+ * `useAgent` binds to the `agents/chat.ts` endpoint, consumes the ai-sdk
44
+ * `UIMessageStream`, and handles AbortController cleanup + StrictMode safety.
45
+ * Edit `agents/chat.ts` to pick your model / add tools.
46
+ */
47
+
48
+ type ConversationItem =
49
+ | { kind: 'message'; id: string; role: 'user' | 'assistant'; content: string; timestamp: string }
50
+ | {
51
+ kind: 'tool'
52
+ id: string
53
+ tool: string
54
+ target?: string
55
+ status: ToolCallStatus
56
+ output?: string
57
+ timestamp: string
58
+ }
59
+ | { kind: 'error'; id: string; message: string; timestamp: string }
60
+
61
+ const QUICK_ACTIONS: QuickAction[] = [
62
+ { id: 'summarize', label: 'Summarize this page', icon: Sparkles },
63
+ { id: 'tools', label: 'Show available tools', icon: Wrench },
64
+ { id: 'reset', label: 'Start a new conversation', icon: RotateCcw },
65
+ ]
66
+
67
+ const COMMAND_ITEMS: CommandItem[] = QUICK_ACTIONS.map((a) => ({
68
+ id: a.id,
69
+ label: a.label,
70
+ icon: a.icon,
71
+ group: 'Quick actions',
72
+ }))
73
+
74
+ // Display-only context-window hint. The agent's real model lives in `agents/chat.ts`
75
+ // (`model: 'openai/gpt-4o-mini'`); wire real token counts from the stream when you need them.
76
+ const CONTEXT_USED = 4_200
77
+ const CONTEXT_TOTAL = 200_000
78
+ const MODEL_NAME = 'gpt-4o-mini'
79
+
80
+ const ASSISTANT_AVATAR = (
81
+ <Avatar size="sm" tone="primary">
82
+ <Avatar.Fallback>TH</Avatar.Fallback>
83
+ </Avatar>
84
+ )
85
+ const USER_AVATAR = (
86
+ <Avatar size="sm" tone="muted">
87
+ <Avatar.Fallback>YOU</Avatar.Fallback>
88
+ </Avatar>
89
+ )
90
+
1
91
  export default function Page() {
92
+ const [composerValue, setComposerValue] = useState('')
93
+ const [userMessages, setUserMessages] = useState<ConversationItem[]>([])
94
+ const [paletteOpen, setPaletteOpen] = useState(false)
95
+ const { messages, send, status, reset } = useAgent<{ message: string }>('/api/agents/chat')
96
+
97
+ // ⌘K / Ctrl+K opens the CommandPalette.
98
+ useEffect(() => {
99
+ function onKey(e: KeyboardEvent) {
100
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
101
+ e.preventDefault()
102
+ setPaletteOpen((v) => !v)
103
+ }
104
+ }
105
+ window.addEventListener('keydown', onKey)
106
+ return () => window.removeEventListener('keydown', onKey)
107
+ }, [])
108
+
109
+ // Derive the conversation view from the reconstructed assistant UIMessages
110
+ // (ai-sdk `UIMessageStream`): text parts → messages, tool parts → tool cards.
111
+ const items = useMemo<ConversationItem[]>(() => {
112
+ const ts = new Date().toISOString()
113
+ const agentItems: ConversationItem[] = []
114
+ for (const message of messages) {
115
+ if (message.role !== 'assistant') continue
116
+ message.parts.forEach((part, i) => {
117
+ const id = `${message.id}-${i}`
118
+ if (part.type === 'text') {
119
+ agentItems.push({
120
+ kind: 'message',
121
+ id,
122
+ role: 'assistant',
123
+ content: part.text,
124
+ timestamp: ts,
125
+ })
126
+ } else if (part.type === 'dynamic-tool') {
127
+ agentItems.push({
128
+ kind: 'tool',
129
+ id,
130
+ tool: part.toolName,
131
+ target:
132
+ part.input && typeof part.input === 'object'
133
+ ? Object.entries(part.input as Record<string, unknown>)
134
+ .map(([k, v]) => `${k}=${JSON.stringify(v)}`)
135
+ .join(' ')
136
+ : undefined,
137
+ status:
138
+ part.state === 'output-available'
139
+ ? 'success'
140
+ : part.state === 'output-error'
141
+ ? 'error'
142
+ : 'running',
143
+ output:
144
+ part.state === 'output-available'
145
+ ? typeof part.output === 'string'
146
+ ? part.output
147
+ : JSON.stringify(part.output, null, 2)
148
+ : part.state === 'output-error'
149
+ ? part.errorText
150
+ : undefined,
151
+ timestamp: ts,
152
+ })
153
+ }
154
+ })
155
+ }
156
+ return [...userMessages, ...agentItems]
157
+ }, [userMessages, messages])
158
+
159
+ function handleSubmit(value: string) {
160
+ const trimmed = value.trim()
161
+ if (!trimmed) return
162
+ const id = `u-${userMessages.length}`
163
+ setUserMessages((prev) => [
164
+ ...prev,
165
+ { kind: 'message', id, role: 'user', content: trimmed, timestamp: new Date().toISOString() },
166
+ ])
167
+ send({ message: trimmed })
168
+ setComposerValue('')
169
+ }
170
+
171
+ function handleQuickAction(id: string) {
172
+ setPaletteOpen(false)
173
+ if (id === 'reset') {
174
+ setUserMessages([])
175
+ reset()
176
+ return
177
+ }
178
+ const action = QUICK_ACTIONS.find((a) => a.id === id)
179
+ if (action) handleSubmit(action.label)
180
+ }
181
+
182
+ const isStreaming = status === 'streaming'
183
+ const isEmpty = items.length === 0 && !isStreaming
184
+ const hasError = status === 'error'
185
+
2
186
  return (
3
- <div className="page">
4
- <div className="main">
5
- <header className="hero">
6
- <img src="/logo.png" alt="TheoKit" width={72} height={72} className="hero-logo" />
7
- <h1>TheoKit</h1>
8
- <p className="tagline">Build the app your agent lives in.</p>
9
- <nav className="ctas">
10
- <a
11
- href="https://usetheo.dev"
12
- target="_blank"
13
- rel="noopener noreferrer"
14
- className="btn primary"
15
- >
16
- Get Started
17
- </a>
18
- <a
19
- href="https://github.com/usetheodev/theokit"
20
- target="_blank"
21
- rel="noopener noreferrer"
22
- className="btn secondary"
23
- >
24
- Documentation
25
- </a>
26
- </nav>
27
- <p className="hint">
28
- Edit <code>app/page.tsx</code> to get started. Changes hot-reload instantly.
29
- </p>
30
- </header>
31
-
32
- <div className="grid features">
33
- <div className="feature">
34
- <h3>defineRoute</h3>
35
- <p>
36
- Typed API routes with Zod validation. See <code>server/routes/</code>
37
- </p>
38
- </div>
39
- <div className="feature">
40
- <h3>Drizzle + SQLite</h3>
41
- <p>
42
- Type-safe database with zero config. Schema in <code>server/db/schema.ts</code>
43
- </p>
44
- </div>
45
- <div className="feature">
46
- <h3>@Agent + @Tool</h3>
47
- <p>AI agents with SSE streaming, budget control, and human-in-the-loop approval.</p>
48
- </div>
49
- <div className="feature">
50
- <h3>React + Vite</h3>
51
- <p>File-based routing, HMR, SSR streaming. Edit and see changes instantly.</p>
52
- </div>
187
+ <>
188
+ <ContextWindowBar
189
+ used={CONTEXT_USED}
190
+ total={CONTEXT_TOTAL}
191
+ trailing={MODEL_NAME}
192
+ label="Context window"
193
+ compact
194
+ className="border-border/60 border-b px-6 py-2"
195
+ />
196
+
197
+ <ScrollArea className="flex-1">
198
+ <div className="mx-auto flex w-full max-w-3xl flex-col gap-4 px-6 py-6">
199
+ {isEmpty ? (
200
+ <EmptyState
201
+ eyebrow="Theo Agent"
202
+ icon={Sparkles}
203
+ title="What should we build today?"
204
+ description="Ask anything. This scaffold ships an agent at agents/chat.ts — edit it to pick your model or add tools."
205
+ action={<QuickActionChips actions={QUICK_ACTIONS} onSelect={handleQuickAction} />}
206
+ />
207
+ ) : (
208
+ <ChatThread>
209
+ {items.map((item) => {
210
+ if (item.kind === 'message') {
211
+ const message: Message = {
212
+ id: item.id,
213
+ role: item.role,
214
+ content: item.content,
215
+ timestamp: item.timestamp,
216
+ model: item.role === 'assistant' ? MODEL_NAME : undefined,
217
+ }
218
+ return (
219
+ <ChatMessage
220
+ key={item.id}
221
+ message={message}
222
+ avatar={item.role === 'assistant' ? ASSISTANT_AVATAR : USER_AVATAR}
223
+ />
224
+ )
225
+ }
226
+ if (item.kind === 'tool') {
227
+ return (
228
+ <ToolCallCard
229
+ key={item.id}
230
+ tool={item.tool}
231
+ icon={Wrench}
232
+ target={item.target}
233
+ status={item.status}
234
+ output={item.output}
235
+ timestamp={item.timestamp}
236
+ />
237
+ )
238
+ }
239
+ return (
240
+ <AgentErrorCard
241
+ key={item.id}
242
+ kind="model"
243
+ title="Agent error"
244
+ description={item.message}
245
+ />
246
+ )
247
+ })}
248
+ {isStreaming && <AgentStreaming model={MODEL_NAME} />}
249
+ </ChatThread>
250
+ )}
53
251
  </div>
252
+ </ScrollArea>
54
253
 
55
- <footer className="footer">
56
- Powered by{' '}
57
- <a href="https://usetheo.dev" target="_blank" rel="noopener noreferrer">
58
- TheoKit
59
- </a>
60
- {' · '}
61
- <a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer">
62
- GitHub
63
- </a>
64
- </footer>
254
+ <div className="border-border/60 border-t bg-background/50 backdrop-blur">
255
+ <div className="mx-auto w-full max-w-3xl px-6 py-4">
256
+ {hasError && (
257
+ <div className="mb-3">
258
+ <AgentErrorCard
259
+ kind="network"
260
+ title="Stream ended with an error"
261
+ description="The connection to the agent endpoint was interrupted. Reset to try again."
262
+ action={
263
+ <Button variant="ghost" size="sm" onClick={() => reset()}>
264
+ Reset
265
+ </Button>
266
+ }
267
+ />
268
+ </div>
269
+ )}
270
+ <ChatComposer
271
+ value={composerValue}
272
+ onValueChange={setComposerValue}
273
+ onSubmit={handleSubmit}
274
+ running={isStreaming}
275
+ placeholder="Ask the agent…"
276
+ leadingActions={
277
+ <Tooltip label="Open command palette (⌘K)" side="top">
278
+ <Button
279
+ type="button"
280
+ variant="ghost"
281
+ size="icon"
282
+ onClick={() => setPaletteOpen(true)}
283
+ aria-label="Open command palette"
284
+ >
285
+ <Command className="size-4" />
286
+ </Button>
287
+ </Tooltip>
288
+ }
289
+ />
290
+ </div>
65
291
  </div>
66
- </div>
292
+
293
+ <CommandPalette
294
+ open={paletteOpen}
295
+ onOpenChange={setPaletteOpen}
296
+ items={COMMAND_ITEMS}
297
+ onSelect={handleQuickAction}
298
+ placeholder="Run a command…"
299
+ emptyMessage="No matching commands."
300
+ />
301
+ </>
67
302
  )
68
303
  }