@monoes/monodesign 1.0.1 → 1.1.1

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 (75) hide show
  1. package/NOTICE.md +7 -19
  2. package/cli/bin/cli.js +0 -1
  3. package/cli/bin/commands/ignores.mjs +0 -1
  4. package/cli/engine/browser/injected/index.mjs +0 -1
  5. package/cli/engine/cli/main.mjs +0 -1
  6. package/cli/engine/design-system.mjs +0 -1
  7. package/cli/engine/detect-antipatterns-browser.js +0 -1
  8. package/cli/engine/detect-antipatterns.mjs +0 -1
  9. package/cli/engine/engines/browser/detect-url.mjs +13 -32
  10. package/cli/engine/engines/browser/drivers.mjs +258 -0
  11. package/cli/engine/engines/regex/detect-text.mjs +0 -1
  12. package/cli/engine/engines/static-html/css-cascade.mjs +0 -1
  13. package/cli/engine/engines/static-html/detect-html.mjs +0 -1
  14. package/cli/engine/engines/visual/screenshot-contrast.mjs +9 -1
  15. package/cli/engine/findings.mjs +0 -1
  16. package/cli/engine/node/file-system.mjs +0 -1
  17. package/cli/engine/profile/profiler.mjs +0 -1
  18. package/cli/engine/registry/antipatterns.mjs +0 -1
  19. package/cli/engine/rules/checks.mjs +0 -1
  20. package/cli/engine/shared/color.mjs +0 -1
  21. package/cli/engine/shared/constants.mjs +0 -1
  22. package/cli/engine/shared/fonts.mjs +0 -1
  23. package/cli/engine/shared/inline-ignores.mjs +0 -1
  24. package/cli/engine/shared/page.mjs +0 -1
  25. package/cli/lib/monodesign-config.mjs +0 -1
  26. package/package.json +2 -1
  27. package/skill/SKILL.src.md +1 -1
  28. package/skill/scripts/context-signals.mjs +0 -1
  29. package/skill/scripts/context.mjs +0 -1
  30. package/skill/scripts/critique-storage.mjs +0 -1
  31. package/skill/scripts/detect-csp.mjs +0 -1
  32. package/skill/scripts/detect.mjs +0 -1
  33. package/skill/scripts/hook-admin.mjs +0 -1
  34. package/skill/scripts/hook-before-edit.mjs +0 -1
  35. package/skill/scripts/hook-lib.mjs +0 -1
  36. package/skill/scripts/hook.mjs +0 -1
  37. package/skill/scripts/lib/design-parser.mjs +0 -1
  38. package/skill/scripts/lib/is-generated.mjs +0 -1
  39. package/skill/scripts/lib/monodesign-paths.mjs +0 -1
  40. package/skill/scripts/lib/provider.mjs +0 -1
  41. package/skill/scripts/lib/target-args.mjs +0 -1
  42. package/skill/scripts/live/browser-script-parts.mjs +0 -1
  43. package/skill/scripts/live/completion.mjs +0 -1
  44. package/skill/scripts/live/event-validation.mjs +0 -1
  45. package/skill/scripts/live/insert-ui.mjs +0 -1
  46. package/skill/scripts/live/manual-apply.mjs +0 -1
  47. package/skill/scripts/live/manual-edit-routes.mjs +0 -1
  48. package/skill/scripts/live/manual-edits-buffer.mjs +0 -1
  49. package/skill/scripts/live/session-store.mjs +0 -1
  50. package/skill/scripts/live/svelte-component.mjs +0 -1
  51. package/skill/scripts/live/sveltekit-adapter.mjs +0 -1
  52. package/skill/scripts/live/ui-core.mjs +0 -1
  53. package/skill/scripts/live/vocabulary.mjs +0 -1
  54. package/skill/scripts/live-accept.mjs +0 -1
  55. package/skill/scripts/live-browser-dom.js +0 -1
  56. package/skill/scripts/live-browser-session.js +0 -1
  57. package/skill/scripts/live-browser.js +0 -1
  58. package/skill/scripts/live-commit-manual-edits.mjs +0 -1
  59. package/skill/scripts/live-complete.mjs +0 -1
  60. package/skill/scripts/live-copy-edit-agent.mjs +0 -1
  61. package/skill/scripts/live-discard-manual-edits.mjs +0 -1
  62. package/skill/scripts/live-inject.mjs +0 -1
  63. package/skill/scripts/live-insert.mjs +0 -1
  64. package/skill/scripts/live-manual-edit-evidence.mjs +0 -1
  65. package/skill/scripts/live-poll.mjs +0 -1
  66. package/skill/scripts/live-resume.mjs +0 -1
  67. package/skill/scripts/live-server.mjs +0 -1
  68. package/skill/scripts/live-status.mjs +0 -1
  69. package/skill/scripts/live-target.mjs +0 -1
  70. package/skill/scripts/live-wrap.mjs +0 -1
  71. package/skill/scripts/live.mjs +0 -1
  72. package/skill/scripts/modern-screenshot.umd.js +0 -1
  73. package/skill/scripts/palette.mjs +0 -1
  74. package/skill/scripts/pin.mjs +0 -1
  75. package/src/tokens.css +0 -1
package/NOTICE.md CHANGED
@@ -1,21 +1,9 @@
1
- # Third-Party Notices
1
+ # Notices
2
2
 
3
- This package includes content derived from third-party work, used under the terms of its original license.
3
+ Monodesign is an independent monomind project.
4
4
 
5
- ## Impeccable
6
-
7
- The design engine (`cli/engine/`, including the 46-rule antipattern registry and detector), the skill command references and router (`skill/SKILL.src.md`, `skill/reference/`, `skill/commands/`), the live browser iteration mode (`skill/scripts/live*.mjs` and helpers), the design-check hooks, and the sub-agent definitions (`skill/agents/`) are adapted from Paul Bakaus's Impeccable (v3.2.1), rebranded and integrated for monomind as monodesign.
8
-
9
- **Original work:** https://github.com/pbakaus/impeccable
10
- **Original license:** Apache-2.0
11
- **Author:** Paul Bakaus
12
-
13
- Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
14
-
15
- ## Platform Design Skills
16
-
17
- The `skill/reference/ios.md` and `skill/reference/android.md` platform reference files are distilled from ehmo's `platform-design-skills` (Apple Human Interface Guidelines and Material Design 3 rules), rewritten in the skill's voice.
18
-
19
- **Original work:** https://github.com/ehmo/platform-design-skills
20
- **Original license:** MIT
21
- **Author:** ehmo
5
+ Portions of this package incorporate code originally published under the
6
+ Apache License 2.0 by Paul Bakaus (impeccable, https://github.com/pbakaus/impeccable)
7
+ and guidance distilled from ehmo/platform-design-skills (MIT). This notice is
8
+ retained to satisfy those licenses; monodesign is developed and maintained
9
+ independently.
package/cli/bin/cli.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
 
4
3
  /**
5
4
  * Monodesign CLI
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import path from 'node:path';
3
2
 
4
3
  import {
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  const IS_BROWSER = typeof window !== 'undefined';
3
2
 
4
3
  // ─── Section 7: Browser UI (IS_BROWSER only) ────────────────────────────────
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import fs from 'node:fs';
3
2
  import path from 'node:path';
4
3
 
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import fs from 'node:fs';
3
2
  import path from 'node:path';
4
3
 
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Anti-Pattern Browser Detector for Monodesign
4
3
  * Copyright (c) 2026 Paul Bakaus
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
 
4
3
  /**
5
4
  * Anti-Pattern Detector for Monodesign
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import fs from 'node:fs';
3
2
  import path from 'node:path';
4
3
  import { fileURLToPath } from 'node:url';
@@ -7,6 +6,7 @@ import { finding } from '../../findings.mjs';
7
6
  import { filterByProviders } from '../../registry/antipatterns.mjs';
8
7
  import { profileFindingsAsync, profileStep, profileStepAsync } from '../../profile/profiler.mjs';
9
8
  import { captureVisualContrastCandidate } from '../visual/screenshot-contrast.mjs';
9
+ import { launchDetectionBrowser, normalizeBrowserHandle } from './drivers.mjs';
10
10
 
11
11
  function serializeDesignSystemForBrowser(designSystem) {
12
12
  if (!designSystem?.present) return null;
@@ -101,7 +101,8 @@ async function runVisualContrastFallback(page, serializedGroups, options, profil
101
101
  }
102
102
 
103
103
  // ---------------------------------------------------------------------------
104
- // Puppeteer detection (for URLs)
104
+ // Browser detection (for URLs) — driver-based: monobrowse (native CDP,
105
+ // preferred) with puppeteer as fallback. See ./drivers.mjs for the seam.
105
106
  // ---------------------------------------------------------------------------
106
107
 
107
108
  async function detectUrl(url, options = {}) {
@@ -109,20 +110,9 @@ async function detectUrl(url, options = {}) {
109
110
  const waitUntil = options?.waitUntil || 'networkidle0';
110
111
  const settleMs = Number.isFinite(options?.settleMs) ? options.settleMs : 0;
111
112
  const viewport = options?.viewport || { width: 1280, height: 800 };
112
- const externalBrowser = options?.browser || null;
113
- let puppeteer;
114
- if (!externalBrowser) {
115
- try {
116
- puppeteer = await profileStepAsync(profile, {
117
- engine: 'browser',
118
- phase: 'setup',
119
- ruleId: 'import-puppeteer',
120
- target: url,
121
- }, () => import('puppeteer'));
122
- } catch {
123
- throw new Error('puppeteer is required for URL scanning. Install: npm install puppeteer');
124
- }
125
- }
113
+ // options.browser accepts either a driver handle (from createBrowserDetector)
114
+ // or a raw puppeteer Browser (legacy callers).
115
+ const externalBrowser = options?.browser ? normalizeBrowserHandle(options.browser) : null;
126
116
 
127
117
  // Read the browser detection script — reuse it instead of reimplementing
128
118
  const browserScriptPath = path.resolve(
@@ -143,16 +133,12 @@ async function detectUrl(url, options = {}) {
143
133
  throw new Error(`Browser script not found at ${browserScriptPath}`);
144
134
  }
145
135
 
146
- // CI runners (GitHub Actions Ubuntu) block unprivileged user namespaces, so
147
- // Chrome can't initialize its sandbox there. Disable the sandbox only when
148
- // running in CI; local users keep the default hardened launch.
149
- const launchArgs = process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [];
150
136
  const browser = externalBrowser || await profileStepAsync(profile, {
151
137
  engine: 'browser',
152
138
  phase: 'load',
153
139
  ruleId: 'launch-browser',
154
140
  target: url,
155
- }, () => puppeteer.default.launch({ headless: true, args: launchArgs }));
141
+ }, () => launchDetectionBrowser({ launchArgs: options?.launchArgs, headless: true }));
156
142
  const page = await profileStepAsync(profile, {
157
143
  engine: 'browser',
158
144
  phase: 'load',
@@ -243,17 +229,12 @@ async function detectUrl(url, options = {}) {
243
229
  }
244
230
 
245
231
  async function createBrowserDetector(options = {}) {
246
- let puppeteer;
247
- try {
248
- puppeteer = await import('puppeteer');
249
- } catch {
250
- throw new Error('puppeteer is required for URL scanning. Install: npm install puppeteer');
251
- }
252
- const launchArgs = options.launchArgs || (process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : []);
253
- const browser = options.browser || await puppeteer.default.launch({
254
- headless: options.headless ?? true,
255
- args: launchArgs,
256
- });
232
+ // Pooled reuse for multiple URLs. `browser` is a driver handle (monobrowse
233
+ // or puppeteer); pass a raw puppeteer Browser via options.browser to reuse
234
+ // an externally managed instance.
235
+ const browser = options.browser
236
+ ? normalizeBrowserHandle(options.browser)
237
+ : await launchDetectionBrowser({ launchArgs: options.launchArgs, headless: options.headless ?? true });
257
238
  const ownsBrowser = !options.browser;
258
239
  const defaults = {
259
240
  waitUntil: options.waitUntil || 'load',
@@ -0,0 +1,258 @@
1
+ //
2
+ // Browser driver seam for URL detection.
3
+ //
4
+ // The detection engine (detect-url.mjs) and the visual-contrast fallback
5
+ // (engines/visual/screenshot-contrast.mjs) only need a tiny, puppeteer-shaped
6
+ // page surface:
7
+ //
8
+ // browser: { driverName, newPage(): Promise<page>, close(): Promise<void> }
9
+ // page: {
10
+ // setViewport({ width, height })
11
+ // goto(url, { waitUntil, timeout })
12
+ // evaluate(fnOrExpressionString, ...serializableArgs) -> serializable value
13
+ // screenshot({ encoding: 'base64', clip?, captureBeyondViewport? }) -> base64 string
14
+ // close()
15
+ // }
16
+ //
17
+ // Two drivers implement it:
18
+ // - monobrowse (preferred): @monoes/monobrowse, the monorepo's native CDP
19
+ // client. Drives a locally installed Chrome/Edge/Chromium — no bundled
20
+ // Chromium download needed. Supports the full surface including clip
21
+ // screenshots (Page.captureScreenshot) and evaluate roundtrips
22
+ // (Runtime.evaluate), so the visual-contrast pixel fallback runs under it.
23
+ // - puppeteer (fallback): the original optionalDependency path, kept for
24
+ // environments that have puppeteer + its downloaded Chrome but no system
25
+ // browser. A raw puppeteer Page already satisfies the page surface, so the
26
+ // puppeteer driver returns real puppeteer pages untouched.
27
+ //
28
+ // Selection: MONODESIGN_BROWSER_DRIVER=monobrowse|puppeteer forces one;
29
+ // otherwise monobrowse is tried first and puppeteer is the fallback.
30
+
31
+ const DRIVER_ENV = 'MONODESIGN_BROWSER_DRIVER';
32
+
33
+ // Ports we launch headless Chrome on for detection runs. Deliberately away
34
+ // from 9222 (the default `monomind browse` port): we must never attach to a
35
+ // browser we did not launch, because browser.close() issues Browser.close.
36
+ const MONOBROWSE_PORT_MIN = 9520;
37
+ const MONOBROWSE_PORT_RANGE = 380;
38
+
39
+ function serializeEvalArg(arg) {
40
+ const json = JSON.stringify(arg);
41
+ return json === undefined ? 'undefined' : json;
42
+ }
43
+
44
+ function mapWaitUntil(waitUntil) {
45
+ if (waitUntil === 'domcontentloaded') return 'domcontentloaded';
46
+ if (waitUntil === 'networkidle0' || waitUntil === 'networkidle2' || waitUntil === 'networkidle') return 'networkidle';
47
+ return 'load';
48
+ }
49
+
50
+ function withTimeout(promise, ms, label) {
51
+ let handle;
52
+ const timeout = new Promise((_, reject) => {
53
+ handle = setTimeout(() => reject(new Error(`Timeout waiting for ${label} after ${ms}ms`)), ms);
54
+ handle.unref?.();
55
+ });
56
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(handle));
57
+ }
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // monobrowse driver
61
+ // ---------------------------------------------------------------------------
62
+
63
+ function createMonobrowsePage(mb, client, sessionId, targetId, port) {
64
+ return {
65
+ async setViewport({ width, height }) {
66
+ await mb.setViewport(client, sessionId, width, height);
67
+ },
68
+ async goto(url, { waitUntil = 'load', timeout = 30000 } = {}) {
69
+ const condition = mapWaitUntil(waitUntil);
70
+ if (condition === 'networkidle') {
71
+ const nav = await client.send('Page.navigate', { url }, sessionId);
72
+ if (nav?.errorText) throw new Error(`Navigation to ${url} failed: ${nav.errorText}`);
73
+ await mb.waitForLoad(client, sessionId, 'networkidle', timeout);
74
+ return;
75
+ }
76
+ // Register the lifecycle listener BEFORE navigating so a fast page load
77
+ // cannot slip past it (waitForLoad's readyState pre-check would otherwise
78
+ // race against the previous document's readyState).
79
+ const event = condition === 'load' ? 'Page.loadEventFired' : 'Page.domContentEventFired';
80
+ const [eventPromise, cancel] = client.onceWithOff(event, sessionId);
81
+ try {
82
+ const nav = await client.send('Page.navigate', { url }, sessionId);
83
+ if (nav?.errorText) throw new Error(`Navigation to ${url} failed: ${nav.errorText}`);
84
+ await withTimeout(eventPromise, timeout, condition);
85
+ } finally {
86
+ cancel();
87
+ }
88
+ },
89
+ async evaluate(fnOrExpression, ...args) {
90
+ const expression = typeof fnOrExpression === 'function'
91
+ ? `(${String(fnOrExpression)})(${args.map(serializeEvalArg).join(', ')})`
92
+ : String(fnOrExpression);
93
+ const result = await client.send('Runtime.evaluate', {
94
+ expression,
95
+ returnByValue: true,
96
+ awaitPromise: true,
97
+ }, sessionId);
98
+ if (result?.exceptionDetails) {
99
+ const details = result.exceptionDetails;
100
+ throw new Error(details.exception?.description || details.exception?.value || details.text || 'Evaluation failed');
101
+ }
102
+ return result?.result?.value;
103
+ },
104
+ // Always returns a base64 string (the only mode the engine uses).
105
+ async screenshot({ clip } = {}) {
106
+ const params = { format: 'png' };
107
+ if (clip) {
108
+ params.clip = { x: clip.x, y: clip.y, width: clip.width, height: clip.height, scale: 1 };
109
+ params.captureBeyondViewport = true;
110
+ }
111
+ const result = await client.send('Page.captureScreenshot', params, sessionId);
112
+ return result?.data;
113
+ },
114
+ async close() {
115
+ // HTTP endpoint works regardless of which domains the ws connection has.
116
+ try {
117
+ await fetch(`http://127.0.0.1:${port}/json/close/${encodeURIComponent(targetId)}`);
118
+ } catch {
119
+ // Tab may already be gone; browser close cleans up anything left.
120
+ }
121
+ client.close();
122
+ },
123
+ };
124
+ }
125
+
126
+ async function launchMonobrowseBrowser(options = {}) {
127
+ const mb = await import('@monoes/monobrowse');
128
+ const headless = options.headless ?? true;
129
+ const launchArgs = options.launchArgs || (process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : []);
130
+
131
+ const forcedPort = Number(process.env.MONODESIGN_MONOBROWSE_PORT);
132
+ let port = null;
133
+ if (Number.isInteger(forcedPort) && forcedPort > 0) {
134
+ port = forcedPort;
135
+ } else {
136
+ // Pick a free-looking port; skip ports where a CDP endpoint already
137
+ // listens so we never take over (and later Browser.close) someone
138
+ // else's browser session.
139
+ for (let attempt = 0; attempt < 5 && port === null; attempt++) {
140
+ const candidate = MONOBROWSE_PORT_MIN + Math.floor(Math.random() * MONOBROWSE_PORT_RANGE);
141
+ if (!(await mb.isPortOpen(candidate))) port = candidate;
142
+ }
143
+ if (port === null) throw new Error('Could not find a free CDP port for monobrowse');
144
+ }
145
+
146
+ const launchedPort = await mb.launchBrowser({ port, headless, args: launchArgs });
147
+ // Control connection (to the initial about:blank tab) — used for lifecycle
148
+ // commands; detection pages get their own dedicated connections.
149
+ const control = await mb.connectToTarget(launchedPort);
150
+
151
+ async function openPage() {
152
+ const target = await mb.fetchNewTarget(launchedPort, 'about:blank');
153
+ const wsUrl = target.webSocketDebuggerUrl || `ws://127.0.0.1:${launchedPort}/devtools/page/${target.id}`;
154
+ const client = new mb.CdpClient();
155
+ await client.connect(wsUrl);
156
+ const { sessionId } = await client.send('Target.attachToTarget', { targetId: target.id, flatten: true });
157
+ await Promise.all([
158
+ client.send('Page.enable', {}, sessionId),
159
+ client.send('Runtime.enable', {}, sessionId),
160
+ client.send('Network.enable', {}, sessionId),
161
+ ]);
162
+ return createMonobrowsePage(mb, client, sessionId, target.id, launchedPort);
163
+ }
164
+
165
+ return {
166
+ driverName: 'monobrowse',
167
+ newPage: openPage,
168
+ async close() {
169
+ try {
170
+ if (typeof mb.closeBrowser === 'function') {
171
+ // Newer monobrowse: graceful Browser.close with PID-kill fallback.
172
+ await mb.closeBrowser(control.client, launchedPort);
173
+ } else {
174
+ // Published 1.0.3 has no closeBrowser export — send the CDP
175
+ // Browser.close command directly (best effort, short timeout).
176
+ await withTimeout(control.client.send('Browser.close', {}), 3000, 'Browser.close').catch(() => {});
177
+ }
178
+ } finally {
179
+ control.client.close();
180
+ }
181
+ },
182
+ };
183
+ }
184
+
185
+ // ---------------------------------------------------------------------------
186
+ // puppeteer driver (original behavior, kept as fallback)
187
+ // ---------------------------------------------------------------------------
188
+
189
+ function wrapPuppeteerBrowser(browser) {
190
+ return {
191
+ driverName: 'puppeteer',
192
+ raw: browser,
193
+ newPage: () => browser.newPage(),
194
+ close: () => browser.close(),
195
+ };
196
+ }
197
+
198
+ async function launchPuppeteerBrowser(options = {}) {
199
+ let puppeteer;
200
+ try {
201
+ puppeteer = await import('puppeteer');
202
+ } catch {
203
+ throw new Error('puppeteer is required for URL scanning. Install: npm install puppeteer');
204
+ }
205
+ // CI runners (GitHub Actions Ubuntu) block unprivileged user namespaces, so
206
+ // Chrome can't initialize its sandbox there. Disable the sandbox only when
207
+ // running in CI; local users keep the default hardened launch.
208
+ const launchArgs = options.launchArgs || (process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : []);
209
+ const browser = await puppeteer.default.launch({
210
+ headless: options.headless ?? true,
211
+ args: launchArgs,
212
+ });
213
+ return wrapPuppeteerBrowser(browser);
214
+ }
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // Selection
218
+ // ---------------------------------------------------------------------------
219
+
220
+ /** Accepts either a driver browser handle or a raw puppeteer Browser. */
221
+ function normalizeBrowserHandle(browser) {
222
+ if (!browser) return null;
223
+ if (browser.driverName && typeof browser.newPage === 'function') return browser;
224
+ if (typeof browser.newPage === 'function') return wrapPuppeteerBrowser(browser);
225
+ throw new Error('Unsupported browser handle passed to detectUrl (expected a driver handle or puppeteer Browser)');
226
+ }
227
+
228
+ async function launchDetectionBrowser(options = {}) {
229
+ const forced = (process.env[DRIVER_ENV] || '').trim().toLowerCase();
230
+ if (forced && forced !== 'monobrowse' && forced !== 'puppeteer') {
231
+ throw new Error(`Invalid ${DRIVER_ENV}=${forced} (expected "monobrowse" or "puppeteer")`);
232
+ }
233
+ const order = forced ? [forced] : ['monobrowse', 'puppeteer'];
234
+ const errors = [];
235
+ for (const name of order) {
236
+ try {
237
+ return name === 'monobrowse'
238
+ ? await launchMonobrowseBrowser(options)
239
+ : await launchPuppeteerBrowser(options);
240
+ } catch (error) {
241
+ errors.push(`${name}: ${error?.message || error}`);
242
+ }
243
+ }
244
+ throw new Error(
245
+ 'A browser is required for URL scanning and no driver could start one ' +
246
+ `(${errors.join(' | ')}). ` +
247
+ 'Install Google Chrome/Chromium/Edge for the native @monoes/monobrowse driver, ' +
248
+ 'or puppeteer is required for URL scanning. Install: npm install puppeteer'
249
+ );
250
+ }
251
+
252
+ export {
253
+ launchDetectionBrowser,
254
+ launchMonobrowseBrowser,
255
+ launchPuppeteerBrowser,
256
+ normalizeBrowserHandle,
257
+ wrapPuppeteerBrowser,
258
+ };
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
3
2
  import { isNeutralColor } from '../../shared/color.mjs';
4
3
  import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import fs from 'node:fs';
3
2
  import path from 'node:path';
4
3
 
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import fs from 'node:fs';
3
2
  import path from 'node:path';
4
3
 
@@ -1,4 +1,12 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
1
+ //
2
+ // Visual-contrast pixel fallback. `page` is the driver page abstraction from
3
+ // ../browser/drivers.mjs (a raw puppeteer Page also satisfies it): it only
4
+ // needs `evaluate(fn, arg)` roundtrips and `screenshot({ encoding: 'base64',
5
+ // clip, captureBeyondViewport })` returning a base64 PNG string. The logic is
6
+ // driver-agnostic: hide the glyphs via CSS, screenshot the clip twice, and
7
+ // diff the pixels in-page on a canvas. Both the monobrowse (CDP
8
+ // Page.captureScreenshot + Runtime.evaluate) and puppeteer drivers support
9
+ // this fully.
2
10
  function sanitizeScreenshotClip(clip, viewport) {
3
11
  if (!clip) return null;
4
12
  const x = Math.max(0, Math.floor(clip.x || 0));
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import { getAntipattern } from './registry/antipatterns.mjs';
3
2
 
4
3
  function getAP(id) {
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import fs from 'node:fs';
3
2
  import path from 'node:path';
4
3
 
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  function profileNow() {
3
2
  return typeof performance !== 'undefined' && performance.now
4
3
  ? performance.now()
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  const ANTIPATTERNS = [
3
2
  // ── AI slop: tells that something was AI-generated ──
4
3
  {
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import {
3
2
  BORDER_SAFE_TAGS,
4
3
  GENERIC_FONTS,
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  // ─── Section 2: Color Utilities ─────────────────────────────────────────────
3
2
 
4
3
  function isNeutralColor(color) {
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  // ─── Section 1: Constants ───────────────────────────────────────────────────
3
2
 
4
3
  const SAFE_TAGS = new Set([
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  const GOOGLE_FONTS_URL_RE = /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi;
3
2
 
4
3
  function normalizeGoogleFontFamilyParam(value) {
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Inline, in-file ignore directives — eslint-disable-style waivers that live at
4
3
  * the point they apply and travel with the artifact instead of (or alongside)
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /** Check if content looks like a full page (not a component/partial) */
3
2
  function isFullPage(content) {
4
3
  const stripped = content.replace(/<!--[\s\S]*?-->/g, '');
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * CLI-side reader/writer for the unified `.monodesign` config.
4
3
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monoes/monodesign",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Frontend design intelligence for AI agents \u2014 design tokens, antipattern detection, and the monodesign skill",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -45,6 +45,7 @@
45
45
  "marked": "^18.0.0"
46
46
  },
47
47
  "optionalDependencies": {
48
+ "@monoes/monobrowse": "^1.0.3",
48
49
  "puppeteer": "^25.1.0"
49
50
  }
50
51
  }
@@ -56,7 +56,7 @@ allowed-tools:
56
56
  - Bash
57
57
  - WebSearch
58
58
  - WebFetch
59
- license: "Apache 2.0 — Pattern adapted from impeccable (Apache-2.0) and Anthropic's frontend-design skill; see NOTICE.md"
59
+ license: Apache-2.0
60
60
  ---
61
61
 
62
62
  Designs and iterates production-grade frontend interfaces. Real working code, committed design choices, exceptional craft.
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * Context-signals gatherer for the bare Monodesign invocation
5
4
  * (no-argument) path. Collects cheap, deterministic signals about the current
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Context loader: prints PRODUCT.md (and DESIGN.md if present) as one
4
3
  * markdown block on stdout, or prints a `NO_PRODUCT_MD:` message when no
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * Critique persistence helper.
5
4
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Scan a project tree for Content-Security-Policy signals and classify the
4
3
  * shape so the agent knows which patch template to propose.
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
 
4
3
  import fs from 'node:fs';
5
4
  import path from 'node:path';
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * The Monodesign hooks command manages the design hook runtime
5
4
  * via the `hook` key and shared detector ignores via the `detector` key in
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * Monodesign design hook — Cursor preToolUse write gate.
5
4
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Shared library for the Monodesign design hook.
4
3
  *
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * Monodesign design hook — PostToolUse entry point.
5
4
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  // Parse a DESIGN.md (Stitch-spec format) into a structured JSON model that
3
2
  // the live-mode design-system panel can render. Deterministic, dependency-free.
4
3
  //
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Decide whether a given file is "generated" (regenerated by a build step,
4
3
  * unsafe to write variants into) or "source" (safe to edit, changes persist).
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import fs from 'node:fs';
3
2
  import path from 'node:path';
4
3
  import { resolveProjectRoot } from '../context.mjs';
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  // Source scripts default to slash commands. The provider build replaces only
3
2
  // this exact declaration, avoiding heuristic rewrites across executable code.
4
3
  export const MONODESIGN_COMMAND_PREFIX = '/'; // @monodesign-provider-command-prefix
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  class TargetArgError extends Error {
3
2
  constructor(message, code) {
4
3
  super(message);
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import fs from 'node:fs';
3
2
  import path from 'node:path';
4
3
 
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  export function completionTypeForAcceptResult(eventType, acceptResult) {
3
2
  if (eventType === 'discard') return acceptResult?.handled === true ? 'discarded' : 'error';
4
3
  if (acceptResult?.handled === true && acceptResult?.carbonize === true) return 'agent_done';
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Shared event validation for the live helper server.
4
3
  * Extracted for unit testing (insert mode rules).
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Pure helpers for live-mode insert UI (browser + tests).
4
3
  * Kept separate from live-browser.js so insert logic is unit-testable.
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import { randomUUID } from 'node:crypto';
3
2
  import fs from 'node:fs';
4
3
  import path from 'node:path';
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import { validateEvent } from './event-validation.mjs';
3
2
  import {
4
3
  countByPage as countPendingByPage,
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Shared helpers for the pending-manual-edits buffer on disk.
4
3
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import fs from 'node:fs';
3
2
  import path from 'node:path';
4
3
  import { getLegacyLiveSessionsDir, getLiveSessionsDir } from '../lib/monodesign-paths.mjs';
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Svelte live-mode component injection helpers.
4
3
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * SvelteKit live-mode adapter.
4
3
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Framework-neutral Monodesign live chrome contract.
4
3
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Canonical design-command vocabulary for Live Mode: each command's value, human
4
3
  * label, and SVG icon. Icons stack above the chip label; strokes use currentColor
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * CLI helper: deterministic accept/discard of variant sessions.
4
3
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Browser-side DOM helpers for Monodesign live mode.
4
3
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Browser-side durable session helpers for Monodesign live mode.
4
3
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * Monodesign Live Variant Mode - Browser Script
4
3
  *
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * CLI helper: apply pending live copy edits as one AI-owned batch.
5
4
  *
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * Canonical durable completion acknowledgement for Monodesign live sessions.
5
4
  */
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * Applies staged live copy-edit batches by waking a local AI coding agent.
5
4
  *
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * CLI helper: discard pending manual edits from the buffer without applying.
5
4
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * CLI helper: insert/remove the live variant mode script tag in the project's
4
3
  * main HTML entry point.
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * CLI helper: find an anchor element in source and splice an insert-variant
4
3
  * wrapper before or after it (no original variant — net-new content).
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * Collect evidence for pending live copy edits.
5
4
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * CLI client for the live variant mode poll/reply protocol.
4
3
  *
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * Recover the next agent action from the durable live-session journal.
5
4
  */
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * Live variant mode server (self-contained, zero dependencies).
5
4
  *
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * Print durable recovery status for Monodesign live sessions.
5
4
  */
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  import path from 'node:path';
3
2
  import { resolveProjectRoot } from './context.mjs';
4
3
  import { parseTargetPath } from './lib/target-args.mjs';
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * CLI helper: find an element in source and wrap it in a variant container.
4
3
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  /**
3
2
  * CLI entry point: prepare everything needed to enter the live variant poll loop.
4
3
  *
@@ -1,4 +1,3 @@
1
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
2
1
  (function(y,v){typeof exports=="object"&&typeof module!="undefined"?v(exports):typeof define=="function"&&define.amd?define(["exports"],v):(y=typeof globalThis!="undefined"?globalThis:y||self,v(y.modernScreenshot={}))})(this,function(y){"use strict";var rr=Object.defineProperty,nr=Object.defineProperties;var or=Object.getOwnPropertyDescriptors;var Z=Object.getOwnPropertySymbols;var xe=Object.prototype.hasOwnProperty,Me=Object.prototype.propertyIsEnumerable;var Oe=Math.pow,Le=(y,v,N)=>v in y?rr(y,v,{enumerable:!0,configurable:!0,writable:!0,value:N}):y[v]=N,D=(y,v)=>{for(var N in v||(v={}))xe.call(v,N)&&Le(y,N,v[N]);if(Z)for(var N of Z(v))Me.call(v,N)&&Le(y,N,v[N]);return y},M=(y,v)=>nr(y,or(v));var je=(y,v)=>{var N={};for(var R in y)xe.call(y,R)&&v.indexOf(R)<0&&(N[R]=y[R]);if(y!=null&&Z)for(var R of Z(y))v.indexOf(R)<0&&Me.call(y,R)&&(N[R]=y[R]);return N};var C=(y,v,N)=>new Promise((R,O)=>{var X=P=>{try{q(N.next(P))}catch(W){O(W)}},j=P=>{try{q(N.throw(P))}catch(W){O(W)}},q=P=>P.done?R(P.value):Promise.resolve(P.value).then(X,j);q((N=N.apply(y,v)).next())});var Be;function v(e,t){return e[13]=1,e[14]=t>>8,e[15]=t&255,e[16]=t>>8,e[17]=t&255,e}const N=112,R=72,O=89,X=115;let j;function q(){const e=new Int32Array(256);for(let t=0;t<256;t++){let r=t;for(let n=0;n<8;n++)r=r&1?3988292384^r>>>1:r>>>1;e[t]=r}return e}function P(e){let t=-1;j||(j=q());for(let r=0;r<e.length;r++)t=j[(t^e[r])&255]^t>>>8;return t^-1}function W(e){const t=e.length-1;for(let r=t;r>=4;r--)if(e[r-4]===9&&e[r-3]===N&&e[r-2]===R&&e[r-1]===O&&e[r]===X)return r-3;return 0}function ae(e,t,r=!1){const n=new Uint8Array(13);t*=39.3701,n[0]=N,n[1]=R,n[2]=O,n[3]=X,n[4]=t>>>24,n[5]=t>>>16,n[6]=t>>>8,n[7]=t&255,n[8]=n[4],n[9]=n[5],n[10]=n[6],n[11]=n[7],n[12]=1;const i=P(n),a=new Uint8Array(4);if(a[0]=i>>>24,a[1]=i>>>16,a[2]=i>>>8,a[3]=i&255,r){const s=W(e);return e.set(n,s),e.set(a,s+13),e}else{const s=new Uint8Array(4);s[0]=0,s[1]=0,s[2]=0,s[3]=9;const o=new Uint8Array(54);return o.set(e,0),o.set(s,33),o.set(n,37),o.set(a,50),o}}const qe="AAlwSFlz",We="AAAJcEhZ",He="AAAACXBI";function Ve(e){let t=e.indexOf(qe);return t===-1&&(t=e.indexOf(We)),t===-1&&(t=e.indexOf(He)),t}const se="[modern-screenshot]",U=typeof window!="undefined",ze=U&&"Worker"in window,ie=U&&"atob"in window,Xe=U&&"btoa"in window,ee=U?(Be=window.navigator)==null?void 0:Be.userAgent:"",le=ee.includes("Chrome"),G=ee.includes("AppleWebKit")&&!le,te=ee.includes("Firefox"),Ge=e=>e&&"__CONTEXT__"in e,Ye=e=>e.constructor.name==="CSSFontFaceRule",Je=e=>e.constructor.name==="CSSImportRule",Ke=e=>e.constructor.name==="CSSLayerBlockRule",I=e=>e.nodeType===1,H=e=>typeof e.className=="object",ce=e=>e.tagName==="image",Qe=e=>e.tagName==="use",V=e=>I(e)&&typeof e.style!="undefined"&&!H(e),Ze=e=>e.nodeType===8,et=e=>e.nodeType===3,$=e=>e.tagName==="IMG",Y=e=>e.tagName==="VIDEO",tt=e=>e.tagName==="CANVAS",rt=e=>e.tagName==="TEXTAREA",nt=e=>e.tagName==="INPUT",ot=e=>e.tagName==="STYLE",at=e=>e.tagName==="SCRIPT",st=e=>e.tagName==="SELECT",it=e=>e.tagName==="SLOT",lt=e=>e.tagName==="IFRAME",ct=(...e)=>console.warn(se,...e);function ut(e){var r;const t=(r=e==null?void 0:e.createElement)==null?void 0:r.call(e,"canvas");return t&&(t.height=t.width=1),!!t&&"toDataURL"in t&&!!t.toDataURL("image/webp").includes("image/webp")}const re=e=>e.startsWith("data:");function ue(e,t){if(e.match(/^[a-z]+:\/\//i))return e;if(U&&e.match(/^\/\//))return window.location.protocol+e;if(e.match(/^[a-z]+:/i)||!U)return e;const r=J().implementation.createHTMLDocument(),n=r.createElement("base"),i=r.createElement("a");return r.head.appendChild(n),r.body.appendChild(i),t&&(n.href=t),i.href=e,i.href}function J(e){var t;return(t=e&&I(e)?e==null?void 0:e.ownerDocument:e)!=null?t:window.document}const K="http://www.w3.org/2000/svg";function fe(e,t,r){const n=J(r).createElementNS(K,"svg");return n.setAttributeNS(null,"width",e.toString()),n.setAttributeNS(null,"height",t.toString()),n.setAttributeNS(null,"viewBox",`0 0 ${e} ${t}`),n}function de(e,t){let r=new XMLSerializer().serializeToString(e);return t&&(r=r.replace(/[\u0000-\u0008\v\f\u000E-\u001F\uD800-\uDFFF\uFFFE\uFFFF]/gu,"")),`data:image/svg+xml;charset=utf-8,${encodeURIComponent(r)}`}function ft(e,t="image/png",r=1){return C(this,null,function*(){try{return yield new Promise((n,i)=>{e.toBlob(a=>{a?n(a):i(new Error("Blob is null"))},t,r)})}catch(n){if(ie)return dt(e.toDataURL(t,r));throw n}})}function dt(e){var o,c;const[t,r]=e.split(","),n=(c=(o=t.match(/data:(.+);/))==null?void 0:o[1])!=null?c:void 0,i=window.atob(r),a=i.length,s=new Uint8Array(a);for(let u=0;u<a;u+=1)s[u]=i.charCodeAt(u);return new Blob([s],{type:n})}function ge(e,t){return new Promise((r,n)=>{const i=new FileReader;i.onload=()=>r(i.result),i.onerror=()=>n(i.error),i.onabort=()=>n(new Error(`Failed read blob to ${t}`)),t==="dataUrl"?i.readAsDataURL(e):t==="arrayBuffer"&&i.readAsArrayBuffer(e)})}const gt=e=>ge(e,"dataUrl"),mt=e=>ge(e,"arrayBuffer");function _(e,t){const r=J(t).createElement("img");return r.decoding="sync",r.loading="eager",r.src=e,r}function L(e,t){return new Promise(r=>{const{timeout:n,ownerDocument:i,onError:a,onWarn:s}=t!=null?t:{},o=typeof e=="string"?_(e,J(i)):e;let c=null,u=null;function l(){r(o),c&&clearTimeout(c),u==null||u()}if(n&&(c=setTimeout(l,n)),Y(o)){const d=o.currentSrc||o.src;if(!d)return o.poster?L(o.poster,t).then(r):l();if(o.readyState>=2)return l();const m=l,f=h=>{s==null||s("Failed video load",d,h),a==null||a(h),l()};u=()=>{o.removeEventListener("loadeddata",m),o.removeEventListener("error",f)},o.addEventListener("loadeddata",m,{once:!0}),o.addEventListener("error",f,{once:!0})}else{const d=ce(o)?o.href.baseVal:o.currentSrc||o.src;if(!d)return l();const m=()=>C(this,null,function*(){if($(o)&&"decode"in o)try{yield o.decode()}catch(h){s==null||s("Failed to decode image, trying to render anyway",o.dataset.originalSrc||d,h)}l()}),f=h=>{s==null||s("Failed image load",o.dataset.originalSrc||d,h),l()};if($(o)&&o.complete)return m();u=()=>{o.removeEventListener("load",m),o.removeEventListener("error",f)},o.addEventListener("load",m,{once:!0}),o.addEventListener("error",f,{once:!0})}})}function me(e,t){return C(this,null,function*(){V(e)&&($(e)||Y(e)?yield L(e,t):yield Promise.all(["img","video"].flatMap(r=>Array.from(e.querySelectorAll(r)).map(n=>L(n,t)))))})}const he=function(){let t=0;const r=()=>`0000${(Math.random()*Oe(36,4)<<0).toString(36)}`.slice(-4);return()=>(t+=1,`u${r()}${t}`)}();function we(e){return e==null?void 0:e.split(",").map(t=>t.trim().replace(/"|'/g,"").toLowerCase()).filter(Boolean)}let pe=0;function ht(e){const t=`${se}[#${pe}]`;return pe++,{time:r=>e&&console.time(`${t} ${r}`),timeEnd:r=>e&&console.timeEnd(`${t} ${r}`),warn:(...r)=>e&&ct(...r)}}function wt(e){return{cache:e?"no-cache":"force-cache"}}function k(e,t){return C(this,null,function*(){return Ge(e)?e:ye(e,M(D({},t),{autoDestruct:!0}))})}function ye(e,t){return C(this,null,function*(){var f,h,g,p,E;const{scale:r=1,workerUrl:n,workerNumber:i=1}=t||{},a=!!(t!=null&&t.debug),s=(f=t==null?void 0:t.features)!=null?f:!0,o=(h=e.ownerDocument)!=null?h:U?window.document:void 0,c=(p=(g=e.ownerDocument)==null?void 0:g.defaultView)!=null?p:U?window:void 0,u=new Map,l=M(D({width:0,height:0,quality:1,type:"image/png",scale:r,backgroundColor:null,style:null,filter:null,maximumCanvasSize:0,timeout:3e4,progress:null,debug:a,fetch:D({requestInit:wt((E=t==null?void 0:t.fetch)==null?void 0:E.bypassingCache),placeholderImage:"data:image/png;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",bypassingCache:!1},t==null?void 0:t.fetch),fetchFn:null,font:{},drawImageInterval:100,workerUrl:null,workerNumber:i,onCloneEachNode:null,onCloneNode:null,onEmbedNode:null,onCreateForeignObjectSvg:null,includeStyleProperties:null,autoDestruct:!1},t),{__CONTEXT__:!0,log:ht(a),node:e,ownerDocument:o,ownerWindow:c,dpi:r===1?null:96*r,svgStyleElement:be(o),svgDefsElement:o==null?void 0:o.createElementNS(K,"defs"),svgStyles:new Map,defaultComputedStyles:new Map,workers:[...Array.from({length:ze&&n&&i?i:0})].map(()=>{try{const b=new Worker(n);return b.onmessage=w=>C(this,null,function*(){var A,F,B,$e;const{url:S,result:T}=w.data;T?(F=(A=u.get(S))==null?void 0:A.resolve)==null||F.call(A,T):($e=(B=u.get(S))==null?void 0:B.reject)==null||$e.call(B,new Error(`Error receiving message from worker: ${S}`))}),b.onmessageerror=w=>{var T,A;const{url:S}=w.data;(A=(T=u.get(S))==null?void 0:T.reject)==null||A.call(T,new Error(`Error receiving message from worker: ${S}`))},b}catch(b){return l.log.warn("Failed to new Worker",b),null}}).filter(Boolean),fontFamilies:new Map,fontCssTexts:new Map,acceptOfImage:`${[ut(o)&&"image/webp","image/svg+xml","image/*","*/*"].filter(Boolean).join(",")};q=0.8`,requests:u,drawImageCount:0,tasks:[],features:s,isEnable:b=>{var w,S;return b==="restoreScrollPosition"?typeof s=="boolean"?!1:(w=s[b])!=null?w:!1:typeof s=="boolean"?s:(S=s[b])!=null?S:!0},shadowRoots:[]});l.log.time("wait until load"),yield me(e,{timeout:l.timeout,onWarn:l.log.warn}),l.log.timeEnd("wait until load");const{width:d,height:m}=pt(e,l);return l.width=d,l.height=m,l})}function be(e){if(!e)return;const t=e.createElement("style"),r=t.ownerDocument.createTextNode(`
3
2
  .______background-clip--text {
4
3
  background-clip: text;
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * Brand-seed picker. Returns one OKLCH seed color + the mood it most
5
4
  * naturally evokes, and teaches the model how to compose a full palette
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
3
2
  /**
4
3
  * Pin/unpin sub-commands as standalone skill shortcuts.
5
4
  *
package/src/tokens.css CHANGED
@@ -1,6 +1,5 @@
1
1
  /* monodesign — design token system
2
2
  * OKLCH color space, 8px grid, expo easing
3
- * Apache 2.0 — pattern adapted from impeccable (Apache-2.0), rebranded for monomind
4
3
  */
5
4
 
6
5
  :root {