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