infernoflow 0.37.0 → 0.37.3

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 (88) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/dist/bin/infernoflow.mjs +29 -277
  3. package/dist/lib/adopters/angular.mjs +1 -128
  4. package/dist/lib/adopters/css.mjs +1 -111
  5. package/dist/lib/adopters/react.mjs +1 -104
  6. package/dist/lib/ai/ideDetection.mjs +1 -31
  7. package/dist/lib/ai/localProvider.mjs +1 -88
  8. package/dist/lib/ai/providerRouter.mjs +2 -295
  9. package/dist/lib/commands/adopt.mjs +20 -869
  10. package/dist/lib/commands/adoptWizard.mjs +9 -320
  11. package/dist/lib/commands/agent.mjs +5 -191
  12. package/dist/lib/commands/ai.mjs +2 -407
  13. package/dist/lib/commands/ask.mjs +4 -299
  14. package/dist/lib/commands/audit.mjs +13 -300
  15. package/dist/lib/commands/changelog.mjs +26 -594
  16. package/dist/lib/commands/check.mjs +3 -184
  17. package/dist/lib/commands/ci.mjs +3 -208
  18. package/dist/lib/commands/claudeMd.mjs +30 -135
  19. package/dist/lib/commands/cloud.mjs +10 -773
  20. package/dist/lib/commands/context.mjs +34 -346
  21. package/dist/lib/commands/coverage.mjs +2 -282
  22. package/dist/lib/commands/dashboard.mjs +123 -635
  23. package/dist/lib/commands/demo.mjs +8 -465
  24. package/dist/lib/commands/diff.mjs +5 -274
  25. package/dist/lib/commands/docGate.mjs +2 -81
  26. package/dist/lib/commands/doctor.mjs +3 -321
  27. package/dist/lib/commands/explain.mjs +8 -438
  28. package/dist/lib/commands/export.mjs +10 -239
  29. package/dist/lib/commands/feedback.mjs +12 -216
  30. package/dist/lib/commands/generateSkills.mjs +38 -163
  31. package/dist/lib/commands/graph.mjs +11 -378
  32. package/dist/lib/commands/health.mjs +2 -309
  33. package/dist/lib/commands/impact.mjs +2 -325
  34. package/dist/lib/commands/implement.mjs +7 -103
  35. package/dist/lib/commands/init.mjs +45 -631
  36. package/dist/lib/commands/installCursorHooks.mjs +1 -36
  37. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
  38. package/dist/lib/commands/link.mjs +2 -342
  39. package/dist/lib/commands/log.mjs +18 -248
  40. package/dist/lib/commands/monorepo.mjs +4 -428
  41. package/dist/lib/commands/notify.mjs +4 -258
  42. package/dist/lib/commands/onboard.mjs +4 -296
  43. package/dist/lib/commands/prComment.mjs +2 -361
  44. package/dist/lib/commands/prImpact.mjs +2 -157
  45. package/dist/lib/commands/publish.mjs +15 -316
  46. package/dist/lib/commands/recap.mjs +6 -380
  47. package/dist/lib/commands/report.mjs +28 -272
  48. package/dist/lib/commands/review.mjs +9 -223
  49. package/dist/lib/commands/run.mjs +8 -336
  50. package/dist/lib/commands/scaffold.mjs +54 -419
  51. package/dist/lib/commands/scan.mjs +11 -1118
  52. package/dist/lib/commands/scout.mjs +2 -291
  53. package/dist/lib/commands/setup.mjs +5 -310
  54. package/dist/lib/commands/share.mjs +13 -196
  55. package/dist/lib/commands/snapshot.mjs +3 -383
  56. package/dist/lib/commands/stability.mjs +2 -293
  57. package/dist/lib/commands/stats.mjs +5 -402
  58. package/dist/lib/commands/status.mjs +4 -172
  59. package/dist/lib/commands/suggest.mjs +21 -563
  60. package/dist/lib/commands/switch.mjs +13 -517
  61. package/dist/lib/commands/syncAuto.mjs +1 -96
  62. package/dist/lib/commands/synthesize.mjs +10 -228
  63. package/dist/lib/commands/teamSync.mjs +2 -388
  64. package/dist/lib/commands/test.mjs +6 -363
  65. package/dist/lib/commands/theme.mjs +18 -195
  66. package/dist/lib/commands/uninstall.mjs +13 -406
  67. package/dist/lib/commands/upgrade.mjs +20 -153
  68. package/dist/lib/commands/version.mjs +2 -282
  69. package/dist/lib/commands/vibe.mjs +7 -357
  70. package/dist/lib/commands/watch.mjs +4 -203
  71. package/dist/lib/commands/why.mjs +4 -358
  72. package/dist/lib/cursorHooksInstall.mjs +1 -60
  73. package/dist/lib/draftToolingInstall.mjs +7 -68
  74. package/dist/lib/git/detect-drift.mjs +4 -208
  75. package/dist/lib/learning/adapt.mjs +6 -101
  76. package/dist/lib/learning/observe.mjs +1 -119
  77. package/dist/lib/learning/patternDetector.mjs +1 -298
  78. package/dist/lib/learning/profile.mjs +2 -279
  79. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  80. package/dist/lib/telemetry.mjs +19 -269
  81. package/dist/lib/templates/index.mjs +1 -131
  82. package/dist/lib/theme/scanner.mjs +4 -343
  83. package/dist/lib/ui/errors.mjs +1 -142
  84. package/dist/lib/ui/output.mjs +6 -95
  85. package/dist/lib/ui/prompts.mjs +6 -147
  86. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  87. package/package.json +2 -4
  88. package/scripts/postinstall.js +2 -2
@@ -1,343 +1,4 @@
1
- /**
2
- * infernoflow theme scanner
3
- *
4
- * Extracts fonts, colors, CSS variables, and framework info
5
- * from CSS, SCSS, CSS-in-JS, Tailwind config, and HTML files.
6
- *
7
- * Captures what AI can't reliably infer from scattered style files —
8
- * the actual design system: palette, typography, spacing tokens.
9
- */
10
-
11
- import * as fs from "node:fs";
12
- import * as path from "node:path";
13
-
14
- // ── File discovery ────────────────────────────────────────────────────────────
15
-
16
- const STYLE_EXTENSIONS = new Set([".css", ".scss", ".sass", ".less", ".styl"]);
17
- const JS_EXTENSIONS = new Set([".js", ".mjs", ".jsx", ".ts", ".tsx"]);
18
- const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", "out", ".next", "coverage", ".cache"]);
19
-
20
- function walkDir(dir, maxDepth = 6, depth = 0) {
21
- if (depth > maxDepth) return [];
22
- let files = [];
23
- try {
24
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
25
- if (SKIP_DIRS.has(entry.name)) continue;
26
- const full = path.join(dir, entry.name);
27
- if (entry.isDirectory()) {
28
- files = files.concat(walkDir(full, maxDepth, depth + 1));
29
- } else if (entry.isFile()) {
30
- files.push(full);
31
- }
32
- }
33
- } catch {}
34
- return files;
35
- }
36
-
37
- // ── Color extraction ──────────────────────────────────────────────────────────
38
-
39
- const HEX_RE = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/g;
40
- const RGB_RE = /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*[\d.]+)?\s*\)/g;
41
- const HSL_RE = /hsla?\(\s*(\d{1,3})\s*,\s*[\d.]+%\s*,\s*[\d.]+%(?:\s*,\s*[\d.]+)?\s*\)/g;
42
-
43
- // Colors to ignore — common resets, white/black, etc.
44
- const SKIP_COLORS = new Set([
45
- "#000", "#000000", "#fff", "#ffffff", "#transparent",
46
- "#333", "#666", "#999", "#ccc", "#eee",
47
- ]);
48
-
49
- function normalizeHex(hex) {
50
- if (hex.length === 4) {
51
- return "#" + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
52
- }
53
- return hex.toLowerCase();
54
- }
55
-
56
- function rgbToHex(r, g, b) {
57
- return "#" + [r, g, b].map(v => parseInt(v).toString(16).padStart(2, "0")).join("");
58
- }
59
-
60
- function extractColors(text) {
61
- const freq = {};
62
-
63
- for (const m of text.matchAll(HEX_RE)) {
64
- const hex = normalizeHex(m[0]);
65
- if (!SKIP_COLORS.has(hex)) freq[hex] = (freq[hex] || 0) + 1;
66
- }
67
-
68
- for (const m of text.matchAll(RGB_RE)) {
69
- const hex = rgbToHex(m[1], m[2], m[3]);
70
- if (!SKIP_COLORS.has(hex)) freq[hex] = (freq[hex] || 0) + 1;
71
- }
72
-
73
- return freq;
74
- }
75
-
76
- // ── Font extraction ───────────────────────────────────────────────────────────
77
-
78
- const FONT_FAMILY_RE = /font-family\s*:\s*([^;}{]+)/gi;
79
- const FONT_FACE_RE = /@font-face\s*\{[^}]*font-family\s*:\s*['"]?([^'";]+)['"]?/gi;
80
- const GOOGLE_FONT_RE = /fonts\.googleapis\.com\/css[^"']*family=([^"'&]+)/gi;
81
- const NEXT_FONT_RE = /from\s+['"]next\/font['"]\s*.*?{\s*([A-Z][a-zA-Z_]+)\s*}/gs;
82
- const IMPORT_FONT_RE = /import\s+\{([^}]+)\}\s+from\s+['"]@fontsource\/([^'"]+)['"]/g;
83
-
84
- function cleanFontName(raw) {
85
- return raw.split(",")[0].trim().replace(/['"]/g, "").replace(/\s+/g, " ").trim();
86
- }
87
-
88
- function extractFonts(text, filePath) {
89
- const fonts = new Set();
90
- const sources = new Set();
91
-
92
- for (const m of text.matchAll(FONT_FAMILY_RE)) {
93
- const name = cleanFontName(m[1]);
94
- if (name && !name.includes("var(") && name.length > 2) fonts.add(name);
95
- }
96
-
97
- for (const m of text.matchAll(FONT_FACE_RE)) {
98
- const name = m[1].trim().replace(/['"]/g, "");
99
- if (name) { fonts.add(name); sources.add("local/@font-face"); }
100
- }
101
-
102
- for (const m of text.matchAll(GOOGLE_FONT_RE)) {
103
- const families = decodeURIComponent(m[1]).split("|");
104
- for (const fam of families) {
105
- const name = fam.split(":")[0].replace(/\+/g, " ").trim();
106
- if (name) { fonts.add(name); sources.add("Google Fonts"); }
107
- }
108
- }
109
-
110
- for (const m of text.matchAll(IMPORT_FONT_RE)) {
111
- const pkg = m[2].trim();
112
- const name = pkg.split("/").pop().replace(/-/g, " ").replace(/\b\w/g, c => c.toUpperCase());
113
- fonts.add(name);
114
- sources.add("@fontsource");
115
- }
116
-
117
- return { fonts: [...fonts], sources: [...sources] };
118
- }
119
-
120
- // ── CSS variable extraction ───────────────────────────────────────────────────
121
-
122
- const CSS_VAR_DEF_RE = /--([\w-]+)\s*:\s*([^;}{]+)/g;
123
-
124
- function extractCssVars(text) {
125
- // Only extract vars defined in :root or at top-level (not inside selectors)
126
- const vars = {};
127
-
128
- // Find :root blocks
129
- const rootBlocks = [];
130
- const rootRe = /(?::root|html)\s*\{([^}]+)\}/gi;
131
- for (const m of text.matchAll(rootRe)) rootBlocks.push(m[1]);
132
-
133
- // Also scan top-level (some projects define vars without :root)
134
- rootBlocks.push(text);
135
-
136
- for (const block of rootBlocks) {
137
- for (const m of block.matchAll(CSS_VAR_DEF_RE)) {
138
- const name = "--" + m[1].trim();
139
- const value = m[2].trim();
140
- if (value && !value.includes("{")) {
141
- vars[name] = value;
142
- }
143
- }
144
- }
145
-
146
- return vars;
147
- }
148
-
149
- // ── Tailwind config extraction ────────────────────────────────────────────────
150
-
151
- function extractTailwindTheme(filePath) {
152
- try {
153
- const text = fs.readFileSync(filePath, "utf8");
154
-
155
- const colors = {};
156
- const fonts = {};
157
-
158
- // Extract colors from theme.colors / theme.extend.colors
159
- const colorBlockRe = /colors\s*:\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/g;
160
- for (const m of text.matchAll(colorBlockRe)) {
161
- const block = m[1];
162
- for (const entry of block.matchAll(/['"]?([\w-]+)['"]?\s*:\s*['"]?(#[0-9a-fA-F]{3,6})['"]?/g)) {
163
- colors[entry[1]] = normalizeHex(entry[2]);
164
- }
165
- }
166
-
167
- // Extract fontFamily
168
- const fontBlockRe = /fontFamily\s*:\s*\{([^}]+)\}/g;
169
- for (const m of text.matchAll(fontBlockRe)) {
170
- const block = m[1];
171
- for (const entry of block.matchAll(/['"]?([\w-]+)['"]?\s*:\s*\[['"]([^'"]+)['"]/g)) {
172
- fonts[entry[1]] = entry[2];
173
- }
174
- }
175
-
176
- return { colors, fonts };
177
- } catch { return null; }
178
- }
179
-
180
- // ── Framework detection ───────────────────────────────────────────────────────
181
-
182
- function detectFramework(files, allText) {
183
- const hasFile = (name) => files.some(f => path.basename(f) === name);
184
- const hasText = (s) => allText.includes(s);
185
-
186
- if (hasFile("tailwind.config.js") || hasFile("tailwind.config.ts") || hasFile("tailwind.config.mjs")) return "tailwind";
187
- if (hasText("styled-components") || hasText("createGlobalStyle")) return "styled-components";
188
- if (hasText("@emotion/react") || hasText("css`") && hasText("emotion")) return "emotion";
189
- if (hasText("createTheme") && hasText("@mui/material")) return "mui";
190
- if (hasText("ChakraProvider") || hasText("@chakra-ui")) return "chakra";
191
- if (hasText(".module.css") || hasText(".module.scss")) return "css-modules";
192
- if (files.some(f => STYLE_EXTENSIONS.has(path.extname(f)))) return "plain-css";
193
- return "unknown";
194
- }
195
-
196
- // ── Color palette builder ─────────────────────────────────────────────────────
197
-
198
- function buildPalette(colorFreq, cssVars) {
199
- // Sort by frequency descending
200
- const sorted = Object.entries(colorFreq).sort((a, b) => b[1] - a[1]);
201
-
202
- // Top 12 most-used colors
203
- const topColors = sorted.slice(0, 12).map(([hex]) => hex);
204
-
205
- // Try to classify by role based on CSS var names
206
- const palette = {};
207
- const varColorMap = {};
208
-
209
- for (const [name, val] of Object.entries(cssVars)) {
210
- if (/^#[0-9a-fA-F]{3,6}$/.test(val)) {
211
- varColorMap[normalizeHex(val)] = name;
212
- }
213
- }
214
-
215
- // Classify colors
216
- for (const hex of topColors) {
217
- const varName = varColorMap[hex];
218
- if (varName) {
219
- // Use the var name to guess role
220
- const role = varName.replace(/^--/, "").replace(/-color$/, "");
221
- palette[role] = hex;
222
- }
223
- }
224
-
225
- // If palette is sparse, add raw top colors
226
- if (Object.keys(palette).length < 3) {
227
- topColors.slice(0, 6).forEach((hex, i) => {
228
- if (!Object.values(palette).includes(hex)) {
229
- palette[`color${i + 1}`] = hex;
230
- }
231
- });
232
- }
233
-
234
- // Detect dark/light mode
235
- const bgColors = Object.entries(palette)
236
- .filter(([k]) => /bg|background|surface|base/.test(k))
237
- .map(([, v]) => v);
238
-
239
- let mode = "unknown";
240
- if (bgColors.length) {
241
- const bg = parseInt(bgColors[0].slice(1), 16);
242
- const brightness = ((bg >> 16) & 0xff) * 0.299 + ((bg >> 8) & 0xff) * 0.587 + (bg & 0xff) * 0.114;
243
- mode = brightness < 128 ? "dark" : "light";
244
- }
245
-
246
- return { palette, mode, raw: topColors };
247
- }
248
-
249
- // ── Main scanner ──────────────────────────────────────────────────────────────
250
-
251
- export function scanTheme(cwd = process.cwd()) {
252
- const allFiles = walkDir(cwd);
253
- const styleFiles = allFiles.filter(f => STYLE_EXTENSIONS.has(path.extname(f)));
254
- const jsFiles = allFiles.filter(f => JS_EXTENSIONS.has(path.extname(f)));
255
- const htmlFiles = allFiles.filter(f => [".html", ".htm"].includes(path.extname(f)));
256
-
257
- const tailwindConfig = allFiles.find(f =>
258
- /tailwind\.config\.(js|ts|mjs|cjs)$/.test(f)
259
- );
260
-
261
- // Read all style + HTML content
262
- const styleTexts = [...styleFiles, ...htmlFiles].map(f => {
263
- try { return fs.readFileSync(f, "utf8"); } catch { return ""; }
264
- });
265
-
266
- // Read JS files for CSS-in-JS patterns (but skip large ones)
267
- const jsTexts = jsFiles
268
- .filter(f => { try { return fs.statSync(f).size < 200_000; } catch { return false; } })
269
- .map(f => { try { return fs.readFileSync(f, "utf8"); } catch { return ""; } });
270
-
271
- const allStyleText = styleTexts.join("\n");
272
- const allJsText = jsTexts.join("\n");
273
- const allText = allStyleText + "\n" + allJsText;
274
-
275
- // Extract
276
- const colorFreq = extractColors(allStyleText);
277
-
278
- // Also get colors from JS (CSS-in-JS)
279
- const jsColorFreq = extractColors(allJsText);
280
- for (const [hex, count] of Object.entries(jsColorFreq)) {
281
- colorFreq[hex] = (colorFreq[hex] || 0) + Math.round(count * 0.5); // weight JS colors less
282
- }
283
-
284
- const cssVars = extractCssVars(allStyleText);
285
-
286
- const fontData = { fonts: new Set(), sources: new Set() };
287
- for (const text of [...styleTexts, ...jsTexts]) {
288
- const { fonts, sources } = extractFonts(text, "");
289
- fonts.forEach(f => fontData.fonts.add(f));
290
- sources.forEach(s => fontData.sources.add(s));
291
- }
292
-
293
- const framework = detectFramework(allFiles, allText);
294
-
295
- // Tailwind override
296
- let tailwindTheme = null;
297
- if (tailwindConfig) {
298
- tailwindTheme = extractTailwindTheme(tailwindConfig);
299
- if (tailwindTheme) {
300
- for (const [name, hex] of Object.entries(tailwindTheme.colors)) {
301
- colorFreq[hex] = (colorFreq[hex] || 0) + 5; // boost Tailwind colors
302
- }
303
- for (const [role, family] of Object.entries(tailwindTheme.fonts)) {
304
- fontData.fonts.add(family);
305
- }
306
- }
307
- }
308
-
309
- const { palette, mode, raw } = buildPalette(colorFreq, cssVars);
310
-
311
- // Classify fonts
312
- const fontList = [...fontData.fonts].filter(f =>
313
- !["inherit", "initial", "unset", "system-ui", "sans-serif", "serif", "monospace",
314
- "-apple-system", "BlinkMacSystemFont", "Segoe UI"].includes(f)
315
- );
316
-
317
- const monoKeywords = /mono|code|courier|consol|jetbrain|fira|hack|source code/i;
318
- const monoFont = fontList.find(f => monoKeywords.test(f));
319
- const primaryFont = fontList.find(f => !monoKeywords.test(f));
320
-
321
- return {
322
- fonts: {
323
- primary: primaryFont || null,
324
- mono: monoFont || null,
325
- all: fontList,
326
- sources: [...fontData.sources],
327
- },
328
- colors: {
329
- palette,
330
- mode,
331
- raw: raw.slice(0, 20),
332
- },
333
- cssVars,
334
- framework,
335
- tailwind: tailwindTheme,
336
- stats: {
337
- styleFiles: styleFiles.length,
338
- jsFiles: jsFiles.length,
339
- colorsFound: Object.keys(colorFreq).length,
340
- varsFound: Object.keys(cssVars).length,
341
- },
342
- };
343
- }
1
+ import*as p from"node:fs";import*as d from"node:path";const k=new Set([".css",".scss",".sass",".less",".styl"]),v=new Set([".js",".mjs",".jsx",".ts",".tsx"]),L=new Set(["node_modules",".git","dist","build","out",".next","coverage",".cache"]);function T(s,c=6,n=0){if(n>c)return[];let t=[];try{for(const o of p.readdirSync(s,{withFileTypes:!0})){if(L.has(o.name))continue;const r=d.join(s,o.name);o.isDirectory()?t=t.concat(T(r,c,n+1)):o.isFile()&&t.push(r)}}catch{}return t}const P=/#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/g,B=/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*[\d.]+)?\s*\)/g,Z=/hsla?\(\s*(\d{1,3})\s*,\s*[\d.]+%\s*,\s*[\d.]+%(?:\s*,\s*[\d.]+)?\s*\)/g,_=new Set(["#000","#000000","#fff","#ffffff","#transparent","#333","#666","#999","#ccc","#eee"]);function b(s){return s.length===4?"#"+s[1]+s[1]+s[2]+s[2]+s[3]+s[3]:s.toLowerCase()}function G(s,c,n){return"#"+[s,c,n].map(t=>parseInt(t).toString(16).padStart(2,"0")).join("")}function E(s){const c={};for(const n of s.matchAll(P)){const t=b(n[0]);_.has(t)||(c[t]=(c[t]||0)+1)}for(const n of s.matchAll(B)){const t=G(n[1],n[2],n[3]);_.has(t)||(c[t]=(c[t]||0)+1)}return c}const M=/font-family\s*:\s*([^;}{]+)/gi,D=/@font-face\s*\{[^}]*font-family\s*:\s*['"]?([^'";]+)['"]?/gi,H=/fonts\.googleapis\.com\/css[^"']*family=([^"'&]+)/gi,Q=/from\s+['"]next\/font['"]\s*.*?{\s*([A-Z][a-zA-Z_]+)\s*}/gs,X=/import\s+\{([^}]+)\}\s+from\s+['"]@fontsource\/([^'"]+)['"]/g;function $(s){return s.split(",")[0].trim().replace(/['"]/g,"").replace(/\s+/g," ").trim()}function q(s,c){const n=new Set,t=new Set;for(const o of s.matchAll(M)){const r=$(o[1]);r&&!r.includes("var(")&&r.length>2&&n.add(r)}for(const o of s.matchAll(D)){const r=o[1].trim().replace(/['"]/g,"");r&&(n.add(r),t.add("local/@font-face"))}for(const o of s.matchAll(H)){const r=decodeURIComponent(o[1]).split("|");for(const i of r){const l=i.split(":")[0].replace(/\+/g," ").trim();l&&(n.add(l),t.add("Google Fonts"))}}for(const o of s.matchAll(X)){const i=o[2].trim().split("/").pop().replace(/-/g," ").replace(/\b\w/g,l=>l.toUpperCase());n.add(i),t.add("@fontsource")}return{fonts:[...n],sources:[...t]}}const z=/--([\w-]+)\s*:\s*([^;}{]+)/g;function K(s){const c={},n=[],t=/(?::root|html)\s*\{([^}]+)\}/gi;for(const o of s.matchAll(t))n.push(o[1]);n.push(s);for(const o of n)for(const r of o.matchAll(z)){const i="--"+r[1].trim(),l=r[2].trim();l&&!l.includes("{")&&(c[i]=l)}return c}function U(s){try{const c=p.readFileSync(s,"utf8"),n={},t={},o=/colors\s*:\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/g;for(const i of c.matchAll(o)){const l=i[1];for(const e of l.matchAll(/['"]?([\w-]+)['"]?\s*:\s*['"]?(#[0-9a-fA-F]{3,6})['"]?/g))n[e[1]]=b(e[2])}const r=/fontFamily\s*:\s*\{([^}]+)\}/g;for(const i of c.matchAll(r)){const l=i[1];for(const e of l.matchAll(/['"]?([\w-]+)['"]?\s*:\s*\[['"]([^'"]+)['"]/g))t[e[1]]=e[2]}return{colors:n,fonts:t}}catch{return null}}function V(s,c){const n=o=>s.some(r=>d.basename(r)===o),t=o=>c.includes(o);return n("tailwind.config.js")||n("tailwind.config.ts")||n("tailwind.config.mjs")?"tailwind":t("styled-components")||t("createGlobalStyle")?"styled-components":t("@emotion/react")||t("css`")&&t("emotion")?"emotion":t("createTheme")&&t("@mui/material")?"mui":t("ChakraProvider")||t("@chakra-ui")?"chakra":t(".module.css")||t(".module.scss")?"css-modules":s.some(o=>k.has(d.extname(o)))?"plain-css":"unknown"}function J(s,c){const t=Object.entries(s).sort((e,f)=>f[1]-e[1]).slice(0,12).map(([e])=>e),o={},r={};for(const[e,f]of Object.entries(c))/^#[0-9a-fA-F]{3,6}$/.test(f)&&(r[b(f)]=e);for(const e of t){const f=r[e];if(f){const y=f.replace(/^--/,"").replace(/-color$/,"");o[y]=e}}Object.keys(o).length<3&&t.slice(0,6).forEach((e,f)=>{Object.values(o).includes(e)||(o[`color${f+1}`]=e)});const i=Object.entries(o).filter(([e])=>/bg|background|surface|base/.test(e)).map(([,e])=>e);let l="unknown";if(i.length){const e=parseInt(i[0].slice(1),16);l=(e>>16&255)*.299+(e>>8&255)*.587+(e&255)*.114<128?"dark":"light"}return{palette:o,mode:l,raw:t}}function W(s=process.cwd()){const c=T(s),n=c.filter(a=>k.has(d.extname(a))),t=c.filter(a=>v.has(d.extname(a))),o=c.filter(a=>[".html",".htm"].includes(d.extname(a))),r=c.find(a=>/tailwind\.config\.(js|ts|mjs|cjs)$/.test(a)),i=[...n,...o].map(a=>{try{return p.readFileSync(a,"utf8")}catch{return""}}),l=t.filter(a=>{try{return p.statSync(a).size<2e5}catch{return!1}}).map(a=>{try{return p.readFileSync(a,"utf8")}catch{return""}}),e=i.join(`
2
+ `),f=l.join(`
3
+ `),y=e+`
4
+ `+f,u=E(e),O=E(f);for(const[a,m]of Object.entries(O))u[a]=(u[a]||0)+Math.round(m*.5);const F=K(e),h={fonts:new Set,sources:new Set};for(const a of[...i,...l]){const{fonts:m,sources:I}=q(a,"");m.forEach(S=>h.fonts.add(S)),I.forEach(S=>h.sources.add(S))}const A=V(c,y);let g=null;if(r&&(g=U(r),g)){for(const[a,m]of Object.entries(g.colors))u[m]=(u[m]||0)+5;for(const[a,m]of Object.entries(g.fonts))h.fonts.add(m)}const{palette:R,mode:x,raw:C}=J(u,F),w=[...h.fonts].filter(a=>!["inherit","initial","unset","system-ui","sans-serif","serif","monospace","-apple-system","BlinkMacSystemFont","Segoe UI"].includes(a)),j=/mono|code|courier|consol|jetbrain|fira|hack|source code/i,N=w.find(a=>j.test(a));return{fonts:{primary:w.find(a=>!j.test(a))||null,mono:N||null,all:w,sources:[...h.sources]},colors:{palette:R,mode:x,raw:C.slice(0,20)},cssVars:F,framework:A,tailwind:g,stats:{styleFiles:n.length,jsFiles:t.length,colorsFound:Object.keys(u).length,varsFound:Object.keys(F).length}}}export{W as scanTheme};
@@ -1,142 +1 @@
1
- /**
2
- * Shared error handling utilities for infernoflow commands.
3
- *
4
- * Provides consistent, friendly error messages with actionable hints.
5
- */
6
-
7
- import { warn, info, bold, cyan, gray, red } from "./output.mjs";
8
-
9
- // ── Common error types ────────────────────────────────────────────────────────
10
-
11
- export const ERR = {
12
- NO_INFERNO_DIR: "inferno/ directory not found.",
13
- NO_CONTRACT: "No contract.json or capabilities.json found.",
14
- NO_GIT: "This directory is not a git repository.",
15
- NO_NETWORK: "Network request failed.",
16
- INVALID_JSON: "Invalid JSON in response.",
17
- PERMISSION: "Permission denied.",
18
- TIMEOUT: "Command timed out.",
19
- };
20
-
21
- // ── Error formatter ───────────────────────────────────────────────────────────
22
-
23
- export function fatalError(message, hint, jsonMode = false) {
24
- if (jsonMode) {
25
- console.log(JSON.stringify({ ok: false, error: message, hint: hint || undefined }));
26
- } else {
27
- console.error();
28
- console.error(` ${red("✗")} ${bold(message)}`);
29
- if (hint) console.error(` ${gray(hint)}`);
30
- console.error();
31
- }
32
- process.exit(1);
33
- }
34
-
35
- export function softWarn(message, hint, jsonMode = false) {
36
- if (!jsonMode) {
37
- warn(message);
38
- if (hint) console.error(` ${gray(hint)}`);
39
- }
40
- }
41
-
42
- // ── Pre-flight checks ─────────────────────────────────────────────────────────
43
-
44
- import * as fs from "node:fs";
45
- import * as path from "node:path";
46
- import { execSync } from "node:child_process";
47
-
48
- /**
49
- * Ensure inferno/ exists. Exits with a friendly message if not.
50
- */
51
- export function requireInfernoDir(cwd, jsonMode = false) {
52
- const infernoDir = path.join(cwd, "inferno");
53
- if (!fs.existsSync(infernoDir)) {
54
- fatalError(
55
- ERR.NO_INFERNO_DIR,
56
- "Run: infernoflow init (or: infernoflow setup for full setup)",
57
- jsonMode
58
- );
59
- }
60
- return infernoDir;
61
- }
62
-
63
- /**
64
- * Ensure a git repo exists. Exits with a friendly message if not.
65
- */
66
- export function requireGitRepo(cwd, jsonMode = false) {
67
- try {
68
- execSync("git rev-parse --git-dir", { cwd, stdio: "ignore" });
69
- } catch {
70
- fatalError(
71
- ERR.NO_GIT,
72
- "Run: git init && git add . && git commit -m 'init'",
73
- jsonMode
74
- );
75
- }
76
- }
77
-
78
- /**
79
- * Read and parse a JSON file, with a friendly error on failure.
80
- */
81
- export function readJsonFile(filePath, label, jsonMode = false) {
82
- if (!fs.existsSync(filePath)) return null;
83
- try {
84
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
85
- } catch (err) {
86
- fatalError(
87
- `Could not parse ${label}: ${err.message}`,
88
- `Check the file for syntax errors: ${filePath}`,
89
- jsonMode
90
- );
91
- }
92
- }
93
-
94
- /**
95
- * Read contract.json or capabilities.json, whichever exists.
96
- */
97
- export function readContract(infernoDir, jsonMode = false) {
98
- for (const f of ["contract.json", "capabilities.json"]) {
99
- const p = path.join(infernoDir, f);
100
- const data = readJsonFile(p, f, jsonMode);
101
- if (data) return data;
102
- }
103
- fatalError(ERR.NO_CONTRACT, "Run: infernoflow init", jsonMode);
104
- }
105
-
106
- /**
107
- * Parse a semver string. Returns { major, minor, patch } or null.
108
- */
109
- export function parseSemver(v) {
110
- const m = String(v || "").match(/^(\d+)\.(\d+)\.(\d+)/);
111
- if (!m) return null;
112
- return { major: +m[1], minor: +m[2], patch: +m[3], raw: m[0] };
113
- }
114
-
115
- /**
116
- * Validate a URL string. Returns true if valid http/https URL.
117
- */
118
- export function isValidUrl(str) {
119
- try {
120
- const u = new URL(str);
121
- return u.protocol === "http:" || u.protocol === "https:";
122
- } catch { return false; }
123
- }
124
-
125
- /**
126
- * Wrap an async command handler with top-level error catching.
127
- * Prints a friendly message instead of a raw stack trace.
128
- */
129
- export function withErrorHandling(fn, jsonMode = false) {
130
- return fn().catch(err => {
131
- if (jsonMode) {
132
- console.log(JSON.stringify({ ok: false, error: err.message }));
133
- } else {
134
- console.error();
135
- console.error(` ${red("✗")} ${bold("Unexpected error:")} ${err.message}`);
136
- if (process.env.DEBUG) console.error(err.stack);
137
- else console.error(` ${gray("Set DEBUG=1 for a full stack trace.")}`);
138
- console.error();
139
- }
140
- process.exit(1);
141
- });
142
- }
1
+ import{warn as p,bold as a,gray as i,red as l}from"./output.mjs";const s={NO_INFERNO_DIR:"inferno/ directory not found.",NO_CONTRACT:"No contract.json or capabilities.json found.",NO_GIT:"This directory is not a git repository.",NO_NETWORK:"Network request failed.",INVALID_JSON:"Invalid JSON in response.",PERMISSION:"Permission denied.",TIMEOUT:"Command timed out."};function n(o,r,e=!1){e?console.log(JSON.stringify({ok:!1,error:o,hint:r||void 0})):(console.error(),console.error(` ${l("\u2717")} ${a(o)}`),r&&console.error(` ${i(r)}`),console.error()),process.exit(1)}function g(o,r,e=!1){e||(p(o),r&&console.error(` ${i(r)}`))}import*as c from"node:fs";import*as u from"node:path";import{execSync as d}from"node:child_process";function R(o,r=!1){const e=u.join(o,"inferno");return c.existsSync(e)||n(s.NO_INFERNO_DIR,"Run: infernoflow init (or: infernoflow setup for full setup)",r),e}function S(o,r=!1){try{d("git rev-parse --git-dir",{cwd:o,stdio:"ignore"})}catch{n(s.NO_GIT,"Run: git init && git add . && git commit -m 'init'",r)}}function N(o,r,e=!1){if(!c.existsSync(o))return null;try{return JSON.parse(c.readFileSync(o,"utf8"))}catch(t){n(`Could not parse ${r}: ${t.message}`,`Check the file for syntax errors: ${o}`,e)}}function I(o,r=!1){for(const e of["contract.json","capabilities.json"]){const t=u.join(o,e),f=N(t,e,r);if(f)return f}n(s.NO_CONTRACT,"Run: infernoflow init",r)}function $(o){const r=String(o||"").match(/^(\d+)\.(\d+)\.(\d+)/);return r?{major:+r[1],minor:+r[2],patch:+r[3],raw:r[0]}:null}function E(o){try{const r=new URL(o);return r.protocol==="http:"||r.protocol==="https:"}catch{return!1}}function T(o,r=!1){return o().catch(e=>{r?console.log(JSON.stringify({ok:!1,error:e.message})):(console.error(),console.error(` ${l("\u2717")} ${a("Unexpected error:")} ${e.message}`),process.env.DEBUG?console.error(e.stack):console.error(` ${i("Set DEBUG=1 for a full stack trace.")}`),console.error()),process.exit(1)})}export{s as ERR,n as fatalError,E as isValidUrl,$ as parseSemver,I as readContract,N as readJsonFile,S as requireGitRepo,R as requireInfernoDir,g as softWarn,T as withErrorHandling};
@@ -1,95 +1,6 @@
1
- // Zero-dependency color/output utilities using ANSI codes
2
-
3
- // ── Terminal capability detection ─────────────────────────────────────────
4
- function supportsUnicode() {
5
- if (process.platform === "win32") {
6
- // Windows Terminal sets WT_SESSION
7
- if (process.env.WT_SESSION) return true;
8
- // ConEmu / Cmder
9
- if (process.env.ConEmuPID) return true;
10
- // VS Code integrated terminal
11
- if (process.env.TERM_PROGRAM === "vscode") return true;
12
- // Default cmd.exe / PowerShell — no unicode box drawing
13
- return false;
14
- }
15
- return true; // Mac/Linux always support it
16
- }
17
-
18
- export const CHARS = supportsUnicode()
19
- ? { h: "─", v: "│", tl: "┌", tr: "┐", bl: "└", br: "┘",
20
- dot: "·", arrow: "→", check: "✔", cross: "✘", warn: "⚠", fire: "🔥" }
21
- : { h: "-", v: "|", tl: "+", tr: "+", bl: "+", br: "+",
22
- dot: "*", arrow: "->", check: "[OK]", cross: "[X]", warn: "[!]", fire: "**" };
23
-
24
- export const HR = CHARS.h.repeat(50);
25
-
26
- const c = {
27
- reset: "\x1b[0m",
28
- bold: "\x1b[1m",
29
- red: "\x1b[31m",
30
- green: "\x1b[32m",
31
- yellow: "\x1b[33m",
32
- blue: "\x1b[34m",
33
- cyan: "\x1b[36m",
34
- white: "\x1b[37m",
35
- gray: "\x1b[90m",
36
- orange: "\x1b[38;5;208m",
37
- };
38
-
39
- const noColor = !process.stdout.isTTY || process.env.NO_COLOR;
40
-
41
- function paint(code, text) {
42
- if (noColor) return text;
43
- return `${code}${text}${c.reset}`;
44
- }
45
-
46
- export const bold = t => paint(c.bold, t);
47
- export const red = t => paint(c.red, t);
48
- export const green = t => paint(c.green, t);
49
- export const yellow = t => paint(c.yellow, t);
50
- export const cyan = t => paint(c.cyan, t);
51
- export const gray = t => paint(c.gray, t);
52
- export const white = t => paint(c.white, t);
53
- export const orange = t => paint(c.orange, t);
54
- export const boldRed = t => paint(c.bold + c.red, t);
55
- export const boldGreen = t => paint(c.bold + c.green, t);
56
- export const boldYellow = t => paint(c.bold + c.yellow, t);
57
- export const boldOrange = t => paint(c.bold + c.orange, t);
58
-
59
- function strip(str) {
60
- return str.replace(/\x1b\[[0-9;]*m/g, "");
61
- }
62
-
63
- export function header(text) {
64
- const title = boldOrange(CHARS.fire + " infernoflow") + gray(" — " + text);
65
- console.log("\n" + title);
66
- console.log(gray(HR));
67
- }
68
-
69
- export function ok(msg) { console.log(" " + green(CHARS.check) + " " + msg); }
70
- export function fail(msg, hint) {
71
- console.log(" " + red(CHARS.cross) + " " + red(msg));
72
- if (hint) console.log(" " + gray(CHARS.arrow + " " + hint));
73
- }
74
- export function warn(msg) { console.log(" " + yellow(CHARS.warn) + " " + yellow(msg)); }
75
- export function info(msg) { console.log(" " + cyan("ℹ") + " " + msg); }
76
- export function section(title) { console.log("\n" + bold(white(title))); }
77
-
78
- export function done(msg) {
79
- console.log("\n" + boldGreen("✨ " + msg) + "\n");
80
- }
81
-
82
- export function nextSteps(steps) {
83
- console.log(bold("Next steps:"));
84
- steps.forEach((s, i) => {
85
- console.log(" " + gray((i + 1) + ".") + " " + s);
86
- });
87
- console.log();
88
- }
89
-
90
- export function errorAndExit(msg, hint) {
91
- console.error("\n" + boldRed("Error: ") + red(msg));
92
- if (hint) console.error(gray(" → " + hint));
93
- console.error();
94
- process.exit(1);
95
- }
1
+ function i(){return process.platform==="win32"?!!(process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode"):!0}const t=i()?{h:"\u2500",v:"\u2502",tl:"\u250C",tr:"\u2510",bl:"\u2514",br:"\u2518",dot:"\xB7",arrow:"\u2192",check:"\u2714",cross:"\u2718",warn:"\u26A0",fire:"\u{1F525}"}:{h:"-",v:"|",tl:"+",tr:"+",bl:"+",br:"+",dot:"*",arrow:"->",check:"[OK]",cross:"[X]",warn:"[!]",fire:"**"},f=t.h.repeat(50),e={reset:"\x1B[0m",bold:"\x1B[1m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m",white:"\x1B[37m",gray:"\x1B[90m",orange:"\x1B[38;5;208m"},u=!process.stdout.isTTY||process.env.NO_COLOR;function n(o,r){return u?r:`${o}${r}${e.reset}`}const l=o=>n(e.bold,o),s=o=>n(e.red,o),b=o=>n(e.green,o),p=o=>n(e.yellow,o),a=o=>n(e.cyan,o),c=o=>n(e.gray,o),g=o=>n(e.white,o),y=o=>n(e.orange,o),d=o=>n(e.bold+e.red,o),w=o=>n(e.bold+e.green,o),h=o=>n(e.bold+e.yellow,o),m=o=>n(e.bold+e.orange,o);function v(o){return o.replace(/\x1b\[[0-9;]*m/g,"")}function O(o){const r=m(t.fire+" infernoflow")+c(" \u2014 "+o);console.log(`
2
+ `+r),console.log(c(f))}function R(o){console.log(" "+b(t.check)+" "+o)}function E(o,r){console.log(" "+s(t.cross)+" "+s(o)),r&&console.log(" "+c(t.arrow+" "+r))}function S(o){console.log(" "+p(t.warn)+" "+p(o))}function k(o){console.log(" "+a("\u2139")+" "+o)}function C(o){console.log(`
3
+ `+l(g(o)))}function T(o){console.log(`
4
+ `+w("\u2728 "+o)+`
5
+ `)}function A(o){console.log(l("Next steps:")),o.forEach((r,x)=>{console.log(" "+c(x+1+".")+" "+r)}),console.log()}function N(o,r){console.error(`
6
+ `+d("Error: ")+s(o)),r&&console.error(c(" \u2192 "+r)),console.error(),process.exit(1)}export{t as CHARS,f as HR,l as bold,w as boldGreen,m as boldOrange,d as boldRed,h as boldYellow,a as cyan,T as done,N as errorAndExit,E as fail,c as gray,b as green,O as header,k as info,A as nextSteps,R as ok,y as orange,s as red,C as section,S as warn,g as white,p as yellow};