@usesidekick/react 0.1.0

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 (48) hide show
  1. package/README.md +246 -0
  2. package/dist/index.d.mts +358 -0
  3. package/dist/index.d.ts +358 -0
  4. package/dist/index.js +2470 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.mjs +2403 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/jsx-dev-runtime.d.mts +21 -0
  9. package/dist/jsx-dev-runtime.d.ts +21 -0
  10. package/dist/jsx-dev-runtime.js +160 -0
  11. package/dist/jsx-dev-runtime.js.map +1 -0
  12. package/dist/jsx-dev-runtime.mjs +122 -0
  13. package/dist/jsx-dev-runtime.mjs.map +1 -0
  14. package/dist/jsx-runtime.d.mts +26 -0
  15. package/dist/jsx-runtime.d.ts +26 -0
  16. package/dist/jsx-runtime.js +150 -0
  17. package/dist/jsx-runtime.js.map +1 -0
  18. package/dist/jsx-runtime.mjs +109 -0
  19. package/dist/jsx-runtime.mjs.map +1 -0
  20. package/dist/server/index.d.mts +235 -0
  21. package/dist/server/index.d.ts +235 -0
  22. package/dist/server/index.js +642 -0
  23. package/dist/server/index.js.map +1 -0
  24. package/dist/server/index.mjs +597 -0
  25. package/dist/server/index.mjs.map +1 -0
  26. package/package.json +64 -0
  27. package/src/components/SidekickPanel.tsx +868 -0
  28. package/src/components/index.ts +1 -0
  29. package/src/context.tsx +157 -0
  30. package/src/flags.ts +47 -0
  31. package/src/index.ts +71 -0
  32. package/src/jsx-dev-runtime.ts +138 -0
  33. package/src/jsx-runtime.ts +159 -0
  34. package/src/loader.ts +35 -0
  35. package/src/primitives/behavior.ts +70 -0
  36. package/src/primitives/data.ts +91 -0
  37. package/src/primitives/index.ts +3 -0
  38. package/src/primitives/ui.ts +268 -0
  39. package/src/provider.tsx +1264 -0
  40. package/src/runtime-loader.ts +106 -0
  41. package/src/server/drizzle-adapter.ts +53 -0
  42. package/src/server/drizzle-schema.ts +16 -0
  43. package/src/server/generate.ts +578 -0
  44. package/src/server/handler.ts +343 -0
  45. package/src/server/index.ts +20 -0
  46. package/src/server/storage.ts +1 -0
  47. package/src/server/types.ts +49 -0
  48. package/src/types.ts +295 -0
@@ -0,0 +1,597 @@
1
+ // src/server/handler.ts
2
+ import * as fs from "fs/promises";
3
+ import * as path from "path";
4
+ import { NextResponse } from "next/server";
5
+
6
+ // src/server/generate.ts
7
+ var ALLOWED_IMPORTS = ["@usesidekick/react", "react"];
8
+ var FORBIDDEN_PATTERNS = [
9
+ /\bfetch\s*\(/,
10
+ /\beval\s*\(/,
11
+ /\bFunction\s*\(/,
12
+ /\bdocument\./,
13
+ /\bwindow\./,
14
+ /\blocalStorage\./,
15
+ /\bsessionStorage\./,
16
+ /\bXMLHttpRequest/,
17
+ /\bWebSocket/,
18
+ /\bimport\s*\(/,
19
+ /require\s*\(/
20
+ ];
21
+ function formatDesignSystem(schema) {
22
+ const ds = schema.designSystem;
23
+ if (!ds) {
24
+ return "No design system information available. Use clean, minimal inline styles with neutral colors and standard spacing.";
25
+ }
26
+ const parts = [];
27
+ parts.push("All generated UI MUST look like it was built by the same team that built the host app. Study the style samples below and match the exact same patterns \u2014 colors, spacing, typography, border radii, hover states, and dark mode support.");
28
+ if (ds.framework) {
29
+ parts.push(`
30
+ **CSS Framework:** ${ds.framework}`);
31
+ }
32
+ if (ds.fontFamily) {
33
+ parts.push(`**Font:** ${ds.fontFamily}`);
34
+ }
35
+ if (ds.globalCss) {
36
+ parts.push("**Global CSS:**\n```css\n" + ds.globalCss + "\n```");
37
+ }
38
+ if (ds.componentStyles && ds.componentStyles.length > 0) {
39
+ parts.push("\n**Component Style Samples (actual className strings from the app \u2014 use these as your reference):**");
40
+ for (const cs of ds.componentStyles) {
41
+ parts.push(`
42
+ ${cs.component} (${cs.file}):`);
43
+ for (const cn of cs.classNames) {
44
+ parts.push(` "${cn}"`);
45
+ }
46
+ }
47
+ parts.push("\nWhen adding new UI elements to an existing component, copy the exact className patterns from that component above. When creating standalone elements, pick the closest matching component pattern and follow it.");
48
+ }
49
+ return parts.join("\n");
50
+ }
51
+ function formatComponents(schema) {
52
+ const components = schema.components || [];
53
+ return components.map((c) => {
54
+ let entry = "- " + c.name + " (props: " + (c.props.join(", ") || "none") + ")";
55
+ if (c.renderStructure) {
56
+ entry += "\n Renders: " + c.renderStructure;
57
+ }
58
+ return entry;
59
+ }).join("\n");
60
+ }
61
+ function buildSystemPrompt(schema) {
62
+ const tick = "`";
63
+ const ticks = "```";
64
+ const dollar = "$";
65
+ return `You are a code generator for Sidekick, an SDK that allows users to customize web applications.
66
+ Your task is to generate override modules based on user requests.
67
+
68
+ ## SDK Primitives Available
69
+
70
+ UI Primitives:
71
+ - sdk.ui.wrap(componentName, wrapperFn, options?) - Wrap any component by name to add behavior around it. options: { priority?, where?: (props) => boolean }
72
+ - sdk.ui.replace(componentName, Component) - Replace any component entirely by name
73
+ - sdk.ui.addColumn(tableId, config) - Add a column to a table (config: { header, accessor, render? })
74
+ - sdk.ui.renameColumn(tableId, originalHeader, newHeader) - Rename a column header
75
+ - sdk.ui.reorderColumns(tableId, headerOrder) - Reorder columns by header name (array of header strings)
76
+ - sdk.ui.hideColumn(tableId, header) - Hide a column by header name
77
+ - sdk.ui.filterRows(tableId, filterFn) - Filter table rows (filterFn receives a row object, returns true to keep)
78
+ - sdk.ui.addStyles(css) - Add CSS styles globally
79
+ - sdk.ui.setText(selector, text) - Change text content of elements matching CSS selector
80
+ - sdk.ui.setAttribute(selector, attr, value) - Set an attribute on matching elements
81
+ - sdk.ui.setStyle(selector, styles) - Apply inline styles to matching elements (styles is an object like { color: 'red' })
82
+ - sdk.ui.addClass(selector, className) - Add a CSS class to matching elements
83
+ - sdk.ui.removeClass(selector, className) - Remove a CSS class from matching elements
84
+ - sdk.ui.inject(selector, Component, position?) - Inject a React component before/after/prepend/append an element (default: 'after')
85
+
86
+ Data Primitives:
87
+ - sdk.data.computed(fieldName, computeFn) - Add a computed field
88
+ - sdk.data.addFilter(name, filterFn) - Add a filter preset
89
+ - sdk.data.transform(dataKey, transformFn) - Transform data
90
+ - sdk.data.intercept(pathPattern, handler) - Intercept and modify API responses (handler receives response JSON and { path, method })
91
+
92
+ Behavior Primitives:
93
+ - sdk.behavior.addKeyboardShortcut(keys, action, description?) - Register a keyboard shortcut (e.g., "ctrl+k", "shift+?")
94
+ - sdk.behavior.onDOMEvent(selector, eventType, handler) - Listen for DOM events on elements matching a CSS selector
95
+ - sdk.behavior.modifyRoute(pathPattern, handler) - Intercept navigation and optionally redirect
96
+
97
+ ## App Design System
98
+
99
+ ` + formatDesignSystem(schema) + "\n\n## App Schema\n\nComponents (targetable by name with wrap/replace):\n" + formatComponents(schema) + "\n\nData Models: " + JSON.stringify(schema.dataModels || []) + "\n\n## Example Overrides\n\n### Example 1: Wrap a component to add a badge (RECOMMENDED APPROACH)\n" + ticks + `tsx
100
+ import { createOverride, SDK } from '@usesidekick/react';
101
+ import { ComponentType } from 'react';
102
+
103
+ export default createOverride(
104
+ {
105
+ id: 'task-card-badge',
106
+ name: 'Task Card Badge',
107
+ description: 'Adds a "New" badge to task cards',
108
+ version: '1.0.0',
109
+ primitives: ['ui.wrap'],
110
+ },
111
+ (sdk: SDK) => {
112
+ sdk.ui.wrap('TaskCard', (Original: ComponentType<Record<string, unknown>>) => {
113
+ return function WrappedTaskCard(props: Record<string, unknown>) {
114
+ return (
115
+ <div style={{ position: 'relative' }}>
116
+ <span style={{
117
+ position: 'absolute',
118
+ top: '-8px',
119
+ right: '-8px',
120
+ backgroundColor: '#EF4444',
121
+ color: 'white',
122
+ fontSize: '10px',
123
+ fontWeight: 'bold',
124
+ padding: '2px 6px',
125
+ borderRadius: '9999px',
126
+ }}>
127
+ NEW
128
+ </span>
129
+ <Original {...props} />
130
+ </div>
131
+ );
132
+ };
133
+ });
134
+ }
135
+ );
136
+ ` + ticks + "\n\n### Example 2: Replace a component entirely\n" + ticks + "tsx\nimport { createOverride, SDK } from '@usesidekick/react';\n\nconst CustomTaskModal = (props: Record<string, unknown>) => {\n const task = props.task as { title?: string; description?: string } | undefined;\n const onClose = props.onClose as () => void;\n\n return (\n <div style={{\n position: 'fixed',\n inset: 0,\n backgroundColor: 'rgba(0,0,0,0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n <div style={{\n backgroundColor: 'white',\n borderRadius: '16px',\n padding: '24px',\n maxWidth: '500px',\n width: '100%',\n }}>\n <h2 style={{ fontSize: '24px', fontWeight: 'bold', marginBottom: '16px' }}>\n {task?.title || 'Task Details'}\n </h2>\n <p>{task?.description || 'No description'}</p>\n <button\n onClick={onClose}\n style={{\n marginTop: '16px',\n backgroundColor: '#3B82F6',\n color: 'white',\n padding: '8px 16px',\n borderRadius: '8px',\n border: 'none',\n cursor: 'pointer',\n }}\n >\n Close\n </button>\n </div>\n </div>\n );\n};\n\nexport default createOverride(\n {\n id: 'custom-task-modal',\n name: 'Custom Task Modal',\n description: 'Replaces the task modal with a custom design',\n version: '1.0.0',\n primitives: ['ui.replace'],\n },\n (sdk: SDK) => {\n sdk.ui.replace('TaskModal', CustomTaskModal);\n }\n);\n" + ticks + "\n\n### Example 3: Add a table column\n" + ticks + "tsx\nimport { createOverride, SDK } from '@usesidekick/react';\n\nexport default createOverride(\n {\n id: 'days-left-column',\n name: 'Days Left Column',\n description: 'Shows days until due date',\n version: '1.0.0',\n primitives: ['ui.addColumn'],\n },\n (sdk: SDK) => {\n sdk.ui.addColumn('task-table', {\n header: 'Days Left',\n accessor: (row: Record<string, unknown>) => {\n const dueDate = row.dueDate as string | undefined;\n if (!dueDate) return null;\n const days = Math.ceil((new Date(dueDate).getTime() - Date.now()) / (1000 * 60 * 60 * 24));\n return days;\n },\n render: (value: unknown) => {\n if (value === null) return <span>-</span>;\n const days = value as number;\n const color = days < 0 ? 'red' : days <= 3 ? 'orange' : 'green';\n return <span style={{ color }}>{days}d</span>;\n },\n });\n }\n);\n" + ticks + "\n\n### Example 4: Add global styles\n" + ticks + "tsx\nimport { createOverride, SDK } from '@usesidekick/react';\n\nexport default createOverride(\n {\n id: 'custom-theme',\n name: 'Custom Theme',\n description: 'Adds custom styling to the app',\n version: '1.0.0',\n primitives: ['ui.addStyles'],\n },\n (sdk: SDK) => {\n sdk.ui.addStyles(" + tick + "\n .task-card {\n border-radius: 12px;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n }\n .task-card:hover {\n transform: translateY(-2px);\n transition: transform 0.2s ease;\n }\n " + tick + ");\n }\n);\n" + ticks + "\n\n### Example 5: Column operations (rename, reorder, hide)\n" + ticks + "tsx\nimport { createOverride, SDK } from '@usesidekick/react';\n\nexport default createOverride(\n {\n id: 'reorder-columns',\n name: 'Reorder Table Columns',\n description: 'Reorders Status and Importance columns and hides Assignee',\n version: '1.0.0',\n primitives: ['ui.reorderColumns', 'ui.hideColumn'],\n },\n (sdk: SDK) => {\n sdk.ui.reorderColumns('task-table', ['Title', 'Importance', 'Status', 'Project', 'Due Date']);\n sdk.ui.hideColumn('task-table', 'Assignee');\n }\n);\n" + ticks + "\n\n### Example 6: Interactive filter with sidebar nav button\n" + ticks + "tsx\nimport { createOverride, SDK } from '@usesidekick/react';\nimport { ComponentType, useState, useEffect } from 'react';\n\nlet _filterActive = false;\nconst _listeners = new Set<(active: boolean) => void>();\n\nfunction setFilterActive(active: boolean) {\n _filterActive = active;\n _listeners.forEach(fn => fn(active));\n}\n\nfunction useFilterActive(): [boolean, (active: boolean) => void] {\n const [active, setActive] = useState(_filterActive);\n useEffect(() => {\n const handler = (val: boolean) => setActive(val);\n _listeners.add(handler);\n return () => { _listeners.delete(handler); };\n }, []);\n return [active, setFilterActive];\n}\n\nexport default createOverride(\n {\n id: 'high-importance-filter',\n name: 'High Importance Filter',\n description: 'Adds a sidebar button to filter high importance tasks',\n version: '1.0.0',\n primitives: ['ui.wrap'],\n },\n (sdk: SDK) => {\n sdk.ui.wrap('Sidebar', (Original: ComponentType<Record<string, unknown>>) => {\n return function SidebarWithFilter(props: Record<string, unknown>) {\n const [isActive, setActive] = useFilterActive();\n return (\n <div style={{ display: 'contents' }}>\n <Original {...props} />\n <div style={{ padding: '0 16px', marginTop: '8px' }}>\n <button\n onClick={() => setActive(!isActive)}\n style={{\n backgroundColor: isActive ? '#EFF6FF' : 'transparent',\n color: isActive ? '#1D4ED8' : '#374151',\n }}\n >\n High Importance\n </button>\n </div>\n </div>\n );\n };\n });\n\n sdk.ui.wrap('TaskTable', (Original: ComponentType<Record<string, unknown>>) => {\n return function FilteredTaskTable(props: Record<string, unknown>) {\n const [isActive] = useFilterActive();\n if (!isActive) return <Original {...props} />;\n const tasks = (props.tasks || []) as Array<Record<string, unknown>>;\n const filtered = tasks.filter(t => t.importance === 'high');\n return <Original {...props} tasks={filtered} />;\n };\n });\n }\n);\n" + ticks + "\n\n### Example 7: Permanent row filter (always active when override is ON)\n" + ticks + "tsx\nimport { createOverride, SDK } from '@usesidekick/react';\n\nexport default createOverride(\n {\n id: 'hide-done-tasks',\n name: 'Hide Done Tasks',\n description: 'Hides completed tasks from the table',\n version: '1.0.0',\n primitives: ['ui.filterRows'],\n },\n (sdk: SDK) => {\n sdk.ui.filterRows('task-table', (row: Record<string, unknown>) => {\n return row.status !== 'done';\n });\n }\n);\n" + ticks + "\n\n### Example 8: Keyboard shortcut\n" + ticks + "tsx\nimport { createOverride, SDK } from '@usesidekick/react';\n\nexport default createOverride(\n {\n id: 'quick-search-shortcut',\n name: 'Quick Search Shortcut',\n description: 'Adds Ctrl+K shortcut to focus search',\n version: '1.0.0',\n primitives: ['behavior.addKeyboardShortcut'],\n },\n (sdk: SDK) => {\n sdk.behavior.addKeyboardShortcut('ctrl+k', () => {\n // Toggle search or custom action\n }, 'Open quick search');\n }\n);\n" + ticks + "\n\n### Example 9: DOM modification\n" + ticks + "tsx\nimport { createOverride, SDK } from '@usesidekick/react';\n\nexport default createOverride(\n {\n id: 'custom-dom-mods',\n name: 'Custom DOM Modifications',\n description: 'Changes sidebar title and styles task cards',\n version: '1.0.0',\n primitives: ['ui.setText', 'ui.setStyle'],\n },\n (sdk: SDK) => {\n sdk.ui.setText('.sidebar-title', 'My Custom App');\n sdk.ui.setStyle('.task-card', { borderLeft: '4px solid #3B82F6' });\n }\n);\n" + ticks + `
137
+
138
+ ## Rules
139
+
140
+ 1. ONLY import from '@usesidekick/react' or 'react' - NO other imports allowed
141
+ 2. DO NOT use fetch, eval, document.*, window.*, localStorage, etc.
142
+ 3. Use the createOverride function from @usesidekick/react
143
+ 4. The activate function receives an SDK object with ui and data primitives
144
+ 5. Generate React components using JSX with inline styles
145
+ 6. Keep code simple and focused on the user's request
146
+ 7. Use sdk.ui.wrap() to add behavior around existing components, use sdk.ui.replace() to fully swap components
147
+ 8. When wrapping, always pass through all props to the Original component
148
+ 9. ALWAYS prefer SDK primitives over raw wrapping for table operations
149
+ 10. When using sdk.ui.wrap(), ALWAYS render <Original {...props} /> as-is
150
+ 11. A wrapper's ONLY job is to add content BEFORE or AFTER <Original {...props} />, or to modify the props passed to it
151
+ 12. For permanent row filtering, use sdk.ui.filterRows(tableId, filterFn)
152
+ 13. Overrides MUST always act on the UI
153
+ 14. For interactive/togglable behavior, use module-level shared state with a listener pattern
154
+ 15. VISUAL INTEGRATION IS MANDATORY
155
+ 16. Study the component style samples in the design system carefully
156
+ 17. NEVER use absolute/fixed positioning to place elements inside existing components
157
+ 18. New elements MUST be visually indistinguishable from existing elements
158
+ 19. All interactive elements MUST have hover states
159
+ 20. BEFORE writing any wrap() code, read the target component's "Renders:" tree
160
+ 21. Structure wrappers as: optional-new-content-above + <Original {...props} /> + optional-new-content-below
161
+ 22. CSS selectors MUST be valid CSS selectors supported by document.querySelectorAll()
162
+
163
+ ## Output Format
164
+
165
+ Respond with ONLY a JSON object (no markdown, no explanation):
166
+ {
167
+ "manifest": {
168
+ "id": "override-id-kebab-case",
169
+ "name": "Human Readable Name",
170
+ "description": "What this override does",
171
+ "version": "1.0.0",
172
+ "primitives": ["list", "of", "primitives", "used"]
173
+ },
174
+ "code": "// Full TypeScript/TSX code here"
175
+ }`;
176
+ }
177
+ function buildUserPrompt(request, previousErrors, existingCode) {
178
+ let prompt;
179
+ if (existingCode) {
180
+ prompt = "You are MODIFYING an existing override. Here is the current code:\n```tsx\n" + existingCode + '\n```\n\nApply this change: "' + request + '"\n\nIMPORTANT: Keep the same override ID. Preserve all existing functionality unless the user explicitly asks to change it. Return the complete modified code.';
181
+ } else {
182
+ prompt = 'Generate an override module for this request: "' + request + '"';
183
+ }
184
+ if (previousErrors && previousErrors.length > 0) {
185
+ prompt += "\n\nIMPORTANT: Previous generation attempt failed validation with these errors:\n";
186
+ prompt += previousErrors.map((e) => "- " + e).join("\n");
187
+ prompt += "\n\nPlease fix these issues in your response.";
188
+ }
189
+ return prompt;
190
+ }
191
+ async function callAI(request, schema, apiKey, previousErrors, existingCode) {
192
+ const systemPrompt = buildSystemPrompt(schema);
193
+ const userPrompt = buildUserPrompt(request, previousErrors, existingCode);
194
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
195
+ method: "POST",
196
+ headers: {
197
+ "Content-Type": "application/json",
198
+ "x-api-key": apiKey,
199
+ "anthropic-version": "2023-06-01"
200
+ },
201
+ body: JSON.stringify({
202
+ model: "claude-sonnet-4-20250514",
203
+ max_tokens: 4096,
204
+ system: systemPrompt,
205
+ messages: [{ role: "user", content: userPrompt }]
206
+ })
207
+ });
208
+ if (!response.ok) {
209
+ const error = await response.text();
210
+ throw new Error("API request failed: " + response.status + " " + error);
211
+ }
212
+ const result = await response.json();
213
+ const textContent = result.content.find((c) => c.type === "text");
214
+ if (!textContent || !textContent.text) {
215
+ throw new Error("No text content in API response");
216
+ }
217
+ return parseAIResponse(textContent.text);
218
+ }
219
+ function parseAIResponse(text2) {
220
+ const jsonMatch = text2.match(/\{[\s\S]*\}/);
221
+ if (!jsonMatch) {
222
+ throw new Error("No JSON found in AI response");
223
+ }
224
+ try {
225
+ const parsed = JSON.parse(jsonMatch[0]);
226
+ if (!parsed.manifest || !parsed.code) {
227
+ throw new Error("Invalid response structure");
228
+ }
229
+ return {
230
+ manifest: parsed.manifest,
231
+ code: parsed.code
232
+ };
233
+ } catch (e) {
234
+ throw new Error("Failed to parse AI response: " + (e instanceof Error ? e.message : String(e)));
235
+ }
236
+ }
237
+ function validateCode(code) {
238
+ const errors = [];
239
+ const importMatches = code.matchAll(/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g);
240
+ for (const match of importMatches) {
241
+ const moduleName = match[1];
242
+ const isAllowed = ALLOWED_IMPORTS.some(
243
+ (allowed) => moduleName === allowed || moduleName.startsWith(allowed + "/")
244
+ );
245
+ if (!isAllowed) {
246
+ errors.push("Forbidden import: " + moduleName);
247
+ }
248
+ }
249
+ for (const pattern of FORBIDDEN_PATTERNS) {
250
+ if (pattern.test(code)) {
251
+ errors.push("Forbidden pattern: " + pattern.source);
252
+ }
253
+ }
254
+ return { valid: errors.length === 0, errors };
255
+ }
256
+
257
+ // src/server/handler.ts
258
+ function getAction(req) {
259
+ const url = new URL(req.url);
260
+ const segments = url.pathname.split("/").filter(Boolean);
261
+ return segments[segments.length - 1] || "";
262
+ }
263
+ async function readSchema(schemaPath) {
264
+ const candidates = schemaPath ? [schemaPath] : [
265
+ path.join(process.cwd(), "src/sidekick/schema.json"),
266
+ path.join(process.cwd(), "sidekick/schema.json")
267
+ ];
268
+ for (const candidate of candidates) {
269
+ try {
270
+ const content = await fs.readFile(candidate, "utf-8");
271
+ return JSON.parse(content);
272
+ } catch {
273
+ continue;
274
+ }
275
+ }
276
+ throw new Error("Failed to read schema.json");
277
+ }
278
+ function safeParsePrimitives(primitives) {
279
+ try {
280
+ const parsed = JSON.parse(primitives);
281
+ return Array.isArray(parsed) ? parsed : [];
282
+ } catch {
283
+ return [];
284
+ }
285
+ }
286
+ async function handleListOverrides(storage) {
287
+ const overrides = await storage.listOverrides();
288
+ return NextResponse.json(
289
+ {
290
+ success: true,
291
+ overrides: overrides.map((o) => ({
292
+ id: o.id,
293
+ name: o.name,
294
+ description: o.description,
295
+ version: o.version,
296
+ primitives: safeParsePrimitives(o.primitives),
297
+ code: o.code,
298
+ enabled: o.enabled,
299
+ createdAt: o.createdAt,
300
+ updatedAt: o.updatedAt
301
+ }))
302
+ },
303
+ {
304
+ headers: {
305
+ "Cache-Control": "no-store, no-cache, must-revalidate"
306
+ }
307
+ }
308
+ );
309
+ }
310
+ async function safeParseBody(req) {
311
+ try {
312
+ return await req.json();
313
+ } catch {
314
+ return null;
315
+ }
316
+ }
317
+ async function handleGenerate(req, storage, schemaPath) {
318
+ const body = await safeParseBody(req);
319
+ if (!body) {
320
+ return NextResponse.json(
321
+ { success: false, error: "Invalid JSON in request body" },
322
+ { status: 400 }
323
+ );
324
+ }
325
+ const { request, overrideId } = body;
326
+ if (!request || typeof request !== "string") {
327
+ return NextResponse.json(
328
+ { success: false, error: "Missing request parameter" },
329
+ { status: 400 }
330
+ );
331
+ }
332
+ let existingCode;
333
+ if (overrideId) {
334
+ const existing = await storage.getOverride(overrideId);
335
+ if (!existing) {
336
+ return NextResponse.json(
337
+ { success: false, error: `Override "${overrideId}" not found` },
338
+ { status: 404 }
339
+ );
340
+ }
341
+ existingCode = existing.code;
342
+ }
343
+ const apiKey = process.env.ANTHROPIC_API_KEY;
344
+ if (!apiKey) {
345
+ return NextResponse.json(
346
+ { success: false, error: "ANTHROPIC_API_KEY not configured" },
347
+ { status: 500 }
348
+ );
349
+ }
350
+ let schema;
351
+ try {
352
+ schema = await readSchema(schemaPath);
353
+ } catch {
354
+ return NextResponse.json(
355
+ { success: false, error: "Failed to read schema.json" },
356
+ { status: 500 }
357
+ );
358
+ }
359
+ const MAX_RETRIES = 3;
360
+ let lastError;
361
+ let lastValidationErrors;
362
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
363
+ try {
364
+ const generated = await callAI(request, schema, apiKey, lastValidationErrors, existingCode);
365
+ if (overrideId) {
366
+ generated.manifest.id = overrideId;
367
+ }
368
+ const validation = validateCode(generated.code);
369
+ if (!validation.valid) {
370
+ lastValidationErrors = validation.errors;
371
+ lastError = `Validation failed: ${validation.errors.join(", ")}`;
372
+ if (attempt < MAX_RETRIES) {
373
+ console.log(`[Sidekick] Attempt ${attempt} failed validation, retrying...`);
374
+ continue;
375
+ }
376
+ return NextResponse.json({
377
+ success: false,
378
+ error: lastError,
379
+ validationErrors: lastValidationErrors
380
+ });
381
+ }
382
+ const existing = await storage.getOverride(generated.manifest.id);
383
+ if (existing) {
384
+ await storage.updateOverride(generated.manifest.id, {
385
+ name: generated.manifest.name,
386
+ description: generated.manifest.description,
387
+ version: generated.manifest.version,
388
+ primitives: JSON.stringify(generated.manifest.primitives),
389
+ code: generated.code
390
+ });
391
+ } else {
392
+ await storage.createOverride({
393
+ id: generated.manifest.id,
394
+ name: generated.manifest.name,
395
+ description: generated.manifest.description,
396
+ version: generated.manifest.version,
397
+ primitives: JSON.stringify(generated.manifest.primitives),
398
+ code: generated.code,
399
+ enabled: true,
400
+ createdAt: /* @__PURE__ */ new Date(),
401
+ updatedAt: /* @__PURE__ */ new Date()
402
+ });
403
+ }
404
+ return NextResponse.json({
405
+ success: true,
406
+ overrideId: generated.manifest.id,
407
+ name: generated.manifest.name,
408
+ description: generated.manifest.description
409
+ });
410
+ } catch (e) {
411
+ lastError = e instanceof Error ? e.message : String(e);
412
+ if (attempt === MAX_RETRIES) {
413
+ return NextResponse.json({
414
+ success: false,
415
+ error: `Failed after ${MAX_RETRIES} attempts: ${lastError}`
416
+ });
417
+ }
418
+ }
419
+ }
420
+ return NextResponse.json({
421
+ success: false,
422
+ error: lastError || "Unknown error"
423
+ });
424
+ }
425
+ async function handleToggle(req, storage) {
426
+ const body = await safeParseBody(req);
427
+ if (!body) {
428
+ return NextResponse.json(
429
+ { success: false, error: "Invalid JSON in request body" },
430
+ { status: 400 }
431
+ );
432
+ }
433
+ const { overrideId, enabled } = body;
434
+ if (!overrideId || typeof overrideId !== "string") {
435
+ return NextResponse.json(
436
+ { success: false, error: "Missing overrideId parameter" },
437
+ { status: 400 }
438
+ );
439
+ }
440
+ if (typeof enabled !== "boolean") {
441
+ return NextResponse.json(
442
+ { success: false, error: "Missing or invalid enabled parameter" },
443
+ { status: 400 }
444
+ );
445
+ }
446
+ const existing = await storage.getOverride(overrideId);
447
+ if (!existing) {
448
+ return NextResponse.json(
449
+ { success: false, error: "Override not found" },
450
+ { status: 404 }
451
+ );
452
+ }
453
+ await storage.updateOverride(overrideId, { enabled });
454
+ const updated = await storage.getOverride(overrideId);
455
+ return NextResponse.json({
456
+ success: true,
457
+ overrideId,
458
+ enabled: updated?.enabled ?? enabled
459
+ });
460
+ }
461
+ async function handleDelete(req, storage) {
462
+ const body = await safeParseBody(req);
463
+ if (!body) {
464
+ return NextResponse.json(
465
+ { success: false, error: "Invalid JSON in request body" },
466
+ { status: 400 }
467
+ );
468
+ }
469
+ const { overrideId } = body;
470
+ if (!overrideId || typeof overrideId !== "string") {
471
+ return NextResponse.json(
472
+ { success: false, error: "Missing overrideId parameter" },
473
+ { status: 400 }
474
+ );
475
+ }
476
+ const existing = await storage.getOverride(overrideId);
477
+ if (!existing) {
478
+ return NextResponse.json(
479
+ { success: false, error: "Override not found" },
480
+ { status: 404 }
481
+ );
482
+ }
483
+ await storage.deleteOverride(overrideId);
484
+ return NextResponse.json({
485
+ success: true,
486
+ message: `Override "${overrideId}" deleted successfully`
487
+ });
488
+ }
489
+ function createSidekickHandler(options) {
490
+ const { storage, schemaPath } = options;
491
+ return {
492
+ async GET(req) {
493
+ try {
494
+ const action = getAction(req);
495
+ if (action === "overrides") {
496
+ return handleListOverrides(storage);
497
+ }
498
+ return NextResponse.json(
499
+ { success: false, error: `Unknown GET action: ${action}` },
500
+ { status: 404 }
501
+ );
502
+ } catch (e) {
503
+ console.error("[Sidekick] Handler error:", e);
504
+ return NextResponse.json(
505
+ { success: false, error: e instanceof Error ? e.message : "Unknown error" },
506
+ { status: 500 }
507
+ );
508
+ }
509
+ },
510
+ async POST(req) {
511
+ try {
512
+ const action = getAction(req);
513
+ switch (action) {
514
+ case "generate":
515
+ return handleGenerate(req, storage, schemaPath);
516
+ case "toggle":
517
+ return handleToggle(req, storage);
518
+ case "delete":
519
+ return handleDelete(req, storage);
520
+ default:
521
+ return NextResponse.json(
522
+ { success: false, error: `Unknown POST action: ${action}` },
523
+ { status: 404 }
524
+ );
525
+ }
526
+ } catch (e) {
527
+ console.error("[Sidekick] Handler error:", e);
528
+ return NextResponse.json(
529
+ { success: false, error: e instanceof Error ? e.message : "Unknown error" },
530
+ { status: 500 }
531
+ );
532
+ }
533
+ }
534
+ };
535
+ }
536
+
537
+ // src/server/drizzle-adapter.ts
538
+ import { eq } from "drizzle-orm";
539
+ function createDrizzleStorage(db, overridesTable) {
540
+ return {
541
+ async listOverrides() {
542
+ const rows = await db.select().from(overridesTable).orderBy(overridesTable.createdAt);
543
+ return rows;
544
+ },
545
+ async getOverride(id) {
546
+ const rows = await db.select().from(overridesTable).where(eq(overridesTable.id, id));
547
+ return rows[0] ?? null;
548
+ },
549
+ async createOverride(data) {
550
+ await db.insert(overridesTable).values({
551
+ id: data.id,
552
+ name: data.name,
553
+ description: data.description,
554
+ version: data.version,
555
+ primitives: data.primitives,
556
+ code: data.code,
557
+ enabled: data.enabled ?? true,
558
+ createdAt: data.createdAt ?? /* @__PURE__ */ new Date(),
559
+ updatedAt: data.updatedAt ?? /* @__PURE__ */ new Date()
560
+ });
561
+ },
562
+ async updateOverride(id, data) {
563
+ const { id: _id, createdAt: _ca, ...updateData } = data;
564
+ await db.update(overridesTable).set({ ...updateData, updatedAt: /* @__PURE__ */ new Date() }).where(eq(overridesTable.id, id));
565
+ },
566
+ async deleteOverride(id) {
567
+ await db.delete(overridesTable).where(eq(overridesTable.id, id));
568
+ }
569
+ };
570
+ }
571
+
572
+ // src/server/drizzle-schema.ts
573
+ import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core";
574
+ var sidekickOverrides = pgTable("overrides", {
575
+ id: text("id").primaryKey(),
576
+ name: text("name").notNull(),
577
+ description: text("description").notNull(),
578
+ version: text("version").notNull().default("1.0.0"),
579
+ primitives: text("primitives").notNull(),
580
+ // JSON array stored as text
581
+ code: text("code").notNull(),
582
+ enabled: boolean("enabled").notNull().default(true),
583
+ createdAt: timestamp("created_at").defaultNow().notNull(),
584
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
585
+ });
586
+ export {
587
+ buildSystemPrompt,
588
+ buildUserPrompt,
589
+ callAI,
590
+ createDrizzleStorage,
591
+ createSidekickHandler,
592
+ formatDesignSystem,
593
+ parseAIResponse,
594
+ sidekickOverrides,
595
+ validateCode
596
+ };
597
+ //# sourceMappingURL=index.mjs.map