design-brain-memory 0.8.2 → 0.9.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.
- package/dist/agentBrowser.d.ts +1 -0
- package/dist/agentBrowser.js +1 -0
- package/dist/agentBrowser.js.map +1 -1
- package/dist/classify.d.ts +21 -0
- package/dist/classify.js +205 -0
- package/dist/classify.js.map +1 -0
- package/dist/cli.js +216 -219
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +1 -60
- package/dist/commands.js +10 -322
- package/dist/commands.js.map +1 -1
- package/dist/extractFromUrl.d.ts +0 -7
- package/dist/extractFromUrl.js +353 -82
- package/dist/extractFromUrl.js.map +1 -1
- package/dist/index.d.ts +12 -24
- package/dist/index.js +11 -16
- package/dist/index.js.map +1 -1
- package/dist/llm.d.ts +6 -0
- package/dist/llm.js +12 -1
- package/dist/llm.js.map +1 -1
- package/dist/persona.d.ts +2 -0
- package/dist/persona.js +234 -0
- package/dist/persona.js.map +1 -0
- package/dist/query.js +6 -1
- package/dist/query.js.map +1 -1
- package/dist/render.d.ts +10 -2
- package/dist/render.js +175 -80
- package/dist/render.js.map +1 -1
- package/dist/scan.d.ts +10 -0
- package/dist/scan.js +393 -0
- package/dist/scan.js.map +1 -0
- package/dist/scanRenderer.d.ts +2 -0
- package/dist/scanRenderer.js +155 -0
- package/dist/scanRenderer.js.map +1 -0
- package/dist/skillPrompt.d.ts +3 -1
- package/dist/skillPrompt.js +148 -22
- package/dist/skillPrompt.js.map +1 -1
- package/dist/store.d.ts +5 -1
- package/dist/store.js +19 -0
- package/dist/store.js.map +1 -1
- package/dist/taste.d.ts +9 -0
- package/dist/taste.js +598 -0
- package/dist/taste.js.map +1 -0
- package/dist/tasteDiff.d.ts +19 -0
- package/dist/tasteDiff.js +340 -0
- package/dist/tasteDiff.js.map +1 -0
- package/dist/tasteGenerate.d.ts +12 -0
- package/dist/tasteGenerate.js +140 -0
- package/dist/tasteGenerate.js.map +1 -0
- package/dist/tasteRefine.d.ts +13 -0
- package/dist/tasteRefine.js +351 -0
- package/dist/tasteRefine.js.map +1 -0
- package/dist/tasteRenderer.d.ts +4 -0
- package/dist/tasteRenderer.js +133 -0
- package/dist/tasteRenderer.js.map +1 -0
- package/dist/theatrical.d.ts +5 -0
- package/dist/theatrical.js +258 -0
- package/dist/theatrical.js.map +1 -0
- package/dist/types.d.ts +196 -27
- package/package.json +4 -3
- package/skills/SKILL.md +36 -0
- package/skills/design-brain/SKILL.md +77 -0
- package/README.md +0 -242
- package/dist/aggregate.d.ts +0 -9
- package/dist/aggregate.js +0 -53
- package/dist/aggregate.js.map +0 -1
- package/dist/batch.d.ts +0 -16
- package/dist/batch.js +0 -44
- package/dist/batch.js.map +0 -1
- package/dist/compare.d.ts +0 -33
- package/dist/compare.js +0 -83
- package/dist/compare.js.map +0 -1
- package/dist/componentGraph.d.ts +0 -22
- package/dist/componentGraph.js +0 -106
- package/dist/componentGraph.js.map +0 -1
- package/dist/contextLayer.d.ts +0 -12
- package/dist/contextLayer.js +0 -263
- package/dist/contextLayer.js.map +0 -1
- package/dist/cssInJs.d.ts +0 -9
- package/dist/cssInJs.js +0 -124
- package/dist/cssInJs.js.map +0 -1
- package/dist/graphView.d.ts +0 -21
- package/dist/graphView.js +0 -492
- package/dist/graphView.js.map +0 -1
- package/dist/knowledge.d.ts +0 -20
- package/dist/knowledge.js +0 -208
- package/dist/knowledge.js.map +0 -1
- package/dist/liveView.d.ts +0 -15
- package/dist/liveView.js +0 -476
- package/dist/liveView.js.map +0 -1
- package/dist/moodboard.d.ts +0 -3
- package/dist/moodboard.js +0 -139
- package/dist/moodboard.js.map +0 -1
- package/dist/reviewChecklist.d.ts +0 -17
- package/dist/reviewChecklist.js +0 -126
- package/dist/reviewChecklist.js.map +0 -1
- package/dist/scorecard.d.ts +0 -48
- package/dist/scorecard.js +0 -201
- package/dist/scorecard.js.map +0 -1
- package/dist/styleDictionary.d.ts +0 -16
- package/dist/styleDictionary.js +0 -89
- package/dist/styleDictionary.js.map +0 -1
- package/dist/svg.d.ts +0 -5
- package/dist/svg.js +0 -162
- package/dist/svg.js.map +0 -1
- package/dist/systemDiff.d.ts +0 -28
- package/dist/systemDiff.js +0 -107
- package/dist/systemDiff.js.map +0 -1
- package/dist/tailwind.d.ts +0 -2
- package/dist/tailwind.js +0 -122
- package/dist/tailwind.js.map +0 -1
- package/dist/tokenNaming.d.ts +0 -5
- package/dist/tokenNaming.js +0 -229
- package/dist/tokenNaming.js.map +0 -1
- package/dist/tokens.d.ts +0 -17
- package/dist/tokens.js +0 -44
- package/dist/tokens.js.map +0 -1
- package/dist/trends.d.ts +0 -12
- package/dist/trends.js +0 -178
- package/dist/trends.js.map +0 -1
- package/dist/wiki.d.ts +0 -10
- package/dist/wiki.js +0 -346
- package/dist/wiki.js.map +0 -1
- package/dist/writingStyle.d.ts +0 -38
- package/dist/writingStyle.js +0 -224
- package/dist/writingStyle.js.map +0 -1
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import { runAgentBrowserJson } from './agentBrowser.js';
|
|
5
|
+
const SESSION_NAME = 'design-brain-demo';
|
|
6
|
+
/* ─── Highlight injection script ─── */
|
|
7
|
+
const HIGHLIGHT_SCRIPT = String.raw `(() => {
|
|
8
|
+
const style = document.createElement('style');
|
|
9
|
+
style.id = 'design-brain-highlights';
|
|
10
|
+
style.textContent = [
|
|
11
|
+
'@keyframes db-pulse { 0%,100% { opacity: 0.7; } 50% { opacity: 1; } }',
|
|
12
|
+
'.db-highlight-color { outline: 3px solid #00ff88 !important; animation: db-pulse 1s infinite; }',
|
|
13
|
+
'.db-highlight-typo { text-decoration: underline wavy #ff6b00 !important; text-underline-offset: 4px; }',
|
|
14
|
+
'.db-highlight-component { outline: 3px solid #6366f1 !important; outline-offset: 2px; box-shadow: 0 0 12px rgba(99,102,241,0.4) !important; }',
|
|
15
|
+
].join('\n');
|
|
16
|
+
document.head.appendChild(style);
|
|
17
|
+
|
|
18
|
+
const nodes = Array.from(document.querySelectorAll('body *')).slice(0, 500);
|
|
19
|
+
let colorCount = 0, typoCount = 0, compCount = 0;
|
|
20
|
+
|
|
21
|
+
nodes.forEach(el => {
|
|
22
|
+
const s = getComputedStyle(el);
|
|
23
|
+
const bg = s.backgroundColor;
|
|
24
|
+
if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent' && colorCount < 15) {
|
|
25
|
+
el.classList.add('db-highlight-color');
|
|
26
|
+
colorCount++;
|
|
27
|
+
}
|
|
28
|
+
if (el.textContent?.trim().length > 3 && s.fontSize && parseInt(s.fontSize) >= 20 && typoCount < 10) {
|
|
29
|
+
el.classList.add('db-highlight-typo');
|
|
30
|
+
typoCount++;
|
|
31
|
+
}
|
|
32
|
+
const tag = el.tagName.toLowerCase();
|
|
33
|
+
if ((tag === 'button' || tag === 'a' || el.getAttribute('role') === 'button') && compCount < 10) {
|
|
34
|
+
el.classList.add('db-highlight-component');
|
|
35
|
+
compCount++;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return { colorCount, typoCount, compCount };
|
|
40
|
+
})()`;
|
|
41
|
+
/* ─── Cleanup script ─── */
|
|
42
|
+
const CLEANUP_SCRIPT = String.raw `(() => {
|
|
43
|
+
document.getElementById('design-brain-highlights')?.remove();
|
|
44
|
+
document.querySelectorAll('.db-highlight-color,.db-highlight-typo,.db-highlight-component')
|
|
45
|
+
.forEach(el => el.classList.remove('db-highlight-color','db-highlight-typo','db-highlight-component'));
|
|
46
|
+
})()`;
|
|
47
|
+
/* ─── Scroll script ─── */
|
|
48
|
+
const SCROLL_SCRIPT = String.raw `(() => {
|
|
49
|
+
window.scrollBy({ top: 400, behavior: 'smooth' });
|
|
50
|
+
return { scrollY: window.scrollY };
|
|
51
|
+
})()`;
|
|
52
|
+
/* ─── Extraction script (same as extractFromUrl.ts) ─── */
|
|
53
|
+
const EXTRACTION_SCRIPT = String.raw `(() => {
|
|
54
|
+
const maxNodes = 2000;
|
|
55
|
+
const nodes = Array.from(document.querySelectorAll('body *')).slice(0, maxNodes);
|
|
56
|
+
|
|
57
|
+
const colorMap = new Map();
|
|
58
|
+
const typographyMap = new Map();
|
|
59
|
+
const componentList = [];
|
|
60
|
+
const motionList = [];
|
|
61
|
+
const layoutList = [];
|
|
62
|
+
const variableMap = {};
|
|
63
|
+
|
|
64
|
+
const componentTags = new Set(['button', 'a', 'input', 'select', 'textarea', 'nav', 'header', 'footer', 'form']);
|
|
65
|
+
const maxComponents = 220;
|
|
66
|
+
const maxMotion = 220;
|
|
67
|
+
|
|
68
|
+
function normalizeWhitespace(text) {
|
|
69
|
+
return (text || '').replace(/\s+/g, ' ').trim();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function pushColor(value, sample) {
|
|
73
|
+
if (!value || value === 'transparent' || value === 'rgba(0, 0, 0, 0)') return;
|
|
74
|
+
const parsed = parseCssColor(value);
|
|
75
|
+
if (!parsed) return;
|
|
76
|
+
const key = parsed.toUpperCase();
|
|
77
|
+
const entry = colorMap.get(key) || { hex: key, count: 0, samples: [] };
|
|
78
|
+
entry.count += 1;
|
|
79
|
+
if (sample && entry.samples.length < 5 && !entry.samples.includes(sample)) {
|
|
80
|
+
entry.samples.push(sample);
|
|
81
|
+
}
|
|
82
|
+
colorMap.set(key, entry);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseCssColor(input) {
|
|
86
|
+
if (!input) return null;
|
|
87
|
+
const value = input.toString().trim().toLowerCase();
|
|
88
|
+
if (value.startsWith('#')) {
|
|
89
|
+
if (value.length === 4) return '#' + value[1] + value[1] + value[2] + value[2] + value[3] + value[3];
|
|
90
|
+
if (value.length === 7) return value;
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const rgbMatch = value.match(/rgba?\(([^)]+)\)/);
|
|
94
|
+
if (rgbMatch) {
|
|
95
|
+
const parts = rgbMatch[1].split(',').map(p => p.trim());
|
|
96
|
+
const r = Number(parts[0]), g = Number(parts[1]), b = Number(parts[2]);
|
|
97
|
+
if ([r, g, b].some(n => Number.isNaN(n))) return null;
|
|
98
|
+
const toHex = n => Math.max(0, Math.min(255, n)).toString(16).padStart(2, '0');
|
|
99
|
+
return '#' + toHex(r) + toHex(g) + toHex(b);
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildSelector(el) {
|
|
105
|
+
const tag = el.tagName.toLowerCase();
|
|
106
|
+
if (el.id) return tag + '#' + el.id;
|
|
107
|
+
if (typeof el.className === 'string' && el.className.trim()) {
|
|
108
|
+
const firstClass = el.className.trim().split(/\s+/).slice(0, 2).join('.');
|
|
109
|
+
if (firstClass) return tag + '.' + firstClass;
|
|
110
|
+
}
|
|
111
|
+
return tag;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function addTypography(style) {
|
|
115
|
+
const key = [style.fontFamily, style.fontSize, style.fontWeight, style.lineHeight].join('|');
|
|
116
|
+
const existing = typographyMap.get(key) || {
|
|
117
|
+
fontFamily: style.fontFamily, fontSize: style.fontSize,
|
|
118
|
+
fontWeight: style.fontWeight, lineHeight: style.lineHeight, count: 0,
|
|
119
|
+
};
|
|
120
|
+
existing.count += 1;
|
|
121
|
+
typographyMap.set(key, existing);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function shouldCaptureAsComponent(el, selector) {
|
|
125
|
+
const tag = el.tagName.toLowerCase();
|
|
126
|
+
if (componentTags.has(tag)) return true;
|
|
127
|
+
const className = typeof el.className === 'string' ? el.className.toLowerCase() : '';
|
|
128
|
+
if (className.includes('btn') || className.includes('button') || className.includes('card') || className.includes('hero')) return true;
|
|
129
|
+
if (el.getAttribute('role') === 'button' || el.getAttribute('role') === 'navigation') return true;
|
|
130
|
+
return selector.startsWith('section.') || selector.startsWith('div.card');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function inferComponentKind(el) {
|
|
134
|
+
const tag = el.tagName.toLowerCase();
|
|
135
|
+
const className = typeof el.className === 'string' ? el.className.toLowerCase() : '';
|
|
136
|
+
if (tag === 'button' || className.includes('btn')) return 'button';
|
|
137
|
+
if (tag === 'a') return 'link';
|
|
138
|
+
if (tag === 'input' || tag === 'textarea' || tag === 'select') return 'form-control';
|
|
139
|
+
if (tag === 'nav' || className.includes('nav')) return 'navigation';
|
|
140
|
+
if (className.includes('card')) return 'card';
|
|
141
|
+
if (tag === 'header') return 'header';
|
|
142
|
+
if (tag === 'footer') return 'footer';
|
|
143
|
+
if (className.includes('hero')) return 'hero';
|
|
144
|
+
return tag;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
nodes.forEach(el => {
|
|
148
|
+
const style = window.getComputedStyle(el);
|
|
149
|
+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return;
|
|
150
|
+
const selector = buildSelector(el);
|
|
151
|
+
pushColor(style.color, selector + ':color');
|
|
152
|
+
pushColor(style.backgroundColor, selector + ':background');
|
|
153
|
+
if (style.borderColor && style.borderColor !== 'rgba(0, 0, 0, 0)') {
|
|
154
|
+
pushColor(style.borderColor, selector + ':border');
|
|
155
|
+
}
|
|
156
|
+
const hasText = normalizeWhitespace(el.textContent || '').length > 0;
|
|
157
|
+
if (hasText) addTypography(style);
|
|
158
|
+
if (shouldCaptureAsComponent(el, selector) && componentList.length < maxComponents) {
|
|
159
|
+
componentList.push({
|
|
160
|
+
kind: inferComponentKind(el), tag: el.tagName.toLowerCase(), selector,
|
|
161
|
+
text: normalizeWhitespace(el.textContent || '').slice(0, 90),
|
|
162
|
+
className: typeof el.className === 'string' ? normalizeWhitespace(el.className) : '',
|
|
163
|
+
styles: {
|
|
164
|
+
color: style.color, backgroundColor: style.backgroundColor,
|
|
165
|
+
borderRadius: style.borderRadius, border: style.border,
|
|
166
|
+
padding: style.padding, margin: style.margin,
|
|
167
|
+
fontSize: style.fontSize, fontWeight: style.fontWeight, boxShadow: style.boxShadow,
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const hasTransition = style.transitionDuration && style.transitionDuration !== '0s';
|
|
172
|
+
const hasAnimation = style.animationName && style.animationName !== 'none';
|
|
173
|
+
const hasTransform = style.transform && style.transform !== 'none';
|
|
174
|
+
if ((hasTransition || hasAnimation || hasTransform) && motionList.length < maxMotion) {
|
|
175
|
+
motionList.push({ selector, transition: style.transition, animation: style.animation, transform: style.transform });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const layoutSelectors = Array.from(document.querySelectorAll('header, nav, main, section, article, aside, footer')).slice(0, 160);
|
|
180
|
+
layoutSelectors.forEach(el => {
|
|
181
|
+
layoutList.push({ tag: el.tagName.toLowerCase(), selector: buildSelector(el), role: el.getAttribute('role') || '', children: el.children.length });
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
[document.documentElement, document.body].forEach(root => {
|
|
185
|
+
if (!root) return;
|
|
186
|
+
const style = window.getComputedStyle(root);
|
|
187
|
+
for (const propertyName of style) {
|
|
188
|
+
if (!propertyName.startsWith('--')) continue;
|
|
189
|
+
const value = style.getPropertyValue(propertyName).trim();
|
|
190
|
+
if (value) variableMap[propertyName] = value;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const colors = Array.from(colorMap.values()).sort((a, b) => b.count - a.count).slice(0, 70);
|
|
195
|
+
const typography = Array.from(typographyMap.values()).sort((a, b) => b.count - a.count).slice(0, 90);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
pageTitle: document.title, pageUrl: window.location.href,
|
|
199
|
+
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
200
|
+
colors, typography, components: componentList, motion: motionList,
|
|
201
|
+
layout: layoutList, cssVariables: variableMap,
|
|
202
|
+
};
|
|
203
|
+
})()`;
|
|
204
|
+
/* ─── Normalize extraction result ─── */
|
|
205
|
+
function normalizeResult(result) {
|
|
206
|
+
return {
|
|
207
|
+
pageTitle: result.pageTitle ?? undefined,
|
|
208
|
+
pageUrl: result.pageUrl ?? undefined,
|
|
209
|
+
viewport: result.viewport ?? undefined,
|
|
210
|
+
colors: result.colors ?? [],
|
|
211
|
+
typography: result.typography ?? [],
|
|
212
|
+
components: result.components ?? [],
|
|
213
|
+
motion: result.motion ?? [],
|
|
214
|
+
layout: result.layout ?? [],
|
|
215
|
+
cssVariables: result.cssVariables ?? {},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/* ─── Theatrical scan sequence ─── */
|
|
219
|
+
export async function theatricalScan(url) {
|
|
220
|
+
const screenshotDir = path.join(os.tmpdir(), 'design-brain-theatrical');
|
|
221
|
+
await fs.ensureDir(screenshotDir);
|
|
222
|
+
const screenshotPath = path.join(screenshotDir, `scan-${Date.now()}.png`);
|
|
223
|
+
const browserOpts = { session: SESSION_NAME, headed: true };
|
|
224
|
+
// 1. Open the URL in a visible browser
|
|
225
|
+
await runAgentBrowserJson(['open', url], browserOpts);
|
|
226
|
+
try {
|
|
227
|
+
// 2. Wait for page to settle
|
|
228
|
+
await runAgentBrowserJson(['wait', '1500'], browserOpts);
|
|
229
|
+
// 3. Slow scroll — 4 steps, ~600ms between each
|
|
230
|
+
for (let i = 0; i < 4; i++) {
|
|
231
|
+
await runAgentBrowserJson(['eval', SCROLL_SCRIPT], browserOpts);
|
|
232
|
+
await runAgentBrowserJson(['wait', '600'], browserOpts);
|
|
233
|
+
}
|
|
234
|
+
// Scroll back to top for highlight injection
|
|
235
|
+
await runAgentBrowserJson(['eval', 'window.scrollTo({ top: 0, behavior: "smooth" })'], browserOpts);
|
|
236
|
+
await runAgentBrowserJson(['wait', '800'], browserOpts);
|
|
237
|
+
// 4. Inject highlight overlays
|
|
238
|
+
await runAgentBrowserJson(['eval', HIGHLIGHT_SCRIPT], browserOpts);
|
|
239
|
+
// 5. Pause for visual impact
|
|
240
|
+
await runAgentBrowserJson(['wait', '2000'], browserOpts);
|
|
241
|
+
// 6. Take screenshot with overlays visible
|
|
242
|
+
await runAgentBrowserJson(['screenshot', screenshotPath, '--full'], browserOpts);
|
|
243
|
+
// 7. Clean overlays
|
|
244
|
+
await runAgentBrowserJson(['eval', CLEANUP_SCRIPT], browserOpts);
|
|
245
|
+
await runAgentBrowserJson(['wait', '300'], browserOpts);
|
|
246
|
+
// 8. Extract design tokens (real extraction on clean page)
|
|
247
|
+
const extraction = await runAgentBrowserJson(['eval', EXTRACTION_SCRIPT], browserOpts);
|
|
248
|
+
if (!extraction.success) {
|
|
249
|
+
throw new Error(`Extraction failed: ${extraction.error ?? 'Unknown error'}`);
|
|
250
|
+
}
|
|
251
|
+
const analysis = normalizeResult(extraction.data.result ?? {});
|
|
252
|
+
return { analysis, screenshotPath };
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
await runAgentBrowserJson(['close'], browserOpts).catch(() => undefined);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=theatrical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theatrical.js","sourceRoot":"","sources":["../src/theatrical.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -18,37 +18,68 @@ export interface ComponentToken {
|
|
|
18
18
|
text: string;
|
|
19
19
|
className: string;
|
|
20
20
|
styles: Record<string, string>;
|
|
21
|
-
html?: string;
|
|
22
21
|
}
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
export type AnimationLibrary = 'css' | 'gsap' | 'lottie' | 'framer-motion' | 'react-spring' | 'motion-one' | 'unknown';
|
|
23
|
+
export type MotionIntent = 'fade' | 'slide' | 'scale' | 'rotate' | 'color-shift' | 'spring' | 'bounce' | 'morph' | 'reveal' | 'parallax' | 'complex';
|
|
24
|
+
export type TriggerEvent = 'load' | 'hover' | 'focus' | 'click' | 'scroll' | 'viewport-enter' | 'media-query' | 'unknown';
|
|
25
|
+
export interface KeyframeStop {
|
|
26
|
+
offset: number;
|
|
27
|
+
properties: Record<string, string>;
|
|
28
|
+
easing?: string;
|
|
28
29
|
}
|
|
29
|
-
export interface
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
export interface AnimationTiming {
|
|
31
|
+
duration: number;
|
|
32
|
+
delay: number;
|
|
33
|
+
easing: string;
|
|
34
|
+
iterations: number;
|
|
35
|
+
direction: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';
|
|
36
|
+
fillMode: 'none' | 'forwards' | 'backwards' | 'both';
|
|
37
|
+
}
|
|
38
|
+
export interface ScrollBinding {
|
|
39
|
+
triggerSelector?: string;
|
|
40
|
+
hasScrollTrigger: boolean;
|
|
41
|
+
hasIntersectionObserver: boolean;
|
|
42
|
+
scrollTimelineAxis?: 'block' | 'inline';
|
|
43
|
+
}
|
|
44
|
+
export interface PhysicsParams {
|
|
45
|
+
type: 'spring' | 'bounce' | 'inertia';
|
|
46
|
+
mass?: number;
|
|
47
|
+
stiffness?: number;
|
|
48
|
+
damping?: number;
|
|
49
|
+
oscillationCount?: number;
|
|
50
|
+
overshootPercent?: number;
|
|
51
|
+
}
|
|
52
|
+
export interface AnimationGroup {
|
|
53
|
+
groupId: string;
|
|
54
|
+
role: 'lead' | 'follower';
|
|
55
|
+
staggerDelay?: number;
|
|
44
56
|
}
|
|
57
|
+
export interface AnimationToken {
|
|
58
|
+
selector: string;
|
|
59
|
+
library: AnimationLibrary;
|
|
60
|
+
motionIntent: MotionIntent;
|
|
61
|
+
timing?: AnimationTiming;
|
|
62
|
+
keyframes?: KeyframeStop[];
|
|
63
|
+
trigger: TriggerEvent;
|
|
64
|
+
scrollBinding?: ScrollBinding;
|
|
65
|
+
physics?: PhysicsParams;
|
|
66
|
+
group?: AnimationGroup;
|
|
67
|
+
gsapVars?: Record<string, unknown>;
|
|
68
|
+
lottieMetadata?: {
|
|
69
|
+
frameRate: number;
|
|
70
|
+
totalFrames: number;
|
|
71
|
+
duration: number;
|
|
72
|
+
};
|
|
73
|
+
rawTransition?: string;
|
|
74
|
+
rawAnimation?: string;
|
|
75
|
+
rawTransform?: string;
|
|
76
|
+
}
|
|
77
|
+
/** @deprecated Use AnimationToken instead */
|
|
45
78
|
export interface MotionToken {
|
|
46
79
|
selector: string;
|
|
47
80
|
transition: string;
|
|
48
81
|
animation: string;
|
|
49
82
|
transform: string;
|
|
50
|
-
transitions?: TransitionDetail[];
|
|
51
|
-
animations?: AnimationDetail[];
|
|
52
83
|
}
|
|
53
84
|
export interface LayoutToken {
|
|
54
85
|
tag: string;
|
|
@@ -91,8 +122,7 @@ export interface DesignAnalysis {
|
|
|
91
122
|
colors: ColorToken[];
|
|
92
123
|
typography: TypographyToken[];
|
|
93
124
|
components: ComponentToken[];
|
|
94
|
-
motion: MotionToken[];
|
|
95
|
-
keyframes?: KeyframeRule[];
|
|
125
|
+
motion: (MotionToken | AnimationToken)[];
|
|
96
126
|
layout: LayoutToken[];
|
|
97
127
|
cssVariables: Record<string, string>;
|
|
98
128
|
accessibilitySnapshot?: string;
|
|
@@ -188,8 +218,6 @@ export interface IngestOptions {
|
|
|
188
218
|
width: number;
|
|
189
219
|
height: number;
|
|
190
220
|
}>;
|
|
191
|
-
skipVisuals?: boolean;
|
|
192
|
-
live?: boolean;
|
|
193
221
|
}
|
|
194
222
|
export interface OutcomeOptions {
|
|
195
223
|
rootDir: string;
|
|
@@ -200,3 +228,144 @@ export interface OutcomeOptions {
|
|
|
200
228
|
artifactUrl?: string;
|
|
201
229
|
tags: string[];
|
|
202
230
|
}
|
|
231
|
+
export interface ScanTokens {
|
|
232
|
+
colors: string[];
|
|
233
|
+
fontFamilies: string[];
|
|
234
|
+
fontSizes: string[];
|
|
235
|
+
transitions: string[];
|
|
236
|
+
spacingValues: string[];
|
|
237
|
+
cssVariableCount: number;
|
|
238
|
+
framework: string | null;
|
|
239
|
+
}
|
|
240
|
+
export interface ScanScore {
|
|
241
|
+
colorDiscipline: number;
|
|
242
|
+
typographySystem: number;
|
|
243
|
+
spacingLayout: number;
|
|
244
|
+
motionPolish: number;
|
|
245
|
+
total: number;
|
|
246
|
+
}
|
|
247
|
+
export interface PersonaMatch {
|
|
248
|
+
name: string;
|
|
249
|
+
tagline: string;
|
|
250
|
+
score: number;
|
|
251
|
+
reasoning: string;
|
|
252
|
+
}
|
|
253
|
+
export interface ScanResult {
|
|
254
|
+
path: string;
|
|
255
|
+
filesScanned: number;
|
|
256
|
+
tokens: ScanTokens;
|
|
257
|
+
score: ScanScore;
|
|
258
|
+
persona: PersonaMatch;
|
|
259
|
+
framework: string | null;
|
|
260
|
+
}
|
|
261
|
+
export interface AgentInfo {
|
|
262
|
+
name: string;
|
|
263
|
+
configDir: string;
|
|
264
|
+
skillDir: string;
|
|
265
|
+
}
|
|
266
|
+
export interface InstallResult {
|
|
267
|
+
agent: AgentInfo;
|
|
268
|
+
success: boolean;
|
|
269
|
+
error?: string;
|
|
270
|
+
}
|
|
271
|
+
export interface TasteColorPreference {
|
|
272
|
+
palette: Array<{
|
|
273
|
+
hex: string;
|
|
274
|
+
role: string;
|
|
275
|
+
source: string;
|
|
276
|
+
}>;
|
|
277
|
+
harmony: string;
|
|
278
|
+
hueRange: {
|
|
279
|
+
min: number;
|
|
280
|
+
max: number;
|
|
281
|
+
};
|
|
282
|
+
saturationBias: 'muted' | 'vibrant' | 'neutral';
|
|
283
|
+
lightnessBias: 'light' | 'dark' | 'balanced';
|
|
284
|
+
}
|
|
285
|
+
export interface TasteTypographyPreference {
|
|
286
|
+
primaryFont: string;
|
|
287
|
+
secondaryFont: string | null;
|
|
288
|
+
scaleType: string;
|
|
289
|
+
sizes: string[];
|
|
290
|
+
weightRange: {
|
|
291
|
+
min: string;
|
|
292
|
+
max: string;
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
export interface TasteSpacingPreference {
|
|
296
|
+
baseUnit: number;
|
|
297
|
+
scale: number[];
|
|
298
|
+
gridAlignmentRatio: number;
|
|
299
|
+
}
|
|
300
|
+
export interface TasteMotionPreference {
|
|
301
|
+
easing: string;
|
|
302
|
+
durations: string[];
|
|
303
|
+
intensity: 'none' | 'subtle' | 'moderate' | 'expressive';
|
|
304
|
+
}
|
|
305
|
+
export interface TasteComponentPreference {
|
|
306
|
+
cherryPicks: ComponentCherryPick[];
|
|
307
|
+
borderRadius: string;
|
|
308
|
+
shadowStyle: 'none' | 'subtle' | 'elevated' | 'dramatic';
|
|
309
|
+
}
|
|
310
|
+
export interface ComponentCherryPick {
|
|
311
|
+
componentKind: string;
|
|
312
|
+
sourceInspirationId: string;
|
|
313
|
+
sourceUrl: string;
|
|
314
|
+
tokens: ComponentToken[];
|
|
315
|
+
styles: Record<string, string>;
|
|
316
|
+
note?: string;
|
|
317
|
+
}
|
|
318
|
+
export interface TasteDecision {
|
|
319
|
+
id: string;
|
|
320
|
+
question: string;
|
|
321
|
+
answer: string;
|
|
322
|
+
dimension: 'color' | 'typography' | 'spacing' | 'motion' | 'component' | 'layout' | 'general';
|
|
323
|
+
decidedAt: string;
|
|
324
|
+
}
|
|
325
|
+
export interface TasteConflict {
|
|
326
|
+
dimension: string;
|
|
327
|
+
description: string;
|
|
328
|
+
options: string[];
|
|
329
|
+
resolved: boolean;
|
|
330
|
+
resolvedByDecisionId?: string;
|
|
331
|
+
}
|
|
332
|
+
export interface TasteProfile {
|
|
333
|
+
id: string;
|
|
334
|
+
name: string;
|
|
335
|
+
version: number;
|
|
336
|
+
sourceInspirationIds: string[];
|
|
337
|
+
sourceUrls: string[];
|
|
338
|
+
persona: PersonaMatch;
|
|
339
|
+
aggregateScore: ScanScore;
|
|
340
|
+
color: TasteColorPreference;
|
|
341
|
+
typography: TasteTypographyPreference;
|
|
342
|
+
spacing: TasteSpacingPreference;
|
|
343
|
+
motion: TasteMotionPreference;
|
|
344
|
+
components: TasteComponentPreference;
|
|
345
|
+
decisions: TasteDecision[];
|
|
346
|
+
conflicts: TasteConflict[];
|
|
347
|
+
narrative?: string;
|
|
348
|
+
principles?: string[];
|
|
349
|
+
createdAt: string;
|
|
350
|
+
updatedAt: string;
|
|
351
|
+
}
|
|
352
|
+
export interface TasteDiffResult {
|
|
353
|
+
alignment: number;
|
|
354
|
+
dimensions: {
|
|
355
|
+
color: TasteDimensionDiff;
|
|
356
|
+
typography: TasteDimensionDiff;
|
|
357
|
+
spacing: TasteDimensionDiff;
|
|
358
|
+
motion: TasteDimensionDiff;
|
|
359
|
+
};
|
|
360
|
+
deltas: TasteDelta[];
|
|
361
|
+
}
|
|
362
|
+
export interface TasteDimensionDiff {
|
|
363
|
+
alignment: number;
|
|
364
|
+
summary: string;
|
|
365
|
+
}
|
|
366
|
+
export interface TasteDelta {
|
|
367
|
+
dimension: string;
|
|
368
|
+
issue: string;
|
|
369
|
+
suggestion: string;
|
|
370
|
+
severity: 'info' | 'warning' | 'mismatch';
|
|
371
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "design-brain-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Relational markdown design memory powered by Agent Browser CLI",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"main": "./dist/index.js",
|
|
15
15
|
"bin": {
|
|
16
|
-
"design-brain-memory": "dist/cli.js"
|
|
16
|
+
"design-brain-memory": "./dist/cli.js"
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
|
-
"dist"
|
|
19
|
+
"dist",
|
|
20
|
+
"skills"
|
|
20
21
|
],
|
|
21
22
|
"scripts": {
|
|
22
23
|
"build": "tsc -p tsconfig.json",
|
package/skills/SKILL.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: design-brain
|
|
3
|
+
description: Design system awareness for AI coding agents. Provides design token guidance, component patterns, and design principles extracted from your codebase.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Design Brain Skill
|
|
7
|
+
|
|
8
|
+
You have access to design system knowledge extracted by design-brain-memory.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
- When writing UI components, reference the color palette and typography system
|
|
13
|
+
- When adding spacing/layout, follow the established spacing scale
|
|
14
|
+
- When creating new components, follow existing component patterns
|
|
15
|
+
- When adding transitions/animations, match existing motion patterns
|
|
16
|
+
|
|
17
|
+
## Design Principles
|
|
18
|
+
|
|
19
|
+
1. **Consistency** — Use existing design tokens rather than ad-hoc values
|
|
20
|
+
2. **Systematic spacing** — Prefer multiples of 4px (4, 8, 12, 16, 24, 32, 48, 64)
|
|
21
|
+
3. **Color discipline** — Use palette colors from CSS variables or design tokens
|
|
22
|
+
4. **Typography scale** — Stick to the established font size scale
|
|
23
|
+
5. **Motion restraint** — Use consistent transition timing across the UI
|
|
24
|
+
|
|
25
|
+
## Quick Reference
|
|
26
|
+
|
|
27
|
+
- Run `npx design-brain-memory scan` to see your design health score
|
|
28
|
+
- Run `npx design-brain-memory scan .` to scan the current directory
|
|
29
|
+
- Design tokens are extracted from CSS, SCSS, and component files
|
|
30
|
+
|
|
31
|
+
## Tips for Code Generation
|
|
32
|
+
|
|
33
|
+
- Prefer CSS custom properties (--var-name) over hardcoded values
|
|
34
|
+
- Use the project's existing color palette — don't introduce new colors without reason
|
|
35
|
+
- Match existing component patterns before creating new abstractions
|
|
36
|
+
- Keep transition durations consistent (prefer 150ms, 200ms, or 300ms)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: design-brain
|
|
3
|
+
description: Build and query a relational markdown design memory using design-brain-memory and Agent Browser CLI.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Design Brain
|
|
8
|
+
|
|
9
|
+
Capture inspiration from websites/screenshots and turn it into a searchable design memory.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
design-brain-memory install-skill
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
or
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx -y skills add design-brain/design-brain
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Workflow
|
|
24
|
+
|
|
25
|
+
1. Initialize:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
design-brain-memory init --root <workspace>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. Ingest inspiration:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
design-brain-memory ingest --project <id> --url <url> --name "<name>" --tags <tags>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
or
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
design-brain-memory ingest --project <id> --screenshot <path> --name "<name>" --tags <tags>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
3. Record outcome:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
design-brain-memory outcome --project <id> --title "<title>" --description "<what was built>" --inspired-by <inspiration-id>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
4. Query:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
design-brain-memory search --query "<keywords>"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
design-brain-memory ask --query "<question>" --llm-base-url <url> --llm-api-key <key> --llm-model <model>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Taste Profile Context
|
|
60
|
+
|
|
61
|
+
Before generating UI code, check for a taste profile:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
cat .design-brain/taste/*.json 2>/dev/null
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If a taste profile exists, all generated UI code should use:
|
|
68
|
+
- Colors from the palette (exact hex values)
|
|
69
|
+
- The specified font families
|
|
70
|
+
- Spacing values from the scale
|
|
71
|
+
- Motion parameters (durations, easing)
|
|
72
|
+
- Cherry-picked component styles where applicable
|
|
73
|
+
|
|
74
|
+
Commands:
|
|
75
|
+
- `design-brain-memory taste show --project <id> --json` to read taste JSON
|
|
76
|
+
- `design-brain-memory taste score . --project <id>` to check alignment
|
|
77
|
+
- `design-brain-memory taste generate --target tokens --project <id>` to emit CSS variables
|