uilint-core 0.2.0 → 0.2.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.
package/dist/node.js CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  truncateHTML,
36
36
  validateStyleGuide,
37
37
  validateViolations
38
- } from "./chunk-FIESH4VM.js";
38
+ } from "./chunk-23BX6XC7.js";
39
39
 
40
40
  // src/logger.ts
41
41
  import pc from "picocolors";
@@ -92,7 +92,7 @@ import { spawn, spawnSync } from "child_process";
92
92
  import readline from "readline";
93
93
  var DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434";
94
94
  function sleep(ms) {
95
- return new Promise((resolve) => setTimeout(resolve, ms));
95
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
96
96
  }
97
97
  function isInteractiveTTY() {
98
98
  return Boolean(process.stdin.isTTY && process.stdout.isTTY);
@@ -102,11 +102,11 @@ async function promptYesNo(question) {
102
102
  input: process.stdin,
103
103
  output: process.stdout
104
104
  });
105
- return await new Promise((resolve) => {
105
+ return await new Promise((resolve2) => {
106
106
  rl.question(`${question} (y/N) `, (answer) => {
107
107
  rl.close();
108
108
  const normalized = (answer || "").trim().toLowerCase();
109
- resolve(normalized === "y" || normalized === "yes");
109
+ resolve2(normalized === "y" || normalized === "yes");
110
110
  });
111
111
  });
112
112
  }
@@ -149,11 +149,11 @@ async function maybeInstallOllamaWithBrew() {
149
149
  "Ollama is not installed. Install with Homebrew now?"
150
150
  );
151
151
  if (!ok) return false;
152
- await new Promise((resolve, reject) => {
152
+ await new Promise((resolve2, reject) => {
153
153
  const child = spawn("brew", ["install", "ollama"], { stdio: "inherit" });
154
154
  child.on("error", reject);
155
155
  child.on("exit", (code) => {
156
- if (code === 0) resolve();
156
+ if (code === 0) resolve2();
157
157
  else reject(new Error(`brew install ollama failed (exit ${code})`));
158
158
  });
159
159
  });
@@ -226,11 +226,11 @@ async function ensureOllamaModelPulled(options) {
226
226
  const baseUrl = options?.baseUrl || DEFAULT_OLLAMA_BASE_URL;
227
227
  const tags = await fetchOllamaTags(baseUrl);
228
228
  if (tags.includes(desired)) return;
229
- await new Promise((resolve, reject) => {
229
+ await new Promise((resolve2, reject) => {
230
230
  const child = spawn("ollama", ["pull", desired], { stdio: "inherit" });
231
231
  child.on("error", reject);
232
232
  child.on("exit", (code) => {
233
- if (code === 0) resolve();
233
+ if (code === 0) resolve2();
234
234
  else reject(new Error(`ollama pull ${desired} failed (exit ${code})`));
235
235
  });
236
236
  });
@@ -295,11 +295,11 @@ function isJSON(str) {
295
295
  }
296
296
  async function readStdin() {
297
297
  const chunks = [];
298
- return new Promise((resolve, reject) => {
298
+ return new Promise((resolve2, reject) => {
299
299
  process.stdin.on("data", (chunk) => {
300
300
  chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
301
301
  });
302
- process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
302
+ process.stdin.on("end", () => resolve2(Buffer.concat(chunks).toString("utf-8")));
303
303
  process.stdin.on("error", reject);
304
304
  });
305
305
  }
@@ -481,10 +481,1493 @@ function addColorTokens(out, colors) {
481
481
  }
482
482
  }
483
483
  }
484
+
485
+ // ../../node_modules/.pnpm/ollama@0.6.3/node_modules/ollama/dist/index.mjs
486
+ import fs, { promises } from "fs";
487
+ import { resolve } from "path";
488
+
489
+ // ../../node_modules/.pnpm/whatwg-fetch@3.6.20/node_modules/whatwg-fetch/fetch.js
490
+ var g = typeof globalThis !== "undefined" && globalThis || typeof self !== "undefined" && self || // eslint-disable-next-line no-undef
491
+ typeof global !== "undefined" && global || {};
492
+ var support = {
493
+ searchParams: "URLSearchParams" in g,
494
+ iterable: "Symbol" in g && "iterator" in Symbol,
495
+ blob: "FileReader" in g && "Blob" in g && (function() {
496
+ try {
497
+ new Blob();
498
+ return true;
499
+ } catch (e) {
500
+ return false;
501
+ }
502
+ })(),
503
+ formData: "FormData" in g,
504
+ arrayBuffer: "ArrayBuffer" in g
505
+ };
506
+ function isDataView(obj) {
507
+ return obj && DataView.prototype.isPrototypeOf(obj);
508
+ }
509
+ if (support.arrayBuffer) {
510
+ viewClasses = [
511
+ "[object Int8Array]",
512
+ "[object Uint8Array]",
513
+ "[object Uint8ClampedArray]",
514
+ "[object Int16Array]",
515
+ "[object Uint16Array]",
516
+ "[object Int32Array]",
517
+ "[object Uint32Array]",
518
+ "[object Float32Array]",
519
+ "[object Float64Array]"
520
+ ];
521
+ isArrayBufferView = ArrayBuffer.isView || function(obj) {
522
+ return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;
523
+ };
524
+ }
525
+ var viewClasses;
526
+ var isArrayBufferView;
527
+ function normalizeName(name) {
528
+ if (typeof name !== "string") {
529
+ name = String(name);
530
+ }
531
+ if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === "") {
532
+ throw new TypeError('Invalid character in header field name: "' + name + '"');
533
+ }
534
+ return name.toLowerCase();
535
+ }
536
+ function normalizeValue(value) {
537
+ if (typeof value !== "string") {
538
+ value = String(value);
539
+ }
540
+ return value;
541
+ }
542
+ function iteratorFor(items) {
543
+ var iterator = {
544
+ next: function() {
545
+ var value = items.shift();
546
+ return { done: value === void 0, value };
547
+ }
548
+ };
549
+ if (support.iterable) {
550
+ iterator[Symbol.iterator] = function() {
551
+ return iterator;
552
+ };
553
+ }
554
+ return iterator;
555
+ }
556
+ function Headers2(headers) {
557
+ this.map = {};
558
+ if (headers instanceof Headers2) {
559
+ headers.forEach(function(value, name) {
560
+ this.append(name, value);
561
+ }, this);
562
+ } else if (Array.isArray(headers)) {
563
+ headers.forEach(function(header) {
564
+ if (header.length != 2) {
565
+ throw new TypeError("Headers constructor: expected name/value pair to be length 2, found" + header.length);
566
+ }
567
+ this.append(header[0], header[1]);
568
+ }, this);
569
+ } else if (headers) {
570
+ Object.getOwnPropertyNames(headers).forEach(function(name) {
571
+ this.append(name, headers[name]);
572
+ }, this);
573
+ }
574
+ }
575
+ Headers2.prototype.append = function(name, value) {
576
+ name = normalizeName(name);
577
+ value = normalizeValue(value);
578
+ var oldValue = this.map[name];
579
+ this.map[name] = oldValue ? oldValue + ", " + value : value;
580
+ };
581
+ Headers2.prototype["delete"] = function(name) {
582
+ delete this.map[normalizeName(name)];
583
+ };
584
+ Headers2.prototype.get = function(name) {
585
+ name = normalizeName(name);
586
+ return this.has(name) ? this.map[name] : null;
587
+ };
588
+ Headers2.prototype.has = function(name) {
589
+ return this.map.hasOwnProperty(normalizeName(name));
590
+ };
591
+ Headers2.prototype.set = function(name, value) {
592
+ this.map[normalizeName(name)] = normalizeValue(value);
593
+ };
594
+ Headers2.prototype.forEach = function(callback, thisArg) {
595
+ for (var name in this.map) {
596
+ if (this.map.hasOwnProperty(name)) {
597
+ callback.call(thisArg, this.map[name], name, this);
598
+ }
599
+ }
600
+ };
601
+ Headers2.prototype.keys = function() {
602
+ var items = [];
603
+ this.forEach(function(value, name) {
604
+ items.push(name);
605
+ });
606
+ return iteratorFor(items);
607
+ };
608
+ Headers2.prototype.values = function() {
609
+ var items = [];
610
+ this.forEach(function(value) {
611
+ items.push(value);
612
+ });
613
+ return iteratorFor(items);
614
+ };
615
+ Headers2.prototype.entries = function() {
616
+ var items = [];
617
+ this.forEach(function(value, name) {
618
+ items.push([name, value]);
619
+ });
620
+ return iteratorFor(items);
621
+ };
622
+ if (support.iterable) {
623
+ Headers2.prototype[Symbol.iterator] = Headers2.prototype.entries;
624
+ }
625
+ function consumed(body) {
626
+ if (body._noBody) return;
627
+ if (body.bodyUsed) {
628
+ return Promise.reject(new TypeError("Already read"));
629
+ }
630
+ body.bodyUsed = true;
631
+ }
632
+ function fileReaderReady(reader) {
633
+ return new Promise(function(resolve2, reject) {
634
+ reader.onload = function() {
635
+ resolve2(reader.result);
636
+ };
637
+ reader.onerror = function() {
638
+ reject(reader.error);
639
+ };
640
+ });
641
+ }
642
+ function readBlobAsArrayBuffer(blob) {
643
+ var reader = new FileReader();
644
+ var promise = fileReaderReady(reader);
645
+ reader.readAsArrayBuffer(blob);
646
+ return promise;
647
+ }
648
+ function readBlobAsText(blob) {
649
+ var reader = new FileReader();
650
+ var promise = fileReaderReady(reader);
651
+ var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type);
652
+ var encoding = match ? match[1] : "utf-8";
653
+ reader.readAsText(blob, encoding);
654
+ return promise;
655
+ }
656
+ function readArrayBufferAsText(buf) {
657
+ var view = new Uint8Array(buf);
658
+ var chars = new Array(view.length);
659
+ for (var i = 0; i < view.length; i++) {
660
+ chars[i] = String.fromCharCode(view[i]);
661
+ }
662
+ return chars.join("");
663
+ }
664
+ function bufferClone(buf) {
665
+ if (buf.slice) {
666
+ return buf.slice(0);
667
+ } else {
668
+ var view = new Uint8Array(buf.byteLength);
669
+ view.set(new Uint8Array(buf));
670
+ return view.buffer;
671
+ }
672
+ }
673
+ function Body() {
674
+ this.bodyUsed = false;
675
+ this._initBody = function(body) {
676
+ this.bodyUsed = this.bodyUsed;
677
+ this._bodyInit = body;
678
+ if (!body) {
679
+ this._noBody = true;
680
+ this._bodyText = "";
681
+ } else if (typeof body === "string") {
682
+ this._bodyText = body;
683
+ } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
684
+ this._bodyBlob = body;
685
+ } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
686
+ this._bodyFormData = body;
687
+ } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
688
+ this._bodyText = body.toString();
689
+ } else if (support.arrayBuffer && support.blob && isDataView(body)) {
690
+ this._bodyArrayBuffer = bufferClone(body.buffer);
691
+ this._bodyInit = new Blob([this._bodyArrayBuffer]);
692
+ } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
693
+ this._bodyArrayBuffer = bufferClone(body);
694
+ } else {
695
+ this._bodyText = body = Object.prototype.toString.call(body);
696
+ }
697
+ if (!this.headers.get("content-type")) {
698
+ if (typeof body === "string") {
699
+ this.headers.set("content-type", "text/plain;charset=UTF-8");
700
+ } else if (this._bodyBlob && this._bodyBlob.type) {
701
+ this.headers.set("content-type", this._bodyBlob.type);
702
+ } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
703
+ this.headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
704
+ }
705
+ }
706
+ };
707
+ if (support.blob) {
708
+ this.blob = function() {
709
+ var rejected = consumed(this);
710
+ if (rejected) {
711
+ return rejected;
712
+ }
713
+ if (this._bodyBlob) {
714
+ return Promise.resolve(this._bodyBlob);
715
+ } else if (this._bodyArrayBuffer) {
716
+ return Promise.resolve(new Blob([this._bodyArrayBuffer]));
717
+ } else if (this._bodyFormData) {
718
+ throw new Error("could not read FormData body as blob");
719
+ } else {
720
+ return Promise.resolve(new Blob([this._bodyText]));
721
+ }
722
+ };
723
+ }
724
+ this.arrayBuffer = function() {
725
+ if (this._bodyArrayBuffer) {
726
+ var isConsumed = consumed(this);
727
+ if (isConsumed) {
728
+ return isConsumed;
729
+ } else if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
730
+ return Promise.resolve(
731
+ this._bodyArrayBuffer.buffer.slice(
732
+ this._bodyArrayBuffer.byteOffset,
733
+ this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
734
+ )
735
+ );
736
+ } else {
737
+ return Promise.resolve(this._bodyArrayBuffer);
738
+ }
739
+ } else if (support.blob) {
740
+ return this.blob().then(readBlobAsArrayBuffer);
741
+ } else {
742
+ throw new Error("could not read as ArrayBuffer");
743
+ }
744
+ };
745
+ this.text = function() {
746
+ var rejected = consumed(this);
747
+ if (rejected) {
748
+ return rejected;
749
+ }
750
+ if (this._bodyBlob) {
751
+ return readBlobAsText(this._bodyBlob);
752
+ } else if (this._bodyArrayBuffer) {
753
+ return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer));
754
+ } else if (this._bodyFormData) {
755
+ throw new Error("could not read FormData body as text");
756
+ } else {
757
+ return Promise.resolve(this._bodyText);
758
+ }
759
+ };
760
+ if (support.formData) {
761
+ this.formData = function() {
762
+ return this.text().then(decode);
763
+ };
764
+ }
765
+ this.json = function() {
766
+ return this.text().then(JSON.parse);
767
+ };
768
+ return this;
769
+ }
770
+ var methods = ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"];
771
+ function normalizeMethod(method) {
772
+ var upcased = method.toUpperCase();
773
+ return methods.indexOf(upcased) > -1 ? upcased : method;
774
+ }
775
+ function Request(input, options) {
776
+ if (!(this instanceof Request)) {
777
+ throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');
778
+ }
779
+ options = options || {};
780
+ var body = options.body;
781
+ if (input instanceof Request) {
782
+ if (input.bodyUsed) {
783
+ throw new TypeError("Already read");
784
+ }
785
+ this.url = input.url;
786
+ this.credentials = input.credentials;
787
+ if (!options.headers) {
788
+ this.headers = new Headers2(input.headers);
789
+ }
790
+ this.method = input.method;
791
+ this.mode = input.mode;
792
+ this.signal = input.signal;
793
+ if (!body && input._bodyInit != null) {
794
+ body = input._bodyInit;
795
+ input.bodyUsed = true;
796
+ }
797
+ } else {
798
+ this.url = String(input);
799
+ }
800
+ this.credentials = options.credentials || this.credentials || "same-origin";
801
+ if (options.headers || !this.headers) {
802
+ this.headers = new Headers2(options.headers);
803
+ }
804
+ this.method = normalizeMethod(options.method || this.method || "GET");
805
+ this.mode = options.mode || this.mode || null;
806
+ this.signal = options.signal || this.signal || (function() {
807
+ if ("AbortController" in g) {
808
+ var ctrl = new AbortController();
809
+ return ctrl.signal;
810
+ }
811
+ })();
812
+ this.referrer = null;
813
+ if ((this.method === "GET" || this.method === "HEAD") && body) {
814
+ throw new TypeError("Body not allowed for GET or HEAD requests");
815
+ }
816
+ this._initBody(body);
817
+ if (this.method === "GET" || this.method === "HEAD") {
818
+ if (options.cache === "no-store" || options.cache === "no-cache") {
819
+ var reParamSearch = /([?&])_=[^&]*/;
820
+ if (reParamSearch.test(this.url)) {
821
+ this.url = this.url.replace(reParamSearch, "$1_=" + (/* @__PURE__ */ new Date()).getTime());
822
+ } else {
823
+ var reQueryString = /\?/;
824
+ this.url += (reQueryString.test(this.url) ? "&" : "?") + "_=" + (/* @__PURE__ */ new Date()).getTime();
825
+ }
826
+ }
827
+ }
828
+ }
829
+ Request.prototype.clone = function() {
830
+ return new Request(this, { body: this._bodyInit });
831
+ };
832
+ function decode(body) {
833
+ var form = new FormData();
834
+ body.trim().split("&").forEach(function(bytes) {
835
+ if (bytes) {
836
+ var split = bytes.split("=");
837
+ var name = split.shift().replace(/\+/g, " ");
838
+ var value = split.join("=").replace(/\+/g, " ");
839
+ form.append(decodeURIComponent(name), decodeURIComponent(value));
840
+ }
841
+ });
842
+ return form;
843
+ }
844
+ function parseHeaders(rawHeaders) {
845
+ var headers = new Headers2();
846
+ var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, " ");
847
+ preProcessedHeaders.split("\r").map(function(header) {
848
+ return header.indexOf("\n") === 0 ? header.substr(1, header.length) : header;
849
+ }).forEach(function(line) {
850
+ var parts = line.split(":");
851
+ var key = parts.shift().trim();
852
+ if (key) {
853
+ var value = parts.join(":").trim();
854
+ try {
855
+ headers.append(key, value);
856
+ } catch (error) {
857
+ console.warn("Response " + error.message);
858
+ }
859
+ }
860
+ });
861
+ return headers;
862
+ }
863
+ Body.call(Request.prototype);
864
+ function Response(bodyInit, options) {
865
+ if (!(this instanceof Response)) {
866
+ throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');
867
+ }
868
+ if (!options) {
869
+ options = {};
870
+ }
871
+ this.type = "default";
872
+ this.status = options.status === void 0 ? 200 : options.status;
873
+ if (this.status < 200 || this.status > 599) {
874
+ throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].");
875
+ }
876
+ this.ok = this.status >= 200 && this.status < 300;
877
+ this.statusText = options.statusText === void 0 ? "" : "" + options.statusText;
878
+ this.headers = new Headers2(options.headers);
879
+ this.url = options.url || "";
880
+ this._initBody(bodyInit);
881
+ }
882
+ Body.call(Response.prototype);
883
+ Response.prototype.clone = function() {
884
+ return new Response(this._bodyInit, {
885
+ status: this.status,
886
+ statusText: this.statusText,
887
+ headers: new Headers2(this.headers),
888
+ url: this.url
889
+ });
890
+ };
891
+ Response.error = function() {
892
+ var response = new Response(null, { status: 200, statusText: "" });
893
+ response.ok = false;
894
+ response.status = 0;
895
+ response.type = "error";
896
+ return response;
897
+ };
898
+ var redirectStatuses = [301, 302, 303, 307, 308];
899
+ Response.redirect = function(url, status) {
900
+ if (redirectStatuses.indexOf(status) === -1) {
901
+ throw new RangeError("Invalid status code");
902
+ }
903
+ return new Response(null, { status, headers: { location: url } });
904
+ };
905
+ var DOMException = g.DOMException;
906
+ try {
907
+ new DOMException();
908
+ } catch (err) {
909
+ DOMException = function(message, name) {
910
+ this.message = message;
911
+ this.name = name;
912
+ var error = Error(message);
913
+ this.stack = error.stack;
914
+ };
915
+ DOMException.prototype = Object.create(Error.prototype);
916
+ DOMException.prototype.constructor = DOMException;
917
+ }
918
+ function fetch2(input, init) {
919
+ return new Promise(function(resolve2, reject) {
920
+ var request = new Request(input, init);
921
+ if (request.signal && request.signal.aborted) {
922
+ return reject(new DOMException("Aborted", "AbortError"));
923
+ }
924
+ var xhr = new XMLHttpRequest();
925
+ function abortXhr() {
926
+ xhr.abort();
927
+ }
928
+ xhr.onload = function() {
929
+ var options = {
930
+ statusText: xhr.statusText,
931
+ headers: parseHeaders(xhr.getAllResponseHeaders() || "")
932
+ };
933
+ if (request.url.indexOf("file://") === 0 && (xhr.status < 200 || xhr.status > 599)) {
934
+ options.status = 200;
935
+ } else {
936
+ options.status = xhr.status;
937
+ }
938
+ options.url = "responseURL" in xhr ? xhr.responseURL : options.headers.get("X-Request-URL");
939
+ var body = "response" in xhr ? xhr.response : xhr.responseText;
940
+ setTimeout(function() {
941
+ resolve2(new Response(body, options));
942
+ }, 0);
943
+ };
944
+ xhr.onerror = function() {
945
+ setTimeout(function() {
946
+ reject(new TypeError("Network request failed"));
947
+ }, 0);
948
+ };
949
+ xhr.ontimeout = function() {
950
+ setTimeout(function() {
951
+ reject(new TypeError("Network request timed out"));
952
+ }, 0);
953
+ };
954
+ xhr.onabort = function() {
955
+ setTimeout(function() {
956
+ reject(new DOMException("Aborted", "AbortError"));
957
+ }, 0);
958
+ };
959
+ function fixUrl(url) {
960
+ try {
961
+ return url === "" && g.location.href ? g.location.href : url;
962
+ } catch (e) {
963
+ return url;
964
+ }
965
+ }
966
+ xhr.open(request.method, fixUrl(request.url), true);
967
+ if (request.credentials === "include") {
968
+ xhr.withCredentials = true;
969
+ } else if (request.credentials === "omit") {
970
+ xhr.withCredentials = false;
971
+ }
972
+ if ("responseType" in xhr) {
973
+ if (support.blob) {
974
+ xhr.responseType = "blob";
975
+ } else if (support.arrayBuffer) {
976
+ xhr.responseType = "arraybuffer";
977
+ }
978
+ }
979
+ if (init && typeof init.headers === "object" && !(init.headers instanceof Headers2 || g.Headers && init.headers instanceof g.Headers)) {
980
+ var names = [];
981
+ Object.getOwnPropertyNames(init.headers).forEach(function(name) {
982
+ names.push(normalizeName(name));
983
+ xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
984
+ });
985
+ request.headers.forEach(function(value, name) {
986
+ if (names.indexOf(name) === -1) {
987
+ xhr.setRequestHeader(name, value);
988
+ }
989
+ });
990
+ } else {
991
+ request.headers.forEach(function(value, name) {
992
+ xhr.setRequestHeader(name, value);
993
+ });
994
+ }
995
+ if (request.signal) {
996
+ request.signal.addEventListener("abort", abortXhr);
997
+ xhr.onreadystatechange = function() {
998
+ if (xhr.readyState === 4) {
999
+ request.signal.removeEventListener("abort", abortXhr);
1000
+ }
1001
+ };
1002
+ }
1003
+ xhr.send(typeof request._bodyInit === "undefined" ? null : request._bodyInit);
1004
+ });
1005
+ }
1006
+ fetch2.polyfill = true;
1007
+ if (!g.fetch) {
1008
+ g.fetch = fetch2;
1009
+ g.Headers = Headers2;
1010
+ g.Request = Request;
1011
+ g.Response = Response;
1012
+ }
1013
+
1014
+ // ../../node_modules/.pnpm/ollama@0.6.3/node_modules/ollama/dist/browser.mjs
1015
+ var defaultPort = "11434";
1016
+ var defaultHost = `http://127.0.0.1:${defaultPort}`;
1017
+ var version = "0.6.3";
1018
+ var __defProp$1 = Object.defineProperty;
1019
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1020
+ var __publicField$1 = (obj, key, value) => {
1021
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1022
+ return value;
1023
+ };
1024
+ var ResponseError = class _ResponseError extends Error {
1025
+ constructor(error, status_code) {
1026
+ super(error);
1027
+ this.error = error;
1028
+ this.status_code = status_code;
1029
+ this.name = "ResponseError";
1030
+ if (Error.captureStackTrace) {
1031
+ Error.captureStackTrace(this, _ResponseError);
1032
+ }
1033
+ }
1034
+ };
1035
+ var AbortableAsyncIterator = class {
1036
+ constructor(abortController, itr, doneCallback) {
1037
+ __publicField$1(this, "abortController");
1038
+ __publicField$1(this, "itr");
1039
+ __publicField$1(this, "doneCallback");
1040
+ this.abortController = abortController;
1041
+ this.itr = itr;
1042
+ this.doneCallback = doneCallback;
1043
+ }
1044
+ abort() {
1045
+ this.abortController.abort();
1046
+ }
1047
+ async *[Symbol.asyncIterator]() {
1048
+ for await (const message of this.itr) {
1049
+ if ("error" in message) {
1050
+ throw new Error(message.error);
1051
+ }
1052
+ yield message;
1053
+ if (message.done || message.status === "success") {
1054
+ this.doneCallback();
1055
+ return;
1056
+ }
1057
+ }
1058
+ throw new Error("Did not receive done or success response in stream.");
1059
+ }
1060
+ };
1061
+ var checkOk = async (response) => {
1062
+ if (response.ok) {
1063
+ return;
1064
+ }
1065
+ let message = `Error ${response.status}: ${response.statusText}`;
1066
+ let errorData = null;
1067
+ if (response.headers.get("content-type")?.includes("application/json")) {
1068
+ try {
1069
+ errorData = await response.json();
1070
+ message = errorData.error || message;
1071
+ } catch (error) {
1072
+ console.log("Failed to parse error response as JSON");
1073
+ }
1074
+ } else {
1075
+ try {
1076
+ console.log("Getting text from response");
1077
+ const textResponse = await response.text();
1078
+ message = textResponse || message;
1079
+ } catch (error) {
1080
+ console.log("Failed to get text from error response");
1081
+ }
1082
+ }
1083
+ throw new ResponseError(message, response.status);
1084
+ };
1085
+ function getPlatform() {
1086
+ if (typeof window !== "undefined" && window.navigator) {
1087
+ const nav = navigator;
1088
+ if ("userAgentData" in nav && nav.userAgentData?.platform) {
1089
+ return `${nav.userAgentData.platform.toLowerCase()} Browser/${navigator.userAgent};`;
1090
+ }
1091
+ if (navigator.platform) {
1092
+ return `${navigator.platform.toLowerCase()} Browser/${navigator.userAgent};`;
1093
+ }
1094
+ return `unknown Browser/${navigator.userAgent};`;
1095
+ } else if (typeof process !== "undefined") {
1096
+ return `${process.arch} ${process.platform} Node.js/${process.version}`;
1097
+ }
1098
+ return "";
1099
+ }
1100
+ function normalizeHeaders(headers) {
1101
+ if (headers instanceof Headers) {
1102
+ const obj = {};
1103
+ headers.forEach((value, key) => {
1104
+ obj[key] = value;
1105
+ });
1106
+ return obj;
1107
+ } else if (Array.isArray(headers)) {
1108
+ return Object.fromEntries(headers);
1109
+ } else {
1110
+ return headers || {};
1111
+ }
1112
+ }
1113
+ var readEnvVar = (obj, key) => {
1114
+ return obj[key];
1115
+ };
1116
+ var fetchWithHeaders = async (fetch3, url, options = {}) => {
1117
+ const defaultHeaders = {
1118
+ "Content-Type": "application/json",
1119
+ Accept: "application/json",
1120
+ "User-Agent": `ollama-js/${version} (${getPlatform()})`
1121
+ };
1122
+ options.headers = normalizeHeaders(options.headers);
1123
+ try {
1124
+ const parsed = new URL(url);
1125
+ if (parsed.protocol === "https:" && parsed.hostname === "ollama.com") {
1126
+ const apiKey = typeof process === "object" && process !== null && typeof process.env === "object" && process.env !== null ? readEnvVar(process.env, "OLLAMA_API_KEY") : void 0;
1127
+ const authorization = options.headers["authorization"] || options.headers["Authorization"];
1128
+ if (!authorization && apiKey) {
1129
+ options.headers["Authorization"] = `Bearer ${apiKey}`;
1130
+ }
1131
+ }
1132
+ } catch (error) {
1133
+ console.error("error parsing url", error);
1134
+ }
1135
+ const customHeaders = Object.fromEntries(
1136
+ Object.entries(options.headers).filter(
1137
+ ([key]) => !Object.keys(defaultHeaders).some(
1138
+ (defaultKey) => defaultKey.toLowerCase() === key.toLowerCase()
1139
+ )
1140
+ )
1141
+ );
1142
+ options.headers = {
1143
+ ...defaultHeaders,
1144
+ ...customHeaders
1145
+ };
1146
+ return fetch3(url, options);
1147
+ };
1148
+ var get = async (fetch3, host, options) => {
1149
+ const response = await fetchWithHeaders(fetch3, host, {
1150
+ headers: options?.headers
1151
+ });
1152
+ await checkOk(response);
1153
+ return response;
1154
+ };
1155
+ var post = async (fetch3, host, data, options) => {
1156
+ const isRecord = (input) => {
1157
+ return input !== null && typeof input === "object" && !Array.isArray(input);
1158
+ };
1159
+ const formattedData = isRecord(data) ? JSON.stringify(data) : data;
1160
+ const response = await fetchWithHeaders(fetch3, host, {
1161
+ method: "POST",
1162
+ body: formattedData,
1163
+ signal: options?.signal,
1164
+ headers: options?.headers
1165
+ });
1166
+ await checkOk(response);
1167
+ return response;
1168
+ };
1169
+ var del = async (fetch3, host, data, options) => {
1170
+ const response = await fetchWithHeaders(fetch3, host, {
1171
+ method: "DELETE",
1172
+ body: JSON.stringify(data),
1173
+ headers: options?.headers
1174
+ });
1175
+ await checkOk(response);
1176
+ return response;
1177
+ };
1178
+ var parseJSON = async function* (itr) {
1179
+ const decoder = new TextDecoder("utf-8");
1180
+ let buffer = "";
1181
+ const reader = itr.getReader();
1182
+ while (true) {
1183
+ const { done, value: chunk } = await reader.read();
1184
+ if (done) {
1185
+ break;
1186
+ }
1187
+ buffer += decoder.decode(chunk, { stream: true });
1188
+ const parts = buffer.split("\n");
1189
+ buffer = parts.pop() ?? "";
1190
+ for (const part of parts) {
1191
+ try {
1192
+ yield JSON.parse(part);
1193
+ } catch (error) {
1194
+ console.warn("invalid json: ", part);
1195
+ }
1196
+ }
1197
+ }
1198
+ buffer += decoder.decode();
1199
+ for (const part of buffer.split("\n").filter((p) => p !== "")) {
1200
+ try {
1201
+ yield JSON.parse(part);
1202
+ } catch (error) {
1203
+ console.warn("invalid json: ", part);
1204
+ }
1205
+ }
1206
+ };
1207
+ var formatHost = (host) => {
1208
+ if (!host) {
1209
+ return defaultHost;
1210
+ }
1211
+ let isExplicitProtocol = host.includes("://");
1212
+ if (host.startsWith(":")) {
1213
+ host = `http://127.0.0.1${host}`;
1214
+ isExplicitProtocol = true;
1215
+ }
1216
+ if (!isExplicitProtocol) {
1217
+ host = `http://${host}`;
1218
+ }
1219
+ const url = new URL(host);
1220
+ let port = url.port;
1221
+ if (!port) {
1222
+ if (!isExplicitProtocol) {
1223
+ port = defaultPort;
1224
+ } else {
1225
+ port = url.protocol === "https:" ? "443" : "80";
1226
+ }
1227
+ }
1228
+ let auth = "";
1229
+ if (url.username) {
1230
+ auth = url.username;
1231
+ if (url.password) {
1232
+ auth += `:${url.password}`;
1233
+ }
1234
+ auth += "@";
1235
+ }
1236
+ let formattedHost = `${url.protocol}//${auth}${url.hostname}:${port}${url.pathname}`;
1237
+ if (formattedHost.endsWith("/")) {
1238
+ formattedHost = formattedHost.slice(0, -1);
1239
+ }
1240
+ return formattedHost;
1241
+ };
1242
+ var __defProp = Object.defineProperty;
1243
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1244
+ var __publicField = (obj, key, value) => {
1245
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1246
+ return value;
1247
+ };
1248
+ var Ollama$1 = class Ollama {
1249
+ constructor(config) {
1250
+ __publicField(this, "config");
1251
+ __publicField(this, "fetch");
1252
+ __publicField(this, "ongoingStreamedRequests", []);
1253
+ this.config = {
1254
+ host: "",
1255
+ headers: config?.headers
1256
+ };
1257
+ if (!config?.proxy) {
1258
+ this.config.host = formatHost(config?.host ?? defaultHost);
1259
+ }
1260
+ this.fetch = config?.fetch ?? fetch;
1261
+ }
1262
+ // Abort any ongoing streamed requests to Ollama
1263
+ abort() {
1264
+ for (const request of this.ongoingStreamedRequests) {
1265
+ request.abort();
1266
+ }
1267
+ this.ongoingStreamedRequests.length = 0;
1268
+ }
1269
+ /**
1270
+ * Processes a request to the Ollama server. If the request is streamable, it will return a
1271
+ * AbortableAsyncIterator that yields the response messages. Otherwise, it will return the response
1272
+ * object.
1273
+ * @param endpoint {string} - The endpoint to send the request to.
1274
+ * @param request {object} - The request object to send to the endpoint.
1275
+ * @protected {T | AbortableAsyncIterator<T>} - The response object or a AbortableAsyncIterator that yields
1276
+ * response messages.
1277
+ * @throws {Error} - If the response body is missing or if the response is an error.
1278
+ * @returns {Promise<T | AbortableAsyncIterator<T>>} - The response object or a AbortableAsyncIterator that yields the streamed response.
1279
+ */
1280
+ async processStreamableRequest(endpoint, request) {
1281
+ request.stream = request.stream ?? false;
1282
+ const host = `${this.config.host}/api/${endpoint}`;
1283
+ if (request.stream) {
1284
+ const abortController = new AbortController();
1285
+ const response2 = await post(this.fetch, host, request, {
1286
+ signal: abortController.signal,
1287
+ headers: this.config.headers
1288
+ });
1289
+ if (!response2.body) {
1290
+ throw new Error("Missing body");
1291
+ }
1292
+ const itr = parseJSON(response2.body);
1293
+ const abortableAsyncIterator = new AbortableAsyncIterator(
1294
+ abortController,
1295
+ itr,
1296
+ () => {
1297
+ const i = this.ongoingStreamedRequests.indexOf(abortableAsyncIterator);
1298
+ if (i > -1) {
1299
+ this.ongoingStreamedRequests.splice(i, 1);
1300
+ }
1301
+ }
1302
+ );
1303
+ this.ongoingStreamedRequests.push(abortableAsyncIterator);
1304
+ return abortableAsyncIterator;
1305
+ }
1306
+ const response = await post(this.fetch, host, request, {
1307
+ headers: this.config.headers
1308
+ });
1309
+ return await response.json();
1310
+ }
1311
+ /**
1312
+ * Encodes an image to base64 if it is a Uint8Array.
1313
+ * @param image {Uint8Array | string} - The image to encode.
1314
+ * @returns {Promise<string>} - The base64 encoded image.
1315
+ */
1316
+ async encodeImage(image) {
1317
+ if (typeof image !== "string") {
1318
+ const uint8Array = new Uint8Array(image);
1319
+ let byteString = "";
1320
+ const len = uint8Array.byteLength;
1321
+ for (let i = 0; i < len; i++) {
1322
+ byteString += String.fromCharCode(uint8Array[i]);
1323
+ }
1324
+ return btoa(byteString);
1325
+ }
1326
+ return image;
1327
+ }
1328
+ /**
1329
+ * Generates a response from a text prompt.
1330
+ * @param request {GenerateRequest} - The request object.
1331
+ * @returns {Promise<GenerateResponse | AbortableAsyncIterator<GenerateResponse>>} - The response object or
1332
+ * an AbortableAsyncIterator that yields response messages.
1333
+ */
1334
+ async generate(request) {
1335
+ if (request.images) {
1336
+ request.images = await Promise.all(request.images.map(this.encodeImage.bind(this)));
1337
+ }
1338
+ return this.processStreamableRequest("generate", request);
1339
+ }
1340
+ /**
1341
+ * Chats with the model. The request object can contain messages with images that are either
1342
+ * Uint8Arrays or base64 encoded strings. The images will be base64 encoded before sending the
1343
+ * request.
1344
+ * @param request {ChatRequest} - The request object.
1345
+ * @returns {Promise<ChatResponse | AbortableAsyncIterator<ChatResponse>>} - The response object or an
1346
+ * AbortableAsyncIterator that yields response messages.
1347
+ */
1348
+ async chat(request) {
1349
+ if (request.messages) {
1350
+ for (const message of request.messages) {
1351
+ if (message.images) {
1352
+ message.images = await Promise.all(
1353
+ message.images.map(this.encodeImage.bind(this))
1354
+ );
1355
+ }
1356
+ }
1357
+ }
1358
+ return this.processStreamableRequest("chat", request);
1359
+ }
1360
+ /**
1361
+ * Creates a new model from a stream of data.
1362
+ * @param request {CreateRequest} - The request object.
1363
+ * @returns {Promise<ProgressResponse | AbortableAsyncIterator<ProgressResponse>>} - The response object or a stream of progress responses.
1364
+ */
1365
+ async create(request) {
1366
+ return this.processStreamableRequest("create", {
1367
+ ...request
1368
+ });
1369
+ }
1370
+ /**
1371
+ * Pulls a model from the Ollama registry. The request object can contain a stream flag to indicate if the
1372
+ * response should be streamed.
1373
+ * @param request {PullRequest} - The request object.
1374
+ * @returns {Promise<ProgressResponse | AbortableAsyncIterator<ProgressResponse>>} - The response object or
1375
+ * an AbortableAsyncIterator that yields response messages.
1376
+ */
1377
+ async pull(request) {
1378
+ return this.processStreamableRequest("pull", {
1379
+ name: request.model,
1380
+ stream: request.stream,
1381
+ insecure: request.insecure
1382
+ });
1383
+ }
1384
+ /**
1385
+ * Pushes a model to the Ollama registry. The request object can contain a stream flag to indicate if the
1386
+ * response should be streamed.
1387
+ * @param request {PushRequest} - The request object.
1388
+ * @returns {Promise<ProgressResponse | AbortableAsyncIterator<ProgressResponse>>} - The response object or
1389
+ * an AbortableAsyncIterator that yields response messages.
1390
+ */
1391
+ async push(request) {
1392
+ return this.processStreamableRequest("push", {
1393
+ name: request.model,
1394
+ stream: request.stream,
1395
+ insecure: request.insecure
1396
+ });
1397
+ }
1398
+ /**
1399
+ * Deletes a model from the server. The request object should contain the name of the model to
1400
+ * delete.
1401
+ * @param request {DeleteRequest} - The request object.
1402
+ * @returns {Promise<StatusResponse>} - The response object.
1403
+ */
1404
+ async delete(request) {
1405
+ await del(
1406
+ this.fetch,
1407
+ `${this.config.host}/api/delete`,
1408
+ { name: request.model },
1409
+ { headers: this.config.headers }
1410
+ );
1411
+ return { status: "success" };
1412
+ }
1413
+ /**
1414
+ * Copies a model from one name to another. The request object should contain the name of the
1415
+ * model to copy and the new name.
1416
+ * @param request {CopyRequest} - The request object.
1417
+ * @returns {Promise<StatusResponse>} - The response object.
1418
+ */
1419
+ async copy(request) {
1420
+ await post(this.fetch, `${this.config.host}/api/copy`, { ...request }, {
1421
+ headers: this.config.headers
1422
+ });
1423
+ return { status: "success" };
1424
+ }
1425
+ /**
1426
+ * Lists the models on the server.
1427
+ * @returns {Promise<ListResponse>} - The response object.
1428
+ * @throws {Error} - If the response body is missing.
1429
+ */
1430
+ async list() {
1431
+ const response = await get(this.fetch, `${this.config.host}/api/tags`, {
1432
+ headers: this.config.headers
1433
+ });
1434
+ return await response.json();
1435
+ }
1436
+ /**
1437
+ * Shows the metadata of a model. The request object should contain the name of the model.
1438
+ * @param request {ShowRequest} - The request object.
1439
+ * @returns {Promise<ShowResponse>} - The response object.
1440
+ */
1441
+ async show(request) {
1442
+ const response = await post(this.fetch, `${this.config.host}/api/show`, {
1443
+ ...request
1444
+ }, {
1445
+ headers: this.config.headers
1446
+ });
1447
+ return await response.json();
1448
+ }
1449
+ /**
1450
+ * Embeds text input into vectors.
1451
+ * @param request {EmbedRequest} - The request object.
1452
+ * @returns {Promise<EmbedResponse>} - The response object.
1453
+ */
1454
+ async embed(request) {
1455
+ const response = await post(this.fetch, `${this.config.host}/api/embed`, {
1456
+ ...request
1457
+ }, {
1458
+ headers: this.config.headers
1459
+ });
1460
+ return await response.json();
1461
+ }
1462
+ /**
1463
+ * Embeds a text prompt into a vector.
1464
+ * @param request {EmbeddingsRequest} - The request object.
1465
+ * @returns {Promise<EmbeddingsResponse>} - The response object.
1466
+ */
1467
+ async embeddings(request) {
1468
+ const response = await post(this.fetch, `${this.config.host}/api/embeddings`, {
1469
+ ...request
1470
+ }, {
1471
+ headers: this.config.headers
1472
+ });
1473
+ return await response.json();
1474
+ }
1475
+ /**
1476
+ * Lists the running models on the server
1477
+ * @returns {Promise<ListResponse>} - The response object.
1478
+ * @throws {Error} - If the response body is missing.
1479
+ */
1480
+ async ps() {
1481
+ const response = await get(this.fetch, `${this.config.host}/api/ps`, {
1482
+ headers: this.config.headers
1483
+ });
1484
+ return await response.json();
1485
+ }
1486
+ /**
1487
+ * Returns the Ollama server version.
1488
+ * @returns {Promise<VersionResponse>} - The server version object.
1489
+ */
1490
+ async version() {
1491
+ const response = await get(this.fetch, `${this.config.host}/api/version`, {
1492
+ headers: this.config.headers
1493
+ });
1494
+ return await response.json();
1495
+ }
1496
+ /**
1497
+ * Performs web search using the Ollama web search API
1498
+ * @param request {WebSearchRequest} - The search request containing query and options
1499
+ * @returns {Promise<WebSearchResponse>} - The search results
1500
+ * @throws {Error} - If the request is invalid or the server returns an error
1501
+ */
1502
+ async webSearch(request) {
1503
+ if (!request.query || request.query.length === 0) {
1504
+ throw new Error("Query is required");
1505
+ }
1506
+ const response = await post(this.fetch, `https://ollama.com/api/web_search`, { ...request }, {
1507
+ headers: this.config.headers
1508
+ });
1509
+ return await response.json();
1510
+ }
1511
+ /**
1512
+ * Fetches a single page using the Ollama web fetch API
1513
+ * @param request {WebFetchRequest} - The fetch request containing a URL
1514
+ * @returns {Promise<WebFetchResponse>} - The fetch result
1515
+ * @throws {Error} - If the request is invalid or the server returns an error
1516
+ */
1517
+ async webFetch(request) {
1518
+ if (!request.url || request.url.length === 0) {
1519
+ throw new Error("URL is required");
1520
+ }
1521
+ const response = await post(this.fetch, `https://ollama.com/api/web_fetch`, { ...request }, { headers: this.config.headers });
1522
+ return await response.json();
1523
+ }
1524
+ };
1525
+ var browser = new Ollama$1();
1526
+
1527
+ // ../../node_modules/.pnpm/ollama@0.6.3/node_modules/ollama/dist/index.mjs
1528
+ var Ollama2 = class extends Ollama$1 {
1529
+ async encodeImage(image) {
1530
+ if (typeof image !== "string") {
1531
+ return Buffer.from(image).toString("base64");
1532
+ }
1533
+ try {
1534
+ if (fs.existsSync(image)) {
1535
+ const fileBuffer = await promises.readFile(resolve(image));
1536
+ return Buffer.from(fileBuffer).toString("base64");
1537
+ }
1538
+ } catch {
1539
+ }
1540
+ return image;
1541
+ }
1542
+ /**
1543
+ * checks if a file exists
1544
+ * @param path {string} - The path to the file
1545
+ * @private @internal
1546
+ * @returns {Promise<boolean>} - Whether the file exists or not
1547
+ */
1548
+ async fileExists(path) {
1549
+ try {
1550
+ await promises.access(path);
1551
+ return true;
1552
+ } catch {
1553
+ return false;
1554
+ }
1555
+ }
1556
+ async create(request) {
1557
+ if (request.from && await this.fileExists(resolve(request.from))) {
1558
+ throw Error("Creating with a local path is not currently supported from ollama-js");
1559
+ }
1560
+ if (request.stream) {
1561
+ return super.create(request);
1562
+ } else {
1563
+ return super.create(request);
1564
+ }
1565
+ }
1566
+ };
1567
+ var index = new Ollama2();
1568
+
1569
+ // src/scanner/vision-analyzer.ts
1570
+ var UILINT_DEFAULT_VISION_MODEL = "qwen3-vl:8b-instruct";
1571
+ function stripCodeFences(input) {
1572
+ const trimmed = (input ?? "").trim();
1573
+ if (!trimmed.startsWith("```")) return trimmed;
1574
+ const m = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
1575
+ return m ? m[1].trim() : trimmed;
1576
+ }
1577
+ function extractJsonObject(input) {
1578
+ const s = stripCodeFences(input).trim();
1579
+ if (!s) return s;
1580
+ if (s.startsWith("{") && s.endsWith("}")) return s;
1581
+ const start = s.indexOf("{");
1582
+ const end = s.lastIndexOf("}");
1583
+ if (start !== -1 && end !== -1 && end > start) {
1584
+ return s.slice(start, end + 1).trim();
1585
+ }
1586
+ return s;
1587
+ }
1588
+ var DEFAULT_BASE_URL = "http://localhost:11434";
1589
+ var DEFAULT_TIMEOUT = 12e4;
1590
+ var VisionAnalyzer = class {
1591
+ baseUrl;
1592
+ visionModel;
1593
+ timeout;
1594
+ instrumentation;
1595
+ client;
1596
+ constructor(options = {}) {
1597
+ this.baseUrl = options.baseUrl || DEFAULT_BASE_URL;
1598
+ this.visionModel = options.visionModel || UILINT_DEFAULT_VISION_MODEL;
1599
+ this.timeout = options.timeout || DEFAULT_TIMEOUT;
1600
+ this.instrumentation = options.instrumentation;
1601
+ this.client = new Ollama2({ host: this.baseUrl });
1602
+ }
1603
+ /**
1604
+ * Analyzes a screenshot with element manifest for UI consistency issues
1605
+ */
1606
+ async analyzeScreenshot(imageBase64, manifest, options = {}) {
1607
+ const startTime = Date.now();
1608
+ const prompt = this.buildVisionPrompt(manifest, options);
1609
+ const spanResult = this.instrumentation?.onGenerationStart?.({
1610
+ name: "vision-analyze",
1611
+ model: this.visionModel,
1612
+ prompt,
1613
+ metadata: {
1614
+ manifestSize: manifest.length,
1615
+ hasStyleGuide: !!options.styleGuide
1616
+ }
1617
+ });
1618
+ const span = spanResult || void 0;
1619
+ try {
1620
+ const rawResponse = options.onProgress ? await this.chatVisionStreaming(
1621
+ imageBase64,
1622
+ prompt,
1623
+ options.onProgress,
1624
+ span
1625
+ ) : await this.chatVision(imageBase64, prompt, span);
1626
+ const issues = this.parseVisionResponse(rawResponse, manifest);
1627
+ return {
1628
+ issues,
1629
+ analysisTime: Date.now() - startTime,
1630
+ prompt,
1631
+ rawResponse
1632
+ };
1633
+ } catch (error) {
1634
+ const analysisTime = Date.now() - startTime;
1635
+ const err = error instanceof Error ? error : new Error(String(error ?? "Unknown error"));
1636
+ console.error("[VisionAnalyzer] Analysis failed", {
1637
+ baseUrl: this.baseUrl,
1638
+ model: this.visionModel,
1639
+ manifestSize: manifest.length,
1640
+ analysisTime,
1641
+ error: err.message
1642
+ });
1643
+ span?.end("", { error: err.message });
1644
+ throw err;
1645
+ }
1646
+ }
1647
+ /**
1648
+ * Builds the vision analysis prompt
1649
+ */
1650
+ buildVisionPrompt(manifest, options) {
1651
+ const styleGuideSection = options.styleGuide ? `## Style Guide
1652
+ ${options.styleGuide}
1653
+
1654
+ ` : "";
1655
+ const additionalSection = options.additionalContext ? `## Additional Context
1656
+ ${options.additionalContext}
1657
+
1658
+ ` : "";
1659
+ const manifestSection = this.buildManifestSection(manifest);
1660
+ return `You are a UI consistency analyzer examining a screenshot of a web page.
1661
+ Analyze the visual appearance and identify any UI consistency issues.
1662
+
1663
+ ${styleGuideSection}${additionalSection}${manifestSection}
1664
+
1665
+ ## Task
1666
+
1667
+ Examine the screenshot and identify visual consistency issues such as:
1668
+ 1. **Spacing inconsistencies**: Elements with uneven padding, margins, or gaps
1669
+ 2. **Alignment issues**: Elements that should be aligned but aren't
1670
+ 3. **Color inconsistencies**: Similar colors that should be identical, or colors that don't match the style guide
1671
+ 4. **Typography issues**: Inconsistent font sizes, weights, or families
1672
+ 5. **Layout problems**: Broken layouts, overlapping elements, or visual hierarchy issues
1673
+ 6. **Contrast issues**: Text that's hard to read or UI elements with insufficient contrast
1674
+
1675
+ For each issue found, reference the element by its visible text so we can map it back to the source code.
1676
+
1677
+ ## Response Format
1678
+
1679
+ Respond with JSON ONLY. Return a single JSON object:
1680
+
1681
+ \`\`\`json
1682
+ {
1683
+ "issues": [
1684
+ {
1685
+ "elementText": "Submit Order",
1686
+ "message": "Button has inconsistent padding compared to other buttons (appears larger)",
1687
+ "category": "spacing",
1688
+ "severity": "warning"
1689
+ }
1690
+ ]
1691
+ }
1692
+ \`\`\`
1693
+
1694
+ Categories: spacing, alignment, color, typography, layout, contrast, visual-hierarchy
1695
+ Severities: error (major issue), warning (should fix), info (minor/suggestion)
1696
+
1697
+ IMPORTANT:
1698
+ - Reference elements by their visible text content
1699
+ - Be specific about what's wrong and what the expected state should be
1700
+ - Only report significant visual inconsistencies
1701
+ - If no issues are found, return {"issues": []}`;
1702
+ }
1703
+ /**
1704
+ * Builds the manifest section of the prompt
1705
+ */
1706
+ buildManifestSection(manifest) {
1707
+ if (manifest.length === 0) {
1708
+ return "";
1709
+ }
1710
+ const elements = manifest.map((el) => {
1711
+ const parts = [`- "${el.text}"`];
1712
+ if (el.role) parts.push(`(${el.role})`);
1713
+ if (el.instanceCount && el.instanceCount > 1) {
1714
+ parts.push(`[${el.instanceCount} instances]`);
1715
+ }
1716
+ return parts.join(" ");
1717
+ });
1718
+ return `## Page Elements
1719
+
1720
+ The following elements are visible on the page (reference by text when reporting issues):
1721
+
1722
+ ${elements.join("\n")}
1723
+
1724
+ `;
1725
+ }
1726
+ /**
1727
+ * Non-streaming chat API call for vision
1728
+ */
1729
+ async chatVision(imageBase64, prompt, span) {
1730
+ const controller = new AbortController();
1731
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1732
+ try {
1733
+ const base64Data = imageBase64.includes(",") ? imageBase64.split(",")[1] : imageBase64;
1734
+ const response = await this.client.chat({
1735
+ model: this.visionModel,
1736
+ messages: [
1737
+ {
1738
+ role: "user",
1739
+ content: prompt,
1740
+ images: [base64Data]
1741
+ }
1742
+ ],
1743
+ format: "json",
1744
+ options: {
1745
+ // Increase context window for large prompts
1746
+ num_ctx: 8192
1747
+ }
1748
+ });
1749
+ const output = response.message?.content || "";
1750
+ if (!output.trim()) {
1751
+ const diag = JSON.stringify(
1752
+ {
1753
+ done_reason: response.done_reason,
1754
+ model: response.model,
1755
+ prompt_eval_count: response.prompt_eval_count,
1756
+ eval_count: response.eval_count
1757
+ },
1758
+ null,
1759
+ 2
1760
+ );
1761
+ const errorMsg = `Vision model returned empty output. Diagnostics: ${diag}`;
1762
+ span?.end("", { error: errorMsg });
1763
+ throw new Error(errorMsg);
1764
+ }
1765
+ span?.end(output, {
1766
+ promptTokens: response.prompt_eval_count,
1767
+ completionTokens: response.eval_count,
1768
+ totalTokens: (response.prompt_eval_count || 0) + (response.eval_count || 0) || void 0
1769
+ });
1770
+ return output;
1771
+ } catch (error) {
1772
+ span?.end("", { error: String(error) });
1773
+ throw error;
1774
+ } finally {
1775
+ clearTimeout(timeoutId);
1776
+ }
1777
+ }
1778
+ /**
1779
+ * Streaming chat API call for vision
1780
+ * Uses ollama package's async iterator for streaming
1781
+ */
1782
+ async chatVisionStreaming(imageBase64, prompt, onProgress, span) {
1783
+ const controller = new AbortController();
1784
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1785
+ let promptTokens;
1786
+ let completionTokens;
1787
+ let chunksReceived = 0;
1788
+ let lastProgressAt = Date.now();
1789
+ let emittedWaitingLine = false;
1790
+ try {
1791
+ const base64Data = imageBase64.includes(",") ? imageBase64.split(",")[1] : imageBase64;
1792
+ const stream = await this.client.chat({
1793
+ model: this.visionModel,
1794
+ messages: [
1795
+ {
1796
+ role: "user",
1797
+ content: prompt,
1798
+ images: [base64Data]
1799
+ }
1800
+ ],
1801
+ // Enable thinking for streaming so we can surface reasoning traces for thinking-capable models.
1802
+ // Models that don't support it should ignore it.
1803
+ think: false,
1804
+ stream: true,
1805
+ format: "json",
1806
+ options: {
1807
+ num_ctx: 8192
1808
+ }
1809
+ });
1810
+ let fullResponse = "";
1811
+ let lastLatestLine = "";
1812
+ for await (const chunk of stream) {
1813
+ chunksReceived++;
1814
+ if (!emittedWaitingLine && !fullResponse.trim()) {
1815
+ onProgress("(waiting for model output\u2026)", fullResponse);
1816
+ emittedWaitingLine = true;
1817
+ }
1818
+ const thinking = chunk?.message?.thinking || "";
1819
+ if (thinking) {
1820
+ onProgress(lastLatestLine, fullResponse, void 0, thinking);
1821
+ lastProgressAt = Date.now();
1822
+ }
1823
+ const content = chunk.message?.content || "";
1824
+ if (content) {
1825
+ fullResponse += content;
1826
+ const responseLines = fullResponse.split("\n");
1827
+ const latestLine = responseLines[responseLines.length - 1] || responseLines[responseLines.length - 2] || "";
1828
+ lastLatestLine = latestLine.trim();
1829
+ onProgress(lastLatestLine, fullResponse, content);
1830
+ lastProgressAt = Date.now();
1831
+ }
1832
+ if (chunk.done) {
1833
+ promptTokens = chunk.prompt_eval_count;
1834
+ completionTokens = chunk.eval_count;
1835
+ }
1836
+ if (!fullResponse.trim() && Date.now() - lastProgressAt > 4e3) {
1837
+ onProgress(
1838
+ `(streaming\u2026 received ${chunksReceived} chunks)`,
1839
+ fullResponse
1840
+ );
1841
+ lastProgressAt = Date.now();
1842
+ }
1843
+ }
1844
+ if (!fullResponse.trim()) {
1845
+ const diag = JSON.stringify(
1846
+ {
1847
+ chunksReceived,
1848
+ promptTokens,
1849
+ completionTokens,
1850
+ model: this.visionModel
1851
+ },
1852
+ null,
1853
+ 2
1854
+ );
1855
+ const errorMsg = `Vision model returned empty output (streaming). Diagnostics: ${diag}`;
1856
+ span?.end("", { error: errorMsg });
1857
+ throw new Error(errorMsg);
1858
+ }
1859
+ span?.end(fullResponse, {
1860
+ promptTokens,
1861
+ completionTokens,
1862
+ totalTokens: (promptTokens || 0) + (completionTokens || 0) || void 0
1863
+ });
1864
+ return fullResponse;
1865
+ } catch (error) {
1866
+ span?.end("", { error: String(error) });
1867
+ throw error;
1868
+ } finally {
1869
+ clearTimeout(timeoutId);
1870
+ }
1871
+ }
1872
+ /**
1873
+ * Parses the vision LLM response and matches elements to manifest
1874
+ */
1875
+ parseVisionResponse(response, manifest) {
1876
+ try {
1877
+ const jsonText = extractJsonObject(response);
1878
+ const parsed = JSON.parse(jsonText);
1879
+ const rawIssues = parsed.issues || [];
1880
+ return rawIssues.map((issue) => {
1881
+ const matchedElement = this.matchElementByText(
1882
+ issue.elementText,
1883
+ manifest
1884
+ );
1885
+ return {
1886
+ ...issue,
1887
+ dataLoc: matchedElement?.dataLoc
1888
+ };
1889
+ });
1890
+ } catch {
1891
+ const s = response ?? "";
1892
+ const previewHead = s ? s.slice(0, 600) : "";
1893
+ const previewTail = s && s.length > 600 ? s.slice(-400) : "";
1894
+ throw new Error(
1895
+ `Vision model returned non-JSON output (expected JSON). length=${s.length} head=${JSON.stringify(previewHead)} tail=${JSON.stringify(
1896
+ previewTail
1897
+ )}`
1898
+ );
1899
+ }
1900
+ }
1901
+ /**
1902
+ * Matches element text from LLM response to manifest entries
1903
+ */
1904
+ matchElementByText(elementText, manifest) {
1905
+ if (!elementText) return void 0;
1906
+ const normalizedSearch = elementText.toLowerCase().trim();
1907
+ for (const entry of manifest) {
1908
+ if (entry.text.toLowerCase().trim() === normalizedSearch) {
1909
+ return entry;
1910
+ }
1911
+ }
1912
+ for (const entry of manifest) {
1913
+ const normalizedEntry = entry.text.toLowerCase().trim();
1914
+ if (normalizedEntry.includes(normalizedSearch) || normalizedSearch.includes(normalizedEntry)) {
1915
+ return entry;
1916
+ }
1917
+ }
1918
+ return void 0;
1919
+ }
1920
+ /**
1921
+ * Checks if the vision model is available
1922
+ */
1923
+ async isAvailable() {
1924
+ try {
1925
+ const models = await this.client.list();
1926
+ return models.models.some(
1927
+ (m) => m.name === this.visionModel || m.name.startsWith(this.visionModel.split(":")[0])
1928
+ );
1929
+ } catch {
1930
+ return false;
1931
+ }
1932
+ }
1933
+ /**
1934
+ * Gets the current vision model
1935
+ */
1936
+ getModel() {
1937
+ return this.visionModel;
1938
+ }
1939
+ /**
1940
+ * Gets the Ollama base URL (for diagnostics)
1941
+ */
1942
+ getBaseUrl() {
1943
+ return this.baseUrl;
1944
+ }
1945
+ /**
1946
+ * Sets the vision model
1947
+ */
1948
+ setModel(model) {
1949
+ this.visionModel = model;
1950
+ }
1951
+ /**
1952
+ * Sets instrumentation callbacks
1953
+ */
1954
+ setInstrumentation(instrumentation) {
1955
+ this.instrumentation = instrumentation;
1956
+ }
1957
+ };
1958
+ var defaultAnalyzer = null;
1959
+ function getVisionAnalyzer(options) {
1960
+ if (!defaultAnalyzer || options) {
1961
+ defaultAnalyzer = new VisionAnalyzer(options);
1962
+ }
1963
+ return defaultAnalyzer;
1964
+ }
484
1965
  export {
485
1966
  OllamaClient,
486
1967
  STYLEGUIDE_PATHS,
487
1968
  UILINT_DEFAULT_OLLAMA_MODEL,
1969
+ UILINT_DEFAULT_VISION_MODEL,
1970
+ VisionAnalyzer,
488
1971
  analyzeConsistency,
489
1972
  buildAnalysisPrompt,
490
1973
  buildConsistencyPrompt,
@@ -517,6 +2000,7 @@ export {
517
2000
  generateStyleGuideFromStyles,
518
2001
  getDefaultStyleGuidePath,
519
2002
  getOllamaClient,
2003
+ getVisionAnalyzer,
520
2004
  hasAnalyzableGroups,
521
2005
  hasStdin,
522
2006
  isJSON,