@utilix-tech/sdk 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +44 -32
  3. package/SECURITY.md +16 -0
  4. package/dist/{chunk-UC656APA.js → chunk-DNCOIO5N.js} +64 -1
  5. package/dist/{chunk-OKSWDVOM.js → chunk-GX7H6TAX.js} +1 -1
  6. package/dist/{chunk-W4UBLYFU.js → chunk-HFRTZE7T.js} +2 -2
  7. package/dist/{chunk-ROTPLW7T.js → chunk-IPR7FSX4.js} +2 -2
  8. package/dist/{chunk-T3JTY2M5.js → chunk-N7C52YGL.js} +1 -1
  9. package/dist/{chunk-XST6X3HT.js → chunk-QJE743LY.js} +10 -10
  10. package/dist/{chunk-3BAHSW4C.js → chunk-V5JKVDBA.js} +60 -1
  11. package/dist/{chunk-NSOARQCM.js → chunk-V5S6OJ4A.js} +14 -14
  12. package/dist/{data-rMjWNiOJ.d.cts → data-CakDxAcT.d.cts} +36 -2
  13. package/dist/{data-rMjWNiOJ.d.ts → data-CakDxAcT.d.ts} +36 -2
  14. package/dist/index.cjs +152 -30
  15. package/dist/index.d.cts +2 -2
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +8 -8
  18. package/dist/{network-CNtmrDeN.d.cts → network-DwyAjwJS.d.cts} +39 -2
  19. package/dist/{network-CNtmrDeN.d.ts → network-DwyAjwJS.d.ts} +39 -2
  20. package/dist/tools/api.cjs +2 -2
  21. package/dist/tools/api.js +1 -1
  22. package/dist/tools/code.cjs +14 -14
  23. package/dist/tools/code.js +1 -1
  24. package/dist/tools/data.cjs +63 -0
  25. package/dist/tools/data.d.cts +1 -1
  26. package/dist/tools/data.d.ts +1 -1
  27. package/dist/tools/data.js +1 -1
  28. package/dist/tools/encoding.cjs +2 -2
  29. package/dist/tools/encoding.js +1 -1
  30. package/dist/tools/media.cjs +1 -1
  31. package/dist/tools/media.js +1 -1
  32. package/dist/tools/misc.cjs +1 -1
  33. package/dist/tools/misc.js +1 -1
  34. package/dist/tools/network.cjs +59 -0
  35. package/dist/tools/network.d.cts +1 -1
  36. package/dist/tools/network.d.ts +1 -1
  37. package/dist/tools/network.js +1 -1
  38. package/dist/tools/units.cjs +10 -10
  39. package/dist/tools/units.js +1 -1
  40. package/package.json +18 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ Full release notes for all Utilix surfaces (browser tools, REST API, SDKs, MCP server) live at [utilix.tech/changelog](https://utilix.tech/changelog). This file tracks just this package.
4
+
5
+ ## 0.3.1
6
+ - Metadata fixes: corrected repository/bug tracker links, updated tool count in the package description.
7
+
8
+ ## 0.3.0
9
+ - Added the `media` module.
10
+
11
+ ## 0.2.0
12
+ - 400+ functions across 14 modules, including the `ai_agent` module for LLM/RAG pipelines.
13
+
14
+ ## 0.1.0
15
+ - Initial release.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/sdk
2
2
 
3
- **100+ developer utility functions for Node.js runs locally, no API key required.**
3
+ **140+ developer utility functions for Node.js: runs locally, no API key required.**
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@utilix-tech/sdk?style=flat-square&color=cb3837)](https://www.npmjs.com/package/@utilix-tech/sdk)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/@utilix-tech/sdk?style=flat-square)](https://www.npmjs.com/package/@utilix-tech/sdk)
@@ -62,7 +62,7 @@ const { formatJson } = require("@utilix-tech/sdk/json");
62
62
 
63
63
  ## Modules
64
64
 
65
- ### `/json` JSON Tools
65
+ ### `/json`: JSON Tools
66
66
 
67
67
  ```ts
68
68
  import { formatJson, minifyJson, diffJson, jsonToTypescript, validateJson,
@@ -71,7 +71,7 @@ import { formatJson, minifyJson, diffJson, jsonToTypescript, validateJson,
71
71
  // Format with 2-space indent
72
72
  formatJson('{"name":"alice","age":30}', { indent: 2 });
73
73
 
74
- // Diff two JSON strings returns line-by-line diff
74
+ // Diff two JSON strings: returns line-by-line diff
75
75
  diffJson(jsonA, jsonB);
76
76
 
77
77
  // Generate TypeScript interface from JSON
@@ -89,7 +89,7 @@ validateJson(data, schema);
89
89
 
90
90
  ---
91
91
 
92
- ### `/encoding` Encode & Decode
92
+ ### `/encoding`: Encode & Decode
93
93
 
94
94
  ```ts
95
95
  import { encodeBase64, decodeBase64, encodeUrl, decodeUrl,
@@ -104,7 +104,7 @@ base32Encode("hello"); // "NBSWY3DPEB3W64TMMQ======"
104
104
 
105
105
  ---
106
106
 
107
- ### `/hashing` Hash & Password
107
+ ### `/hashing`: Hash & Password
108
108
 
109
109
  ```ts
110
110
  import { hashAll, hashOne, hashPassword, verifyPassword, generateHtpasswdFile } from "@utilix-tech/sdk/hashing";
@@ -123,7 +123,7 @@ generateHtpasswdFile([{ username: "alice", password: "secret" }]);
123
123
 
124
124
  ---
125
125
 
126
- ### `/text` String & Text
126
+ ### `/text`: String & Text
127
127
 
128
128
  ```ts
129
129
  import { convertCase, slugify, countWords, generateWords, generateParagraphs,
@@ -152,11 +152,12 @@ detectPassiveVoice("The report was reviewed by the committee. They approved it."
152
152
 
153
153
  ---
154
154
 
155
- ### `/data` CSV / YAML / TOML / XML / INI
155
+ ### `/data`: CSV / YAML / TOML / XML / INI / NDJSON
156
156
 
157
157
  ```ts
158
158
  import { parseCsv, csvToJson, validateYaml, tomlToJson, jsonToToml,
159
- formatXml, xmlToJson, jsonToXml, parseIni } from "@utilix-tech/sdk/data";
159
+ formatXml, xmlToJson, jsonToXml, parseIni,
160
+ validateNdjson, formatNdjson, ndjsonToJsonArray } from "@utilix-tech/sdk/data";
160
161
 
161
162
  // CSV
162
163
  const rows = parseCsv("name,age\nalice,30\nbob,25");
@@ -175,11 +176,16 @@ xmlToJson("<root><item>hello</item></root>");
175
176
 
176
177
  // INI
177
178
  parseIni("[db]\nhost=localhost\nport=5432");
179
+
180
+ // NDJSON (JSON Lines): one JSON value per line
181
+ validateNdjson('{"id":1}\n{"id":2}'); // { valid: true, totalLines: 2, ... }
182
+ formatNdjson('{"id":1}'); // '{\n "id": 1\n}'
183
+ ndjsonToJsonArray('{"id":1}\n{"id":2}'); // '[\n { "id": 1 },\n { "id": 2 }\n]'
178
184
  ```
179
185
 
180
186
  ---
181
187
 
182
- ### `/generators` UUID, Passwords, Fake Data
188
+ ### `/generators`: UUID, Passwords, Fake Data
183
189
 
184
190
  ```ts
185
191
  import { generateUuid, generateV4, generateV7, generateUlid,
@@ -202,7 +208,7 @@ generateData([
202
208
 
203
209
  ---
204
210
 
205
- ### `/time` Dates, Timezones, Cron
211
+ ### `/time`: Dates, Timezones, Cron
206
212
 
207
213
  ```ts
208
214
  import { diffDates, parseCron, convertTime, formatRelative,
@@ -225,7 +231,7 @@ getNextRuns("*/5 * * * *", 5); // next 5 run timestamps
225
231
 
226
232
  ---
227
233
 
228
- ### `/units` Conversions
234
+ ### `/units`: Conversions
229
235
 
230
236
  ```ts
231
237
  import { convertBytes, pxToAll, convert, formatValue } from "@utilix-tech/sdk/units";
@@ -250,11 +256,12 @@ formatValue(1234567.89, { locale: "en-US", currency: "USD" });
250
256
 
251
257
  ---
252
258
 
253
- ### `/network` URLs, IPs, HTTP
259
+ ### `/network`: URLs, IPs, HTTP, HAR
254
260
 
255
261
  ```ts
256
262
  import { getStatusCode, isValidIp, isValidIpv4, isValidIpv6,
257
- searchStatusCodes, buildGeoUrl, countryCodeToFlag } from "@utilix-tech/sdk/network";
263
+ searchStatusCodes, buildGeoUrl, countryCodeToFlag,
264
+ parseHar } from "@utilix-tech/sdk/network";
258
265
 
259
266
  getStatusCode(404);
260
267
  // { code: 404, text: "Not Found", description: "...", category: "Client Error" }
@@ -266,11 +273,16 @@ isValidIpv6("::1"); // true
266
273
  isValidIp("256.0.0.1"); // false
267
274
 
268
275
  countryCodeToFlag("US"); // "🇺🇸"
276
+
277
+ // Parse a DevTools .har network export into entries + summary stats
278
+ const har = await fs.promises.readFile("network.har", "utf-8");
279
+ parseHar(har);
280
+ // { version: "1.2", entries: [...], summary: { totalRequests, totalSize, failedRequests, ... } }
269
281
  ```
270
282
 
271
283
  ---
272
284
 
273
- ### `/api` cURL, JWT, CORS, HTTP
285
+ ### `/api`: cURL, JWT, CORS, HTTP
274
286
 
275
287
  ```ts
276
288
  import { buildCurl, parseCurlCommand, decodeJwt, signJwt,
@@ -289,7 +301,7 @@ buildCurl({
289
301
  // Parse a curl command back into its parts
290
302
  parseCurlCommand('curl -X GET https://api.example.com -H "Authorization: Bearer token"');
291
303
 
292
- // JWT decode without verification
304
+ // JWT: decode without verification
293
305
  decodeJwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...");
294
306
  // { header: {...}, payload: {...}, isExpired: false, expiresIn: "2h" }
295
307
 
@@ -302,7 +314,7 @@ generateCspHeader({ "default-src": ["'self'"], "script-src": ["'self'", "cdn.exa
302
314
 
303
315
  ---
304
316
 
305
- ### `/code` SQL, HTML, RegEx, GQL
317
+ ### `/code`: SQL, HTML, RegEx, GQL
306
318
 
307
319
  ```ts
308
320
  import { formatSql, minifySql, testRegex, formatHtml, formatGql,
@@ -334,7 +346,7 @@ formatGql("query { user(id: 1) { name email } }");
334
346
 
335
347
  ---
336
348
 
337
- ### `/color` Color Conversion & Palettes
349
+ ### `/color`: Color Conversion & Palettes
338
350
 
339
351
  ```ts
340
352
  import { parseColor, convertColor, generatePalette, checkContrast } from "@utilix-tech/sdk/color";
@@ -354,7 +366,7 @@ checkContrast("#ffffff", "#000000");
354
366
 
355
367
  ---
356
368
 
357
- ### `/css` CSS Generators
369
+ ### `/css`: CSS Generators
358
370
 
359
371
  ```ts
360
372
  import { generateGradient, generateBoxShadow, generateBorderRadius,
@@ -376,7 +388,7 @@ generateBorderRadius({ tl: 8, tr: 8, br: 0, bl: 0 });
376
388
 
377
389
  ---
378
390
 
379
- ### `/misc` SVG, QR, Unicode
391
+ ### `/misc`: SVG, QR, Unicode
380
392
 
381
393
  ```ts
382
394
  import { optimizeSvg, sanitizeSvg, analyzeString, toUnicodeEscape,
@@ -400,13 +412,13 @@ analyzeString("café"); // { length: 4, codePoints: [...], ... }
400
412
 
401
413
  ---
402
414
 
403
- ### `/media` Image Header Parsing
415
+ ### `/media`: Image Header Parsing
404
416
 
405
417
  ```ts
406
418
  import { readImageInfo } from "@utilix-tech/sdk/media";
407
419
 
408
420
  // Reads format, dimensions, bit depth, and alpha channel straight from
409
- // file bytes no decoding, no canvas, no image library.
421
+ // file bytes: no decoding, no canvas, no image library.
410
422
  const bytes = await fs.promises.readFile("photo.png");
411
423
  readImageInfo(new Uint8Array(bytes));
412
424
  // { format: "png", width: 1920, height: 1080, bitDepth: 8, colorType: "rgba", hasAlpha: true }
@@ -424,24 +436,24 @@ Supports PNG, JPEG, GIF, WebP, and BMP.
424
436
  | `@utilix-tech/sdk/encoding` | Base64, URL, HTML entities, Base32 encode/decode |
425
437
  | `@utilix-tech/sdk/hashing` | MD5, SHA-1/256/512, bcrypt, HMAC, .htpasswd |
426
438
  | `@utilix-tech/sdk/text` | Case convert, slugify, word count, lorem ipsum, line ops, HTML→Markdown |
427
- | `@utilix-tech/sdk/data` | CSV, YAML, TOML, XML, INI parse and convert |
439
+ | `@utilix-tech/sdk/data` | CSV, YAML, TOML, XML, INI, NDJSON parse and convert |
428
440
  | `@utilix-tech/sdk/generators` | UUID v4/v7, ULID, passwords, strength check, fake data |
429
441
  | `@utilix-tech/sdk/time` | Date diff, timezone convert, cron parse, relative time |
430
442
  | `@utilix-tech/sdk/units` | Bytes, CSS px/rem, number bases, currency format |
431
- | `@utilix-tech/sdk/network` | HTTP status codes, IP validation, country flags, geolocation URLs |
443
+ | `@utilix-tech/sdk/network` | HTTP status codes, IP validation, country flags, geolocation URLs, HAR file parsing |
432
444
  | `@utilix-tech/sdk/api` | cURL build/parse, JWT decode/sign, CORS headers, CSP header |
433
445
  | `@utilix-tech/sdk/code` | SQL format/minify, HTML format/minify, regex tester, GraphQL format |
434
446
  | `@utilix-tech/sdk/color` | Hex/RGB/HSL/HSV/CMYK convert, palette generation, contrast check |
435
447
  | `@utilix-tech/sdk/css` | CSS gradient, box-shadow, border-radius, keyframe animation generators |
436
448
  | `@utilix-tech/sdk/misc` | SVG optimize/sanitize, file size format, Unicode inspector |
437
449
  | `@utilix-tech/sdk/ai_agent` | Token estimate/trim, chunk text, extract URLs/JSON/keywords, sanitize HTML, flatten/merge JSON, dedupe lines, validate schema, PII/secret/injection detect |
438
- | `@utilix-tech/sdk/media` | Image format & dimension reading from raw bytes PNG, JPEG, GIF, WebP, BMP |
450
+ | `@utilix-tech/sdk/media` | Image format & dimension reading from raw bytes: PNG, JPEG, GIF, WebP, BMP |
439
451
 
440
452
  ---
441
453
 
442
454
  ## Python SDK
443
455
 
444
- `@utilix-tech/sdk` has a Python companion [`utilix-sdk`](https://pypi.org/project/utilix-sdk/) on PyPI with the same 100+ tools and identical return shapes.
456
+ `@utilix-tech/sdk` has a Python companion, [`utilix-sdk`](https://pypi.org/project/utilix-sdk/) on PyPI, with the same 140+ tools and identical return shapes.
445
457
 
446
458
  ```bash
447
459
  pip install utilix-sdk
@@ -453,9 +465,9 @@ Both SDKs return plain JSON-serializable objects so you can share test fixtures
453
465
 
454
466
  ## REST API
455
467
 
456
- **This package is Surface A** everything runs in-process in your Node.js runtime. No outbound requests, no API key, no rate limits.
468
+ **This package is Surface A**: everything runs in-process in your Node.js runtime. No outbound requests, no API key, no rate limits.
457
469
 
458
- The same 100+ tools are also available as a hosted **REST API** at `https://api.utilix.tech/v1` for environments where you can't ship a Node.js runtime, or for cross-language teams.
470
+ The same 140+ tools are also available as a hosted **REST API** at `https://api.utilix.tech/v1` for environments where you can't ship a Node.js runtime, or for cross-language teams.
459
471
 
460
472
  ```bash
461
473
  curl -X POST https://api.utilix.tech/v1/tools/hash \
@@ -464,17 +476,17 @@ curl -X POST https://api.utilix.tech/v1/tools/hash \
464
476
  -d '{"input": "hello world", "algorithm": "sha256"}'
465
477
  ```
466
478
 
467
- - **Free**: 1,000 requests/day no credit card required
468
- - **Pro**: 10,000 requests/day $9/month
469
- - Try it live at **[utilix.tech/api](https://utilix.tech/api)** no signup needed for the first 10 endpoints
479
+ - **Free**: 1,000 requests/day: no credit card required
480
+ - **Pro**: 10,000 requests/day: $9/month
481
+ - Try it live at **[utilix.tech/api](https://utilix.tech/api)**: no signup needed for the first 10 endpoints
470
482
  - Get your API key at **[utilix.tech/dashboard](https://utilix.tech/dashboard)**
471
483
 
472
484
  ---
473
485
 
474
486
  ## Publishing (maintainers)
475
487
 
476
- CI publishes automatically via npm Trusted Publishing (OIDC) on every push to `main` that bumps `version`. Requires npm **>=11.5.1** older versions (e.g. the npm bundled with Node 20) sign provenance but skip the OIDC auth exchange, so `npm publish` fails with a 404 as if unauthenticated.
488
+ CI publishes automatically via npm Trusted Publishing (OIDC) on every push to `main` that bumps `version`. Requires npm **>=11.5.1**: older versions (e.g. the npm bundled with Node 20) sign provenance but skip the OIDC auth exchange, so `npm publish` fails with a 404 as if unauthenticated.
477
489
 
478
490
  ## License
479
491
 
480
- MIT see [LICENSE](./LICENSE) for details.
492
+ MIT: see [LICENSE](./LICENSE) for details.
package/SECURITY.md ADDED
@@ -0,0 +1,16 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ If you discover a security vulnerability in this package, please report it privately rather than opening a public issue.
6
+
7
+ Email **hello@utilix.tech** with the subject line "Security report" and include:
8
+ - A description of the vulnerability and its potential impact
9
+ - Steps to reproduce, if possible
10
+ - Any suggested fix
11
+
12
+ We aim to acknowledge reports within 3 business days. Please give us a reasonable window to release a fix before disclosing publicly.
13
+
14
+ ## Supported Versions
15
+
16
+ Only the latest published version of `@utilix-tech/sdk` receives security fixes. We recommend always running the latest release.
@@ -9,6 +9,7 @@ __export(data_exports, {
9
9
  detectDelimiter: () => detectDelimiter,
10
10
  envToExport: () => envToExport,
11
11
  envToJson: () => envToJson,
12
+ formatNdjson: () => formatNdjson,
12
13
  formatToml: () => formatToml,
13
14
  formatXml: () => formatXml,
14
15
  getSection: () => getSection,
@@ -17,11 +18,14 @@ __export(data_exports, {
17
18
  getXmlStats: () => getXmlStats,
18
19
  getYamlStats: () => getYamlStats,
19
20
  iniToJson: () => iniToJson,
21
+ jsonArrayToNdjson: () => jsonArrayToNdjson,
20
22
  jsonToIni: () => jsonToIni,
21
23
  jsonToToml: () => jsonToToml,
22
24
  jsonToXml: () => jsonToXml,
25
+ minifyNdjson: () => minifyNdjson,
23
26
  minifyToml: () => minifyToml,
24
27
  minifyXml: () => minifyXml,
28
+ ndjsonToJsonArray: () => ndjsonToJsonArray,
25
29
  parseCsv: () => parseCsv,
26
30
  parseEnv: () => parseEnv,
27
31
  parseIni: () => parseIni,
@@ -31,6 +35,7 @@ __export(data_exports, {
31
35
  tomlToJson: () => tomlToJson,
32
36
  validateEnvKey: () => validateEnvKey,
33
37
  validateIni: () => validateIni,
38
+ validateNdjson: () => validateNdjson,
34
39
  validateToml: () => validateToml,
35
40
  validateXml: () => validateXml,
36
41
  validateYaml: () => validateYaml,
@@ -1076,5 +1081,63 @@ function envToExport(input) {
1076
1081
  function validateEnvKey(key) {
1077
1082
  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
1078
1083
  }
1084
+ function validateNdjson(input) {
1085
+ const rawLines = input.split("\n");
1086
+ const lines = [];
1087
+ let validCount = 0;
1088
+ rawLines.forEach((raw, idx) => {
1089
+ if (raw.trim() === "") return;
1090
+ const lineNum = idx + 1;
1091
+ try {
1092
+ JSON.parse(raw);
1093
+ lines.push({ line: lineNum, raw, valid: true });
1094
+ validCount++;
1095
+ } catch (e) {
1096
+ lines.push({
1097
+ line: lineNum,
1098
+ raw,
1099
+ valid: false,
1100
+ error: e instanceof Error ? e.message : String(e)
1101
+ });
1102
+ }
1103
+ });
1104
+ return {
1105
+ valid: lines.length > 0 && validCount === lines.length,
1106
+ totalLines: lines.length,
1107
+ validLines: validCount,
1108
+ invalidLines: lines.length - validCount,
1109
+ lines
1110
+ };
1111
+ }
1112
+ function _firstNdjsonError(result) {
1113
+ const firstError = result.lines.find((l) => !l.valid);
1114
+ return firstError ? `Line ${firstError.line}: ${firstError.error}` : "Invalid NDJSON";
1115
+ }
1116
+ function minifyNdjson(input) {
1117
+ const result = validateNdjson(input);
1118
+ if (!result.valid) return { error: _firstNdjsonError(result) };
1119
+ return result.lines.map((l) => JSON.stringify(JSON.parse(l.raw))).join("\n");
1120
+ }
1121
+ function formatNdjson(input, indent = 2) {
1122
+ const result = validateNdjson(input);
1123
+ if (!result.valid) return { error: _firstNdjsonError(result) };
1124
+ return result.lines.map((l) => JSON.stringify(JSON.parse(l.raw), null, indent)).join("\n");
1125
+ }
1126
+ function ndjsonToJsonArray(input) {
1127
+ const result = validateNdjson(input);
1128
+ if (!result.valid) return { error: _firstNdjsonError(result) };
1129
+ const values = result.lines.map((l) => JSON.parse(l.raw));
1130
+ return JSON.stringify(values, null, 2);
1131
+ }
1132
+ function jsonArrayToNdjson(input) {
1133
+ let parsed;
1134
+ try {
1135
+ parsed = JSON.parse(input);
1136
+ } catch (e) {
1137
+ return { error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}` };
1138
+ }
1139
+ if (!Array.isArray(parsed)) return { error: "Input must be a JSON array" };
1140
+ return parsed.map((item) => JSON.stringify(item)).join("\n");
1141
+ }
1079
1142
 
1080
- export { csvToJson, data_exports, detectDelimiter, envToExport, envToJson, formatToml, formatXml, getSection, getSectionNames, getTomlStats, getXmlStats, getYamlStats, iniToJson, jsonToIni, jsonToToml, jsonToXml, minifyToml, minifyXml, parseCsv, parseEnv, parseIni, parseXmlToObject, stringifyIni, toCsvString, tomlToJson, validateEnvKey, validateIni, validateToml, validateXml, validateYaml, xmlToJson };
1143
+ export { csvToJson, data_exports, detectDelimiter, envToExport, envToJson, formatNdjson, formatToml, formatXml, getSection, getSectionNames, getTomlStats, getXmlStats, getYamlStats, iniToJson, jsonArrayToNdjson, jsonToIni, jsonToToml, jsonToXml, minifyNdjson, minifyToml, minifyXml, ndjsonToJsonArray, parseCsv, parseEnv, parseIni, parseXmlToObject, stringifyIni, toCsvString, tomlToJson, validateEnvKey, validateIni, validateNdjson, validateToml, validateXml, validateYaml, xmlToJson };
@@ -570,7 +570,7 @@ function getConversionWarnings(from, to) {
570
570
  warnings.push("Transparency will be lost if the WebP image has an alpha channel");
571
571
  }
572
572
  if (to === "image/jpeg") {
573
- warnings.push("JPEG uses lossy compression \u2014 some quality may be reduced");
573
+ warnings.push("JPEG uses lossy compression: some quality may be reduced");
574
574
  }
575
575
  return warnings;
576
576
  }
@@ -451,13 +451,13 @@ function decodeJwt(token) {
451
451
  try {
452
452
  header = decodeSegment(rawHeader);
453
453
  } catch {
454
- return { ok: false, error: "Failed to decode header \u2014 not valid base64url JSON" };
454
+ return { ok: false, error: "Failed to decode header: not valid base64url JSON" };
455
455
  }
456
456
  let payload;
457
457
  try {
458
458
  payload = decodeSegment(rawPayload);
459
459
  } catch {
460
- return { ok: false, error: "Failed to decode payload \u2014 not valid base64url JSON" };
460
+ return { ok: false, error: "Failed to decode payload: not valid base64url JSON" };
461
461
  }
462
462
  const algorithm = typeof header.alg === "string" ? header.alg : "unknown";
463
463
  const now = Math.floor(Date.now() / 1e3);
@@ -45,7 +45,7 @@ function decodeBase64(input, variant = "standard") {
45
45
  const output = bytes.toString("utf8");
46
46
  return { ok: true, output, outputBytes: bytes.length };
47
47
  } catch {
48
- return { ok: false, error: "Invalid Base64 \u2014 check the input and variant setting" };
48
+ return { ok: false, error: "Invalid Base64: check the input and variant setting" };
49
49
  }
50
50
  }
51
51
  function encodeUrl(input, mode = "component") {
@@ -64,7 +64,7 @@ function decodeUrl(input, mode = "component") {
64
64
  const output = mode === "component" ? decodeURIComponent(trimmed) : decodeURI(trimmed);
65
65
  return { ok: true, output, changed: output !== trimmed };
66
66
  } catch {
67
- return { ok: false, error: "Invalid percent-encoding \u2014 check the input" };
67
+ return { ok: false, error: "Invalid percent-encoding: check the input" };
68
68
  }
69
69
  }
70
70
  function diffSegments(original, encoded) {
@@ -128,7 +128,7 @@ function readImageInfo(bytes) {
128
128
  if (bytes[0] === 255 && bytes[1] === 216) {
129
129
  return readJpeg(bytes);
130
130
  }
131
- return { error: "Unrecognized image format \u2014 supported: PNG, JPEG, GIF, WebP, BMP" };
131
+ return { error: "Unrecognized image format. Supported formats: PNG, JPEG, GIF, WebP, BMP" };
132
132
  }
133
133
 
134
134
  export { media_exports, readImageInfo };
@@ -82,16 +82,16 @@ function formatValue(value, unit, decimals = 4) {
82
82
  return `${value.toFixed(decimals)}${unit}`;
83
83
  }
84
84
  var COMMON_SIZES = [
85
- { px: 8, label: "8px \u2014 Extra small spacing / icon size" },
86
- { px: 12, label: "12px \u2014 Small text / caption" },
87
- { px: 14, label: "14px \u2014 Body small / secondary text" },
88
- { px: 16, label: "16px \u2014 Base font size / body text" },
89
- { px: 18, label: "18px \u2014 Body large / lead text" },
90
- { px: 20, label: "20px \u2014 Subheading / large body" },
91
- { px: 24, label: "24px \u2014 H4 / section title" },
92
- { px: 32, label: "32px \u2014 H3 / card heading" },
93
- { px: 48, label: "48px \u2014 H2 / page section heading" },
94
- { px: 64, label: "64px \u2014 H1 / hero heading" }
85
+ { px: 8, label: "8px: Extra small spacing / icon size" },
86
+ { px: 12, label: "12px: Small text / caption" },
87
+ { px: 14, label: "14px: Body small / secondary text" },
88
+ { px: 16, label: "16px: Base font size / body text" },
89
+ { px: 18, label: "18px: Body large / lead text" },
90
+ { px: 20, label: "20px: Subheading / large body" },
91
+ { px: 24, label: "24px: H4 / section title" },
92
+ { px: 32, label: "32px: H3 / card heading" },
93
+ { px: 48, label: "48px: H2 / page section heading" },
94
+ { px: 64, label: "64px: H1 / hero heading" }
95
95
  ];
96
96
  function pxToViewport(px, viewportWidth = 1440, viewportHeight = 900) {
97
97
  const vw = parseFloat((px / viewportWidth * 100).toFixed(4));
@@ -25,6 +25,7 @@ __export(network_exports, {
25
25
  isValidIpv6: () => isValidIpv6,
26
26
  parseDnsResponse: () => parseDnsResponse,
27
27
  parseGeoResponse: () => parseGeoResponse,
28
+ parseHar: () => parseHar,
28
29
  searchHeaders: () => searchHeaders,
29
30
  searchStatusCodes: () => searchStatusCodes,
30
31
  statusCodeToText: () => statusCodeToText,
@@ -1381,5 +1382,63 @@ function validateHeaderValue(name, value) {
1381
1382
  }
1382
1383
  return { valid: true };
1383
1384
  }
1385
+ function parseHar(input) {
1386
+ if (!input || !input.trim()) {
1387
+ return { error: "Input is empty" };
1388
+ }
1389
+ let data;
1390
+ try {
1391
+ data = JSON.parse(input);
1392
+ } catch (e) {
1393
+ return { error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}` };
1394
+ }
1395
+ if (typeof data !== "object" || data === null || !("log" in data)) {
1396
+ return { error: 'Not a valid HAR file: missing "log" property' };
1397
+ }
1398
+ const log = data.log;
1399
+ if (typeof log !== "object" || log === null || !Array.isArray(log.entries)) {
1400
+ return { error: 'Not a valid HAR file: "log.entries" must be an array' };
1401
+ }
1402
+ const logObj = log;
1403
+ const entries = [];
1404
+ const methods = {};
1405
+ const statusCodes = {};
1406
+ let totalSize = 0;
1407
+ let totalTime = 0;
1408
+ let failedRequests = 0;
1409
+ for (const raw of logObj.entries) {
1410
+ if (typeof raw !== "object" || raw === null) continue;
1411
+ const e = raw;
1412
+ const request = e.request ?? {};
1413
+ const response = e.response ?? {};
1414
+ const content = response.content ?? {};
1415
+ const method = typeof request.method === "string" ? request.method : "GET";
1416
+ const url = typeof request.url === "string" ? request.url : "";
1417
+ const status = typeof response.status === "number" ? response.status : 0;
1418
+ const statusText = typeof response.statusText === "string" ? response.statusText : "";
1419
+ const httpVersion = typeof response.httpVersion === "string" ? response.httpVersion : "";
1420
+ const mimeType = typeof content.mimeType === "string" ? content.mimeType : "";
1421
+ const size = typeof content.size === "number" && content.size > 0 ? content.size : 0;
1422
+ const time = typeof e.time === "number" ? e.time : 0;
1423
+ const startedDateTime = typeof e.startedDateTime === "string" ? e.startedDateTime : "";
1424
+ entries.push({ method, url, status, statusText, httpVersion, mimeType, size, time, startedDateTime });
1425
+ methods[method] = (methods[method] ?? 0) + 1;
1426
+ if (status > 0) statusCodes[String(status)] = (statusCodes[String(status)] ?? 0) + 1;
1427
+ totalSize += size;
1428
+ totalTime += time;
1429
+ if (status === 0 || status >= 400) failedRequests++;
1430
+ }
1431
+ const totalRequests = entries.length;
1432
+ const avgTime = totalRequests > 0 ? Math.round(totalTime / totalRequests * 100) / 100 : 0;
1433
+ return {
1434
+ version: typeof logObj.version === "string" ? logObj.version : "",
1435
+ creator: {
1436
+ name: typeof logObj.creator?.name === "string" ? logObj.creator.name : "",
1437
+ version: typeof logObj.creator?.version === "string" ? logObj.creator.version : ""
1438
+ },
1439
+ entries,
1440
+ summary: { totalRequests, totalSize, totalTime, avgTime, failedRequests, methods, statusCodes }
1441
+ };
1442
+ }
1384
1443
 
1385
- export { DNS_RECORD_TYPES, HTTP_HEADERS, STATUS_CODES, buildDnsQueryUrl, buildGeoUrl, buildMapUrl, countryCodeToFlag, formatCoordinates, formatTtl, getByCategory, getHeader, getHeadersByCategory, getStatusByCategory, getStatusCode, isClientError, isServerError, isSuccess, isValidIp, isValidIpv4, isValidIpv6, network_exports, parseDnsResponse, parseGeoResponse, searchHeaders, searchStatusCodes, statusCodeToText, typeNumberToLabel, validateDomain, validateHeaderName, validateHeaderValue };
1444
+ export { DNS_RECORD_TYPES, HTTP_HEADERS, STATUS_CODES, buildDnsQueryUrl, buildGeoUrl, buildMapUrl, countryCodeToFlag, formatCoordinates, formatTtl, getByCategory, getHeader, getHeadersByCategory, getStatusByCategory, getStatusCode, isClientError, isServerError, isSuccess, isValidIp, isValidIpv4, isValidIpv6, network_exports, parseDnsResponse, parseGeoResponse, parseHar, searchHeaders, searchStatusCodes, statusCodeToText, typeNumberToLabel, validateDomain, validateHeaderName, validateHeaderValue };
@@ -134,24 +134,24 @@ var CHEATSHEET = [
134
134
  { pattern: "{n}", description: "Matches exactly n repetitions", example: "\\d{3} matches exactly 3 digits", category: "Quantifiers" },
135
135
  { pattern: "{n,}", description: "Matches n or more repetitions", example: "\\d{2,} matches 2 or more digits", category: "Quantifiers" },
136
136
  { pattern: "{n,m}", description: "Matches between n and m repetitions", example: "\\d{2,4} matches 2 to 4 digits", category: "Quantifiers" },
137
- { pattern: "*?", description: "Lazy match \u2014 matches as few characters as possible", example: "<.*?> matches the shortest possible HTML tag", category: "Quantifiers" },
137
+ { pattern: "*?", description: "Lazy match: matches as few characters as possible", example: "<.*?> matches the shortest possible HTML tag", category: "Quantifiers" },
138
138
  { pattern: "+?", description: "Lazy version of +", example: "a+? matches as few 'a's as possible", category: "Quantifiers" },
139
- { pattern: "(abc)", description: "Capturing group \u2014 captures the matched substring", example: "(\\d+) captures digit sequences", category: "Groups" },
140
- { pattern: "(?:abc)", description: "Non-capturing group \u2014 groups without capturing", example: "(?:foo|bar)baz matches 'foobaz' or 'barbaz'", category: "Groups" },
139
+ { pattern: "(abc)", description: "Capturing group: captures the matched substring", example: "(\\d+) captures digit sequences", category: "Groups" },
140
+ { pattern: "(?:abc)", description: "Non-capturing group: groups without capturing", example: "(?:foo|bar)baz matches 'foobaz' or 'barbaz'", category: "Groups" },
141
141
  { pattern: "(?<name>abc)", description: "Named capturing group", example: "(?<year>\\d{4}) captures year by name", category: "Groups" },
142
- { pattern: "a|b", description: "Alternation \u2014 matches either a or b", example: "cat|dog matches 'cat' or 'dog'", category: "Groups" },
142
+ { pattern: "a|b", description: "Alternation: matches either a or b", example: "cat|dog matches 'cat' or 'dog'", category: "Groups" },
143
143
  { pattern: "\\1", description: "Backreference to the first capturing group", example: "(\\w+) \\1 matches repeated words like 'the the'", category: "Groups" },
144
144
  { pattern: "\\k<name>", description: "Backreference to a named capturing group", example: "(?<word>\\w+) \\k<word> matches repeated named group", category: "Groups" },
145
- { pattern: "(?=abc)", description: "Positive lookahead \u2014 asserts what follows matches", example: "\\d+(?= dollars) matches digits followed by ' dollars'", category: "Lookahead/Lookbehind" },
146
- { pattern: "(?!abc)", description: "Negative lookahead \u2014 asserts what follows does not match", example: "\\d+(?! dollars) matches digits not followed by ' dollars'", category: "Lookahead/Lookbehind" },
147
- { pattern: "(?<=abc)", description: "Positive lookbehind \u2014 asserts what precedes matches", example: "(?<=\\$)\\d+ matches digits preceded by '$'", category: "Lookahead/Lookbehind" },
148
- { pattern: "(?<!abc)", description: "Negative lookbehind \u2014 asserts what precedes does not match", example: "(?<!\\$)\\d+ matches digits not preceded by '$'", category: "Lookahead/Lookbehind" },
149
- { pattern: "g", description: "Global flag \u2014 finds all matches rather than stopping at the first", example: "/\\d+/g finds all numbers in a string", category: "Flags" },
150
- { pattern: "i", description: "Case-insensitive flag \u2014 ignores letter case", example: "/hello/i matches 'Hello', 'HELLO', 'hello'", category: "Flags" },
151
- { pattern: "m", description: "Multiline flag \u2014 ^ and $ match start/end of each line", example: "/^\\w+/m matches the first word of each line", category: "Flags" },
152
- { pattern: "s", description: "Dotall flag \u2014 makes . match newline characters too", example: "/a.b/s matches 'a\\nb'", category: "Flags" },
153
- { pattern: "u", description: "Unicode flag \u2014 enables full Unicode matching", example: "/\\u{1F600}/u matches a Unicode emoji", category: "Flags" },
154
- { pattern: "y", description: "Sticky flag \u2014 matches only from the lastIndex position", example: "/\\d+/y matches digits starting at the current position", category: "Flags" },
145
+ { pattern: "(?=abc)", description: "Positive lookahead: asserts what follows matches", example: "\\d+(?= dollars) matches digits followed by ' dollars'", category: "Lookahead/Lookbehind" },
146
+ { pattern: "(?!abc)", description: "Negative lookahead: asserts what follows does not match", example: "\\d+(?! dollars) matches digits not followed by ' dollars'", category: "Lookahead/Lookbehind" },
147
+ { pattern: "(?<=abc)", description: "Positive lookbehind: asserts what precedes matches", example: "(?<=\\$)\\d+ matches digits preceded by '$'", category: "Lookahead/Lookbehind" },
148
+ { pattern: "(?<!abc)", description: "Negative lookbehind: asserts what precedes does not match", example: "(?<!\\$)\\d+ matches digits not preceded by '$'", category: "Lookahead/Lookbehind" },
149
+ { pattern: "g", description: "Global flag: finds all matches rather than stopping at the first", example: "/\\d+/g finds all numbers in a string", category: "Flags" },
150
+ { pattern: "i", description: "Case-insensitive flag: ignores letter case", example: "/hello/i matches 'Hello', 'HELLO', 'hello'", category: "Flags" },
151
+ { pattern: "m", description: "Multiline flag: ^ and $ match start/end of each line", example: "/^\\w+/m matches the first word of each line", category: "Flags" },
152
+ { pattern: "s", description: "Dotall flag: makes . match newline characters too", example: "/a.b/s matches 'a\\nb'", category: "Flags" },
153
+ { pattern: "u", description: "Unicode flag: enables full Unicode matching", example: "/\\u{1F600}/u matches a Unicode emoji", category: "Flags" },
154
+ { pattern: "y", description: "Sticky flag: matches only from the lastIndex position", example: "/\\d+/y matches digits starting at the current position", category: "Flags" },
155
155
  { pattern: "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", description: "Matches a basic email address", example: "user@example.com", category: "Common Patterns" },
156
156
  { pattern: "https?://[\\w\\-]+(\\.[\\w\\-]+)+([\\w.,@?^=%&:/~+#\\-]*[\\w@?^=%&/~+#\\-])?", description: "Matches an HTTP or HTTPS URL", example: "https://www.example.com/path?query=value", category: "Common Patterns" },
157
157
  { pattern: "^(?:\\+1)?[-.\\s]?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}$", description: "Matches a North American phone number in various formats", example: "(555) 123-4567 or 555-123-4567", category: "Common Patterns" },