@utilix-tech/sdk 0.42.0 → 0.44.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.
@@ -28,6 +28,7 @@ __export(units_exports, {
28
28
  calculateEmi: () => calculateEmi,
29
29
  calculateLoan: () => calculateLoan,
30
30
  calculateMarginMarkup: () => calculateMarginMarkup,
31
+ calculatePaybackPeriod: () => calculatePaybackPeriod,
31
32
  calculatePercentageChange: () => calculatePercentageChange,
32
33
  calculateRoi: () => calculateRoi,
33
34
  calculateRuleOf72: () => calculateRuleOf72,
@@ -1928,5 +1929,59 @@ function calculateChange(amountDue, amountTendered, denominations) {
1928
1929
  remainder: breakdown.remainder
1929
1930
  };
1930
1931
  }
1932
+ function _computePayback(initialInvestment, flows) {
1933
+ const round4 = (n) => Math.round(n * 1e4) / 1e4;
1934
+ let cumulative = 0;
1935
+ const cumulativeCashFlows = [];
1936
+ let period = null;
1937
+ for (let i = 0; i < flows.length; i++) {
1938
+ const prevCumulative = cumulative;
1939
+ cumulative += flows[i];
1940
+ cumulativeCashFlows.push(round4(cumulative));
1941
+ if (period === null && cumulative >= initialInvestment) {
1942
+ const remaining = initialInvestment - prevCumulative;
1943
+ const fraction = flows[i] !== 0 ? remaining / flows[i] : 0;
1944
+ period = i + fraction;
1945
+ }
1946
+ }
1947
+ return { cumulative: cumulativeCashFlows, period };
1948
+ }
1949
+ function calculatePaybackPeriod(input) {
1950
+ const { initialInvestment, cashFlows, discountRatePercent } = input;
1951
+ const round4 = (n) => Math.round(n * 1e4) / 1e4;
1952
+ if (!Number.isFinite(initialInvestment) || initialInvestment <= 0) {
1953
+ return { error: "initialInvestment must be a finite number greater than zero" };
1954
+ }
1955
+ if (!Array.isArray(cashFlows) || cashFlows.length === 0) {
1956
+ return { error: "cashFlows must be a non-empty array of numbers" };
1957
+ }
1958
+ if (cashFlows.some((f) => !Number.isFinite(f))) {
1959
+ return { error: "Every cash flow must be a finite number" };
1960
+ }
1961
+ const { cumulative, period } = _computePayback(initialInvestment, cashFlows);
1962
+ const result = {
1963
+ initialInvestment: round4(initialInvestment),
1964
+ cashFlows: cashFlows.map(round4),
1965
+ cumulativeCashFlows: cumulative,
1966
+ paybackPeriod: period !== null ? round4(period) : null,
1967
+ paybackPeriodRounded: period !== null ? Math.ceil(period) : null,
1968
+ recovered: period !== null
1969
+ };
1970
+ if (discountRatePercent !== void 0) {
1971
+ if (!Number.isFinite(discountRatePercent) || discountRatePercent < 0) {
1972
+ return { error: "discountRatePercent must be a finite, non-negative number when provided" };
1973
+ }
1974
+ const r = discountRatePercent / 100;
1975
+ const discountedCashFlows = cashFlows.map((f, i) => f / Math.pow(1 + r, i + 1));
1976
+ const { cumulative: discountedCumulative, period: discountedPeriod } = _computePayback(initialInvestment, discountedCashFlows);
1977
+ result.discountRatePercent = round4(discountRatePercent);
1978
+ result.discountedCashFlows = discountedCashFlows.map(round4);
1979
+ result.discountedCumulativeCashFlows = discountedCumulative;
1980
+ result.discountedPaybackPeriod = discountedPeriod !== null ? round4(discountedPeriod) : null;
1981
+ result.discountedPaybackPeriodRounded = discountedPeriod !== null ? Math.ceil(discountedPeriod) : null;
1982
+ result.discountedRecovered = discountedPeriod !== null;
1983
+ }
1984
+ return result;
1985
+ }
1931
1986
 
1932
- export { BASE_LABELS, BASE_PREFIXES, BYTE_UNITS, COMMON_CURRENCIES, COMMON_LOCALES, COMMON_PERMISSIONS, COMMON_RATIOS, COMMON_SIZES, COMMON_TAX_RATES, COMMON_TIP_PERCENTAGES, COMMON_VIEWPORTS, USD_DENOMINATIONS, applySuccessiveDiscounts, autoFormat, bumpVersion, calcAspectRatio, calcSubnet, calculateBreakEven, calculateCashBreakdown, calculateChange, calculateCompoundInterest, calculateDiscount, calculateEmi, calculateLoan, calculateMarginMarkup, calculatePercentageChange, calculateRoi, calculateRuleOf72, calculateSavingsGoal, calculateSimpleInterest, calculateTax, calculateTimeValue, calculateTipSplit, calculateVideoBitrate, compareSemver, compareUnitPrices, convert, convertAprApy, convertBytes, convertIp, coverBox, decimalToIp, decimalToIpCidr, describePermissions, fitIntoBox, formatNumber, formatValue, formatVwValue, formatWithSeparator, gcd, ipFromBinary, ipFromDecimal, ipFromHex, ipToBinary, ipToDecimal, ipToDecimalCidr, ipToHex, ipToOctal, isValid, isValidCidr, isValidIp, isValidIpv4, marginToMarkup, markupToMargin, octalToPermissions, parseFileSize, parseInput, parseNumberString, parseSemver, permissionsToChmod, permissionsToOctal, permissionsToSymbolic, ptToPx, pxToAll, pxToViewport, remToPx, satisfiesRange, scaleByHeight, scaleByWidth, sortVersions, splitSubnet, symbolicToPermissions, units_exports, validateBarcode, validateCardNumber, validateIban, validateIsbn, viewportToPx };
1987
+ export { BASE_LABELS, BASE_PREFIXES, BYTE_UNITS, COMMON_CURRENCIES, COMMON_LOCALES, COMMON_PERMISSIONS, COMMON_RATIOS, COMMON_SIZES, COMMON_TAX_RATES, COMMON_TIP_PERCENTAGES, COMMON_VIEWPORTS, USD_DENOMINATIONS, applySuccessiveDiscounts, autoFormat, bumpVersion, calcAspectRatio, calcSubnet, calculateBreakEven, calculateCashBreakdown, calculateChange, calculateCompoundInterest, calculateDiscount, calculateEmi, calculateLoan, calculateMarginMarkup, calculatePaybackPeriod, calculatePercentageChange, calculateRoi, calculateRuleOf72, calculateSavingsGoal, calculateSimpleInterest, calculateTax, calculateTimeValue, calculateTipSplit, calculateVideoBitrate, compareSemver, compareUnitPrices, convert, convertAprApy, convertBytes, convertIp, coverBox, decimalToIp, decimalToIpCidr, describePermissions, fitIntoBox, formatNumber, formatValue, formatVwValue, formatWithSeparator, gcd, ipFromBinary, ipFromDecimal, ipFromHex, ipToBinary, ipToDecimal, ipToDecimalCidr, ipToHex, ipToOctal, isValid, isValidCidr, isValidIp, isValidIpv4, marginToMarkup, markupToMargin, octalToPermissions, parseFileSize, parseInput, parseNumberString, parseSemver, permissionsToChmod, permissionsToOctal, permissionsToSymbolic, ptToPx, pxToAll, pxToViewport, remToPx, satisfiesRange, scaleByHeight, scaleByWidth, sortVersions, splitSubnet, symbolicToPermissions, units_exports, validateBarcode, validateCardNumber, validateIban, validateIsbn, viewportToPx };
package/dist/index.cjs CHANGED
@@ -8005,6 +8005,7 @@ __export(units_exports, {
8005
8005
  calculateEmi: () => calculateEmi,
8006
8006
  calculateLoan: () => calculateLoan,
8007
8007
  calculateMarginMarkup: () => calculateMarginMarkup,
8008
+ calculatePaybackPeriod: () => calculatePaybackPeriod,
8008
8009
  calculatePercentageChange: () => calculatePercentageChange,
8009
8010
  calculateRoi: () => calculateRoi,
8010
8011
  calculateRuleOf72: () => calculateRuleOf72,
@@ -9905,6 +9906,60 @@ function calculateChange(amountDue, amountTendered, denominations) {
9905
9906
  remainder: breakdown.remainder
9906
9907
  };
9907
9908
  }
9909
+ function _computePayback(initialInvestment, flows) {
9910
+ const round4 = (n) => Math.round(n * 1e4) / 1e4;
9911
+ let cumulative = 0;
9912
+ const cumulativeCashFlows = [];
9913
+ let period = null;
9914
+ for (let i = 0; i < flows.length; i++) {
9915
+ const prevCumulative = cumulative;
9916
+ cumulative += flows[i];
9917
+ cumulativeCashFlows.push(round4(cumulative));
9918
+ if (period === null && cumulative >= initialInvestment) {
9919
+ const remaining = initialInvestment - prevCumulative;
9920
+ const fraction = flows[i] !== 0 ? remaining / flows[i] : 0;
9921
+ period = i + fraction;
9922
+ }
9923
+ }
9924
+ return { cumulative: cumulativeCashFlows, period };
9925
+ }
9926
+ function calculatePaybackPeriod(input) {
9927
+ const { initialInvestment, cashFlows, discountRatePercent } = input;
9928
+ const round4 = (n) => Math.round(n * 1e4) / 1e4;
9929
+ if (!Number.isFinite(initialInvestment) || initialInvestment <= 0) {
9930
+ return { error: "initialInvestment must be a finite number greater than zero" };
9931
+ }
9932
+ if (!Array.isArray(cashFlows) || cashFlows.length === 0) {
9933
+ return { error: "cashFlows must be a non-empty array of numbers" };
9934
+ }
9935
+ if (cashFlows.some((f) => !Number.isFinite(f))) {
9936
+ return { error: "Every cash flow must be a finite number" };
9937
+ }
9938
+ const { cumulative, period } = _computePayback(initialInvestment, cashFlows);
9939
+ const result = {
9940
+ initialInvestment: round4(initialInvestment),
9941
+ cashFlows: cashFlows.map(round4),
9942
+ cumulativeCashFlows: cumulative,
9943
+ paybackPeriod: period !== null ? round4(period) : null,
9944
+ paybackPeriodRounded: period !== null ? Math.ceil(period) : null,
9945
+ recovered: period !== null
9946
+ };
9947
+ if (discountRatePercent !== void 0) {
9948
+ if (!Number.isFinite(discountRatePercent) || discountRatePercent < 0) {
9949
+ return { error: "discountRatePercent must be a finite, non-negative number when provided" };
9950
+ }
9951
+ const r = discountRatePercent / 100;
9952
+ const discountedCashFlows = cashFlows.map((f, i) => f / Math.pow(1 + r, i + 1));
9953
+ const { cumulative: discountedCumulative, period: discountedPeriod } = _computePayback(initialInvestment, discountedCashFlows);
9954
+ result.discountRatePercent = round4(discountRatePercent);
9955
+ result.discountedCashFlows = discountedCashFlows.map(round4);
9956
+ result.discountedCumulativeCashFlows = discountedCumulative;
9957
+ result.discountedPaybackPeriod = discountedPeriod !== null ? round4(discountedPeriod) : null;
9958
+ result.discountedPaybackPeriodRounded = discountedPeriod !== null ? Math.ceil(discountedPeriod) : null;
9959
+ result.discountedRecovered = discountedPeriod !== null;
9960
+ }
9961
+ return result;
9962
+ }
9908
9963
 
9909
9964
  // src/tools/network.ts
9910
9965
  var network_exports = {};
@@ -9912,6 +9967,7 @@ __export(network_exports, {
9912
9967
  DNS_RECORD_TYPES: () => DNS_RECORD_TYPES,
9913
9968
  HTTP_HEADERS: () => HTTP_HEADERS,
9914
9969
  LINK_REL_VALUES: () => LINK_REL_VALUES,
9970
+ PERMISSIONS_POLICY_FEATURES: () => PERMISSIONS_POLICY_FEATURES,
9915
9971
  REFERRER_POLICY_VALUES: () => REFERRER_POLICY_VALUES,
9916
9972
  STATUS_CODES: () => STATUS_CODES,
9917
9973
  analyzeEmailHeaders: () => analyzeEmailHeaders,
@@ -9920,6 +9976,7 @@ __export(network_exports, {
9920
9976
  buildGeoUrl: () => buildGeoUrl,
9921
9977
  buildLinkHeader: () => buildLinkHeader,
9922
9978
  buildMapUrl: () => buildMapUrl,
9979
+ buildPermissionsPolicy: () => buildPermissionsPolicy,
9923
9980
  buildRangeHeader: () => buildRangeHeader,
9924
9981
  buildReferrerPolicy: () => buildReferrerPolicy,
9925
9982
  buildVaryHeader: () => buildVaryHeader,
@@ -9943,6 +10000,7 @@ __export(network_exports, {
9943
10000
  parseGeoResponse: () => parseGeoResponse,
9944
10001
  parseHar: () => parseHar,
9945
10002
  parseIpv6Address: () => parseIpv6Address,
10003
+ parsePermissionsPolicy: () => parsePermissionsPolicy,
9946
10004
  parseUserAgent: () => parseUserAgent,
9947
10005
  parseVaryHeader: () => parseVaryHeader,
9948
10006
  searchHeaders: () => searchHeaders,
@@ -9952,6 +10010,7 @@ __export(network_exports, {
9952
10010
  validateDomain: () => validateDomain,
9953
10011
  validateHeaderName: () => validateHeaderName,
9954
10012
  validateHeaderValue: () => validateHeaderValue,
10013
+ validateManifest: () => validateManifest,
9955
10014
  validateRobotsTxt: () => validateRobotsTxt,
9956
10015
  validateSitemap: () => validateSitemap
9957
10016
  });
@@ -12270,6 +12329,213 @@ function parseVaryHeader(raw) {
12270
12329
  const headers = withoutLabel.split(",").map((h) => h.trim()).filter((h) => h.length > 0);
12271
12330
  return buildVaryHeader(headers);
12272
12331
  }
12332
+ var VALID_MANIFEST_DISPLAY = /* @__PURE__ */ new Set(["fullscreen", "standalone", "minimal-ui", "browser"]);
12333
+ function validateManifest(jsonText) {
12334
+ if (!jsonText || !jsonText.trim()) return { error: "Input is empty" };
12335
+ let manifest;
12336
+ try {
12337
+ manifest = JSON.parse(jsonText);
12338
+ } catch (e) {
12339
+ return { error: `Invalid JSON: ${e.message}` };
12340
+ }
12341
+ if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) {
12342
+ return { error: "Manifest must be a JSON object" };
12343
+ }
12344
+ const m = manifest;
12345
+ const issues = [];
12346
+ if (!m.name && !m.short_name) {
12347
+ issues.push({ severity: "error", path: "name", message: 'Manifest must have at least one of "name" or "short_name"' });
12348
+ }
12349
+ if (m.name !== void 0 && typeof m.name !== "string") {
12350
+ issues.push({ severity: "error", path: "name", message: '"name" must be a string' });
12351
+ } else if (typeof m.name === "string" && m.name.length > 45) {
12352
+ issues.push({ severity: "warning", path: "name", message: `"name" is ${m.name.length} characters; keep it under ~45 to avoid truncation in install prompts` });
12353
+ }
12354
+ if (m.short_name !== void 0 && typeof m.short_name !== "string") {
12355
+ issues.push({ severity: "error", path: "short_name", message: '"short_name" must be a string' });
12356
+ } else if (typeof m.short_name === "string" && m.short_name.length > 12) {
12357
+ issues.push({ severity: "warning", path: "short_name", message: `"short_name" is ${m.short_name.length} characters; keep it under ~12 to avoid truncation on home screens` });
12358
+ }
12359
+ if (!m.start_url) {
12360
+ issues.push({ severity: "error", path: "start_url", message: 'Manifest is missing "start_url"' });
12361
+ } else if (typeof m.start_url !== "string") {
12362
+ issues.push({ severity: "error", path: "start_url", message: '"start_url" must be a string' });
12363
+ } else if (m.start_url.startsWith("//")) {
12364
+ issues.push({ severity: "warning", path: "start_url", message: '"start_url" looks protocol-relative; use a relative path (e.g. "/") or a full absolute URL instead' });
12365
+ }
12366
+ if (m.display !== void 0) {
12367
+ if (typeof m.display !== "string" || !VALID_MANIFEST_DISPLAY.has(m.display)) {
12368
+ issues.push({ severity: "error", path: "display", message: `"display" must be one of: ${[...VALID_MANIFEST_DISPLAY].join(", ")}` });
12369
+ }
12370
+ } else {
12371
+ issues.push({ severity: "warning", path: "display", message: 'Missing "display"; it defaults to "browser", which will not open the installed app in its own standalone window' });
12372
+ }
12373
+ const icons = [];
12374
+ let hasMaskableIcon = false;
12375
+ let has512Icon = false;
12376
+ if (!Array.isArray(m.icons) || m.icons.length === 0) {
12377
+ issues.push({ severity: "error", path: "icons", message: 'Manifest must have a non-empty "icons" array' });
12378
+ } else {
12379
+ m.icons.forEach((icon, i) => {
12380
+ const path = `icons[${i}]`;
12381
+ if (!icon || typeof icon !== "object") {
12382
+ issues.push({ severity: "error", path, message: "Icon entry must be an object" });
12383
+ return;
12384
+ }
12385
+ const iconObj = icon;
12386
+ if (!iconObj.src || typeof iconObj.src !== "string") {
12387
+ issues.push({ severity: "error", path, message: 'Icon entry is missing a required "src"' });
12388
+ }
12389
+ if (iconObj.sizes !== void 0) {
12390
+ if (typeof iconObj.sizes !== "string" || iconObj.sizes !== "any" && !/^\d+x\d+(\s+\d+x\d+)*$/i.test(iconObj.sizes)) {
12391
+ issues.push({ severity: "warning", path, message: `"sizes" value "${String(iconObj.sizes)}" doesn't match the expected "WIDTHxHEIGHT" format` });
12392
+ } else if (/(^|\s)512x512(\s|$)/i.test(iconObj.sizes)) {
12393
+ has512Icon = true;
12394
+ }
12395
+ }
12396
+ if (typeof iconObj.purpose === "string" && iconObj.purpose.toLowerCase().includes("maskable")) {
12397
+ hasMaskableIcon = true;
12398
+ }
12399
+ icons.push({
12400
+ src: typeof iconObj.src === "string" ? iconObj.src : "",
12401
+ sizes: typeof iconObj.sizes === "string" ? iconObj.sizes : void 0,
12402
+ type: typeof iconObj.type === "string" ? iconObj.type : void 0,
12403
+ purpose: typeof iconObj.purpose === "string" ? iconObj.purpose : void 0
12404
+ });
12405
+ });
12406
+ if (icons.length > 0) {
12407
+ if (!has512Icon) {
12408
+ issues.push({ severity: "warning", path: "icons", message: "No icon sized 512x512 found; this size is used for splash screens and app store listings" });
12409
+ }
12410
+ if (!hasMaskableIcon) {
12411
+ issues.push({ severity: "warning", path: "icons", message: 'No icon with purpose "maskable" (or "any maskable") found; without one, Android may crop your icon into its adaptive icon mask' });
12412
+ }
12413
+ }
12414
+ }
12415
+ if (m.theme_color !== void 0 && typeof m.theme_color !== "string") {
12416
+ issues.push({ severity: "error", path: "theme_color", message: '"theme_color" must be a CSS color string' });
12417
+ }
12418
+ if (m.background_color !== void 0 && typeof m.background_color !== "string") {
12419
+ issues.push({ severity: "error", path: "background_color", message: '"background_color" must be a CSS color string' });
12420
+ } else if (m.background_color === void 0) {
12421
+ issues.push({ severity: "warning", path: "background_color", message: 'Missing "background_color"; without it the splash screen background defaults to white' });
12422
+ }
12423
+ const valid = !issues.some((i) => i.severity === "error");
12424
+ return {
12425
+ issues,
12426
+ valid,
12427
+ summary: {
12428
+ name: typeof m.name === "string" ? m.name : void 0,
12429
+ shortName: typeof m.short_name === "string" ? m.short_name : void 0,
12430
+ startUrl: typeof m.start_url === "string" ? m.start_url : void 0,
12431
+ display: typeof m.display === "string" ? m.display : void 0,
12432
+ icons,
12433
+ hasMaskableIcon,
12434
+ has512Icon
12435
+ }
12436
+ };
12437
+ }
12438
+ var PERMISSIONS_POLICY_NOTES = {
12439
+ camera: "Controls access to video capture devices (getUserMedia video).",
12440
+ microphone: "Controls access to audio capture devices (getUserMedia audio).",
12441
+ geolocation: "Controls access to the Geolocation API.",
12442
+ autoplay: "Controls whether media elements can autoplay without a user gesture.",
12443
+ fullscreen: "Controls access to the Fullscreen API (requestFullscreen).",
12444
+ payment: "Controls access to the Payment Request API.",
12445
+ usb: "Controls access to the WebUSB API.",
12446
+ accelerometer: "Controls access to the Accelerometer sensor API.",
12447
+ gyroscope: "Controls access to the Gyroscope sensor API.",
12448
+ magnetometer: "Controls access to the Magnetometer sensor API.",
12449
+ midi: "Controls access to the Web MIDI API.",
12450
+ "picture-in-picture": "Controls whether video elements can enter picture-in-picture mode.",
12451
+ "sync-xhr": "Controls whether synchronous XMLHttpRequest is allowed.",
12452
+ "display-capture": "Controls access to screen/tab capture (getDisplayMedia).",
12453
+ "encrypted-media": "Controls access to the Encrypted Media Extensions API (DRM playback).",
12454
+ "clipboard-read": "Controls read access to the async Clipboard API.",
12455
+ "clipboard-write": "Controls write access to the async Clipboard API.",
12456
+ "screen-wake-lock": "Controls access to the Screen Wake Lock API.",
12457
+ "web-share": "Controls access to the Web Share API.",
12458
+ "xr-spatial-tracking": "Controls access to WebXR device sensors and spatial tracking."
12459
+ };
12460
+ var PERMISSIONS_POLICY_FEATURES = Object.keys(PERMISSIONS_POLICY_NOTES);
12461
+ function _formatPermissionsPolicyAllowlist(allowlist) {
12462
+ if (allowlist.includes("*")) return "*";
12463
+ if (allowlist.length === 0) return "()";
12464
+ return "(" + allowlist.map((o) => o === "self" ? "self" : `"${o}"`).join(" ") + ")";
12465
+ }
12466
+ function buildPermissionsPolicy(features) {
12467
+ if (!Array.isArray(features) || features.length === 0) return { error: "At least one feature is required" };
12468
+ const warnings = [];
12469
+ const seen = /* @__PURE__ */ new Map();
12470
+ for (const f of features) {
12471
+ if (!f || typeof f.feature !== "string" || f.feature.trim() === "") continue;
12472
+ const name = f.feature.trim().toLowerCase();
12473
+ const allowlist = Array.isArray(f.allowlist) ? f.allowlist.map((o) => typeof o === "string" ? o.trim() : "").filter((o) => o.length > 0) : [];
12474
+ if (seen.has(name)) warnings.push(`duplicate feature "${name}" was merged, the last occurrence wins`);
12475
+ seen.set(name, { feature: name, allowlist });
12476
+ }
12477
+ if (seen.size === 0) return { error: "At least one feature is required" };
12478
+ const directives = [];
12479
+ for (const { feature, allowlist } of seen.values()) {
12480
+ if (!PERMISSIONS_POLICY_NOTES[feature]) {
12481
+ warnings.push(`"${feature}" is not a widely recognized feature name, it will still be included as written`);
12482
+ }
12483
+ if (allowlist.includes("*") && allowlist.length > 1) {
12484
+ warnings.push(`"${feature}" combines "*" with other origins; "*" already allows every origin so the others are redundant`);
12485
+ }
12486
+ const invalidOrigin = allowlist.find((o) => o !== "self" && o !== "*" && !/^https?:\/\//i.test(o));
12487
+ if (invalidOrigin) {
12488
+ warnings.push(`"${invalidOrigin}" on "${feature}" doesn't look like an origin (expected "self", "*", or an http(s):// URL); it will still be quoted as written`);
12489
+ }
12490
+ directives.push({
12491
+ feature,
12492
+ value: _formatPermissionsPolicyAllowlist(allowlist),
12493
+ note: PERMISSIONS_POLICY_NOTES[feature] ?? `Controls access to the "${feature}" feature.`
12494
+ });
12495
+ }
12496
+ const headerValue = directives.map((d) => `${d.feature}=${d.value}`).join(", ");
12497
+ return { header: `Permissions-Policy: ${headerValue}`, headerValue, directives, warnings };
12498
+ }
12499
+ function _splitPermissionsPolicyTopLevel(input) {
12500
+ const segments = [];
12501
+ let depth = 0;
12502
+ let current = "";
12503
+ for (const ch of input) {
12504
+ if (ch === "(") depth++;
12505
+ if (ch === ")") depth--;
12506
+ if (ch === "," && depth === 0) {
12507
+ if (current.trim()) segments.push(current.trim());
12508
+ current = "";
12509
+ } else {
12510
+ current += ch;
12511
+ }
12512
+ }
12513
+ if (current.trim()) segments.push(current.trim());
12514
+ return segments;
12515
+ }
12516
+ function _parsePermissionsPolicyAllowlist(rawValue) {
12517
+ if (rawValue === "*") return ["*"];
12518
+ if (rawValue === "()" || rawValue === "") return [];
12519
+ const inner = rawValue.replace(/^\(/, "").replace(/\)$/, "");
12520
+ const tokens = inner.match(/"[^"]*"|\S+/g) ?? [];
12521
+ return tokens.map((t) => t.replace(/^"|"$/g, ""));
12522
+ }
12523
+ function parsePermissionsPolicy(raw) {
12524
+ if (!raw || raw.trim() === "") return { error: "Input is empty" };
12525
+ const withoutLabel = raw.trim().replace(/^permissions-policy:\s*/i, "");
12526
+ const segments = _splitPermissionsPolicyTopLevel(withoutLabel);
12527
+ if (segments.length === 0) return { error: "No directives found" };
12528
+ const features = [];
12529
+ for (const segment of segments) {
12530
+ const eq = segment.indexOf("=");
12531
+ if (eq === -1) continue;
12532
+ const feature = segment.slice(0, eq).trim();
12533
+ const rawValue = segment.slice(eq + 1).trim();
12534
+ features.push({ feature, allowlist: _parsePermissionsPolicyAllowlist(rawValue) });
12535
+ }
12536
+ if (features.length === 0) return { error: "No directives found" };
12537
+ return buildPermissionsPolicy(features);
12538
+ }
12273
12539
 
12274
12540
  // src/tools/api.ts
12275
12541
  var api_exports = {};
@@ -20121,6 +20387,7 @@ __export(ai_agent_exports, {
20121
20387
  extractUrls: () => extractUrls,
20122
20388
  flattenJson: () => flattenJson,
20123
20389
  formatFewShotExamples: () => formatFewShotExamples,
20390
+ lintToolSchema: () => lintToolSchema,
20124
20391
  mergeJson: () => mergeJson,
20125
20392
  redactPii: () => redactPii,
20126
20393
  repairJson: () => repairJson,
@@ -21101,6 +21368,118 @@ Output: ${e.output.trim()}`).join("\n\n");
21101
21368
  }
21102
21369
  return { formatted, exampleCount: valid.length, format };
21103
21370
  }
21371
+ var TOOL_NAME_RE = /^[a-zA-Z0-9_-]{1,64}$/;
21372
+ function normalizeToolDefs(data) {
21373
+ let rawList;
21374
+ if (Array.isArray(data)) {
21375
+ rawList = data;
21376
+ } else if (data && typeof data === "object" && Array.isArray(data.tools)) {
21377
+ rawList = data.tools;
21378
+ } else {
21379
+ rawList = [data];
21380
+ }
21381
+ const out = [];
21382
+ for (const item of rawList) {
21383
+ if (!item || typeof item !== "object") {
21384
+ return { error: "Each tool definition must be a JSON object" };
21385
+ }
21386
+ const obj = item;
21387
+ if (obj.type === "function" && obj.function && typeof obj.function === "object") {
21388
+ const fn = obj.function;
21389
+ out.push({ name: String(fn.name ?? ""), description: typeof fn.description === "string" ? fn.description : void 0, schema: fn.parameters, format: "openai" });
21390
+ } else if ("input_schema" in obj) {
21391
+ out.push({ name: String(obj.name ?? ""), description: typeof obj.description === "string" ? obj.description : void 0, schema: obj.input_schema, format: "anthropic" });
21392
+ } else if ("inputSchema" in obj) {
21393
+ out.push({ name: String(obj.name ?? ""), description: typeof obj.description === "string" ? obj.description : void 0, schema: obj.inputSchema, format: "mcp" });
21394
+ } else if ("parameters" in obj || "name" in obj) {
21395
+ out.push({ name: String(obj.name ?? ""), description: typeof obj.description === "string" ? obj.description : void 0, schema: obj.parameters, format: "bare" });
21396
+ } else {
21397
+ return { error: 'Unrecognized tool-definition format: expected an OpenAI ({type:"function",function:{...}}), Anthropic ({name,input_schema}), or MCP ({name,inputSchema}) shape' };
21398
+ }
21399
+ }
21400
+ return out;
21401
+ }
21402
+ function lintToolSchema(jsonText) {
21403
+ if (!jsonText || !jsonText.trim()) return { error: "Input is empty" };
21404
+ let data;
21405
+ try {
21406
+ data = JSON.parse(jsonText);
21407
+ } catch (e) {
21408
+ return { error: `Invalid JSON: ${e.message}` };
21409
+ }
21410
+ const normalized = normalizeToolDefs(data);
21411
+ if ("error" in normalized) return normalized;
21412
+ if (normalized.length === 0) return { error: "No tool definitions found" };
21413
+ const issues = [];
21414
+ const seenNames = /* @__PURE__ */ new Map();
21415
+ for (const tool of normalized) {
21416
+ const label = tool.name || "(unnamed)";
21417
+ seenNames.set(label, (seenNames.get(label) ?? 0) + 1);
21418
+ if (!tool.name) {
21419
+ issues.push({ severity: "error", tool: label, path: "name", message: 'Tool is missing a "name"' });
21420
+ } else if (!TOOL_NAME_RE.test(tool.name)) {
21421
+ issues.push({ severity: "error", tool: label, path: "name", message: `Tool name "${tool.name}" must match ^[a-zA-Z0-9_-]{1,64}$ (letters, digits, underscores, hyphens, max 64 chars) to be accepted by OpenAI/Anthropic function calling` });
21422
+ }
21423
+ if (!tool.description || !tool.description.trim()) {
21424
+ issues.push({ severity: "error", tool: label, path: "description", message: 'Tool is missing a "description" \u2014 the model relies entirely on this text to decide when to call it' });
21425
+ } else {
21426
+ if (tool.description.trim().length < 10) {
21427
+ issues.push({ severity: "warning", tool: label, path: "description", message: "Description is very short; a vague description measurably hurts the model's ability to pick the right tool" });
21428
+ }
21429
+ if (tool.description.length > 1024) {
21430
+ issues.push({ severity: "warning", tool: label, path: "description", message: `Description is ${tool.description.length} characters; long descriptions cost tokens on every request that includes this tool` });
21431
+ }
21432
+ }
21433
+ if (tool.schema === void 0 || tool.schema === null) {
21434
+ issues.push({ severity: "warning", tool: label, path: "parameters", message: "Tool has no parameter schema; only correct if the tool genuinely takes no arguments" });
21435
+ continue;
21436
+ }
21437
+ if (typeof tool.schema !== "object" || Array.isArray(tool.schema)) {
21438
+ issues.push({ severity: "error", tool: label, path: "parameters", message: "Parameter schema must be a JSON Schema object" });
21439
+ continue;
21440
+ }
21441
+ const schema = tool.schema;
21442
+ if (schema.type !== void 0 && schema.type !== "object") {
21443
+ issues.push({ severity: "error", tool: label, path: "parameters.type", message: `Top-level parameter schema type is "${String(schema.type)}"; OpenAI and Anthropic both require "object" at the top level` });
21444
+ }
21445
+ const properties = schema.properties;
21446
+ if (properties !== void 0 && (typeof properties !== "object" || properties === null || Array.isArray(properties))) {
21447
+ issues.push({ severity: "error", tool: label, path: "parameters.properties", message: '"properties" must be an object' });
21448
+ } else if (properties && typeof properties === "object") {
21449
+ const propsObj = properties;
21450
+ const propNames = Object.keys(propsObj);
21451
+ for (const propName of propNames) {
21452
+ const prop = propsObj[propName];
21453
+ const propPath = `parameters.properties.${propName}`;
21454
+ if (!prop || typeof prop !== "object") {
21455
+ issues.push({ severity: "error", tool: label, path: propPath, message: "Property schema must be an object" });
21456
+ continue;
21457
+ }
21458
+ const propObj = prop;
21459
+ if (!propObj.description) {
21460
+ issues.push({ severity: "warning", tool: label, path: propPath, message: `Property "${propName}" has no description; missing per-parameter descriptions measurably hurt model accuracy on which value to pass` });
21461
+ }
21462
+ if (Array.isArray(propObj.enum) && propObj.enum.length === 0) {
21463
+ issues.push({ severity: "error", tool: label, path: propPath, message: `Property "${propName}" has an empty "enum" array` });
21464
+ }
21465
+ }
21466
+ if (Array.isArray(schema.required)) {
21467
+ for (const req of schema.required) {
21468
+ if (typeof req === "string" && !propNames.includes(req)) {
21469
+ issues.push({ severity: "error", tool: label, path: "parameters.required", message: `"required" lists "${req}", which isn't defined in "properties"` });
21470
+ }
21471
+ }
21472
+ }
21473
+ }
21474
+ }
21475
+ for (const [name, count] of seenNames) {
21476
+ if (count > 1 && name !== "(unnamed)") {
21477
+ issues.push({ severity: "error", tool: name, path: "name", message: `Tool name "${name}" is used ${count} times; tool names must be unique within a single request` });
21478
+ }
21479
+ }
21480
+ const valid = !issues.some((i) => i.severity === "error");
21481
+ return { toolCount: normalized.length, issues, valid };
21482
+ }
21104
21483
 
21105
21484
  // src/tools/media.ts
21106
21485
  var media_exports = {};
package/dist/index.d.cts CHANGED
@@ -5,14 +5,14 @@ export { t as text } from './text-kHkT9VtT.cjs';
5
5
  export { d as data } from './data-BKpEOcOX.cjs';
6
6
  export { g as generators } from './generators-Du8cBesn.cjs';
7
7
  export { t as time } from './time-KqJjdqII.cjs';
8
- export { u as units } from './units-CzPwRC03.cjs';
9
- export { n as network } from './network-oAs83Ztn.cjs';
8
+ export { u as units } from './units-WAgBQ7D7.cjs';
9
+ export { n as network } from './network-BlOVDE3Y.cjs';
10
10
  export { a as api } from './api-DmTNE4c1.cjs';
11
11
  export { c as code } from './code-KXoRsHtl.cjs';
12
12
  export { c as color } from './color-tPwZCr9H.cjs';
13
13
  export { c as css } from './css-Cf7AMGM-.cjs';
14
14
  export { m as misc } from './misc-CA3N198T.cjs';
15
- export { a as ai_agent } from './ai_agent-C1zudGgT.cjs';
15
+ export { a as ai_agent } from './ai_agent-CmrVjvYH.cjs';
16
16
  export { m as media } from './media-fD8dJ5qR.cjs';
17
17
 
18
18
  declare const version = "0.1.0";
package/dist/index.d.ts CHANGED
@@ -5,14 +5,14 @@ export { t as text } from './text-kHkT9VtT.js';
5
5
  export { d as data } from './data-BKpEOcOX.js';
6
6
  export { g as generators } from './generators-Du8cBesn.js';
7
7
  export { t as time } from './time-KqJjdqII.js';
8
- export { u as units } from './units-CzPwRC03.js';
9
- export { n as network } from './network-oAs83Ztn.js';
8
+ export { u as units } from './units-WAgBQ7D7.js';
9
+ export { n as network } from './network-BlOVDE3Y.js';
10
10
  export { a as api } from './api-DmTNE4c1.js';
11
11
  export { c as code } from './code-KXoRsHtl.js';
12
12
  export { c as color } from './color-tPwZCr9H.js';
13
13
  export { c as css } from './css-Cf7AMGM-.js';
14
14
  export { m as misc } from './misc-CA3N198T.js';
15
- export { a as ai_agent } from './ai_agent-C1zudGgT.js';
15
+ export { a as ai_agent } from './ai_agent-CmrVjvYH.js';
16
16
  export { m as media } from './media-fD8dJ5qR.js';
17
17
 
18
18
  declare const version = "0.1.0";
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  export { media_exports as media } from './chunk-L6V4W4SU.js';
2
- export { units_exports as units } from './chunk-2MTHD2LG.js';
3
- export { network_exports as network } from './chunk-UD45LBG7.js';
2
+ export { units_exports as units } from './chunk-WT3Q5LCN.js';
3
+ export { network_exports as network } from './chunk-GZBSKZ6F.js';
4
4
  export { api_exports as api } from './chunk-52ZDKSIX.js';
5
5
  export { code_exports as code } from './chunk-JL7YTXRB.js';
6
6
  export { color_exports as color } from './chunk-BPVAB4P2.js';
7
7
  export { css_exports as css } from './chunk-6YPV2AB5.js';
8
8
  export { misc_exports as misc } from './chunk-GX7H6TAX.js';
9
- export { ai_agent_exports as ai_agent } from './chunk-W3J7GZVQ.js';
9
+ export { ai_agent_exports as ai_agent } from './chunk-EKIXOA7B.js';
10
10
  export { json_exports as json } from './chunk-WVEKRKGU.js';
11
11
  export { encoding_exports as encoding } from './chunk-DOWQZUTP.js';
12
12
  export { hashing_exports as hashing } from './chunk-PET2CGTH.js';