mani-calc 1.1.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.
@@ -0,0 +1,141 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ class HistoryManager {
5
+ constructor() {
6
+ this.historyFile = path.join(process.env.APPDATA || process.env.HOME, 'mani-calc', 'history.json');
7
+ this.maxEntries = 100;
8
+ this.ensureHistoryFile();
9
+ }
10
+
11
+ ensureHistoryFile() {
12
+ const dir = path.dirname(this.historyFile);
13
+
14
+ if (!fs.existsSync(dir)) {
15
+ fs.mkdirSync(dir, { recursive: true });
16
+ }
17
+
18
+ if (!fs.existsSync(this.historyFile)) {
19
+ fs.writeFileSync(this.historyFile, JSON.stringify([], null, 2));
20
+ }
21
+ }
22
+
23
+ addEntry(entry) {
24
+ try {
25
+ const history = this.loadHistory();
26
+
27
+ const newEntry = {
28
+ ...entry,
29
+ timestamp: new Date().toISOString(),
30
+ id: Date.now()
31
+ };
32
+
33
+ history.unshift(newEntry);
34
+
35
+ // Keep only the most recent entries
36
+ if (history.length > this.maxEntries) {
37
+ history.splice(this.maxEntries);
38
+ }
39
+
40
+ this.saveHistory(history);
41
+
42
+ return newEntry;
43
+ } catch (error) {
44
+ console.error('Failed to add history entry:', error);
45
+ }
46
+ }
47
+
48
+ getHistory(limit = 10) {
49
+ try {
50
+ const history = this.loadHistory();
51
+ return {
52
+ type: 'history',
53
+ entries: history.slice(0, limit),
54
+ total: history.length,
55
+ formatted: this.formatHistory(history.slice(0, limit))
56
+ };
57
+ } catch (error) {
58
+ console.error('Failed to get history:', error);
59
+ return {
60
+ type: 'history',
61
+ entries: [],
62
+ total: 0,
63
+ formatted: 'No history available'
64
+ };
65
+ }
66
+ }
67
+
68
+ formatHistory(entries) {
69
+ if (entries.length === 0) {
70
+ return 'No calculation history yet';
71
+ }
72
+
73
+ let formatted = 'Recent Calculations:\n\n';
74
+
75
+ entries.forEach((entry, index) => {
76
+ const date = new Date(entry.timestamp);
77
+ const timeStr = date.toLocaleTimeString('en-US', {
78
+ hour: '2-digit',
79
+ minute: '2-digit'
80
+ });
81
+
82
+ formatted += `${index + 1}. ${entry.formatted} (${timeStr})\n`;
83
+ });
84
+
85
+ return formatted;
86
+ }
87
+
88
+ clearHistory() {
89
+ try {
90
+ this.saveHistory([]);
91
+ return true;
92
+ } catch (error) {
93
+ console.error('Failed to clear history:', error);
94
+ return false;
95
+ }
96
+ }
97
+
98
+ loadHistory() {
99
+ try {
100
+ const data = fs.readFileSync(this.historyFile, 'utf8');
101
+ return JSON.parse(data);
102
+ } catch (error) {
103
+ return [];
104
+ }
105
+ }
106
+
107
+ saveHistory(history) {
108
+ try {
109
+ fs.writeFileSync(this.historyFile, JSON.stringify(history, null, 2));
110
+ } catch (error) {
111
+ console.error('Failed to save history:', error);
112
+ }
113
+ }
114
+
115
+ searchHistory(query) {
116
+ try {
117
+ const history = this.loadHistory();
118
+ const results = history.filter(entry =>
119
+ entry.query.toLowerCase().includes(query.toLowerCase()) ||
120
+ entry.formatted.toLowerCase().includes(query.toLowerCase())
121
+ );
122
+
123
+ return {
124
+ type: 'search',
125
+ query,
126
+ results,
127
+ formatted: this.formatHistory(results)
128
+ };
129
+ } catch (error) {
130
+ console.error('Failed to search history:', error);
131
+ return {
132
+ type: 'search',
133
+ query,
134
+ results: [],
135
+ formatted: 'Search failed'
136
+ };
137
+ }
138
+ }
139
+ }
140
+
141
+ module.exports = HistoryManager;
@@ -0,0 +1,95 @@
1
+ const math = require('mathjs');
2
+
3
+ class MathEngine {
4
+ constructor() {
5
+ // Configure mathjs for better precision and functionality
6
+ this.parser = math.parser();
7
+
8
+ // Add custom functions
9
+ this.addCustomFunctions();
10
+ }
11
+
12
+ addCustomFunctions() {
13
+ // Add percentage calculation
14
+ this.parser.set('percent', (value, total) => {
15
+ return (value / 100) * total;
16
+ });
17
+
18
+ // Add common shortcuts
19
+ this.parser.set('half', (value) => value / 2);
20
+ this.parser.set('double', (value) => value * 2);
21
+ this.parser.set('triple', (value) => value * 3);
22
+ }
23
+
24
+ evaluate(expression) {
25
+ try {
26
+ // Clean the expression
27
+ const cleanExpression = this.cleanExpression(expression);
28
+
29
+ // Evaluate using mathjs
30
+ const result = this.parser.evaluate(cleanExpression);
31
+
32
+ // Format the result
33
+ return this.formatResult(result);
34
+ } catch (error) {
35
+ throw new Error(`Invalid expression: ${error.message}`);
36
+ }
37
+ }
38
+
39
+ cleanExpression(expr) {
40
+ // Remove common prefixes
41
+ expr = expr.replace(/^(calc:|calculate:|=)\s*/i, '');
42
+
43
+ // Replace common words with operators
44
+ expr = expr.replace(/\s+plus\s+/gi, ' + ');
45
+ expr = expr.replace(/\s+minus\s+/gi, ' - ');
46
+ expr = expr.replace(/\s+times\s+/gi, ' * ');
47
+ expr = expr.replace(/\s+divided by\s+/gi, ' / ');
48
+ expr = expr.replace(/\s+multiplied by\s+/gi, ' * ');
49
+
50
+ // Handle implicit multiplication (e.g., "2(3+4)" -> "2*(3+4)")
51
+ expr = expr.replace(/(\d)\(/g, '$1*(');
52
+
53
+ return expr.trim();
54
+ }
55
+
56
+ formatResult(result) {
57
+ // Handle complex numbers
58
+ if (typeof result === 'object' && result.im !== undefined) {
59
+ return result.toString();
60
+ }
61
+
62
+ // Handle arrays/matrices
63
+ if (Array.isArray(result)) {
64
+ return JSON.stringify(result);
65
+ }
66
+
67
+ // Handle regular numbers
68
+ if (typeof result === 'number') {
69
+ // Round to reasonable precision
70
+ if (Math.abs(result) < 1e-10) return 0;
71
+ if (Math.abs(result - Math.round(result)) < 1e-10) {
72
+ return Math.round(result);
73
+ }
74
+ return Math.round(result * 1e10) / 1e10;
75
+ }
76
+
77
+ return result;
78
+ }
79
+
80
+ // Store variables for session
81
+ setVariable(name, value) {
82
+ this.parser.set(name, value);
83
+ }
84
+
85
+ getVariable(name) {
86
+ return this.parser.get(name);
87
+ }
88
+
89
+ clearVariables() {
90
+ this.parser.clear();
91
+ this.addCustomFunctions();
92
+ }
93
+ }
94
+
95
+ module.exports = MathEngine;
@@ -0,0 +1,146 @@
1
+ class NLPParser {
2
+ constructor() {
3
+ this.patterns = this.initializePatterns();
4
+ }
5
+
6
+ initializePatterns() {
7
+ return {
8
+ // Percentage patterns
9
+ percentage: [
10
+ /what is (\d+\.?\d*)\s*(?:percent|%)\s*of\s*(\d+\.?\d*)/i,
11
+ /(\d+\.?\d*)\s*(?:percent|%)\s*of\s*(\d+\.?\d*)/i,
12
+ /calculate (\d+\.?\d*)\s*(?:percent|%)\s*of\s*(\d+\.?\d*)/i
13
+ ],
14
+
15
+ // Fraction patterns
16
+ fraction: [
17
+ /(half|quarter|third)\s*of\s*(\d+\.?\d*)/i,
18
+ /(\d+\.?\d*)\s*(?:divided|split)\s*(?:by|into)\s*(\d+)/i
19
+ ],
20
+
21
+ // Square root patterns
22
+ squareRoot: [
23
+ /(?:square root|sqrt)\s*(?:of\s*)?(\d+\.?\d*)/i,
24
+ /√(\d+\.?\d*)/
25
+ ],
26
+
27
+ // Power patterns
28
+ power: [
29
+ /(\d+\.?\d*)\s*(?:to the power of|raised to|power)\s*(\d+\.?\d*)/i,
30
+ /(\d+\.?\d*)\s*(?:squared|²)/i,
31
+ /(\d+\.?\d*)\s*(?:cubed|³)/i
32
+ ],
33
+
34
+ // Conversion patterns (handled separately)
35
+ conversion: [
36
+ /(\d+\.?\d*)\s*([a-z]+)\s*(?:to|in|as)\s*([a-z]+)/i
37
+ ]
38
+ };
39
+ }
40
+
41
+ parse(query) {
42
+ const cleanQuery = query.trim();
43
+
44
+ // Check for percentage
45
+ for (const pattern of this.patterns.percentage) {
46
+ const match = cleanQuery.match(pattern);
47
+ if (match) {
48
+ const percentage = parseFloat(match[1]);
49
+ const total = parseFloat(match[2]);
50
+ return {
51
+ type: 'natural_language',
52
+ expression: `(${percentage} / 100) * ${total}`,
53
+ original: cleanQuery
54
+ };
55
+ }
56
+ }
57
+
58
+ // Check for fractions
59
+ for (const pattern of this.patterns.fraction) {
60
+ const match = cleanQuery.match(pattern);
61
+ if (match) {
62
+ const fractionMap = {
63
+ 'half': 2,
64
+ 'quarter': 4,
65
+ 'third': 3
66
+ };
67
+
68
+ if (match[1].toLowerCase() in fractionMap) {
69
+ const divisor = fractionMap[match[1].toLowerCase()];
70
+ const value = parseFloat(match[2]);
71
+ return {
72
+ type: 'natural_language',
73
+ expression: `${value} / ${divisor}`,
74
+ original: cleanQuery
75
+ };
76
+ }
77
+ }
78
+ }
79
+
80
+ // Check for square root
81
+ for (const pattern of this.patterns.squareRoot) {
82
+ const match = cleanQuery.match(pattern);
83
+ if (match) {
84
+ const value = parseFloat(match[1]);
85
+ return {
86
+ type: 'natural_language',
87
+ expression: `sqrt(${value})`,
88
+ original: cleanQuery
89
+ };
90
+ }
91
+ }
92
+
93
+ // Check for power
94
+ for (const pattern of this.patterns.power) {
95
+ const match = cleanQuery.match(pattern);
96
+ if (match) {
97
+ if (cleanQuery.includes('squared') || cleanQuery.includes('²')) {
98
+ const value = parseFloat(match[1]);
99
+ return {
100
+ type: 'natural_language',
101
+ expression: `${value}^2`,
102
+ original: cleanQuery
103
+ };
104
+ }
105
+ if (cleanQuery.includes('cubed') || cleanQuery.includes('³')) {
106
+ const value = parseFloat(match[1]);
107
+ return {
108
+ type: 'natural_language',
109
+ expression: `${value}^3`,
110
+ original: cleanQuery
111
+ };
112
+ }
113
+ const base = parseFloat(match[1]);
114
+ const exponent = parseFloat(match[2]);
115
+ return {
116
+ type: 'natural_language',
117
+ expression: `${base}^${exponent}`,
118
+ original: cleanQuery
119
+ };
120
+ }
121
+ }
122
+
123
+ // Check for conversion
124
+ for (const pattern of this.patterns.conversion) {
125
+ const match = cleanQuery.match(pattern);
126
+ if (match) {
127
+ return {
128
+ type: 'conversion',
129
+ value: parseFloat(match[1]),
130
+ fromUnit: match[2].toLowerCase(),
131
+ toUnit: match[3].toLowerCase(),
132
+ original: cleanQuery
133
+ };
134
+ }
135
+ }
136
+
137
+ // No pattern matched, return as direct expression
138
+ return {
139
+ type: 'direct',
140
+ expression: cleanQuery,
141
+ original: cleanQuery
142
+ };
143
+ }
144
+ }
145
+
146
+ module.exports = NLPParser;
@@ -0,0 +1,208 @@
1
+ class UnitConverter {
2
+ constructor() {
3
+ this.conversions = this.initializeConversions();
4
+ }
5
+
6
+ initializeConversions() {
7
+ return {
8
+ // Length conversions (base: meters)
9
+ length: {
10
+ m: 1,
11
+ meter: 1,
12
+ meters: 1,
13
+ km: 1000,
14
+ kilometer: 1000,
15
+ kilometers: 1000,
16
+ cm: 0.01,
17
+ centimeter: 0.01,
18
+ centimeters: 0.01,
19
+ mm: 0.001,
20
+ millimeter: 0.001,
21
+ millimeters: 0.001,
22
+ mile: 1609.34,
23
+ miles: 1609.34,
24
+ yard: 0.9144,
25
+ yards: 0.9144,
26
+ foot: 0.3048,
27
+ feet: 0.3048,
28
+ ft: 0.3048,
29
+ inch: 0.0254,
30
+ inches: 0.0254,
31
+ in: 0.0254
32
+ },
33
+
34
+ // Weight conversions (base: kilograms)
35
+ weight: {
36
+ kg: 1,
37
+ kilogram: 1,
38
+ kilograms: 1,
39
+ g: 0.001,
40
+ gram: 0.001,
41
+ grams: 0.001,
42
+ mg: 0.000001,
43
+ milligram: 0.000001,
44
+ milligrams: 0.000001,
45
+ lb: 0.453592,
46
+ lbs: 0.453592,
47
+ pound: 0.453592,
48
+ pounds: 0.453592,
49
+ oz: 0.0283495,
50
+ ounce: 0.0283495,
51
+ ounces: 0.0283495,
52
+ ton: 1000,
53
+ tons: 1000,
54
+ tonne: 1000,
55
+ tonnes: 1000
56
+ },
57
+
58
+ // Temperature conversions (special handling)
59
+ temperature: {
60
+ celsius: 'C',
61
+ c: 'C',
62
+ fahrenheit: 'F',
63
+ f: 'F',
64
+ kelvin: 'K',
65
+ k: 'K'
66
+ },
67
+
68
+ // Volume conversions (base: liters)
69
+ volume: {
70
+ l: 1,
71
+ liter: 1,
72
+ liters: 1,
73
+ ml: 0.001,
74
+ milliliter: 0.001,
75
+ milliliters: 0.001,
76
+ gallon: 3.78541,
77
+ gallons: 3.78541,
78
+ gal: 3.78541,
79
+ quart: 0.946353,
80
+ quarts: 0.946353,
81
+ qt: 0.946353,
82
+ pint: 0.473176,
83
+ pints: 0.473176,
84
+ pt: 0.473176,
85
+ cup: 0.236588,
86
+ cups: 0.236588,
87
+ floz: 0.0295735,
88
+ 'fluid ounce': 0.0295735,
89
+ 'fluid ounces': 0.0295735
90
+ },
91
+
92
+ // Time conversions (base: seconds)
93
+ time: {
94
+ s: 1,
95
+ sec: 1,
96
+ second: 1,
97
+ seconds: 1,
98
+ min: 60,
99
+ minute: 60,
100
+ minutes: 60,
101
+ h: 3600,
102
+ hr: 3600,
103
+ hour: 3600,
104
+ hours: 3600,
105
+ day: 86400,
106
+ days: 86400,
107
+ week: 604800,
108
+ weeks: 604800,
109
+ month: 2592000,
110
+ months: 2592000,
111
+ year: 31536000,
112
+ years: 31536000
113
+ },
114
+
115
+ // Speed conversions (base: m/s)
116
+ speed: {
117
+ 'm/s': 1,
118
+ 'km/h': 0.277778,
119
+ 'mph': 0.44704,
120
+ 'ft/s': 0.3048,
121
+ knot: 0.514444,
122
+ knots: 0.514444
123
+ }
124
+ };
125
+ }
126
+
127
+ convert(value, fromUnit, toUnit) {
128
+ fromUnit = fromUnit.toLowerCase();
129
+ toUnit = toUnit.toLowerCase();
130
+
131
+ // Find which category the units belong to
132
+ const category = this.findCategory(fromUnit, toUnit);
133
+
134
+ if (!category) {
135
+ throw new Error(`Cannot convert from ${fromUnit} to ${toUnit}`);
136
+ }
137
+
138
+ // Special handling for temperature
139
+ if (category === 'temperature') {
140
+ return this.convertTemperature(value, fromUnit, toUnit);
141
+ }
142
+
143
+ // Standard conversion
144
+ const conversions = this.conversions[category];
145
+
146
+ if (!conversions[fromUnit] || !conversions[toUnit]) {
147
+ throw new Error(`Unknown unit in conversion: ${fromUnit} or ${toUnit}`);
148
+ }
149
+
150
+ // Convert to base unit, then to target unit
151
+ const baseValue = value * conversions[fromUnit];
152
+ const result = baseValue / conversions[toUnit];
153
+
154
+ return this.formatResult(result);
155
+ }
156
+
157
+ findCategory(fromUnit, toUnit) {
158
+ for (const [category, units] of Object.entries(this.conversions)) {
159
+ if (units[fromUnit] !== undefined && units[toUnit] !== undefined) {
160
+ return category;
161
+ }
162
+ }
163
+ return null;
164
+ }
165
+
166
+ convertTemperature(value, fromUnit, toUnit) {
167
+ const temps = this.conversions.temperature;
168
+ const from = temps[fromUnit];
169
+ const to = temps[toUnit];
170
+
171
+ if (!from || !to) {
172
+ throw new Error(`Unknown temperature unit: ${fromUnit} or ${toUnit}`);
173
+ }
174
+
175
+ let celsius;
176
+
177
+ // Convert to Celsius first
178
+ if (from === 'C') {
179
+ celsius = value;
180
+ } else if (from === 'F') {
181
+ celsius = (value - 32) * 5 / 9;
182
+ } else if (from === 'K') {
183
+ celsius = value - 273.15;
184
+ }
185
+
186
+ // Convert from Celsius to target
187
+ let result;
188
+ if (to === 'C') {
189
+ result = celsius;
190
+ } else if (to === 'F') {
191
+ result = (celsius * 9 / 5) + 32;
192
+ } else if (to === 'K') {
193
+ result = celsius + 273.15;
194
+ }
195
+
196
+ return this.formatResult(result);
197
+ }
198
+
199
+ formatResult(result) {
200
+ // Round to reasonable precision
201
+ if (Math.abs(result - Math.round(result)) < 1e-10) {
202
+ return Math.round(result);
203
+ }
204
+ return Math.round(result * 100) / 100;
205
+ }
206
+ }
207
+
208
+ module.exports = UnitConverter;
package/src/index.js ADDED
@@ -0,0 +1,121 @@
1
+ const MathEngine = require('./core/math-engine');
2
+ const NLPParser = require('./core/nlp-parser');
3
+ const UnitConverter = require('./core/unit-converter');
4
+ const HistoryManager = require('./core/history-manager');
5
+ const ClipboardManager = require('./core/clipboard-manager');
6
+ const WindowsSearchIntegration = require('./integration/windows-search');
7
+ const chalk = require('chalk');
8
+
9
+ class ManiCalc {
10
+ constructor() {
11
+ this.mathEngine = new MathEngine();
12
+ this.nlpParser = new NLPParser();
13
+ this.unitConverter = new UnitConverter();
14
+ this.historyManager = new HistoryManager();
15
+ this.clipboardManager = new ClipboardManager();
16
+ this.windowsSearch = new WindowsSearchIntegration(this);
17
+ }
18
+
19
+ async initialize() {
20
+ console.log(chalk.cyan('šŸš€ Mani-Calc initializing...'));
21
+
22
+ try {
23
+ await this.windowsSearch.register();
24
+ console.log(chalk.green('āœ“ Windows Search integration registered'));
25
+ console.log(chalk.yellow('\nšŸ“ You can now use mani-calc directly from Windows Search!'));
26
+ console.log(chalk.gray(' Examples:'));
27
+ console.log(chalk.gray(' - calc: 2 + 3 * 5'));
28
+ console.log(chalk.gray(' - calc: what is 25 percent of 200'));
29
+ console.log(chalk.gray(' - calc: 10 km to miles'));
30
+ console.log(chalk.gray(' - calc: history\n'));
31
+
32
+ return true;
33
+ } catch (error) {
34
+ console.error(chalk.red('āœ— Failed to initialize:'), error.message);
35
+ return false;
36
+ }
37
+ }
38
+
39
+ async processQuery(query) {
40
+ try {
41
+ // Check for special commands
42
+ if (query.toLowerCase().trim() === 'history') {
43
+ return this.historyManager.getHistory();
44
+ }
45
+
46
+ if (query.toLowerCase().trim() === 'clear history') {
47
+ this.historyManager.clearHistory();
48
+ return { result: 'History cleared', type: 'info' };
49
+ }
50
+
51
+ // Try natural language parsing first
52
+ const nlpResult = this.nlpParser.parse(query);
53
+
54
+ if (nlpResult.type === 'conversion') {
55
+ const result = this.unitConverter.convert(
56
+ nlpResult.value,
57
+ nlpResult.fromUnit,
58
+ nlpResult.toUnit
59
+ );
60
+
61
+ const response = {
62
+ query,
63
+ result,
64
+ type: 'conversion',
65
+ formatted: `${nlpResult.value} ${nlpResult.fromUnit} = ${result} ${nlpResult.toUnit}`
66
+ };
67
+
68
+ this.historyManager.addEntry(response);
69
+ await this.clipboardManager.copy(result.toString());
70
+
71
+ return response;
72
+ }
73
+
74
+ if (nlpResult.type === 'natural_language') {
75
+ const mathResult = this.mathEngine.evaluate(nlpResult.expression);
76
+
77
+ const response = {
78
+ query,
79
+ result: mathResult,
80
+ type: 'natural_language',
81
+ formatted: `${query} = ${mathResult}`
82
+ };
83
+
84
+ this.historyManager.addEntry(response);
85
+ await this.clipboardManager.copy(mathResult.toString());
86
+
87
+ return response;
88
+ }
89
+
90
+ // Direct math evaluation
91
+ const mathResult = this.mathEngine.evaluate(query);
92
+
93
+ const response = {
94
+ query,
95
+ result: mathResult,
96
+ type: 'math',
97
+ formatted: `${query} = ${mathResult}`
98
+ };
99
+
100
+ this.historyManager.addEntry(response);
101
+ await this.clipboardManager.copy(mathResult.toString());
102
+
103
+ return response;
104
+
105
+ } catch (error) {
106
+ return {
107
+ query,
108
+ error: error.message,
109
+ type: 'error',
110
+ formatted: `Error: ${error.message}`
111
+ };
112
+ }
113
+ }
114
+
115
+ async shutdown() {
116
+ console.log(chalk.cyan('šŸ‘‹ Mani-Calc shutting down...'));
117
+ await this.windowsSearch.unregister();
118
+ }
119
+ }
120
+
121
+ module.exports = ManiCalc;