uilint 0.2.59 → 0.2.61

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
@@ -1404,7 +1404,7 @@ async function update(options) {
1404
1404
  }
1405
1405
 
1406
1406
  // src/commands/serve.ts
1407
- import { existsSync as existsSync5, statSync as statSync3, readdirSync, readFileSync } from "fs";
1407
+ import { existsSync as existsSync5, statSync as statSync3, readdirSync, readFileSync, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
1408
1408
  import { createRequire } from "module";
1409
1409
  import { dirname as dirname5, resolve as resolve5, relative, join as join3, parse as parse2 } from "path";
1410
1410
  import { WebSocketServer, WebSocket } from "ws";
@@ -1618,6 +1618,37 @@ function pickAppRoot(params) {
1618
1618
  if (containing) return containing.projectPath;
1619
1619
  return matches[0].projectPath;
1620
1620
  }
1621
+ function detectPostToolUseHook(projectRoot) {
1622
+ const claudeSettingsPath = join3(projectRoot, ".claude", "settings.json");
1623
+ if (existsSync5(claudeSettingsPath)) {
1624
+ try {
1625
+ const content = readFileSync(claudeSettingsPath, "utf-8");
1626
+ const settings = JSON.parse(content);
1627
+ const hooks = settings.hooks?.PostToolUse;
1628
+ if (Array.isArray(hooks)) {
1629
+ const hasEditHook = hooks.some(
1630
+ (h) => h.matcher?.includes("Edit") || h.matcher?.includes("Write")
1631
+ );
1632
+ if (hasEditHook) {
1633
+ return { enabled: true, provider: "claude" };
1634
+ }
1635
+ }
1636
+ } catch {
1637
+ }
1638
+ }
1639
+ const cursorHooksPath = join3(projectRoot, ".cursor", "hooks.json");
1640
+ if (existsSync5(cursorHooksPath)) {
1641
+ try {
1642
+ const content = readFileSync(cursorHooksPath, "utf-8");
1643
+ const hooks = JSON.parse(content);
1644
+ if (hooks.hooks?.afterFileEdit?.length > 0) {
1645
+ return { enabled: true, provider: "cursor" };
1646
+ }
1647
+ } catch {
1648
+ }
1649
+ }
1650
+ return { enabled: false, provider: null };
1651
+ }
1621
1652
  var cache = /* @__PURE__ */ new Map();
1622
1653
  var eslintInstances = /* @__PURE__ */ new Map();
1623
1654
  var visionAnalyzer = null;
@@ -1897,7 +1928,13 @@ async function handleMessage(ws, data) {
1897
1928
  )}`
1898
1929
  );
1899
1930
  } else if (message.type === "vision:analyze") {
1931
+ } else if (message.type === "vision:check") {
1900
1932
  } else if (message.type === "config:set") {
1933
+ } else if (message.type === "screenshot:save") {
1934
+ const rid = message.requestId;
1935
+ logInfo(
1936
+ `${pc.dim("[ws]")} ${pc.bold("screenshot:save")} ${pc.dim(message.route)}${rid ? ` ${pc.dim(`(req ${rid})`)}` : ""}`
1937
+ );
1901
1938
  }
1902
1939
  switch (message.type) {
1903
1940
  case "lint:file": {
@@ -2159,6 +2196,29 @@ ${stack}` : ""
2159
2196
  }
2160
2197
  break;
2161
2198
  }
2199
+ case "vision:check": {
2200
+ const { requestId } = message;
2201
+ logInfo(
2202
+ `${pc.dim("[ws]")} ${pc.bold("vision:check")}${requestId ? ` ${pc.dim(`(req ${requestId})`)}` : ""}`
2203
+ );
2204
+ try {
2205
+ const analyzer = getVisionAnalyzerInstance();
2206
+ const model = typeof analyzer.getModel === "function" ? analyzer.getModel() : void 0;
2207
+ sendMessage(ws, {
2208
+ type: "vision:status",
2209
+ available: true,
2210
+ model,
2211
+ requestId
2212
+ });
2213
+ } catch (error) {
2214
+ sendMessage(ws, {
2215
+ type: "vision:status",
2216
+ available: false,
2217
+ requestId
2218
+ });
2219
+ }
2220
+ break;
2221
+ }
2162
2222
  case "config:set": {
2163
2223
  const { key, value } = message;
2164
2224
  handleConfigSet(key, value);
@@ -2234,6 +2294,75 @@ ${stack}` : ""
2234
2294
  }
2235
2295
  break;
2236
2296
  }
2297
+ case "screenshot:save": {
2298
+ const { dataUrl, route, timestamp, requestId } = message;
2299
+ try {
2300
+ if (!dataUrl || typeof dataUrl !== "string") {
2301
+ sendMessage(ws, {
2302
+ type: "screenshot:error",
2303
+ error: "Invalid dataUrl: must be a non-empty string",
2304
+ requestId
2305
+ });
2306
+ break;
2307
+ }
2308
+ const dataUrlPattern = /^data:image\/png;base64,/;
2309
+ if (!dataUrlPattern.test(dataUrl)) {
2310
+ sendMessage(ws, {
2311
+ type: "screenshot:error",
2312
+ error: "Invalid dataUrl: must be a base64 PNG data URL (data:image/png;base64,...)",
2313
+ requestId
2314
+ });
2315
+ break;
2316
+ }
2317
+ const base64Data = dataUrl.replace(dataUrlPattern, "");
2318
+ const sanitizedRoute = route.replace(/^\//, "").replace(/\//g, "-").replace(/[^a-zA-Z0-9_-]/g, "_") || "root";
2319
+ const filename = `uilint-${timestamp}-${sanitizedRoute}.png`;
2320
+ if (!isValidScreenshotFilename(filename)) {
2321
+ sendMessage(ws, {
2322
+ type: "screenshot:error",
2323
+ error: `Generated filename is invalid: ${filename}`,
2324
+ requestId
2325
+ });
2326
+ break;
2327
+ }
2328
+ const screenshotsDir = join3(serverAppRootForVision, ".uilint", "screenshots");
2329
+ if (!existsSync5(screenshotsDir)) {
2330
+ mkdirSync4(screenshotsDir, { recursive: true });
2331
+ }
2332
+ const imagePath = join3(screenshotsDir, filename);
2333
+ const imageBuffer = Buffer.from(base64Data, "base64");
2334
+ writeFileSync4(imagePath, imageBuffer);
2335
+ const sidecarFilename = filename.replace(/\.png$/, ".json");
2336
+ const sidecarPath = join3(screenshotsDir, sidecarFilename);
2337
+ const sidecarData = {
2338
+ route,
2339
+ timestamp,
2340
+ filename,
2341
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
2342
+ };
2343
+ writeFileSync4(sidecarPath, JSON.stringify(sidecarData, null, 2));
2344
+ logInfo(
2345
+ `${pc.dim("[ws]")} screenshot:saved ${pc.dim(filename)} ${pc.dim(
2346
+ `(${Math.round(imageBuffer.length / 1024)}kb)`
2347
+ )}`
2348
+ );
2349
+ sendMessage(ws, {
2350
+ type: "screenshot:saved",
2351
+ filename,
2352
+ path: imagePath,
2353
+ requestId
2354
+ });
2355
+ } catch (error) {
2356
+ const errorMessage = error instanceof Error ? error.message : String(error);
2357
+ logError(`${pc.dim("[ws]")} screenshot:error ${errorMessage}`);
2358
+ sendMessage(ws, {
2359
+ type: "screenshot:error",
2360
+ error: errorMessage,
2361
+ requestId
2362
+ });
2363
+ }
2364
+ break;
2365
+ }
2237
2366
  }
2238
2367
  }
2239
2368
  function handleDisconnect(ws) {
@@ -2519,6 +2648,14 @@ async function serve(options) {
2519
2648
  workspaceRoot: wsRoot,
2520
2649
  serverCwd: cwd
2521
2650
  });
2651
+ const hookInfo = detectPostToolUseHook(appRoot);
2652
+ sendMessage(ws, {
2653
+ type: "workspace:capabilities",
2654
+ postToolUseHook: hookInfo
2655
+ });
2656
+ if (hookInfo.enabled) {
2657
+ logInfo(`${pc.dim("[ws]")} Post-tool-use hook detected: ${pc.bold(hookInfo.provider)}`);
2658
+ }
2522
2659
  const eslintConfigPath = findEslintConfigFile(appRoot);
2523
2660
  const currentRuleConfigs = eslintConfigPath ? readRuleConfigsFromConfig(eslintConfigPath) : /* @__PURE__ */ new Map();
2524
2661
  sendMessage(ws, {
@@ -3679,11 +3816,11 @@ program.command("update").description("Update existing style guide with new styl
3679
3816
  });
3680
3817
  });
3681
3818
  program.command("init").description("Initialize UILint integration").option("--force", "Overwrite existing configuration files").action(async (options) => {
3682
- const { initUI } = await import("./init-ui-D43GYBCT.js");
3819
+ const { initUI } = await import("./init-ui-Z54KCM7G.js");
3683
3820
  await initUI({ force: options.force });
3684
3821
  });
3685
3822
  program.command("remove").description("Remove UILint components from your project").option("--dry-run", "Preview changes without removing anything").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
3686
- const { removeUI } = await import("./remove-ui-WPXAIH3W.js");
3823
+ const { removeUI } = await import("./remove-ui-U2VUMSP7.js");
3687
3824
  await removeUI({ dryRun: options.dryRun, yes: options.yes });
3688
3825
  });
3689
3826
  program.command("serve").description("Start WebSocket server for real-time UI linting").option("-p, --port <number>", "Port to listen on", "9234").action(async (options) => {