uilint 0.2.6 → 0.2.8

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/index.js CHANGED
@@ -237,8 +237,8 @@ async function initializeLangfuseIfEnabled() {
237
237
  },
238
238
  { asType: "generation" }
239
239
  );
240
- await new Promise((resolve8) => {
241
- resolveTrace = resolve8;
240
+ await new Promise((resolve7) => {
241
+ resolveTrace = resolve7;
242
242
  });
243
243
  if (endData && generationRef) {
244
244
  const usageDetails = endData.usage ? Object.fromEntries(
@@ -1249,14 +1249,14 @@ import {
1249
1249
  } from "uilint-core";
1250
1250
  import { ensureOllamaReady as ensureOllamaReady3 } from "uilint-core/node";
1251
1251
  async function readStdin2() {
1252
- return new Promise((resolve8) => {
1252
+ return new Promise((resolve7) => {
1253
1253
  let data = "";
1254
1254
  const rl = createInterface({ input: process.stdin });
1255
1255
  rl.on("line", (line) => {
1256
1256
  data += line;
1257
1257
  });
1258
1258
  rl.on("close", () => {
1259
- resolve8(data);
1259
+ resolve7(data);
1260
1260
  });
1261
1261
  });
1262
1262
  }
@@ -1837,7 +1837,7 @@ function detectPackageManager(projectPath) {
1837
1837
  return "npm";
1838
1838
  }
1839
1839
  function spawnAsync(command, args, cwd) {
1840
- return new Promise((resolve8, reject) => {
1840
+ return new Promise((resolve7, reject) => {
1841
1841
  const child = spawn(command, args, {
1842
1842
  cwd,
1843
1843
  stdio: "inherit",
@@ -1845,7 +1845,7 @@ function spawnAsync(command, args, cwd) {
1845
1845
  });
1846
1846
  child.on("error", reject);
1847
1847
  child.on("close", (code) => {
1848
- if (code === 0) resolve8();
1848
+ if (code === 0) resolve7();
1849
1849
  else
1850
1850
  reject(new Error(`${command} ${args.join(" ")} exited with ${code}`));
1851
1851
  });
@@ -2427,39 +2427,15 @@ async function installEslintPlugin(opts) {
2427
2427
  }
2428
2428
 
2429
2429
  // src/commands/install/analyze.ts
2430
- var LEGACY_HOOK_FILES = ["uilint-validate.sh", "uilint-validate.js"];
2431
- function safeParseJson(filePath) {
2432
- try {
2433
- const content = readFileSync5(filePath, "utf-8");
2434
- return JSON.parse(content);
2435
- } catch {
2436
- return void 0;
2437
- }
2438
- }
2439
2430
  async function analyze2(projectPath = process.cwd()) {
2440
2431
  const workspaceRoot = findWorkspaceRoot5(projectPath);
2441
2432
  const packageManager = detectPackageManager(projectPath);
2442
2433
  const cursorDir = join8(projectPath, ".cursor");
2443
2434
  const cursorDirExists = existsSync9(cursorDir);
2444
- const mcpPath = join8(cursorDir, "mcp.json");
2445
- const mcpExists = existsSync9(mcpPath);
2446
- const mcpConfig = mcpExists ? safeParseJson(mcpPath) : void 0;
2447
- const hooksPath = join8(cursorDir, "hooks.json");
2448
- const hooksExists = existsSync9(hooksPath);
2449
- const hooksConfig = hooksExists ? safeParseJson(hooksPath) : void 0;
2450
- const hooksDir = join8(cursorDir, "hooks");
2451
- const legacyPaths = [];
2452
- for (const legacyFile of LEGACY_HOOK_FILES) {
2453
- const legacyPath = join8(hooksDir, legacyFile);
2454
- if (existsSync9(legacyPath)) {
2455
- legacyPaths.push(legacyPath);
2456
- }
2457
- }
2458
2435
  const styleguidePath = join8(projectPath, ".uilint", "styleguide.md");
2459
2436
  const styleguideExists = existsSync9(styleguidePath);
2460
2437
  const commandsDir = join8(cursorDir, "commands");
2461
2438
  const genstyleguideExists = existsSync9(join8(commandsDir, "genstyleguide.md"));
2462
- const genrulesExists = existsSync9(join8(commandsDir, "genrules.md"));
2463
2439
  const nextApps = [];
2464
2440
  const directDetection = detectNextAppRouter(projectPath);
2465
2441
  if (directDetection) {
@@ -2518,25 +2494,12 @@ async function analyze2(projectPath = process.cwd()) {
2518
2494
  exists: cursorDirExists,
2519
2495
  path: cursorDir
2520
2496
  },
2521
- mcp: {
2522
- exists: mcpExists,
2523
- path: mcpPath,
2524
- config: mcpConfig
2525
- },
2526
- hooks: {
2527
- exists: hooksExists,
2528
- path: hooksPath,
2529
- config: hooksConfig,
2530
- hasLegacy: legacyPaths.length > 0,
2531
- legacyPaths
2532
- },
2533
2497
  styleguide: {
2534
2498
  exists: styleguideExists,
2535
2499
  path: styleguidePath
2536
2500
  },
2537
2501
  commands: {
2538
- genstyleguide: genstyleguideExists,
2539
- genrules: genrulesExists
2502
+ genstyleguide: genstyleguideExists
2540
2503
  },
2541
2504
  nextApps,
2542
2505
  viteApps,
@@ -2549,171 +2512,6 @@ import { join as join11 } from "path";
2549
2512
  import { createRequire as createRequire2 } from "module";
2550
2513
 
2551
2514
  // src/commands/install/constants.ts
2552
- var HOOKS_CONFIG = {
2553
- version: 1,
2554
- hooks: {
2555
- beforeSubmitPrompt: [{ command: ".cursor/hooks/uilint-session-start.sh" }],
2556
- afterFileEdit: [{ command: ".cursor/hooks/uilint-track.sh" }],
2557
- stop: [{ command: ".cursor/hooks/uilint-session-end.sh" }]
2558
- }
2559
- };
2560
- var MCP_CONFIG = {
2561
- mcpServers: {
2562
- uilint: {
2563
- command: "npx",
2564
- args: ["uilint-mcp"]
2565
- }
2566
- }
2567
- };
2568
- var LEGACY_HOOK_COMMANDS = [
2569
- ".cursor/hooks/uilint-validate.sh",
2570
- ".cursor/hooks/uilint-validate.js"
2571
- ];
2572
- var SESSION_START_SCRIPT = `#!/bin/bash
2573
- # UILint session start hook
2574
- # Clears tracked files at the start of each agent turn
2575
- #
2576
- # IMPORTANT: Cursor hooks communicate over stdio using JSON.
2577
- # - stdout must be JSON (Cursor will parse it)
2578
- # - stderr is for logs
2579
-
2580
- echo "[UILint] Session start - clearing tracked files" >&2
2581
-
2582
- # Prefer local monorepo build when developing UILint itself.
2583
- # Fall back to npx for normal consumers.
2584
- uilint() {
2585
- if [ -f "packages/uilint/dist/index.js" ]; then
2586
- node "packages/uilint/dist/index.js" "$@"
2587
- else
2588
- npx uilint "$@"
2589
- fi
2590
- }
2591
-
2592
- # Read JSON input from stdin (required by hook protocol)
2593
- cat > /dev/null
2594
-
2595
- # Clear session state
2596
- result=$(uilint session clear)
2597
- status=$?
2598
-
2599
- echo "[UILint] Clear exit: $status" >&2
2600
-
2601
- if [ $status -eq 0 ] && [ -n "$result" ]; then
2602
- echo "$result"
2603
- else
2604
- echo '{"cleared":false}'
2605
- fi
2606
-
2607
- exit 0
2608
- `;
2609
- var TRACK_SCRIPT = `#!/bin/bash
2610
- # UILint file tracking hook
2611
- # Tracks UI file edits for batch validation on agent stop
2612
- #
2613
- # IMPORTANT: Cursor hooks communicate over stdio using JSON.
2614
- # - stdout must be JSON (Cursor will parse it)
2615
- # - stderr is for logs
2616
-
2617
- out='{}'
2618
-
2619
- # Read JSON input from stdin
2620
- input=$(cat)
2621
-
2622
- echo "[UILint] afterFileEdit hook triggered" >&2
2623
-
2624
- # Prefer local monorepo build when developing UILint itself.
2625
- # Fall back to npx for normal consumers.
2626
- uilint() {
2627
- if [ -f "packages/uilint/dist/index.js" ]; then
2628
- node "packages/uilint/dist/index.js" "$@"
2629
- else
2630
- npx uilint "$@"
2631
- fi
2632
- }
2633
-
2634
- # Extract file_path using grep/sed (works without jq)
2635
- file_path=$(echo "$input" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"file_path"[[:space:]]*:[[:space:]]*"\\([^"]*\\)"/\\1/')
2636
-
2637
- echo "[UILint] Extracted file_path: $file_path" >&2
2638
-
2639
- if [ -z "$file_path" ]; then
2640
- echo "[UILint] No file_path found in input, skipping" >&2
2641
- printf '%s\\n' "$out"
2642
- exit 0
2643
- fi
2644
-
2645
- # Track the file (session command filters for UI files internally)
2646
- echo "[UILint] Tracking file: $file_path" >&2
2647
- result=$(uilint session track "$file_path")
2648
- status=$?
2649
-
2650
- echo "[UILint] Track exit: $status" >&2
2651
-
2652
- if [ $status -eq 0 ] && [ -n "$result" ]; then
2653
- out="$result"
2654
- fi
2655
-
2656
- printf '%s\\n' "$out"
2657
- exit 0
2658
- `;
2659
- var SESSION_END_SCRIPT = `#!/bin/bash
2660
- # UILint session end hook
2661
- # Scans tracked markup files and returns followup_message for auto-fix
2662
- #
2663
- # IMPORTANT: Cursor hooks communicate over stdio using JSON.
2664
- # - stdout must be JSON (Cursor will parse it)
2665
- # - stderr is for logs
2666
-
2667
- echo "[UILint] Session end hook triggered" >&2
2668
-
2669
- # Read JSON input from stdin (contains status, loop_count)
2670
- input=$(cat)
2671
-
2672
- echo "[UILint] Stop input: $input" >&2
2673
-
2674
- # Prefer local monorepo build when developing UILint itself.
2675
- # Fall back to npx for normal consumers.
2676
- uilint() {
2677
- if [ -f "packages/uilint/dist/index.js" ]; then
2678
- node "packages/uilint/dist/index.js" "$@"
2679
- else
2680
- npx uilint "$@"
2681
- fi
2682
- }
2683
-
2684
- # Extract loop_count to prevent infinite loops
2685
- loop_count=$(echo "$input" | grep -o '"loop_count"[[:space:]]*:[[:space:]]*[0-9]*' | grep -o '[0-9]*$')
2686
- loop_count=\${loop_count:-0}
2687
-
2688
- echo "[UILint] Loop count: $loop_count" >&2
2689
-
2690
- # Don't trigger followup if we've already looped 3+ times
2691
- if [ "$loop_count" -ge 3 ]; then
2692
- echo "[UILint] Max loops reached, skipping scan" >&2
2693
- echo '{}'
2694
- exit 0
2695
- fi
2696
-
2697
- # First check what files are tracked
2698
- echo "[UILint] Checking tracked files..." >&2
2699
- tracked=$(uilint session list)
2700
- echo "[UILint] Tracked files: $tracked" >&2
2701
-
2702
- # Run scan with --hook flag for direct JSON output
2703
- echo "[UILint] Running scan..." >&2
2704
- result=$(uilint session scan --hook)
2705
- status=$?
2706
-
2707
- echo "[UILint] Scan exit: $status" >&2
2708
-
2709
- if [ $status -eq 0 ] && [ -n "$result" ]; then
2710
- echo "$result"
2711
- else
2712
- echo '{}'
2713
- fi
2714
-
2715
- exit 0
2716
- `;
2717
2515
  var GENSTYLEGUIDE_COMMAND_MD = `# React Style Guide Generator
2718
2516
 
2719
2517
  Analyze the React UI codebase to produce a **prescriptive, semantic** style guide. Focus on consistency, intent, and relationships\u2014not specific values.
@@ -2832,107 +2630,6 @@ conventions:
2832
2630
  - **Omit if N/A**: Skip sections that don't apply
2833
2631
  - **Max 5 items** per section \u2014 highest impact only
2834
2632
  `;
2835
- var GENRULES_COMMAND_MD = `# ESLint Rule Generator
2836
-
2837
- Generate custom ESLint rules from your UILint styleguide (\`.uilint/styleguide.md\`).
2838
-
2839
- ## Purpose
2840
-
2841
- Transform your semantic styleguide rules into concrete, enforceable ESLint rules that:
2842
- - Run automatically during development
2843
- - Integrate with your editor
2844
- - Catch issues before commit
2845
- - Provide actionable error messages
2846
-
2847
- ## Analysis Steps
2848
-
2849
- ### 1. Read the Styleguide
2850
-
2851
- Look at \`.uilint/styleguide.md\` for:
2852
- - **Component Usage** (\`use:\` section) - which components should be used
2853
- - **Forbidden** patterns - what to disallow
2854
- - **Semantic Rules** - spacing, consistency, hierarchy
2855
- - **Patterns** - form handling, conditionals, state management
2856
-
2857
- ### 2. Identify Rule Candidates
2858
-
2859
- Focus on rules that can be statically analyzed:
2860
- - Import patterns (e.g., "use Button from shadcn, not raw HTML button")
2861
- - Forbidden patterns (e.g., "no inline style={{}}")
2862
- - Component library mixing (e.g., "don't mix MUI and shadcn")
2863
- - Tailwind patterns (e.g., "no arbitrary values")
2864
-
2865
- ### 3. Generate Rule Files
2866
-
2867
- Create TypeScript ESLint rules in \`.uilint/rules/\`:
2868
-
2869
- \`\`\`typescript
2870
- // .uilint/rules/prefer-shadcn-button.ts
2871
- import { createRule } from 'uilint-eslint';
2872
-
2873
- export default createRule({
2874
- name: 'prefer-shadcn-button',
2875
- meta: {
2876
- type: 'problem',
2877
- docs: { description: 'Use Button from shadcn instead of raw <button>' },
2878
- messages: {
2879
- preferButton: 'Use <Button> from @/components/ui/button instead of raw <button>',
2880
- },
2881
- schema: [],
2882
- },
2883
- defaultOptions: [],
2884
- create(context) {
2885
- return {
2886
- JSXOpeningElement(node) {
2887
- if (node.name.type === 'JSXIdentifier' && node.name.name === 'button') {
2888
- context.report({ node, messageId: 'preferButton' });
2889
- }
2890
- },
2891
- };
2892
- },
2893
- });
2894
- \`\`\`
2895
-
2896
- ### 4. Generate ESLint Config
2897
-
2898
- Create or update \`eslint.config.js\` to include the generated rules:
2899
-
2900
- \`\`\`javascript
2901
- import uilint from 'uilint-eslint';
2902
- import preferShadcnButton from './.uilint/rules/prefer-shadcn-button.js';
2903
-
2904
- export default [
2905
- uilint.configs.recommended,
2906
- {
2907
- plugins: {
2908
- 'uilint-custom': {
2909
- rules: {
2910
- 'prefer-shadcn-button': preferShadcnButton,
2911
- },
2912
- },
2913
- },
2914
- rules: {
2915
- 'uilint-custom/prefer-shadcn-button': 'error',
2916
- },
2917
- },
2918
- ];
2919
- \`\`\`
2920
-
2921
- ## Output
2922
-
2923
- Generate in \`.uilint/rules/\`:
2924
- - One TypeScript file per rule
2925
- - An \`index.ts\` that exports all rules
2926
- - Update instructions for \`eslint.config.js\`
2927
-
2928
- ## Guidelines
2929
-
2930
- - **Focus on static analysis** - rules must work without runtime info
2931
- - **Clear error messages** - tell devs exactly what to do
2932
- - **No false positives** - better to miss issues than over-report
2933
- - **Performance** - rules run on every file, keep them fast
2934
- - **Minimal rules** - generate 3-5 high-impact rules, not dozens
2935
- `;
2936
2633
 
2937
2634
  // src/utils/skill-loader.ts
2938
2635
  import { readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync2, existsSync as existsSync10 } from "fs";
@@ -3148,106 +2845,18 @@ function toInstallSpecifier(pkgName) {
3148
2845
  if (range.startsWith("link:")) return pkgName;
3149
2846
  return `${pkgName}@${range}`;
3150
2847
  }
3151
- function mergeHooksConfig(existing, ours) {
3152
- const result = { ...existing };
3153
- for (const [hookName, hookArray] of Object.entries(result.hooks)) {
3154
- if (!Array.isArray(hookArray)) continue;
3155
- result.hooks[hookName] = hookArray.filter(
3156
- (h) => !LEGACY_HOOK_COMMANDS.includes(h.command)
3157
- );
3158
- }
3159
- for (const [hookName, ourHooks] of Object.entries(ours.hooks)) {
3160
- if (!Array.isArray(ourHooks)) continue;
3161
- const existingHooks = result.hooks[hookName] || [];
3162
- for (const ourHook of ourHooks) {
3163
- const alreadyExists = existingHooks.some(
3164
- (h) => h.command === ourHook.command
3165
- );
3166
- if (!alreadyExists) {
3167
- existingHooks.push(ourHook);
3168
- }
3169
- }
3170
- result.hooks[hookName] = existingHooks;
3171
- }
3172
- return result;
3173
- }
3174
2848
  function createPlan(state, choices, options = {}) {
3175
2849
  const actions = [];
3176
2850
  const dependencies = [];
3177
2851
  const { force = false } = options;
3178
2852
  const { items } = choices;
3179
- const needsCursorDir = items.includes("mcp") || items.includes("hooks") || items.includes("genstyleguide") || items.includes("genrules") || items.includes("skill");
2853
+ const needsCursorDir = items.includes("genstyleguide") || items.includes("skill");
3180
2854
  if (needsCursorDir && !state.cursorDir.exists) {
3181
2855
  actions.push({
3182
2856
  type: "create_directory",
3183
2857
  path: state.cursorDir.path
3184
2858
  });
3185
2859
  }
3186
- if (items.includes("mcp")) {
3187
- if (!state.mcp.exists || force) {
3188
- actions.push({
3189
- type: "create_file",
3190
- path: state.mcp.path,
3191
- content: JSON.stringify(MCP_CONFIG, null, 2)
3192
- });
3193
- } else if (choices.mcpMerge) {
3194
- const merged = {
3195
- mcpServers: {
3196
- ...state.mcp.config?.mcpServers || {},
3197
- ...MCP_CONFIG.mcpServers
3198
- }
3199
- };
3200
- actions.push({
3201
- type: "create_file",
3202
- path: state.mcp.path,
3203
- content: JSON.stringify(merged, null, 2)
3204
- });
3205
- }
3206
- }
3207
- if (items.includes("hooks")) {
3208
- const hooksDir = join11(state.cursorDir.path, "hooks");
3209
- actions.push({
3210
- type: "create_directory",
3211
- path: hooksDir
3212
- });
3213
- for (const legacyPath of state.hooks.legacyPaths) {
3214
- actions.push({
3215
- type: "delete_file",
3216
- path: legacyPath
3217
- });
3218
- }
3219
- let finalHooksConfig;
3220
- if (!state.hooks.exists || force) {
3221
- finalHooksConfig = HOOKS_CONFIG;
3222
- } else if (choices.hooksMerge && state.hooks.config) {
3223
- finalHooksConfig = mergeHooksConfig(state.hooks.config, HOOKS_CONFIG);
3224
- } else {
3225
- finalHooksConfig = HOOKS_CONFIG;
3226
- }
3227
- actions.push({
3228
- type: "create_file",
3229
- path: state.hooks.path,
3230
- content: JSON.stringify(finalHooksConfig, null, 2)
3231
- });
3232
- actions.push({
3233
- type: "create_file",
3234
- path: join11(hooksDir, "uilint-session-start.sh"),
3235
- content: SESSION_START_SCRIPT,
3236
- permissions: 493
3237
- });
3238
- actions.push({
3239
- type: "create_file",
3240
- path: join11(hooksDir, "uilint-track.sh"),
3241
- content: TRACK_SCRIPT,
3242
- permissions: 493
3243
- });
3244
- actions.push({
3245
- type: "create_file",
3246
- path: join11(hooksDir, "uilint-session-end.sh"),
3247
- content: SESSION_END_SCRIPT,
3248
- permissions: 493
3249
- });
3250
- }
3251
2860
  if (items.includes("genstyleguide")) {
3252
2861
  const commandsDir = join11(state.cursorDir.path, "commands");
3253
2862
  actions.push({
@@ -3260,18 +2869,6 @@ function createPlan(state, choices, options = {}) {
3260
2869
  content: GENSTYLEGUIDE_COMMAND_MD
3261
2870
  });
3262
2871
  }
3263
- if (items.includes("genrules")) {
3264
- const commandsDir = join11(state.cursorDir.path, "commands");
3265
- actions.push({
3266
- type: "create_directory",
3267
- path: commandsDir
3268
- });
3269
- actions.push({
3270
- type: "create_file",
3271
- path: join11(commandsDir, "genrules.md"),
3272
- content: GENRULES_COMMAND_MD
3273
- });
3274
- }
3275
2872
  if (items.includes("skill")) {
3276
2873
  const skillsDir = join11(state.cursorDir.path, "skills");
3277
2874
  actions.push({
@@ -3472,80 +3069,52 @@ function walkAst(node, visit) {
3472
3069
  }
3473
3070
  }
3474
3071
  }
3475
- function ensureNamedImport(program2, from, name) {
3476
- if (!program2 || program2.type !== "Program") return { changed: false };
3477
- const existing = findImportDeclaration(program2, from);
3478
- if (existing) {
3479
- const has = (existing.specifiers ?? []).some(
3480
- (s) => s?.type === "ImportSpecifier" && (s.imported?.name === name || s.imported?.value === name)
3481
- );
3482
- if (has) return { changed: false };
3483
- const spec = parseModule2(`import { ${name} } from "${from}";`).$ast.body?.[0]?.specifiers?.[0];
3484
- if (!spec) return { changed: false };
3485
- existing.specifiers = [...existing.specifiers ?? [], spec];
3486
- return { changed: true };
3487
- }
3488
- const importDecl = parseModule2(`import { ${name} } from "${from}";`).$ast.body?.[0];
3489
- if (!importDecl) return { changed: false };
3490
- const body = program2.body ?? [];
3491
- let insertAt = 0;
3492
- while (insertAt < body.length && isUseClientDirective(body[insertAt])) {
3493
- insertAt++;
3494
- }
3495
- while (insertAt < body.length && body[insertAt]?.type === "ImportDeclaration") {
3496
- insertAt++;
3497
- }
3498
- program2.body.splice(insertAt, 0, importDecl);
3499
- return { changed: true };
3500
- }
3501
- function hasUILintProviderJsx(program2) {
3072
+ function hasUILintDevtoolsJsx(program2) {
3502
3073
  let found = false;
3503
3074
  walkAst(program2, (node) => {
3504
3075
  if (found) return;
3505
3076
  if (node.type !== "JSXElement") return;
3506
3077
  const name = node.openingElement?.name;
3507
- if (name?.type === "JSXIdentifier" && name.name === "UILintProvider") {
3508
- found = true;
3078
+ if (name?.type === "JSXIdentifier") {
3079
+ if (name.name === "UILintProvider" || name.name === "uilint-devtools") {
3080
+ found = true;
3081
+ }
3509
3082
  }
3510
3083
  });
3511
3084
  return found;
3512
3085
  }
3513
- function wrapFirstChildrenExpressionWithProvider(program2) {
3086
+ function addDevtoolsElementNextJs(program2) {
3514
3087
  if (!program2 || program2.type !== "Program") return { changed: false };
3515
- if (hasUILintProviderJsx(program2)) return { changed: false };
3516
- const providerMod = parseModule2(
3517
- 'const __uilint_provider = (<UILintProvider enabled={process.env.NODE_ENV !== "production"}>{children}</UILintProvider>);'
3088
+ if (hasUILintDevtoolsJsx(program2)) return { changed: false };
3089
+ const devtoolsMod = parseModule2(
3090
+ "const __uilint_devtools = (<uilint-devtools />);"
3518
3091
  );
3519
- const providerJsx = providerMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
3520
- if (!providerJsx || providerJsx.type !== "JSXElement")
3092
+ const devtoolsJsx = devtoolsMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
3093
+ if (!devtoolsJsx || devtoolsJsx.type !== "JSXElement")
3521
3094
  return { changed: false };
3522
- let replaced = false;
3095
+ let added = false;
3523
3096
  walkAst(program2, (node) => {
3524
- if (replaced) return;
3525
- if (node.type === "JSXExpressionContainer" && node.expression?.type === "Identifier" && node.expression.name === "children") {
3526
- Object.keys(node).forEach((k) => delete node[k]);
3527
- Object.assign(node, providerJsx);
3528
- replaced = true;
3529
- }
3097
+ if (added) return;
3098
+ if (node.type !== "JSXElement" && node.type !== "JSXFragment") return;
3099
+ const children = node.children ?? [];
3100
+ const childrenIndex = children.findIndex(
3101
+ (child) => child?.type === "JSXExpressionContainer" && child.expression?.type === "Identifier" && child.expression.name === "children"
3102
+ );
3103
+ if (childrenIndex === -1) return;
3104
+ children.splice(childrenIndex + 1, 0, devtoolsJsx);
3105
+ added = true;
3530
3106
  });
3531
- if (!replaced) {
3532
- throw new Error("Could not find `{children}` in target file to wrap.");
3107
+ if (!added) {
3108
+ throw new Error("Could not find `{children}` in target file to add devtools.");
3533
3109
  }
3534
3110
  return { changed: true };
3535
3111
  }
3536
- function wrapFirstRenderCallArgumentWithProvider(program2) {
3112
+ function addDevtoolsElementVite(program2) {
3537
3113
  if (!program2 || program2.type !== "Program") return { changed: false };
3538
- if (hasUILintProviderJsx(program2)) return { changed: false };
3539
- const providerMod = parseModule2(
3540
- 'const __uilint_provider = (<UILintProvider enabled={process.env.NODE_ENV !== "production"}></UILintProvider>);'
3541
- );
3542
- const providerJsx = providerMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
3543
- if (!providerJsx || providerJsx.type !== "JSXElement")
3544
- return { changed: false };
3545
- providerJsx.children = providerJsx.children ?? [];
3546
- let wrapped = false;
3114
+ if (hasUILintDevtoolsJsx(program2)) return { changed: false };
3115
+ let added = false;
3547
3116
  walkAst(program2, (node) => {
3548
- if (wrapped) return;
3117
+ if (added) return;
3549
3118
  if (node.type !== "CallExpression") return;
3550
3119
  const callee = node.callee;
3551
3120
  if (callee?.type !== "MemberExpression") return;
@@ -3555,17 +3124,44 @@ function wrapFirstRenderCallArgumentWithProvider(program2) {
3555
3124
  const arg0 = node.arguments?.[0];
3556
3125
  if (!arg0) return;
3557
3126
  if (arg0.type !== "JSXElement" && arg0.type !== "JSXFragment") return;
3558
- providerJsx.children = [arg0];
3559
- node.arguments[0] = providerJsx;
3560
- wrapped = true;
3127
+ const devtoolsMod = parseModule2(
3128
+ "const __uilint_devtools = (<uilint-devtools />);"
3129
+ );
3130
+ const devtoolsJsx = devtoolsMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
3131
+ if (!devtoolsJsx) return;
3132
+ const fragmentMod = parseModule2(
3133
+ "const __fragment = (<></>);"
3134
+ );
3135
+ const fragmentJsx = fragmentMod.$ast.body?.[0]?.declarations?.[0]?.init ?? null;
3136
+ if (!fragmentJsx) return;
3137
+ fragmentJsx.children = [arg0, devtoolsJsx];
3138
+ node.arguments[0] = fragmentJsx;
3139
+ added = true;
3561
3140
  });
3562
- if (!wrapped) {
3141
+ if (!added) {
3563
3142
  throw new Error(
3564
- "Could not find a `.render(<...>)` call to wrap. Expected a React entry like `createRoot(...).render(<App />)`."
3143
+ "Could not find a `.render(<...>)` call to add devtools. Expected a React entry like `createRoot(...).render(<App />)`."
3565
3144
  );
3566
3145
  }
3567
3146
  return { changed: true };
3568
3147
  }
3148
+ function ensureSideEffectImport(program2, from) {
3149
+ if (!program2 || program2.type !== "Program") return { changed: false };
3150
+ const existing = findImportDeclaration(program2, from);
3151
+ if (existing) return { changed: false };
3152
+ const importDecl = parseModule2(`import "${from}";`).$ast.body?.[0];
3153
+ if (!importDecl) return { changed: false };
3154
+ const body = program2.body ?? [];
3155
+ let insertAt = 0;
3156
+ while (insertAt < body.length && isUseClientDirective(body[insertAt])) {
3157
+ insertAt++;
3158
+ }
3159
+ while (insertAt < body.length && body[insertAt]?.type === "ImportDeclaration") {
3160
+ insertAt++;
3161
+ }
3162
+ program2.body.splice(insertAt, 0, importDecl);
3163
+ return { changed: true };
3164
+ }
3569
3165
  async function installReactUILintOverlay(opts) {
3570
3166
  const candidates = getDefaultCandidates(opts.projectPath, opts.appRoot);
3571
3167
  if (!candidates.length) {
@@ -3590,17 +3186,15 @@ async function installReactUILintOverlay(opts) {
3590
3186
  );
3591
3187
  }
3592
3188
  const program2 = mod.$ast;
3593
- const alreadyConfigured = !!findImportDeclaration(program2, "uilint-react") && hasUILintProviderJsx(program2);
3189
+ const hasDevtoolsImport = !!findImportDeclaration(program2, "uilint-react/devtools");
3190
+ const hasOldImport = !!findImportDeclaration(program2, "uilint-react");
3191
+ const alreadyConfigured = (hasDevtoolsImport || hasOldImport) && hasUILintDevtoolsJsx(program2);
3594
3192
  let changed = false;
3595
- const importRes = ensureNamedImport(
3596
- program2,
3597
- "uilint-react",
3598
- "UILintProvider"
3599
- );
3193
+ const importRes = ensureSideEffectImport(program2, "uilint-react/devtools");
3600
3194
  if (importRes.changed) changed = true;
3601
3195
  const mode = opts.mode ?? "next";
3602
- const wrapRes = mode === "vite" ? wrapFirstRenderCallArgumentWithProvider(program2) : wrapFirstChildrenExpressionWithProvider(program2);
3603
- if (wrapRes.changed) changed = true;
3196
+ const addRes = mode === "vite" ? addDevtoolsElementVite(program2) : addDevtoolsElementNextJs(program2);
3197
+ if (addRes.changed) changed = true;
3604
3198
  const updated = changed ? generateCode2(mod).code : original;
3605
3199
  const modified = updated !== original;
3606
3200
  if (modified) {
@@ -4605,7 +4199,7 @@ async function executeInjectReact(action, options) {
4605
4199
  return {
4606
4200
  action,
4607
4201
  success: true,
4608
- wouldDo: `Inject UILintProvider into React app: ${action.projectPath}`
4202
+ wouldDo: `Inject <uilint-devtools /> into React app: ${action.projectPath}`
4609
4203
  };
4610
4204
  }
4611
4205
  const result = await installReactUILintOverlay({
@@ -4796,10 +4390,7 @@ async function execute(plan, options = {}) {
4796
4390
  if (!result.success) continue;
4797
4391
  const { action } = result;
4798
4392
  if (action.type === "create_file") {
4799
- if (action.path.includes("mcp.json")) items.push("mcp");
4800
- if (action.path.includes("hooks.json")) items.push("hooks");
4801
4393
  if (action.path.includes("genstyleguide.md")) items.push("genstyleguide");
4802
- if (action.path.includes("genrules.md")) items.push("genrules");
4803
4394
  if (action.path.includes("/skills/") && action.path.includes("SKILL.md")) items.push("skill");
4804
4395
  }
4805
4396
  if (action.type === "inject_eslint") items.push("eslint");
@@ -4838,33 +4429,18 @@ var cliPrompter = {
4838
4429
  {
4839
4430
  value: "next",
4840
4431
  label: "UI overlay",
4841
- hint: "Installs routes + UILintProvider (Alt+Click to inspect)"
4432
+ hint: "Installs routes + devtools (Alt+Click to inspect)"
4842
4433
  },
4843
4434
  {
4844
4435
  value: "vite",
4845
4436
  label: "UI overlay (Vite)",
4846
- hint: "Installs jsx-loc-plugin + UILintProvider (Alt+Click to inspect)"
4437
+ hint: "Installs jsx-loc-plugin + devtools (Alt+Click to inspect)"
4847
4438
  },
4848
4439
  {
4849
4440
  value: "genstyleguide",
4850
4441
  label: "/genstyleguide command",
4851
4442
  hint: "Adds .cursor/commands/genstyleguide.md"
4852
4443
  },
4853
- {
4854
- value: "mcp",
4855
- label: "MCP Server",
4856
- hint: "Recommended - works with any MCP-compatible agent"
4857
- },
4858
- {
4859
- value: "hooks",
4860
- label: "Cursor Hooks",
4861
- hint: "Auto-validates UI files when the agent stops"
4862
- },
4863
- {
4864
- value: "genrules",
4865
- label: "/genrules command",
4866
- hint: "Adds .cursor/commands/genrules.md for ESLint rule generation"
4867
- },
4868
4444
  {
4869
4445
  value: "skill",
4870
4446
  label: "UI Consistency Agent Skill",
@@ -4875,22 +4451,6 @@ var cliPrompter = {
4875
4451
  initialValues: ["eslint", "next", "genstyleguide", "skill"]
4876
4452
  });
4877
4453
  },
4878
- async confirmMcpMerge() {
4879
- return confirm2({
4880
- message: `${pc.dim(
4881
- ".cursor/mcp.json"
4882
- )} already exists. Merge UILint config?`,
4883
- initialValue: true
4884
- });
4885
- },
4886
- async confirmHooksMerge() {
4887
- return confirm2({
4888
- message: `${pc.dim(
4889
- ".cursor/hooks.json"
4890
- )} already exists. Merge UILint hooks?`,
4891
- initialValue: true
4892
- });
4893
- },
4894
4454
  async selectNextApp(apps) {
4895
4455
  const chosen = await select2({
4896
4456
  message: "Which Next.js App Router project should UILint install into?",
@@ -5069,33 +4629,16 @@ async function promptForField(field, ruleName) {
5069
4629
  }
5070
4630
  async function gatherChoices(state, options, prompter) {
5071
4631
  let items;
5072
- const hasExplicitFlags = options.mcp !== void 0 || options.hooks !== void 0 || options.genstyleguide !== void 0 || options.genrules !== void 0 || options.skill !== void 0 || options.routes !== void 0 || options.react !== void 0;
4632
+ const hasExplicitFlags = options.genstyleguide !== void 0 || options.skill !== void 0 || options.routes !== void 0 || options.react !== void 0;
5073
4633
  if (hasExplicitFlags || options.eslint) {
5074
4634
  items = [];
5075
- if (options.mcp) items.push("mcp");
5076
- if (options.hooks) items.push("hooks");
5077
4635
  if (options.genstyleguide) items.push("genstyleguide");
5078
- if (options.genrules) items.push("genrules");
5079
4636
  if (options.skill) items.push("skill");
5080
4637
  if (options.routes || options.react) items.push("next");
5081
4638
  if (options.eslint) items.push("eslint");
5082
- } else if (options.mode) {
5083
- items = [];
5084
- if (options.mode === "mcp" || options.mode === "both") items.push("mcp");
5085
- if (options.mode === "hooks" || options.mode === "both")
5086
- items.push("hooks");
5087
- items.push("genstyleguide");
5088
4639
  } else {
5089
4640
  items = await prompter.selectInstallItems();
5090
4641
  }
5091
- let mcpMerge = true;
5092
- if (items.includes("mcp") && state.mcp.exists && !options.force) {
5093
- mcpMerge = await prompter.confirmMcpMerge();
5094
- }
5095
- let hooksMerge = true;
5096
- if (items.includes("hooks") && state.hooks.exists && !options.force) {
5097
- hooksMerge = await prompter.confirmHooksMerge();
5098
- }
5099
4642
  let nextChoices;
5100
4643
  if (items.includes("next")) {
5101
4644
  if (state.nextApps.length === 0) {
@@ -5170,8 +4713,6 @@ async function gatherChoices(state, options, prompter) {
5170
4713
  }
5171
4714
  return {
5172
4715
  items,
5173
- mcpMerge,
5174
- hooksMerge,
5175
4716
  next: nextChoices,
5176
4717
  vite: viteChoices,
5177
4718
  eslint: eslintChoices
@@ -5202,23 +4743,11 @@ async function configureRuleOptions(rules, prompter) {
5202
4743
  function displayResults(result) {
5203
4744
  const { summary } = result;
5204
4745
  const installedItems = [];
5205
- if (summary.installedItems.includes("mcp")) {
5206
- installedItems.push(`${pc.cyan("MCP Server")} \u2192 .cursor/mcp.json`);
5207
- }
5208
- if (summary.installedItems.includes("hooks")) {
5209
- installedItems.push(`${pc.cyan("Hooks")} \u2192 .cursor/hooks.json`);
5210
- installedItems.push(` ${pc.dim("\u251C")} uilint-session-start.sh`);
5211
- installedItems.push(` ${pc.dim("\u251C")} uilint-track.sh`);
5212
- installedItems.push(` ${pc.dim("\u2514")} uilint-session-end.sh`);
5213
- }
5214
4746
  if (summary.installedItems.includes("genstyleguide")) {
5215
4747
  installedItems.push(
5216
4748
  `${pc.cyan("Command")} \u2192 .cursor/commands/genstyleguide.md`
5217
4749
  );
5218
4750
  }
5219
- if (summary.installedItems.includes("genrules")) {
5220
- installedItems.push(`${pc.cyan("Command")} \u2192 .cursor/commands/genrules.md`);
5221
- }
5222
4751
  if (summary.nextApp) {
5223
4752
  installedItems.push(
5224
4753
  `${pc.cyan("Next Routes")} \u2192 ${pc.dim(
@@ -5226,7 +4755,7 @@ function displayResults(result) {
5226
4755
  )}`
5227
4756
  );
5228
4757
  installedItems.push(
5229
- `${pc.cyan("Next Overlay")} \u2192 ${pc.dim("<UILintProvider> injected")}`
4758
+ `${pc.cyan("Next Devtools")} \u2192 ${pc.dim("<uilint-devtools /> injected")}`
5230
4759
  );
5231
4760
  installedItems.push(
5232
4761
  `${pc.cyan("JSX Loc Plugin")} \u2192 ${pc.dim(
@@ -5236,7 +4765,7 @@ function displayResults(result) {
5236
4765
  }
5237
4766
  if (summary.viteApp) {
5238
4767
  installedItems.push(
5239
- `${pc.cyan("Vite Overlay")} \u2192 ${pc.dim("<UILintProvider> injected")}`
4768
+ `${pc.cyan("Vite Devtools")} \u2192 ${pc.dim("<uilint-devtools /> injected")}`
5240
4769
  );
5241
4770
  installedItems.push(
5242
4771
  `${pc.cyan("JSX Loc Plugin")} \u2192 ${pc.dim(
@@ -5274,15 +4803,9 @@ function displayResults(result) {
5274
4803
  if (!hasStyleguide) {
5275
4804
  steps.push(`Create a styleguide: ${pc.cyan("/genstyleguide")}`);
5276
4805
  }
5277
- if (summary.installedItems.includes("mcp") || summary.installedItems.includes("hooks") || summary.installedItems.includes("genstyleguide")) {
4806
+ if (summary.installedItems.includes("genstyleguide")) {
5278
4807
  steps.push("Restart Cursor to load the new configuration");
5279
4808
  }
5280
- if (summary.installedItems.includes("mcp")) {
5281
- steps.push(`The MCP server exposes: ${pc.dim("scan_file, scan_snippet")}`);
5282
- }
5283
- if (summary.installedItems.includes("hooks")) {
5284
- steps.push("Hooks will auto-validate UI files when the agent stops");
5285
- }
5286
4809
  if (summary.nextApp) {
5287
4810
  steps.push(
5288
4811
  "Run your Next.js dev server - use Alt+Click on any element to inspect"
@@ -6180,12 +5703,12 @@ async function serve(options) {
6180
5703
  `UILint WebSocket server running on ${pc.cyan(`ws://localhost:${port}`)}`
6181
5704
  );
6182
5705
  logInfo("Press Ctrl+C to stop");
6183
- await new Promise((resolve8) => {
5706
+ await new Promise((resolve7) => {
6184
5707
  process.on("SIGINT", () => {
6185
5708
  logInfo("Shutting down...");
6186
5709
  wss.close();
6187
5710
  fileWatcher?.close();
6188
- resolve8();
5711
+ resolve7();
6189
5712
  });
6190
5713
  });
6191
5714
  }
@@ -6673,184 +6196,9 @@ async function vision(options) {
6673
6196
  await flushLangfuse();
6674
6197
  }
6675
6198
 
6676
- // src/commands/session.ts
6677
- import { existsSync as existsSync20, readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync2 } from "fs";
6678
- import { basename, dirname as dirname13, resolve as resolve7 } from "path";
6679
- import { createStyleSummary as createStyleSummary3 } from "uilint-core";
6680
- import {
6681
- ensureOllamaReady as ensureOllamaReady7,
6682
- parseCLIInput as parseCLIInput2,
6683
- readStyleGuideFromProject as readStyleGuideFromProject2,
6684
- readTailwindThemeTokens as readTailwindThemeTokens3
6685
- } from "uilint-core/node";
6686
- var SESSION_FILE = "/tmp/uilint-session.json";
6687
- var UI_FILE_EXTENSIONS = [".tsx", ".jsx", ".css", ".scss", ".module.css"];
6688
- function readSession() {
6689
- if (!existsSync20(SESSION_FILE)) {
6690
- return { files: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
6691
- }
6692
- try {
6693
- const content = readFileSync14(SESSION_FILE, "utf-8");
6694
- return JSON.parse(content);
6695
- } catch {
6696
- return { files: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
6697
- }
6698
- }
6699
- function writeSession(state) {
6700
- writeFileSync10(SESSION_FILE, JSON.stringify(state, null, 2), "utf-8");
6701
- }
6702
- function isUIFile(filePath) {
6703
- return UI_FILE_EXTENSIONS.some((ext) => filePath.endsWith(ext));
6704
- }
6705
- function isScannableMarkupFile(filePath) {
6706
- return [".tsx", ".jsx", ".html", ".htm"].some(
6707
- (ext) => filePath.endsWith(ext)
6708
- );
6709
- }
6710
- async function sessionClear() {
6711
- if (existsSync20(SESSION_FILE)) {
6712
- unlinkSync2(SESSION_FILE);
6713
- }
6714
- console.log(JSON.stringify({ cleared: true }));
6715
- }
6716
- async function sessionTrack(filePath) {
6717
- if (!isUIFile(filePath)) {
6718
- console.log(
6719
- JSON.stringify({
6720
- tracked: false,
6721
- reason: "not_ui_file",
6722
- file: filePath,
6723
- message: `Skipped non-UI file: ${basename(filePath)}`
6724
- })
6725
- );
6726
- return;
6727
- }
6728
- const session = readSession();
6729
- const wasAlreadyTracked = session.files.includes(filePath);
6730
- if (!wasAlreadyTracked) {
6731
- session.files.push(filePath);
6732
- writeSession(session);
6733
- }
6734
- console.log(
6735
- JSON.stringify({
6736
- tracked: true,
6737
- file: filePath,
6738
- total: session.files.length,
6739
- newlyAdded: !wasAlreadyTracked,
6740
- message: wasAlreadyTracked ? `Already tracking: ${basename(filePath)} (${session.files.length} files total)` : `Now tracking: ${basename(filePath)} (${session.files.length} files total)`
6741
- })
6742
- );
6743
- }
6744
- async function sessionScan(options = {}) {
6745
- const session = readSession();
6746
- if (session.files.length === 0) {
6747
- if (options.hookFormat) {
6748
- console.log("{}");
6749
- } else {
6750
- const result = {
6751
- totalFiles: 0,
6752
- filesWithIssues: 0,
6753
- results: [],
6754
- followupMessage: null
6755
- };
6756
- console.log(JSON.stringify(result));
6757
- }
6758
- return;
6759
- }
6760
- const projectPath = process.cwd();
6761
- let styleGuide;
6762
- try {
6763
- styleGuide = await readStyleGuideFromProject2(projectPath);
6764
- } catch {
6765
- if (options.hookFormat) {
6766
- console.log("{}");
6767
- } else {
6768
- const result = {
6769
- totalFiles: session.files.length,
6770
- filesWithIssues: 0,
6771
- results: [],
6772
- followupMessage: null
6773
- };
6774
- console.log(JSON.stringify(result));
6775
- }
6776
- return;
6777
- }
6778
- await ensureOllamaReady7();
6779
- const client = await createLLMClient({});
6780
- const results = [];
6781
- for (const filePath of session.files) {
6782
- if (!existsSync20(filePath)) continue;
6783
- if (!isScannableMarkupFile(filePath)) continue;
6784
- try {
6785
- const absolutePath = resolve7(process.cwd(), filePath);
6786
- const htmlLike = readFileSync14(filePath, "utf-8");
6787
- const snapshot = parseCLIInput2(htmlLike);
6788
- const tailwindSearchDir = dirname13(absolutePath);
6789
- const tailwindTheme = readTailwindThemeTokens3(tailwindSearchDir);
6790
- const styleSummary = createStyleSummary3(snapshot.styles, {
6791
- html: snapshot.html,
6792
- tailwindTheme
6793
- });
6794
- const analysis = await client.analyzeStyles(styleSummary, styleGuide);
6795
- results.push({
6796
- file: filePath,
6797
- issues: analysis.issues
6798
- });
6799
- } catch {
6800
- continue;
6801
- }
6802
- }
6803
- const filesWithIssues = results.filter((r) => r.issues.length > 0);
6804
- let followupMessage = null;
6805
- if (filesWithIssues.length > 0) {
6806
- const issueLines = [];
6807
- for (const fileResult of filesWithIssues) {
6808
- const fileName = basename(fileResult.file);
6809
- for (const issue of fileResult.issues) {
6810
- const type = issue.type?.toUpperCase?.() ?? "ISSUE";
6811
- const detail = issue.currentValue && issue.expectedValue ? ` (${issue.currentValue} \u2192 ${issue.expectedValue})` : issue.currentValue ? ` (${issue.currentValue})` : "";
6812
- issueLines.push(`- ${fileName}: [${type}] ${issue.message}${detail}`);
6813
- if (issue.suggestion) {
6814
- issueLines.push(` Suggestion: ${issue.suggestion}`);
6815
- }
6816
- }
6817
- }
6818
- followupMessage = [
6819
- `UILint scan found UI consistency issues in ${filesWithIssues.length} file(s):`,
6820
- "",
6821
- ...issueLines,
6822
- "",
6823
- "See .uilint/styleguide.md for style rules. Please fix these issues."
6824
- ].join("\n");
6825
- }
6826
- if (options.hookFormat) {
6827
- if (followupMessage) {
6828
- console.log(JSON.stringify({ followup_message: followupMessage }));
6829
- } else {
6830
- console.log("{}");
6831
- }
6832
- } else {
6833
- const result = {
6834
- totalFiles: results.length,
6835
- filesWithIssues: filesWithIssues.length,
6836
- results,
6837
- followupMessage
6838
- };
6839
- console.log(JSON.stringify(result));
6840
- }
6841
- if (existsSync20(SESSION_FILE)) {
6842
- unlinkSync2(SESSION_FILE);
6843
- }
6844
- await flushLangfuse();
6845
- }
6846
- async function sessionList() {
6847
- const session = readSession();
6848
- console.log(JSON.stringify(session));
6849
- }
6850
-
6851
6199
  // src/index.ts
6852
- import { readFileSync as readFileSync15 } from "fs";
6853
- import { dirname as dirname14, join as join20 } from "path";
6200
+ import { readFileSync as readFileSync14 } from "fs";
6201
+ import { dirname as dirname13, join as join20 } from "path";
6854
6202
  import { fileURLToPath as fileURLToPath4 } from "url";
6855
6203
  function assertNodeVersion(minMajor) {
6856
6204
  const ver = process.versions.node || "";
@@ -6867,9 +6215,9 @@ assertNodeVersion(20);
6867
6215
  var program = new Command();
6868
6216
  function getCLIVersion2() {
6869
6217
  try {
6870
- const __dirname3 = dirname14(fileURLToPath4(import.meta.url));
6218
+ const __dirname3 = dirname13(fileURLToPath4(import.meta.url));
6871
6219
  const pkgPath = join20(__dirname3, "..", "package.json");
6872
- const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
6220
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
6873
6221
  return pkg.version || "0.0.0";
6874
6222
  } catch {
6875
6223
  return "0.0.0";
@@ -6942,26 +6290,16 @@ program.command("update").description("Update existing style guide with new styl
6942
6290
  llm: options.llm
6943
6291
  });
6944
6292
  });
6945
- program.command("install").description("Install UILint integration (MCP server and/or Cursor hooks)").option("--force", "Overwrite existing configuration files").option("--mcp", "Install MCP server integration (.cursor/mcp.json)").option("--hooks", "Install Cursor hooks integration (.cursor/hooks.json)").option("--genstyleguide", "Install /genstyleguide Cursor command").option(
6946
- "--genrules",
6947
- "Install /genrules Cursor command for ESLint rule generation"
6948
- ).option("--eslint", "Install uilint-eslint plugin and configure ESLint").option(
6293
+ program.command("install").description("Install UILint integration").option("--force", "Overwrite existing configuration files").option("--genstyleguide", "Install /genstyleguide Cursor command").option("--eslint", "Install uilint-eslint plugin and configure ESLint").option(
6949
6294
  "--routes",
6950
6295
  "Back-compat: install Next.js overlay (routes + deps + inject)"
6951
6296
  ).option(
6952
6297
  "--react",
6953
6298
  "Back-compat: install Next.js overlay (routes + deps + inject)"
6954
- ).option(
6955
- "--mode <mode>",
6956
- "Integration mode: mcp, hooks, or both (skips interactive prompt)"
6957
6299
  ).action(async (options) => {
6958
6300
  await install({
6959
6301
  force: options.force,
6960
- mode: options.mode,
6961
- mcp: options.mcp,
6962
- hooks: options.hooks,
6963
6302
  genstyleguide: options.genstyleguide,
6964
- genrules: options.genrules,
6965
6303
  eslint: options.eslint,
6966
6304
  routes: options.routes,
6967
6305
  react: options.react
@@ -7006,20 +6344,5 @@ program.command("vision").description("Analyze a screenshot with Ollama vision m
7006
6344
  debugDump: options.debugDump
7007
6345
  });
7008
6346
  });
7009
- var sessionCmd = program.command("session").description(
7010
- "Manage file tracking for agentic sessions (used by Cursor hooks)"
7011
- );
7012
- sessionCmd.command("clear").description("Clear tracked files (called at start of agent turn)").action(async () => {
7013
- await sessionClear();
7014
- });
7015
- sessionCmd.command("track <file>").description("Track a file edit (called on each file edit)").action(async (file) => {
7016
- await sessionTrack(file);
7017
- });
7018
- sessionCmd.command("scan").description("Scan all tracked markup files (called on agent stop)").option("--hook", "Output in Cursor hook format (followup_message JSON only)").action(async (options) => {
7019
- await sessionScan({ hookFormat: options.hook });
7020
- });
7021
- sessionCmd.command("list").description("List tracked files (for debugging)").action(async () => {
7022
- await sessionList();
7023
- });
7024
6347
  program.parse();
7025
6348
  //# sourceMappingURL=index.js.map