@capsuleer/calc 1.0.1

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/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @capsuleer/calc
2
+
3
+ Calculator module for Capsuleer agents. The world's most over-engineered calculator — but that's the point. Lets agents evaluate math expressions, round results, and crunch descriptive statistics, with every operation emitting a structured trace event so nothing gets lost in the noise.
4
+
5
+ ```bash
6
+ capsuleer install calc
7
+ ```
8
+
9
+ No dependencies. No `eval()`. No regrets.
10
+
11
+ ---
12
+
13
+ ## API
14
+
15
+ ### Expressions
16
+
17
+ ```typescript
18
+ // Basic arithmetic
19
+ const result = await calc.evaluate("(42 * 1.5) + 8")
20
+ // → 71
21
+
22
+ // Math functions and constants
23
+ const hyp = await calc.evaluate("sqrt(pow(3, 2) + pow(4, 2))")
24
+ // → 5
25
+
26
+ const circle = await calc.evaluate("PI * pow(7, 2)")
27
+ // → 153.93804002589985
28
+
29
+ // Chaining with prior results
30
+ const raw = await calc.evaluate("1 / 3")
31
+ const clean = await calc.round(raw, 4)
32
+ // → 0.3333
33
+ ```
34
+
35
+ **Supported functions:** `sqrt`, `cbrt`, `abs`, `ceil`, `floor`, `round`, `log`, `log2`, `log10`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `pow`, `min`, `max`
36
+
37
+ **Constants:** `PI`, `E`
38
+
39
+ ### Rounding
40
+
41
+ ```typescript
42
+ await calc.round(3.14159, 2) // → 3.14
43
+ await calc.round(1234.5) // → 1235 (nearest integer)
44
+ await calc.round(0.1 + 0.2, 10) // → 0.3 (float noise begone)
45
+ ```
46
+
47
+ ### Statistics
48
+
49
+ ```typescript
50
+ const scores = [88, 92, 75, 100, 61, 84, 90]
51
+ const s = await calc.stats(scores)
52
+ // → {
53
+ // count: 7,
54
+ // sum: 590,
55
+ // mean: 84.28...,
56
+ // median: 88,
57
+ // min: 61,
58
+ // max: 100,
59
+ // range: 39
60
+ // }
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Observability
66
+
67
+ ```jsonl
68
+ { "ok": true, "op": "calc.evaluate", "data": { "expr": "sqrt(144) + 3 ** 2", "result": 21 } }
69
+ { "ok": true, "op": "calc.round", "data": { "value": 3.14159, "decimals": 2, "result": 3.14 } }
70
+ { "ok": true, "op": "calc.stats", "data": { "count": 7, "result": { "mean": 84.28, "median": 88, ... } } }
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Safety
76
+
77
+ `evaluate()` does not use `eval()`. Expressions are validated against an allowlist of characters and identifiers, then executed via `new Function()` with only the math environment in scope. Property access (`.`) and assignment (`=`) are explicitly blocked. Anything that doesn't look like a math expression throws immediately.
78
+
79
+ ---
80
+
81
+ ## Policy
82
+
83
+ ```typescript
84
+ const capsule = await Capsule({
85
+ policy: {
86
+ calc: {
87
+ evaluate: true,
88
+ round: true,
89
+ stats: true,
90
+ }
91
+ }
92
+ })
93
+ ```
94
+
95
+ See the [policy docs](https://axon.arclabs.it/docs/capsuleer/policy) for the full rule syntax.
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "calc",
3
+ "version": "1.0.1",
4
+ "description": "Calculator module — evaluate math expressions, round numbers, and compute descriptive statistics",
5
+ "exports": [
6
+ {
7
+ "name": "evaluate",
8
+ "declaration": "declare function evaluate(expr: string): Promise<number>",
9
+ "schema": {
10
+ "parameters": {
11
+ "type": "object",
12
+ "properties": {
13
+ "expr": {
14
+ "type": "string"
15
+ }
16
+ },
17
+ "required": [
18
+ "expr"
19
+ ]
20
+ },
21
+ "returns": {
22
+ "type": "number"
23
+ }
24
+ }
25
+ },
26
+ {
27
+ "name": "round",
28
+ "declaration": "declare function round(value: number, decimals: number): Promise<number>",
29
+ "schema": {
30
+ "parameters": {
31
+ "type": "object",
32
+ "properties": {
33
+ "value": {
34
+ "type": "number"
35
+ },
36
+ "decimals": {
37
+ "type": "number"
38
+ }
39
+ },
40
+ "required": [
41
+ "value",
42
+ "decimals"
43
+ ]
44
+ },
45
+ "returns": {
46
+ "type": "number"
47
+ }
48
+ }
49
+ },
50
+ {
51
+ "name": "stats",
52
+ "declaration": "declare function stats(values: Array<number>): Promise<{ count: number; sum: number; mean: number; median: number; min: number; max: number; range: number }>",
53
+ "schema": {
54
+ "parameters": {
55
+ "type": "object",
56
+ "properties": {
57
+ "values": {
58
+ "type": "array",
59
+ "items": {
60
+ "type": "number"
61
+ }
62
+ }
63
+ },
64
+ "required": [
65
+ "values"
66
+ ]
67
+ },
68
+ "returns": {
69
+ "type": "object",
70
+ "properties": {
71
+ "count": {
72
+ "type": "number"
73
+ },
74
+ "sum": {
75
+ "type": "number"
76
+ },
77
+ "mean": {
78
+ "type": "number"
79
+ },
80
+ "median": {
81
+ "type": "number"
82
+ },
83
+ "min": {
84
+ "type": "number"
85
+ },
86
+ "max": {
87
+ "type": "number"
88
+ },
89
+ "range": {
90
+ "type": "number"
91
+ }
92
+ },
93
+ "required": [
94
+ "count",
95
+ "sum",
96
+ "mean",
97
+ "median",
98
+ "min",
99
+ "max",
100
+ "range"
101
+ ]
102
+ }
103
+ }
104
+ }
105
+ ]
106
+ }
package/index.ts ADDED
@@ -0,0 +1,94 @@
1
+ import { defineModule } from "@capsuleer/core"
2
+
3
+ type StatsResult = { count: number; sum: number; mean: number; median: number; min: number; max: number; range: number }
4
+
5
+ // Safe expression evaluator — supports +, -, *, /, **, %, parens,
6
+ // and a small set of math functions. No eval(), no arbitrary code.
7
+ const ALLOWED = /^[\d\s\+\-\*\/\%\(\)\.\,]+$|^[\d\s\+\-\*\/\%\(\)\.\,sqrt|cbrt|abs|ceil|floor|round|log|log2|log10|sin|cos|tan|asin|acos|atan|atan2|pow|min|max|PI|E]+$/
8
+
9
+ const mathEnv: Record<string, unknown> = {
10
+ sqrt: Math.sqrt, cbrt: Math.cbrt,
11
+ abs: Math.abs, ceil: Math.ceil, floor: Math.floor, round: Math.round,
12
+ log: Math.log, log2: Math.log2, log10: Math.log10,
13
+ sin: Math.sin, cos: Math.cos, tan: Math.tan,
14
+ asin: Math.asin, acos: Math.acos, atan: Math.atan, atan2: Math.atan2,
15
+ pow: Math.pow, min: Math.min, max: Math.max,
16
+ PI: Math.PI, E: Math.E,
17
+ }
18
+
19
+ function safeEval(expr: string): number {
20
+ const sanitized = expr.trim()
21
+ // Only allow digits, operators, parens, dots, commas, spaces, and known identifiers
22
+ if (/[^0-9\s\+\-\*\/\%\(\)\.\,a-zA-Z_]/.test(sanitized)) {
23
+ throw new Error(`Expression contains disallowed characters: ${expr}`)
24
+ }
25
+ // Block anything that looks like property access or assignment
26
+ if (/\.\s*[a-zA-Z]/.test(sanitized) || /=/.test(sanitized)) {
27
+ throw new Error(`Expression not allowed: ${expr}`)
28
+ }
29
+ const fn = new Function(...Object.keys(mathEnv), `"use strict"; return (${sanitized})`)
30
+ const result = fn(...Object.values(mathEnv))
31
+ if (typeof result !== "number" || !isFinite(result)) {
32
+ throw new Error(`Expression did not produce a finite number: ${result}`)
33
+ }
34
+ return result
35
+ }
36
+
37
+ const calc = {
38
+ async evaluate(expr: string): Promise<number> {
39
+ try {
40
+ const result = safeEval(expr)
41
+ console.log(JSON.stringify({ ok: true, op: "calc.evaluate", data: { expr, result } }))
42
+ return result
43
+ } catch (err: any) {
44
+ console.log(JSON.stringify({ ok: false, op: "calc.evaluate", data: { expr }, error: err.message }))
45
+ throw err
46
+ }
47
+ },
48
+
49
+ async round(value: number, decimals = 0): Promise<number> {
50
+ try {
51
+ const factor = Math.pow(10, decimals)
52
+ const result = Math.round(value * factor) / factor
53
+ console.log(JSON.stringify({ ok: true, op: "calc.round", data: { value, decimals, result } }))
54
+ return result
55
+ } catch (err: any) {
56
+ console.log(JSON.stringify({ ok: false, op: "calc.round", data: { value, decimals }, error: err.message }))
57
+ throw err
58
+ }
59
+ },
60
+
61
+ async stats(values: number[]): Promise<StatsResult> {
62
+ try {
63
+ if (values.length === 0) throw new Error("Cannot compute stats on an empty array")
64
+ const sorted = [...values].sort((a, b) => a - b)
65
+ const sum = values.reduce((a, b) => a + b, 0)
66
+ const mean = sum / values.length
67
+ const mid = Math.floor(sorted.length / 2)
68
+ const median = sorted.length % 2 === 0
69
+ ? (sorted[mid - 1] + sorted[mid]) / 2
70
+ : sorted[mid]
71
+ const result: StatsResult = {
72
+ count: values.length,
73
+ sum,
74
+ mean,
75
+ median,
76
+ min: sorted[0],
77
+ max: sorted[sorted.length - 1],
78
+ range: sorted[sorted.length - 1] - sorted[0],
79
+ }
80
+ console.log(JSON.stringify({ ok: true, op: "calc.stats", data: { count: values.length, result } }))
81
+ return result
82
+ } catch (err: any) {
83
+ console.log(JSON.stringify({ ok: false, op: "calc.stats", data: { count: values.length }, error: err.message }))
84
+ throw err
85
+ }
86
+ },
87
+ }
88
+
89
+ export default defineModule({
90
+ name: "calc",
91
+ version: "1.0.0",
92
+ description: "Calculator module — evaluate math expressions, round numbers, and compute descriptive statistics",
93
+ api: calc,
94
+ })
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@capsuleer/calc",
3
+ "version": "1.0.1",
4
+ "description": "Calculator module for capsuleer — evaluate math expressions, round numbers, and compute descriptive statistics",
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "types": "./index.ts",
8
+ "keywords": [
9
+ "capsuleer",
10
+ "calculator",
11
+ "math",
12
+ "statistics"
13
+ ],
14
+ "author": "cody",
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "@capsuleer/core": "latest"
18
+ },
19
+ "peerDependencies": {
20
+ "bun": ">=1.0.0"
21
+ }
22
+ }