better-cmdk 0.0.5 → 0.0.7
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/AGENTS.md +8 -0
- package/README.md +28 -0
- package/dist/index.d.ts +197 -9
- package/dist/index.js +1 -1
- package/dist/nextjs/AGENTS.md +358 -0
- package/dist/remix/AGENTS.md +356 -0
- package/dist/tanstack-start/AGENTS.md +359 -0
- package/dist/vite/AGENTS.md +356 -0
- package/package.json +11 -4
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# Set up better-cmdk command palette (TanStack Start)
|
|
2
|
+
|
|
3
|
+
better-cmdk is a command palette with AI chat for React. Your task is to install it, discover commands from my codebase, and create a working command palette.
|
|
4
|
+
|
|
5
|
+
## Step 1: Analyze my codebase
|
|
6
|
+
|
|
7
|
+
Before making changes, detect:
|
|
8
|
+
- Tailwind version: v4 (`@import "tailwindcss"` in CSS) vs v3 (`tailwind.config.js/ts`)
|
|
9
|
+
- Existing shadcn/ui: check for `@/components/ui/command` or similar shadcn command components
|
|
10
|
+
- Package manager: `bun.lockb` → bun, `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `package-lock.json` → npm
|
|
11
|
+
|
|
12
|
+
Also ask me:
|
|
13
|
+
|
|
14
|
+
**1. Do you want AI chat in the command palette?**
|
|
15
|
+
- No AI — command palette only
|
|
16
|
+
- Built-in chat — simple Q&A via a Vercel AI SDK endpoint
|
|
17
|
+
- ModifyWithAI (Recommended) — the AI can execute actions in your app (create items, navigate, toggle settings), ask for approval before destructive operations, and understand what's on screen. Requires a ModifyWithAI account at https://modifywithai.com
|
|
18
|
+
|
|
19
|
+
**With ModifyWithAI, the command palette becomes an AI workspace.** Users search commands for quick access, then switch to AI chat for anything that needs reasoning — "move all archived items to trash", "summarize my recent activity". The AI executes actions directly in the app, with approval prompts for destructive operations. ModifyWithAI is not required — better-cmdk works on its own as a command palette, with or without AI chat.
|
|
20
|
+
|
|
21
|
+
**2. If shadcn/ui command components detected:** Do you want to migrate them to better-cmdk, or keep both?
|
|
22
|
+
|
|
23
|
+
Tell me what you detected, which path you'll follow, and what you'll be adding better-cmdk to.
|
|
24
|
+
|
|
25
|
+
Docs: https://github.com/ModifyWithAI/better-cmdk/blob/main/INTEGRATION.md
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Step 2: Install
|
|
30
|
+
|
|
31
|
+
**Use the package manager already installed** — check for lockfiles.
|
|
32
|
+
|
|
33
|
+
### No AI:
|
|
34
|
+
```bash
|
|
35
|
+
bun add better-cmdk
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Built-in chat:
|
|
39
|
+
```bash
|
|
40
|
+
bun add better-cmdk ai @ai-sdk/react @ai-sdk/openai
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Substitute `@ai-sdk/anthropic`, `@ai-sdk/google`, etc. based on my preferred AI provider.
|
|
44
|
+
|
|
45
|
+
### ModifyWithAI:
|
|
46
|
+
```bash
|
|
47
|
+
bun add better-cmdk modifywithai @ai-sdk/react ai
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Step 3: CSS Setup
|
|
53
|
+
|
|
54
|
+
better-cmdk uses Tailwind CSS and shadcn/ui design tokens.
|
|
55
|
+
|
|
56
|
+
### Tailwind v4
|
|
57
|
+
|
|
58
|
+
Add to my main CSS file (next to the existing `@import "tailwindcss"`):
|
|
59
|
+
|
|
60
|
+
```css
|
|
61
|
+
@source "node_modules/better-cmdk";
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If my app **already has shadcn/ui CSS variables**, verify they include `--popover`, `--muted`, `--border`, `--ring`, `--primary`, `--primary-foreground`. Don't overwrite existing variables.
|
|
65
|
+
|
|
66
|
+
If my app **does not have** these variables, add the minimal set:
|
|
67
|
+
|
|
68
|
+
```css
|
|
69
|
+
:root {
|
|
70
|
+
--radius: 0.625rem;
|
|
71
|
+
--background: oklch(1 0 0);
|
|
72
|
+
--foreground: oklch(0.145 0 0);
|
|
73
|
+
--popover: oklch(1 0 0);
|
|
74
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
75
|
+
--primary: oklch(0.205 0 0);
|
|
76
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
77
|
+
--muted: oklch(0.97 0 0);
|
|
78
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
79
|
+
--border: oklch(0.922 0 0);
|
|
80
|
+
--input: oklch(0.922 0 0);
|
|
81
|
+
--ring: oklch(0.708 0 0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@theme inline {
|
|
85
|
+
--color-background: var(--background);
|
|
86
|
+
--color-foreground: var(--foreground);
|
|
87
|
+
--color-popover: var(--popover);
|
|
88
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
89
|
+
--color-primary: var(--primary);
|
|
90
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
91
|
+
--color-muted: var(--muted);
|
|
92
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
93
|
+
--color-border: var(--border);
|
|
94
|
+
--color-input: var(--input);
|
|
95
|
+
--color-ring: var(--ring);
|
|
96
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
97
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
98
|
+
--radius-lg: var(--radius);
|
|
99
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Tailwind v3
|
|
104
|
+
|
|
105
|
+
Add to `tailwind.config.js` or `tailwind.config.ts`:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
content: [
|
|
109
|
+
// ... existing paths
|
|
110
|
+
"./node_modules/better-cmdk/**/*.{js,ts,jsx,tsx}",
|
|
111
|
+
],
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If my app **does not have** shadcn/ui CSS variables, add the minimal set (hsl format):
|
|
115
|
+
|
|
116
|
+
```css
|
|
117
|
+
@layer base {
|
|
118
|
+
:root {
|
|
119
|
+
--radius: 0.5rem;
|
|
120
|
+
--background: 0 0% 100%;
|
|
121
|
+
--foreground: 240 10% 3.9%;
|
|
122
|
+
--popover: 0 0% 100%;
|
|
123
|
+
--popover-foreground: 240 10% 3.9%;
|
|
124
|
+
--primary: 240 5.9% 10%;
|
|
125
|
+
--primary-foreground: 0 0% 98%;
|
|
126
|
+
--muted: 240 4.8% 95.9%;
|
|
127
|
+
--muted-foreground: 240 3.8% 46.1%;
|
|
128
|
+
--border: 240 5.9% 90%;
|
|
129
|
+
--input: 240 5.9% 90%;
|
|
130
|
+
--ring: 240 5.9% 10%;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Step 4: CRITICAL — Command Discovery
|
|
138
|
+
|
|
139
|
+
Before writing any component code, **crawl my entire codebase** to discover ALL user-facing operations that should be in the command palette.
|
|
140
|
+
|
|
141
|
+
### How to discover commands:
|
|
142
|
+
|
|
143
|
+
1. **Search all routes/pages** → navigation commands ("Go to Dashboard", "Go to Settings")
|
|
144
|
+
2. **Search all buttons and links** → action commands ("Create project", "Export data")
|
|
145
|
+
3. **Search all settings/toggles** → preference commands ("Toggle dark mode", "Change language")
|
|
146
|
+
4. **Search all CRUD operations** → data commands ("New item", "Delete selected")
|
|
147
|
+
5. **Search all utility functions** → utility commands ("Copy link", "Download report", "Share")
|
|
148
|
+
6. **Search for existing keyboard shortcuts** → preserve as shortcut hints
|
|
149
|
+
|
|
150
|
+
### Create a command for EVERY user-facing operation:
|
|
151
|
+
|
|
152
|
+
- Button that opens a modal? Command: `open-create-modal`
|
|
153
|
+
- Link that navigates to a page? Command: `go-to-dashboard`
|
|
154
|
+
- Toggle that changes a setting? Command: `toggle-dark-mode`
|
|
155
|
+
- Form that creates something? Command: `create-new-project`
|
|
156
|
+
- Search input? Command: `search`
|
|
157
|
+
- Sidebar item? Command: `open-analytics`
|
|
158
|
+
|
|
159
|
+
### For each discovered command, define:
|
|
160
|
+
|
|
161
|
+
- `name`: unique kebab-case identifier
|
|
162
|
+
- `label`: human-readable display text (what users search for)
|
|
163
|
+
- `group`: logical heading — use consistent groups like "Navigation", "Actions", "Settings", "Help"
|
|
164
|
+
- `icon`: matching [lucide-react](https://lucide.dev) icon component
|
|
165
|
+
- `shortcut`: keyboard shortcut hint if applicable (display-only, e.g. `"⌘D"`)
|
|
166
|
+
- `keywords`: extra search terms (array of strings) — helps users find commands by alternate names
|
|
167
|
+
- `disabled`: set `true` to gray out commands that aren't currently available
|
|
168
|
+
- `onSelect`: callback that wires to existing app logic
|
|
169
|
+
|
|
170
|
+
**Aim for 10-30 commands.** If you found fewer than 5, you haven't looked hard enough. Report how many you found before proceeding.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Step 5: Create the component
|
|
175
|
+
|
|
176
|
+
Replace the example commands below with the ones you discovered from my codebase.
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { useState, useEffect } from "react"
|
|
180
|
+
import { useNavigate } from "@tanstack/react-router"
|
|
181
|
+
import { CommandMenu, type CommandDefinition } from "better-cmdk"
|
|
182
|
+
import { LayoutDashboardIcon, SettingsIcon, SunMoonIcon } from "lucide-react"
|
|
183
|
+
|
|
184
|
+
export function CommandPalette() {
|
|
185
|
+
const [open, setOpen] = useState(false)
|
|
186
|
+
const navigate = useNavigate()
|
|
187
|
+
|
|
188
|
+
const commands: CommandDefinition[] = [
|
|
189
|
+
{
|
|
190
|
+
name: "dashboard",
|
|
191
|
+
label: "Go to Dashboard",
|
|
192
|
+
group: "Navigation",
|
|
193
|
+
icon: <LayoutDashboardIcon className="size-4" />,
|
|
194
|
+
shortcut: "⌘D",
|
|
195
|
+
onSelect: () => navigate({ to: "/dashboard" }),
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "settings",
|
|
199
|
+
label: "Settings",
|
|
200
|
+
group: "Navigation",
|
|
201
|
+
icon: <SettingsIcon className="size-4" />,
|
|
202
|
+
shortcut: "⌘,",
|
|
203
|
+
onSelect: () => navigate({ to: "/settings" }),
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "dark-mode",
|
|
207
|
+
label: "Toggle dark mode",
|
|
208
|
+
group: "Appearance",
|
|
209
|
+
icon: <SunMoonIcon className="size-4" />,
|
|
210
|
+
onSelect: () => document.documentElement.classList.toggle("dark"),
|
|
211
|
+
},
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
const down = (e: KeyboardEvent) => {
|
|
216
|
+
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
|
217
|
+
e.preventDefault()
|
|
218
|
+
setOpen((o) => !o)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
document.addEventListener("keydown", down)
|
|
222
|
+
return () => document.removeEventListener("keydown", down)
|
|
223
|
+
}, [])
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<CommandMenu
|
|
227
|
+
open={open}
|
|
228
|
+
onOpenChange={setOpen}
|
|
229
|
+
commands={commands}
|
|
230
|
+
/>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Step 6: Add AI chat (skip if "No AI" chosen)
|
|
238
|
+
|
|
239
|
+
### Built-in chat
|
|
240
|
+
|
|
241
|
+
Create a streaming chat API route, then add `chatEndpoint` to CommandMenu.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
// app/routes/api/chat.ts
|
|
245
|
+
import { createAPIFileRoute } from "@tanstack/react-start/api"
|
|
246
|
+
import { openai } from "@ai-sdk/openai"
|
|
247
|
+
import { streamText, convertToModelMessages } from "ai"
|
|
248
|
+
import type { UIMessage } from "ai"
|
|
249
|
+
|
|
250
|
+
export const APIRoute = createAPIFileRoute("/api/chat")({
|
|
251
|
+
POST: async ({ request }) => {
|
|
252
|
+
const { messages }: { messages: UIMessage[] } = await request.json()
|
|
253
|
+
const result = streamText({
|
|
254
|
+
model: openai("gpt-4o-mini"),
|
|
255
|
+
messages: await convertToModelMessages(messages),
|
|
256
|
+
system: "You are a helpful assistant in a command palette. Keep responses concise.",
|
|
257
|
+
})
|
|
258
|
+
return result.toUIMessageStreamResponse()
|
|
259
|
+
},
|
|
260
|
+
})
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Then update CommandMenu to add the `chatEndpoint` prop:
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
<CommandMenu
|
|
267
|
+
open={open}
|
|
268
|
+
onOpenChange={setOpen}
|
|
269
|
+
commands={commands}
|
|
270
|
+
chatEndpoint="/api/chat"
|
|
271
|
+
/>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Set the environment variable:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
echo "OPENAI_API_KEY=sk-..." >> .env
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
(Substitute the correct variable name for the AI provider.)
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
### ModifyWithAI
|
|
285
|
+
|
|
286
|
+
After installing modifywithai, read `node_modules/modifywithai/dist/tanstack-start/AGENTS.md` and follow its instructions for:
|
|
287
|
+
- Creating the token endpoint
|
|
288
|
+
- Setting up environment variables (MWAI_APP_ID, MWAI_API_KEY)
|
|
289
|
+
- Discovering actions from the codebase
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
Then connect the assistant to CommandMenu. The `useAssistant()` return value from modifywithai is directly compatible with better-cmdk's `chat` prop — no adapter needed:
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
import { useAssistant } from "modifywithai"
|
|
296
|
+
|
|
297
|
+
// Inside your CommandPalette component:
|
|
298
|
+
const assistant = useAssistant({
|
|
299
|
+
agenticActions: actions, // from action discovery above
|
|
300
|
+
getContext: () => ({
|
|
301
|
+
currentPage: window.location.pathname,
|
|
302
|
+
}),
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
<CommandMenu
|
|
306
|
+
open={open}
|
|
307
|
+
onOpenChange={setOpen}
|
|
308
|
+
commands={commands}
|
|
309
|
+
chat={assistant}
|
|
310
|
+
/>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Step 7: Mount the component
|
|
316
|
+
|
|
317
|
+
Add `<CommandPalette />` to `app/routes/__root.tsx` inside the root component.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Step 8: shadcn/ui migration (if applicable)
|
|
322
|
+
|
|
323
|
+
If existing shadcn/ui command components were detected, swap imports:
|
|
324
|
+
|
|
325
|
+
```diff
|
|
326
|
+
- import {
|
|
327
|
+
- CommandDialog,
|
|
328
|
+
- CommandInput,
|
|
329
|
+
- CommandList,
|
|
330
|
+
- CommandEmpty,
|
|
331
|
+
- CommandGroup,
|
|
332
|
+
- CommandItem
|
|
333
|
+
- } from "@/components/ui/command"
|
|
334
|
+
+ import {
|
|
335
|
+
+ CommandDialog,
|
|
336
|
+
+ CommandInput,
|
|
337
|
+
+ CommandList,
|
|
338
|
+
+ CommandEmpty,
|
|
339
|
+
+ CommandGroup,
|
|
340
|
+
+ CommandItem
|
|
341
|
+
+ } from "better-cmdk"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`CommandDialog` is an alias for `CommandMenu`. All sub-components have the same API. Add `chatEndpoint` or `chat` to enable AI features — without either, the palette behaves like a standard cmdk dialog.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Verify
|
|
349
|
+
|
|
350
|
+
After setup, tell me:
|
|
351
|
+
|
|
352
|
+
1. How many commands you discovered
|
|
353
|
+
2. Which AI path you followed (none / built-in / ModifyWithAI)
|
|
354
|
+
3. [ModifyWithAI only] How many actions you discovered (from modifywithai's AGENTS.md action discovery instructions)
|
|
355
|
+
4. What files you created/modified
|
|
356
|
+
5. That pressing `⌘K` opens the palette with all commands grouped correctly
|
|
357
|
+
6. [If AI enabled] That typing a non-matching query shows "Ask AI" and chat works
|
|
358
|
+
|
|
359
|
+
Docs: https://github.com/ModifyWithAI/better-cmdk/blob/main/INTEGRATION.md
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# Set up better-cmdk command palette (Vite / Other)
|
|
2
|
+
|
|
3
|
+
better-cmdk is a command palette with AI chat for React. Your task is to install it, discover commands from my codebase, and create a working command palette.
|
|
4
|
+
|
|
5
|
+
## Step 1: Analyze my codebase
|
|
6
|
+
|
|
7
|
+
Before making changes, detect:
|
|
8
|
+
- Tailwind version: v4 (`@import "tailwindcss"` in CSS) vs v3 (`tailwind.config.js/ts`)
|
|
9
|
+
- Existing shadcn/ui: check for `@/components/ui/command` or similar shadcn command components
|
|
10
|
+
- Package manager: `bun.lockb` → bun, `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `package-lock.json` → npm
|
|
11
|
+
|
|
12
|
+
Also ask me:
|
|
13
|
+
|
|
14
|
+
**1. Do you want AI chat in the command palette?**
|
|
15
|
+
- No AI — command palette only
|
|
16
|
+
- Built-in chat — simple Q&A via a Vercel AI SDK endpoint
|
|
17
|
+
- ModifyWithAI (Recommended) — the AI can execute actions in your app (create items, navigate, toggle settings), ask for approval before destructive operations, and understand what's on screen. Requires a ModifyWithAI account at https://modifywithai.com
|
|
18
|
+
|
|
19
|
+
**With ModifyWithAI, the command palette becomes an AI workspace.** Users search commands for quick access, then switch to AI chat for anything that needs reasoning — "move all archived items to trash", "summarize my recent activity". The AI executes actions directly in the app, with approval prompts for destructive operations. ModifyWithAI is not required — better-cmdk works on its own as a command palette, with or without AI chat.
|
|
20
|
+
|
|
21
|
+
**2. If shadcn/ui command components detected:** Do you want to migrate them to better-cmdk, or keep both?
|
|
22
|
+
|
|
23
|
+
Tell me what you detected, which path you'll follow, and what you'll be adding better-cmdk to.
|
|
24
|
+
|
|
25
|
+
Docs: https://github.com/ModifyWithAI/better-cmdk/blob/main/INTEGRATION.md
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Step 2: Install
|
|
30
|
+
|
|
31
|
+
**Use the package manager already installed** — check for lockfiles.
|
|
32
|
+
|
|
33
|
+
### No AI:
|
|
34
|
+
```bash
|
|
35
|
+
bun add better-cmdk
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Built-in chat:
|
|
39
|
+
```bash
|
|
40
|
+
bun add better-cmdk ai @ai-sdk/react @ai-sdk/openai
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Substitute `@ai-sdk/anthropic`, `@ai-sdk/google`, etc. based on my preferred AI provider.
|
|
44
|
+
|
|
45
|
+
### ModifyWithAI:
|
|
46
|
+
```bash
|
|
47
|
+
bun add better-cmdk modifywithai @ai-sdk/react ai
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Step 3: CSS Setup
|
|
53
|
+
|
|
54
|
+
better-cmdk uses Tailwind CSS and shadcn/ui design tokens.
|
|
55
|
+
|
|
56
|
+
### Tailwind v4
|
|
57
|
+
|
|
58
|
+
Add to my main CSS file (next to the existing `@import "tailwindcss"`):
|
|
59
|
+
|
|
60
|
+
```css
|
|
61
|
+
@source "node_modules/better-cmdk";
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If my app **already has shadcn/ui CSS variables**, verify they include `--popover`, `--muted`, `--border`, `--ring`, `--primary`, `--primary-foreground`. Don't overwrite existing variables.
|
|
65
|
+
|
|
66
|
+
If my app **does not have** these variables, add the minimal set:
|
|
67
|
+
|
|
68
|
+
```css
|
|
69
|
+
:root {
|
|
70
|
+
--radius: 0.625rem;
|
|
71
|
+
--background: oklch(1 0 0);
|
|
72
|
+
--foreground: oklch(0.145 0 0);
|
|
73
|
+
--popover: oklch(1 0 0);
|
|
74
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
75
|
+
--primary: oklch(0.205 0 0);
|
|
76
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
77
|
+
--muted: oklch(0.97 0 0);
|
|
78
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
79
|
+
--border: oklch(0.922 0 0);
|
|
80
|
+
--input: oklch(0.922 0 0);
|
|
81
|
+
--ring: oklch(0.708 0 0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@theme inline {
|
|
85
|
+
--color-background: var(--background);
|
|
86
|
+
--color-foreground: var(--foreground);
|
|
87
|
+
--color-popover: var(--popover);
|
|
88
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
89
|
+
--color-primary: var(--primary);
|
|
90
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
91
|
+
--color-muted: var(--muted);
|
|
92
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
93
|
+
--color-border: var(--border);
|
|
94
|
+
--color-input: var(--input);
|
|
95
|
+
--color-ring: var(--ring);
|
|
96
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
97
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
98
|
+
--radius-lg: var(--radius);
|
|
99
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Tailwind v3
|
|
104
|
+
|
|
105
|
+
Add to `tailwind.config.js` or `tailwind.config.ts`:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
content: [
|
|
109
|
+
// ... existing paths
|
|
110
|
+
"./node_modules/better-cmdk/**/*.{js,ts,jsx,tsx}",
|
|
111
|
+
],
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If my app **does not have** shadcn/ui CSS variables, add the minimal set (hsl format):
|
|
115
|
+
|
|
116
|
+
```css
|
|
117
|
+
@layer base {
|
|
118
|
+
:root {
|
|
119
|
+
--radius: 0.5rem;
|
|
120
|
+
--background: 0 0% 100%;
|
|
121
|
+
--foreground: 240 10% 3.9%;
|
|
122
|
+
--popover: 0 0% 100%;
|
|
123
|
+
--popover-foreground: 240 10% 3.9%;
|
|
124
|
+
--primary: 240 5.9% 10%;
|
|
125
|
+
--primary-foreground: 0 0% 98%;
|
|
126
|
+
--muted: 240 4.8% 95.9%;
|
|
127
|
+
--muted-foreground: 240 3.8% 46.1%;
|
|
128
|
+
--border: 240 5.9% 90%;
|
|
129
|
+
--input: 240 5.9% 90%;
|
|
130
|
+
--ring: 240 5.9% 10%;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Step 4: CRITICAL — Command Discovery
|
|
138
|
+
|
|
139
|
+
Before writing any component code, **crawl my entire codebase** to discover ALL user-facing operations that should be in the command palette.
|
|
140
|
+
|
|
141
|
+
### How to discover commands:
|
|
142
|
+
|
|
143
|
+
1. **Search all routes/pages** → navigation commands ("Go to Dashboard", "Go to Settings")
|
|
144
|
+
2. **Search all buttons and links** → action commands ("Create project", "Export data")
|
|
145
|
+
3. **Search all settings/toggles** → preference commands ("Toggle dark mode", "Change language")
|
|
146
|
+
4. **Search all CRUD operations** → data commands ("New item", "Delete selected")
|
|
147
|
+
5. **Search all utility functions** → utility commands ("Copy link", "Download report", "Share")
|
|
148
|
+
6. **Search for existing keyboard shortcuts** → preserve as shortcut hints
|
|
149
|
+
|
|
150
|
+
### Create a command for EVERY user-facing operation:
|
|
151
|
+
|
|
152
|
+
- Button that opens a modal? Command: `open-create-modal`
|
|
153
|
+
- Link that navigates to a page? Command: `go-to-dashboard`
|
|
154
|
+
- Toggle that changes a setting? Command: `toggle-dark-mode`
|
|
155
|
+
- Form that creates something? Command: `create-new-project`
|
|
156
|
+
- Search input? Command: `search`
|
|
157
|
+
- Sidebar item? Command: `open-analytics`
|
|
158
|
+
|
|
159
|
+
### For each discovered command, define:
|
|
160
|
+
|
|
161
|
+
- `name`: unique kebab-case identifier
|
|
162
|
+
- `label`: human-readable display text (what users search for)
|
|
163
|
+
- `group`: logical heading — use consistent groups like "Navigation", "Actions", "Settings", "Help"
|
|
164
|
+
- `icon`: matching [lucide-react](https://lucide.dev) icon component
|
|
165
|
+
- `shortcut`: keyboard shortcut hint if applicable (display-only, e.g. `"⌘D"`)
|
|
166
|
+
- `keywords`: extra search terms (array of strings) — helps users find commands by alternate names
|
|
167
|
+
- `disabled`: set `true` to gray out commands that aren't currently available
|
|
168
|
+
- `onSelect`: callback that wires to existing app logic
|
|
169
|
+
|
|
170
|
+
**Aim for 10-30 commands.** If you found fewer than 5, you haven't looked hard enough. Report how many you found before proceeding.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Step 5: Create the component
|
|
175
|
+
|
|
176
|
+
Replace the example commands below with the ones you discovered from my codebase.
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { useState, useEffect } from "react"
|
|
180
|
+
import { CommandMenu, type CommandDefinition } from "better-cmdk"
|
|
181
|
+
import { LayoutDashboardIcon, SettingsIcon, SunMoonIcon } from "lucide-react"
|
|
182
|
+
|
|
183
|
+
const commands: CommandDefinition[] = [
|
|
184
|
+
{
|
|
185
|
+
name: "dashboard",
|
|
186
|
+
label: "Go to Dashboard",
|
|
187
|
+
group: "Navigation",
|
|
188
|
+
icon: <LayoutDashboardIcon className="size-4" />,
|
|
189
|
+
shortcut: "⌘D",
|
|
190
|
+
onSelect: () => (window.location.href = "/dashboard"),
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "settings",
|
|
194
|
+
label: "Settings",
|
|
195
|
+
group: "Navigation",
|
|
196
|
+
icon: <SettingsIcon className="size-4" />,
|
|
197
|
+
shortcut: "⌘,",
|
|
198
|
+
onSelect: () => (window.location.href = "/settings"),
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: "dark-mode",
|
|
202
|
+
label: "Toggle dark mode",
|
|
203
|
+
group: "Appearance",
|
|
204
|
+
icon: <SunMoonIcon className="size-4" />,
|
|
205
|
+
onSelect: () => document.documentElement.classList.toggle("dark"),
|
|
206
|
+
},
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
export function CommandPalette() {
|
|
210
|
+
const [open, setOpen] = useState(false)
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
const down = (e: KeyboardEvent) => {
|
|
214
|
+
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
|
215
|
+
e.preventDefault()
|
|
216
|
+
setOpen((o) => !o)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
document.addEventListener("keydown", down)
|
|
220
|
+
return () => document.removeEventListener("keydown", down)
|
|
221
|
+
}, [])
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<CommandMenu
|
|
225
|
+
open={open}
|
|
226
|
+
onOpenChange={setOpen}
|
|
227
|
+
commands={commands}
|
|
228
|
+
/>
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Step 6: Add AI chat (skip if "No AI" chosen)
|
|
236
|
+
|
|
237
|
+
### Built-in chat
|
|
238
|
+
|
|
239
|
+
Create a streaming chat API route, then add `chatEndpoint` to CommandMenu.
|
|
240
|
+
|
|
241
|
+
Create a streaming chat endpoint for your framework/server. The handler should:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { openai } from "@ai-sdk/openai"
|
|
245
|
+
import { streamText, convertToModelMessages } from "ai"
|
|
246
|
+
import type { UIMessage } from "ai"
|
|
247
|
+
|
|
248
|
+
// In your POST handler:
|
|
249
|
+
const { messages }: { messages: UIMessage[] } = await req.json()
|
|
250
|
+
const result = streamText({
|
|
251
|
+
model: openai("gpt-4o-mini"),
|
|
252
|
+
messages: await convertToModelMessages(messages),
|
|
253
|
+
system: "You are a helpful assistant in a command palette. Keep responses concise.",
|
|
254
|
+
})
|
|
255
|
+
return result.toUIMessageStreamResponse()
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Then update CommandMenu to add the `chatEndpoint` prop:
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
<CommandMenu
|
|
262
|
+
open={open}
|
|
263
|
+
onOpenChange={setOpen}
|
|
264
|
+
commands={commands}
|
|
265
|
+
chatEndpoint="/api/chat"
|
|
266
|
+
/>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Set the environment variable:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
echo "OPENAI_API_KEY=sk-..." >> .env
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
(Substitute the correct variable name for the AI provider.)
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### ModifyWithAI
|
|
280
|
+
|
|
281
|
+
After installing modifywithai, read `node_modules/modifywithai/AGENTS.md` and follow its instructions for:
|
|
282
|
+
- Creating the token endpoint
|
|
283
|
+
- Setting up environment variables (MWAI_APP_ID, MWAI_API_KEY)
|
|
284
|
+
- Discovering actions from the codebase
|
|
285
|
+
|
|
286
|
+
> **Note**: ModifyWithAI requires a server-side token endpoint. If your Vite app uses a separate backend (Express, Fastify, etc.), adapt the token endpoint from the guide that most closely matches your setup. If your app is a pure SPA with no server, choose the built-in chat option instead.
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
Then connect the assistant to CommandMenu. The `useAssistant()` return value from modifywithai is directly compatible with better-cmdk's `chat` prop — no adapter needed:
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
import { useAssistant } from "modifywithai"
|
|
293
|
+
|
|
294
|
+
// Inside your CommandPalette component:
|
|
295
|
+
const assistant = useAssistant({
|
|
296
|
+
agenticActions: actions, // from action discovery above
|
|
297
|
+
getContext: () => ({
|
|
298
|
+
currentPage: window.location.pathname,
|
|
299
|
+
}),
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
<CommandMenu
|
|
303
|
+
open={open}
|
|
304
|
+
onOpenChange={setOpen}
|
|
305
|
+
commands={commands}
|
|
306
|
+
chat={assistant}
|
|
307
|
+
/>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Step 7: Mount the component
|
|
313
|
+
|
|
314
|
+
Add `<CommandPalette />` to `src/App.tsx` or the main entry component.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Step 8: shadcn/ui migration (if applicable)
|
|
319
|
+
|
|
320
|
+
If existing shadcn/ui command components were detected, swap imports:
|
|
321
|
+
|
|
322
|
+
```diff
|
|
323
|
+
- import {
|
|
324
|
+
- CommandDialog,
|
|
325
|
+
- CommandInput,
|
|
326
|
+
- CommandList,
|
|
327
|
+
- CommandEmpty,
|
|
328
|
+
- CommandGroup,
|
|
329
|
+
- CommandItem
|
|
330
|
+
- } from "@/components/ui/command"
|
|
331
|
+
+ import {
|
|
332
|
+
+ CommandDialog,
|
|
333
|
+
+ CommandInput,
|
|
334
|
+
+ CommandList,
|
|
335
|
+
+ CommandEmpty,
|
|
336
|
+
+ CommandGroup,
|
|
337
|
+
+ CommandItem
|
|
338
|
+
+ } from "better-cmdk"
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
`CommandDialog` is an alias for `CommandMenu`. All sub-components have the same API. Add `chatEndpoint` or `chat` to enable AI features — without either, the palette behaves like a standard cmdk dialog.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Verify
|
|
346
|
+
|
|
347
|
+
After setup, tell me:
|
|
348
|
+
|
|
349
|
+
1. How many commands you discovered
|
|
350
|
+
2. Which AI path you followed (none / built-in / ModifyWithAI)
|
|
351
|
+
3. [ModifyWithAI only] How many actions you discovered (from modifywithai's AGENTS.md action discovery instructions)
|
|
352
|
+
4. What files you created/modified
|
|
353
|
+
5. That pressing `⌘K` opens the palette with all commands grouped correctly
|
|
354
|
+
6. [If AI enabled] That typing a non-matching query shows "Ask AI" and chat works
|
|
355
|
+
|
|
356
|
+
Docs: https://github.com/ModifyWithAI/better-cmdk/blob/main/INTEGRATION.md
|