@supercmd/calculator 1.0.1 → 1.0.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/index.js CHANGED
@@ -1798,7 +1798,7 @@ var TIME_IN_PATTERNS = [
1798
1798
  /^(?:what time is it|whats the time|what is the time|current time|time now) (?:in|at) (.+)$/i,
1799
1799
  /^(?:now|current time) in (.+)$/i
1800
1800
  ];
1801
- var TIME_EXPLICIT_CONVERT_PATTERN = /^(midnight|noon|\d{1,2}(?::\d{2})?(?:\s*(?:am|pm))?)\s+(.+?)\s+to\s+(.+?)$/i;
1801
+ var TIME_EXPLICIT_CONVERT_PATTERN = /^(midnight|noon|\d{1,2}(?::\d{2})?(?:\s*(?:am|pm))?)\s+(?:in\s+)?(.+?)\s+to\s+(.+?)(?:\s+time)?$/i;
1802
1802
  var TIME_CONVERT_PATTERN = /^(.+?) to (.+?)(?: time)?$/i;
1803
1803
  var TIME_NOW_PATTERN = /^(?:what(?:'s| is) )?(?:the )?(?:current )?time(?: now)?$/i;
1804
1804
  function tryTimeIntent(input) {
@@ -1806,7 +1806,7 @@ function tryTimeIntent(input) {
1806
1806
  for (const pattern of TIME_IN_PATTERNS) {
1807
1807
  match = input.match(pattern);
1808
1808
  if (match) {
1809
- const place = match[1].trim();
1809
+ const place = normalizeTimePlace(match[1]);
1810
1810
  const tz2 = resolveTimezone(place);
1811
1811
  if (tz2) return { kind: "time", query: input, to: tz2 };
1812
1812
  return { kind: "time", query: input, to: place };
@@ -1815,8 +1815,8 @@ function tryTimeIntent(input) {
1815
1815
  match = input.match(TIME_EXPLICIT_CONVERT_PATTERN);
1816
1816
  if (match) {
1817
1817
  const time = match[1].trim();
1818
- const from = match[2].trim();
1819
- const to = match[3].trim();
1818
+ const from = normalizeTimePlace(match[2]);
1819
+ const to = normalizeTimePlace(match[3]);
1820
1820
  const fromTz = resolveTimezone(from);
1821
1821
  const toTz = resolveTimezone(to);
1822
1822
  if (fromTz && toTz) {
@@ -1825,8 +1825,8 @@ function tryTimeIntent(input) {
1825
1825
  }
1826
1826
  match = input.match(TIME_CONVERT_PATTERN);
1827
1827
  if (match) {
1828
- const from = match[1].trim();
1829
- const to = match[2].trim();
1828
+ const from = normalizeTimePlace(match[1]);
1829
+ const to = normalizeTimePlace(match[2]);
1830
1830
  const fromTz = resolveTimezone(from);
1831
1831
  const toTz = resolveTimezone(to);
1832
1832
  if (fromTz || toTz) {
@@ -1842,11 +1842,11 @@ function tryTimeIntent(input) {
1842
1842
  }
1843
1843
  const placeSuffixMatch = input.match(/^(.+?)\s+(?:time|now|time now)$/i);
1844
1844
  if (placeSuffixMatch) {
1845
- const place = placeSuffixMatch[1].trim();
1845
+ const place = normalizeTimePlace(placeSuffixMatch[1]);
1846
1846
  const tz2 = resolveTimezone(place);
1847
1847
  if (tz2) return { kind: "time", query: place, to: tz2 };
1848
1848
  }
1849
- const tz = resolveTimezone(input);
1849
+ const tz = resolveTimezone(normalizeTimePlace(input));
1850
1850
  if (tz) {
1851
1851
  return { kind: "time", query: input, to: tz };
1852
1852
  }
@@ -1870,7 +1870,10 @@ var DATE_PATTERNS = [
1870
1870
  /^(?:what(?:'s| is) )?(?:the )?(?:current )?date(?: today)?$/i,
1871
1871
  /^(?:(?:what(?:'s|s| is))\s+today|(?:what(?:'s|s| is))\s+today'?s date|what day is it|what date is .+|todays date|today'?s date|date today|current date|what day is .+|when is .+)$/i,
1872
1872
  // Days between dates
1873
- /^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i
1873
+ /^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i,
1874
+ // Days until
1875
+ /^(?:days?\s+)?(?:until|till|to)\s+.+$/i,
1876
+ /^(?:how many days?\s+)?(?:until|till|to|before)\s+.+$/i
1874
1877
  ];
1875
1878
  function tryDateIntent(input) {
1876
1879
  for (const pattern of DATE_PATTERNS) {
@@ -1936,6 +1939,9 @@ function normalizeConversionInput(input) {
1936
1939
  normalized = normalized.replace(/^([$€£¥₹₩₽₺₦₵₪฿])\s*([\d.,]+)/, "$2 $1");
1937
1940
  return normalized;
1938
1941
  }
1942
+ function normalizeTimePlace(value) {
1943
+ return normalizeWhitespace(value).replace(/^(?:time|current time|time now|now)\s+(?:in|at)\s+/i, "").replace(/^(?:in|at)\s+/i, "").replace(/\s+(?:time|now|time now)$/i, "").trim();
1944
+ }
1939
1945
 
1940
1946
  // src/evaluators/math.ts
1941
1947
  var CONSTANTS = {
@@ -2640,6 +2646,25 @@ function evaluateDate(query) {
2640
2646
  return formatDateResult(query, d, { iso: d.toISOString(), unix: Math.floor(d.getTime() / 1e3) });
2641
2647
  }
2642
2648
  }
2649
+ const daysUntilMatch = lower.match(/^(?:(?:how many )?days?\s+)?(?:until|till|to|before)\s+(.+)$/);
2650
+ if (daysUntilMatch) {
2651
+ const target = parseDateTarget(daysUntilMatch[1].trim());
2652
+ if (target) {
2653
+ const now = /* @__PURE__ */ new Date();
2654
+ now.setHours(0, 0, 0, 0);
2655
+ const targetDate = new Date(target);
2656
+ targetDate.setHours(0, 0, 0, 0);
2657
+ const diffMs = targetDate.getTime() - now.getTime();
2658
+ const diffDays = Math.round(diffMs / (1e3 * 60 * 60 * 24));
2659
+ return {
2660
+ type: "date",
2661
+ input: query,
2662
+ result: diffDays,
2663
+ formatted: `${diffDays} days`,
2664
+ metadata: { from: now.toISOString(), to: target.toISOString() }
2665
+ };
2666
+ }
2667
+ }
2643
2668
  const betweenMatch = lower.match(/^(?:days?\s+)?(?:between|from)\s+(.+)\s+(?:to|and|until)\s+(.+)$/);
2644
2669
  if (betweenMatch) {
2645
2670
  const d1 = parseFlexibleDate(betweenMatch[1].trim());
@@ -2732,6 +2757,82 @@ function formatDateResult(input, d, extraMeta) {
2732
2757
  }
2733
2758
  };
2734
2759
  }
2760
+ function getNextOccurrence(month, day) {
2761
+ const now = /* @__PURE__ */ new Date();
2762
+ const thisYear = new Date(now.getFullYear(), month - 1, day);
2763
+ if (thisYear.getTime() >= new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()) {
2764
+ return thisYear;
2765
+ }
2766
+ return new Date(now.getFullYear() + 1, month - 1, day);
2767
+ }
2768
+ var NAMED_DATES = {
2769
+ "christmas": () => getNextOccurrence(12, 25),
2770
+ "christmas day": () => getNextOccurrence(12, 25),
2771
+ "christmas eve": () => getNextOccurrence(12, 24),
2772
+ "new year": () => getNextOccurrence(1, 1),
2773
+ "new years": () => getNextOccurrence(1, 1),
2774
+ "new year's": () => getNextOccurrence(1, 1),
2775
+ "new year's day": () => getNextOccurrence(1, 1),
2776
+ "new years day": () => getNextOccurrence(1, 1),
2777
+ "new years eve": () => getNextOccurrence(12, 31),
2778
+ "new year's eve": () => getNextOccurrence(12, 31),
2779
+ "valentines": () => getNextOccurrence(2, 14),
2780
+ "valentines day": () => getNextOccurrence(2, 14),
2781
+ "valentine's day": () => getNextOccurrence(2, 14),
2782
+ "halloween": () => getNextOccurrence(10, 31),
2783
+ "independence day": () => getNextOccurrence(7, 4),
2784
+ "st patricks day": () => getNextOccurrence(3, 17),
2785
+ "st patrick's day": () => getNextOccurrence(3, 17)
2786
+ };
2787
+ var MONTH_NAMES = {
2788
+ january: 1,
2789
+ jan: 1,
2790
+ february: 2,
2791
+ feb: 2,
2792
+ march: 3,
2793
+ mar: 3,
2794
+ april: 4,
2795
+ apr: 4,
2796
+ may: 5,
2797
+ june: 6,
2798
+ jun: 6,
2799
+ july: 7,
2800
+ jul: 7,
2801
+ august: 8,
2802
+ aug: 8,
2803
+ september: 9,
2804
+ sep: 9,
2805
+ sept: 9,
2806
+ october: 10,
2807
+ oct: 10,
2808
+ november: 11,
2809
+ nov: 11,
2810
+ december: 12,
2811
+ dec: 12
2812
+ };
2813
+ function parseMonthDay(str) {
2814
+ const cleaned = str.replace(/(\d+)(?:st|nd|rd|th)/gi, "$1");
2815
+ let match = cleaned.match(/^(\d{1,2})\s+([a-z]+)$/i);
2816
+ if (match) {
2817
+ const day = parseInt(match[1]);
2818
+ const month = MONTH_NAMES[match[2].toLowerCase()];
2819
+ if (month && day >= 1 && day <= 31) return getNextOccurrence(month, day);
2820
+ }
2821
+ match = cleaned.match(/^([a-z]+)\s+(\d{1,2})$/i);
2822
+ if (match) {
2823
+ const month = MONTH_NAMES[match[1].toLowerCase()];
2824
+ const day = parseInt(match[2]);
2825
+ if (month && day >= 1 && day <= 31) return getNextOccurrence(month, day);
2826
+ }
2827
+ return null;
2828
+ }
2829
+ function parseDateTarget(str) {
2830
+ const lower = str.toLowerCase().trim();
2831
+ if (NAMED_DATES[lower]) return NAMED_DATES[lower]();
2832
+ const monthDay = parseMonthDay(lower);
2833
+ if (monthDay) return monthDay;
2834
+ return parseFlexibleDate(lower);
2835
+ }
2735
2836
  function parseFlexibleDate(str) {
2736
2837
  if (str === "today" || str === "now" || str === "date") return /* @__PURE__ */ new Date();
2737
2838
  if (str === "tomorrow") {
@@ -2870,6 +2971,46 @@ async function calculate(input, options) {
2870
2971
  const locale = options?.locale ?? "en-US";
2871
2972
  const precision = options?.precision ?? 10;
2872
2973
  const rateProvider = options?.rateProvider ?? getDefaultProvider();
2974
+ const convMathMatch = trimmed.match(
2975
+ /^(.+?\s+(?:to|in)\s+[a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)\s*([+\-*/^])\s*(.+)$/i
2976
+ );
2977
+ if (convMathMatch) {
2978
+ const convPart = convMathMatch[1].trim();
2979
+ const operator = convMathMatch[2];
2980
+ const mathPart = convMathMatch[3].trim();
2981
+ const convIntent = detectIntent(convPart);
2982
+ if (convIntent.kind === "currency" || convIntent.kind === "crypto" || convIntent.kind === "unit") {
2983
+ let convResult;
2984
+ switch (convIntent.kind) {
2985
+ case "currency":
2986
+ convResult = await evaluateCurrency(convIntent.amount, convIntent.from, convIntent.to, rateProvider, locale, precision);
2987
+ break;
2988
+ case "crypto":
2989
+ convResult = await evaluateCrypto(convIntent.amount, convIntent.from, convIntent.to, rateProvider, locale, precision);
2990
+ break;
2991
+ case "unit":
2992
+ convResult = evaluateUnit(convIntent.amount, convIntent.from, convIntent.to, locale, precision);
2993
+ break;
2994
+ }
2995
+ if (convResult.type !== "error" && typeof convResult.result === "number") {
2996
+ const mathExpr = `${convResult.result} ${operator} ${mathPart}`;
2997
+ const mathResult = evaluateMath(mathExpr, locale, precision);
2998
+ if (mathResult.type !== "error") {
2999
+ return {
3000
+ ...mathResult,
3001
+ type: convResult.type,
3002
+ input: trimmed,
3003
+ metadata: {
3004
+ ...mathResult.metadata,
3005
+ conversionResult: convResult.result,
3006
+ conversionFormatted: convResult.formatted,
3007
+ operation: `${convResult.formatted} ${operator} ${mathPart}`
3008
+ }
3009
+ };
3010
+ }
3011
+ }
3012
+ }
3013
+ }
2873
3014
  const intent = detectIntent(trimmed);
2874
3015
  switch (intent.kind) {
2875
3016
  case "time":
package/dist/index.mjs CHANGED
@@ -1761,7 +1761,7 @@ var TIME_IN_PATTERNS = [
1761
1761
  /^(?:what time is it|whats the time|what is the time|current time|time now) (?:in|at) (.+)$/i,
1762
1762
  /^(?:now|current time) in (.+)$/i
1763
1763
  ];
1764
- var TIME_EXPLICIT_CONVERT_PATTERN = /^(midnight|noon|\d{1,2}(?::\d{2})?(?:\s*(?:am|pm))?)\s+(.+?)\s+to\s+(.+?)$/i;
1764
+ var TIME_EXPLICIT_CONVERT_PATTERN = /^(midnight|noon|\d{1,2}(?::\d{2})?(?:\s*(?:am|pm))?)\s+(?:in\s+)?(.+?)\s+to\s+(.+?)(?:\s+time)?$/i;
1765
1765
  var TIME_CONVERT_PATTERN = /^(.+?) to (.+?)(?: time)?$/i;
1766
1766
  var TIME_NOW_PATTERN = /^(?:what(?:'s| is) )?(?:the )?(?:current )?time(?: now)?$/i;
1767
1767
  function tryTimeIntent(input) {
@@ -1769,7 +1769,7 @@ function tryTimeIntent(input) {
1769
1769
  for (const pattern of TIME_IN_PATTERNS) {
1770
1770
  match = input.match(pattern);
1771
1771
  if (match) {
1772
- const place = match[1].trim();
1772
+ const place = normalizeTimePlace(match[1]);
1773
1773
  const tz2 = resolveTimezone(place);
1774
1774
  if (tz2) return { kind: "time", query: input, to: tz2 };
1775
1775
  return { kind: "time", query: input, to: place };
@@ -1778,8 +1778,8 @@ function tryTimeIntent(input) {
1778
1778
  match = input.match(TIME_EXPLICIT_CONVERT_PATTERN);
1779
1779
  if (match) {
1780
1780
  const time = match[1].trim();
1781
- const from = match[2].trim();
1782
- const to = match[3].trim();
1781
+ const from = normalizeTimePlace(match[2]);
1782
+ const to = normalizeTimePlace(match[3]);
1783
1783
  const fromTz = resolveTimezone(from);
1784
1784
  const toTz = resolveTimezone(to);
1785
1785
  if (fromTz && toTz) {
@@ -1788,8 +1788,8 @@ function tryTimeIntent(input) {
1788
1788
  }
1789
1789
  match = input.match(TIME_CONVERT_PATTERN);
1790
1790
  if (match) {
1791
- const from = match[1].trim();
1792
- const to = match[2].trim();
1791
+ const from = normalizeTimePlace(match[1]);
1792
+ const to = normalizeTimePlace(match[2]);
1793
1793
  const fromTz = resolveTimezone(from);
1794
1794
  const toTz = resolveTimezone(to);
1795
1795
  if (fromTz || toTz) {
@@ -1805,11 +1805,11 @@ function tryTimeIntent(input) {
1805
1805
  }
1806
1806
  const placeSuffixMatch = input.match(/^(.+?)\s+(?:time|now|time now)$/i);
1807
1807
  if (placeSuffixMatch) {
1808
- const place = placeSuffixMatch[1].trim();
1808
+ const place = normalizeTimePlace(placeSuffixMatch[1]);
1809
1809
  const tz2 = resolveTimezone(place);
1810
1810
  if (tz2) return { kind: "time", query: place, to: tz2 };
1811
1811
  }
1812
- const tz = resolveTimezone(input);
1812
+ const tz = resolveTimezone(normalizeTimePlace(input));
1813
1813
  if (tz) {
1814
1814
  return { kind: "time", query: input, to: tz };
1815
1815
  }
@@ -1833,7 +1833,10 @@ var DATE_PATTERNS = [
1833
1833
  /^(?:what(?:'s| is) )?(?:the )?(?:current )?date(?: today)?$/i,
1834
1834
  /^(?:(?:what(?:'s|s| is))\s+today|(?:what(?:'s|s| is))\s+today'?s date|what day is it|what date is .+|todays date|today'?s date|date today|current date|what day is .+|when is .+)$/i,
1835
1835
  // Days between dates
1836
- /^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i
1836
+ /^(?:days?\s+)?(?:between|from)\s+.+\s+(?:to|and|until)\s+.+$/i,
1837
+ // Days until
1838
+ /^(?:days?\s+)?(?:until|till|to)\s+.+$/i,
1839
+ /^(?:how many days?\s+)?(?:until|till|to|before)\s+.+$/i
1837
1840
  ];
1838
1841
  function tryDateIntent(input) {
1839
1842
  for (const pattern of DATE_PATTERNS) {
@@ -1899,6 +1902,9 @@ function normalizeConversionInput(input) {
1899
1902
  normalized = normalized.replace(/^([$€£¥₹₩₽₺₦₵₪฿])\s*([\d.,]+)/, "$2 $1");
1900
1903
  return normalized;
1901
1904
  }
1905
+ function normalizeTimePlace(value) {
1906
+ return normalizeWhitespace(value).replace(/^(?:time|current time|time now|now)\s+(?:in|at)\s+/i, "").replace(/^(?:in|at)\s+/i, "").replace(/\s+(?:time|now|time now)$/i, "").trim();
1907
+ }
1902
1908
 
1903
1909
  // src/evaluators/math.ts
1904
1910
  var CONSTANTS = {
@@ -2603,6 +2609,25 @@ function evaluateDate(query) {
2603
2609
  return formatDateResult(query, d, { iso: d.toISOString(), unix: Math.floor(d.getTime() / 1e3) });
2604
2610
  }
2605
2611
  }
2612
+ const daysUntilMatch = lower.match(/^(?:(?:how many )?days?\s+)?(?:until|till|to|before)\s+(.+)$/);
2613
+ if (daysUntilMatch) {
2614
+ const target = parseDateTarget(daysUntilMatch[1].trim());
2615
+ if (target) {
2616
+ const now = /* @__PURE__ */ new Date();
2617
+ now.setHours(0, 0, 0, 0);
2618
+ const targetDate = new Date(target);
2619
+ targetDate.setHours(0, 0, 0, 0);
2620
+ const diffMs = targetDate.getTime() - now.getTime();
2621
+ const diffDays = Math.round(diffMs / (1e3 * 60 * 60 * 24));
2622
+ return {
2623
+ type: "date",
2624
+ input: query,
2625
+ result: diffDays,
2626
+ formatted: `${diffDays} days`,
2627
+ metadata: { from: now.toISOString(), to: target.toISOString() }
2628
+ };
2629
+ }
2630
+ }
2606
2631
  const betweenMatch = lower.match(/^(?:days?\s+)?(?:between|from)\s+(.+)\s+(?:to|and|until)\s+(.+)$/);
2607
2632
  if (betweenMatch) {
2608
2633
  const d1 = parseFlexibleDate(betweenMatch[1].trim());
@@ -2695,6 +2720,82 @@ function formatDateResult(input, d, extraMeta) {
2695
2720
  }
2696
2721
  };
2697
2722
  }
2723
+ function getNextOccurrence(month, day) {
2724
+ const now = /* @__PURE__ */ new Date();
2725
+ const thisYear = new Date(now.getFullYear(), month - 1, day);
2726
+ if (thisYear.getTime() >= new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()) {
2727
+ return thisYear;
2728
+ }
2729
+ return new Date(now.getFullYear() + 1, month - 1, day);
2730
+ }
2731
+ var NAMED_DATES = {
2732
+ "christmas": () => getNextOccurrence(12, 25),
2733
+ "christmas day": () => getNextOccurrence(12, 25),
2734
+ "christmas eve": () => getNextOccurrence(12, 24),
2735
+ "new year": () => getNextOccurrence(1, 1),
2736
+ "new years": () => getNextOccurrence(1, 1),
2737
+ "new year's": () => getNextOccurrence(1, 1),
2738
+ "new year's day": () => getNextOccurrence(1, 1),
2739
+ "new years day": () => getNextOccurrence(1, 1),
2740
+ "new years eve": () => getNextOccurrence(12, 31),
2741
+ "new year's eve": () => getNextOccurrence(12, 31),
2742
+ "valentines": () => getNextOccurrence(2, 14),
2743
+ "valentines day": () => getNextOccurrence(2, 14),
2744
+ "valentine's day": () => getNextOccurrence(2, 14),
2745
+ "halloween": () => getNextOccurrence(10, 31),
2746
+ "independence day": () => getNextOccurrence(7, 4),
2747
+ "st patricks day": () => getNextOccurrence(3, 17),
2748
+ "st patrick's day": () => getNextOccurrence(3, 17)
2749
+ };
2750
+ var MONTH_NAMES = {
2751
+ january: 1,
2752
+ jan: 1,
2753
+ february: 2,
2754
+ feb: 2,
2755
+ march: 3,
2756
+ mar: 3,
2757
+ april: 4,
2758
+ apr: 4,
2759
+ may: 5,
2760
+ june: 6,
2761
+ jun: 6,
2762
+ july: 7,
2763
+ jul: 7,
2764
+ august: 8,
2765
+ aug: 8,
2766
+ september: 9,
2767
+ sep: 9,
2768
+ sept: 9,
2769
+ october: 10,
2770
+ oct: 10,
2771
+ november: 11,
2772
+ nov: 11,
2773
+ december: 12,
2774
+ dec: 12
2775
+ };
2776
+ function parseMonthDay(str) {
2777
+ const cleaned = str.replace(/(\d+)(?:st|nd|rd|th)/gi, "$1");
2778
+ let match = cleaned.match(/^(\d{1,2})\s+([a-z]+)$/i);
2779
+ if (match) {
2780
+ const day = parseInt(match[1]);
2781
+ const month = MONTH_NAMES[match[2].toLowerCase()];
2782
+ if (month && day >= 1 && day <= 31) return getNextOccurrence(month, day);
2783
+ }
2784
+ match = cleaned.match(/^([a-z]+)\s+(\d{1,2})$/i);
2785
+ if (match) {
2786
+ const month = MONTH_NAMES[match[1].toLowerCase()];
2787
+ const day = parseInt(match[2]);
2788
+ if (month && day >= 1 && day <= 31) return getNextOccurrence(month, day);
2789
+ }
2790
+ return null;
2791
+ }
2792
+ function parseDateTarget(str) {
2793
+ const lower = str.toLowerCase().trim();
2794
+ if (NAMED_DATES[lower]) return NAMED_DATES[lower]();
2795
+ const monthDay = parseMonthDay(lower);
2796
+ if (monthDay) return monthDay;
2797
+ return parseFlexibleDate(lower);
2798
+ }
2698
2799
  function parseFlexibleDate(str) {
2699
2800
  if (str === "today" || str === "now" || str === "date") return /* @__PURE__ */ new Date();
2700
2801
  if (str === "tomorrow") {
@@ -2833,6 +2934,46 @@ async function calculate(input, options) {
2833
2934
  const locale = options?.locale ?? "en-US";
2834
2935
  const precision = options?.precision ?? 10;
2835
2936
  const rateProvider = options?.rateProvider ?? getDefaultProvider();
2937
+ const convMathMatch = trimmed.match(
2938
+ /^(.+?\s+(?:to|in)\s+[a-zA-Z$€£¥₹₩₽₺₦₵₪฿]+(?:\s+[a-zA-Z]+)?)\s*([+\-*/^])\s*(.+)$/i
2939
+ );
2940
+ if (convMathMatch) {
2941
+ const convPart = convMathMatch[1].trim();
2942
+ const operator = convMathMatch[2];
2943
+ const mathPart = convMathMatch[3].trim();
2944
+ const convIntent = detectIntent(convPart);
2945
+ if (convIntent.kind === "currency" || convIntent.kind === "crypto" || convIntent.kind === "unit") {
2946
+ let convResult;
2947
+ switch (convIntent.kind) {
2948
+ case "currency":
2949
+ convResult = await evaluateCurrency(convIntent.amount, convIntent.from, convIntent.to, rateProvider, locale, precision);
2950
+ break;
2951
+ case "crypto":
2952
+ convResult = await evaluateCrypto(convIntent.amount, convIntent.from, convIntent.to, rateProvider, locale, precision);
2953
+ break;
2954
+ case "unit":
2955
+ convResult = evaluateUnit(convIntent.amount, convIntent.from, convIntent.to, locale, precision);
2956
+ break;
2957
+ }
2958
+ if (convResult.type !== "error" && typeof convResult.result === "number") {
2959
+ const mathExpr = `${convResult.result} ${operator} ${mathPart}`;
2960
+ const mathResult = evaluateMath(mathExpr, locale, precision);
2961
+ if (mathResult.type !== "error") {
2962
+ return {
2963
+ ...mathResult,
2964
+ type: convResult.type,
2965
+ input: trimmed,
2966
+ metadata: {
2967
+ ...mathResult.metadata,
2968
+ conversionResult: convResult.result,
2969
+ conversionFormatted: convResult.formatted,
2970
+ operation: `${convResult.formatted} ${operator} ${mathPart}`
2971
+ }
2972
+ };
2973
+ }
2974
+ }
2975
+ }
2976
+ }
2836
2977
  const intent = detectIntent(trimmed);
2837
2978
  switch (intent.kind) {
2838
2979
  case "time":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supercmd/calculator",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Powerful natural language calculator — math, units, currency, crypto, time zones, dates",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",