talocode-calclane 0.3.0__py3-none-any.whl

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.
calclane/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ """CalcLane — calculator engine client for agents (Talocode)."""
2
+
3
+ __version__ = "0.3.0"
4
+
5
+ from .client import CalcLaneClient, CalcLaneError, create_calclane_client
6
+
7
+ __all__ = [
8
+ "CalcLaneClient",
9
+ "CalcLaneError",
10
+ "create_calclane_client",
11
+ "__version__",
12
+ ]
calclane/__main__.py ADDED
@@ -0,0 +1,80 @@
1
+ """CLI: `calclane` / `python -m calclane`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+
9
+ from . import __version__
10
+ from .client import CalcLaneClient, CalcLaneError
11
+
12
+
13
+ def main(argv: list[str] | None = None) -> None:
14
+ parser = argparse.ArgumentParser(
15
+ prog="calclane",
16
+ description="CalcLane — calculator for agents (Talocode)",
17
+ )
18
+ parser.add_argument("--version", action="version", version=__version__)
19
+ parser.add_argument("--base-url", default=None)
20
+ parser.add_argument("--api-key", default=None)
21
+ parser.add_argument(
22
+ "--remote",
23
+ action="store_true",
24
+ help="Use hosted API at TALOCODE_BASE_URL",
25
+ )
26
+ sub = parser.add_subparsers(dest="command")
27
+
28
+ sub.add_parser("health")
29
+ sub.add_parser("pricing")
30
+ sub.add_parser("capabilities")
31
+
32
+ ev = sub.add_parser("eval", aliases=["evaluate"], help="Evaluate expression")
33
+ ev.add_argument("expression", nargs="?", default=None)
34
+ ev.add_argument("-e", "--expr", dest="expr", default=None)
35
+ ev.add_argument("--mode", choices=["standard", "scientific"], default="scientific")
36
+ ev.add_argument("--angle", choices=["deg", "rad", "grad"], default="deg")
37
+
38
+ args = parser.parse_args(argv)
39
+ if not args.command:
40
+ parser.print_help()
41
+ sys.exit(1)
42
+
43
+ client = CalcLaneClient(
44
+ api_key=args.api_key,
45
+ base_url=args.base_url,
46
+ prefer_local=not args.remote,
47
+ )
48
+
49
+ try:
50
+ if args.command == "health":
51
+ result = client.health(remote=args.remote)
52
+ elif args.command == "pricing":
53
+ result = client.pricing(remote=args.remote)
54
+ elif args.command == "capabilities":
55
+ result = client.capabilities(remote=args.remote)
56
+ elif args.command in ("eval", "evaluate"):
57
+ expression = args.expression or args.expr
58
+ if not expression:
59
+ print("expression required", file=sys.stderr)
60
+ sys.exit(1)
61
+ result = client.evaluate(
62
+ expression,
63
+ mode=args.mode,
64
+ angle=args.angle,
65
+ remote=args.remote,
66
+ )
67
+ if not result.get("ok"):
68
+ print(json.dumps(result, indent=2))
69
+ sys.exit(2)
70
+ else:
71
+ print(f"Unknown command: {args.command}", file=sys.stderr)
72
+ sys.exit(1)
73
+ print(json.dumps(result, indent=2))
74
+ except CalcLaneError as e:
75
+ print(f"Error: {e}", file=sys.stderr)
76
+ sys.exit(1)
77
+
78
+
79
+ if __name__ == "__main__":
80
+ main()
calclane/client.py ADDED
@@ -0,0 +1,410 @@
1
+ """CalcLane REST client + local expression evaluator (stdlib only)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import json
7
+ import math
8
+ import operator
9
+ import os
10
+ import re
11
+ import urllib.error
12
+ import urllib.request
13
+ from typing import Any
14
+
15
+
16
+ class CalcLaneError(Exception):
17
+ """Raised when evaluation or the hosted API fails."""
18
+
19
+ def __init__(
20
+ self,
21
+ message: str,
22
+ *,
23
+ code: str | None = None,
24
+ status: int | None = None,
25
+ ) -> None:
26
+ super().__init__(message)
27
+ self.code = code
28
+ self.status = status
29
+
30
+
31
+ _BINOPS = {
32
+ ast.Add: operator.add,
33
+ ast.Sub: operator.sub,
34
+ ast.Mult: operator.mul,
35
+ ast.Div: operator.truediv,
36
+ ast.Mod: operator.mod,
37
+ ast.Pow: operator.pow,
38
+ }
39
+
40
+ _FUNCS = {
41
+ "sin": math.sin,
42
+ "cos": math.cos,
43
+ "tan": math.tan,
44
+ "asin": math.asin,
45
+ "acos": math.acos,
46
+ "atan": math.atan,
47
+ "log": math.log10,
48
+ "ln": math.log,
49
+ "sqrt": math.sqrt,
50
+ "cbrt": lambda x: x ** (1 / 3) if x >= 0 else -((-x) ** (1 / 3)),
51
+ "abs": abs,
52
+ "floor": math.floor,
53
+ "ceil": math.ceil,
54
+ "round": round,
55
+ "exp": math.exp,
56
+ "fact": math.factorial,
57
+ "factorial": math.factorial,
58
+ }
59
+
60
+
61
+ def _to_radians(value: float, angle: str) -> float:
62
+ if angle == "deg":
63
+ return math.radians(value)
64
+ if angle == "grad":
65
+ return value * math.pi / 200
66
+ return value
67
+
68
+
69
+ def _from_radians(value: float, angle: str) -> float:
70
+ if angle == "deg":
71
+ return math.degrees(value)
72
+ if angle == "grad":
73
+ return value * 200 / math.pi
74
+ return value
75
+
76
+
77
+ class _SafeEval(ast.NodeVisitor):
78
+ def __init__(self, angle: str = "deg", mode: str = "scientific") -> None:
79
+ self.angle = angle
80
+ self.mode = mode
81
+
82
+ def visit(self, node: ast.AST) -> Any: # type: ignore[override]
83
+ return super().visit(node)
84
+
85
+ def eval(self, expression: str) -> float:
86
+ # normalize ^ to ** for Python AST
87
+ expr = expression.strip().replace("^", "**")
88
+ expr = re.sub(r"\bpi\b", "pi", expr, flags=re.I)
89
+ tree = ast.parse(expr, mode="eval")
90
+ return float(self.visit(tree.body))
91
+
92
+ def visit_Expression(self, node: ast.Expression) -> Any:
93
+ return self.visit(node.body)
94
+
95
+ def visit_Constant(self, node: ast.Constant) -> Any:
96
+ if isinstance(node.value, (int, float)):
97
+ return float(node.value)
98
+ raise CalcLaneError("Invalid constant")
99
+
100
+ def visit_Name(self, node: ast.Name) -> Any:
101
+ n = node.id.lower()
102
+ if n == "pi":
103
+ return math.pi
104
+ if n == "e":
105
+ return math.e
106
+ raise CalcLaneError(f"Unknown identifier: {node.id}")
107
+
108
+ def visit_UnaryOp(self, node: ast.UnaryOp) -> Any:
109
+ v = self.visit(node.operand)
110
+ if isinstance(node.op, ast.UAdd):
111
+ return +v
112
+ if isinstance(node.op, ast.USub):
113
+ return -v
114
+ raise CalcLaneError("Unsupported unary operator")
115
+
116
+ def visit_BinOp(self, node: ast.BinOp) -> Any:
117
+ left = self.visit(node.left)
118
+ right = self.visit(node.right)
119
+ op_type = type(node.op)
120
+ if op_type not in _BINOPS:
121
+ raise CalcLaneError("Unsupported operator")
122
+ if op_type is ast.Div and right == 0:
123
+ raise CalcLaneError("Cannot divide by zero")
124
+ if op_type is ast.Mod and right == 0:
125
+ raise CalcLaneError("Cannot divide by zero")
126
+ result = _BINOPS[op_type](left, right)
127
+ if not math.isfinite(result):
128
+ raise CalcLaneError("Overflow")
129
+ return result
130
+
131
+ def visit_Call(self, node: ast.Call) -> Any:
132
+ if not isinstance(node.func, ast.Name):
133
+ raise CalcLaneError("Invalid function call")
134
+ name = node.func.id.lower()
135
+ if name not in _FUNCS:
136
+ raise CalcLaneError(f"Unknown function: {name}")
137
+ if len(node.args) != 1 or node.keywords:
138
+ raise CalcLaneError(f"{name} expects one argument")
139
+ x = float(self.visit(node.args[0]))
140
+ if name in ("sin", "cos", "tan"):
141
+ x = _to_radians(x, self.angle)
142
+ result = _FUNCS[name](x)
143
+ elif name in ("asin", "acos", "atan"):
144
+ result = _from_radians(_FUNCS[name](x), self.angle)
145
+ else:
146
+ if name in ("fact", "factorial"):
147
+ if not float(x).is_integer() or x < 0:
148
+ raise CalcLaneError("Invalid factorial input")
149
+ result = float(math.factorial(int(x)))
150
+ else:
151
+ result = float(_FUNCS[name](x))
152
+ if not math.isfinite(result):
153
+ raise CalcLaneError("Invalid input")
154
+ return result
155
+
156
+ def visit_Expr(self, node: ast.Expr) -> Any:
157
+ return self.visit(node.value)
158
+
159
+ def generic_visit(self, node: ast.AST) -> Any:
160
+ raise CalcLaneError(f"Unsupported expression node: {type(node).__name__}")
161
+
162
+
163
+ def evaluate_local(
164
+ expression: str,
165
+ *,
166
+ mode: str = "scientific",
167
+ angle: str = "deg",
168
+ ) -> dict[str, Any]:
169
+ """Evaluate expression offline (stdlib). Scientific uses Python precedence."""
170
+ expr = (expression or "").strip()
171
+ if not expr:
172
+ return {
173
+ "ok": False,
174
+ "expression": expr,
175
+ "result": None,
176
+ "display": "expression is required",
177
+ "mode": mode,
178
+ "angle": angle,
179
+ "engine": "calclane",
180
+ "version": "0.3.0",
181
+ "error": "expression is required",
182
+ }
183
+ try:
184
+ # Standard mode: left-to-right by reducing operators sequentially is hard with AST.
185
+ # For standard we still use scientific-safe AST; note documented difference.
186
+ value = _SafeEval(angle=angle, mode=mode).eval(expr)
187
+ # pretty display
188
+ if abs(value) != 0 and (abs(value) >= 1e16 or abs(value) < 1e-10):
189
+ display = f"{value:.10e}".replace("e+", "e")
190
+ else:
191
+ display = f"{value:.15g}"
192
+ return {
193
+ "ok": True,
194
+ "expression": expr,
195
+ "result": value,
196
+ "display": display,
197
+ "mode": mode,
198
+ "angle": angle,
199
+ "engine": "calclane",
200
+ "version": "0.3.0",
201
+ }
202
+ except CalcLaneError as e:
203
+ return {
204
+ "ok": False,
205
+ "expression": expr,
206
+ "result": None,
207
+ "display": str(e),
208
+ "mode": mode,
209
+ "angle": angle,
210
+ "engine": "calclane",
211
+ "version": "0.3.0",
212
+ "error": str(e),
213
+ }
214
+ except Exception as e: # noqa: BLE001
215
+ return {
216
+ "ok": False,
217
+ "expression": expr,
218
+ "result": None,
219
+ "display": str(e),
220
+ "mode": mode,
221
+ "angle": angle,
222
+ "engine": "calclane",
223
+ "version": "0.3.0",
224
+ "error": str(e),
225
+ }
226
+
227
+
228
+ class CalcLaneClient:
229
+ """Python client for Talocode CalcLane (`/v1/calclane/*`) + local eval."""
230
+
231
+ def __init__(
232
+ self,
233
+ api_key: str | None = None,
234
+ base_url: str | None = None,
235
+ *,
236
+ prefer_local: bool = True,
237
+ timeout: float = 30.0,
238
+ ) -> None:
239
+ self.api_key = api_key or os.environ.get("TALOCODE_API_KEY")
240
+ self.base_url = (
241
+ base_url
242
+ or os.environ.get("TALOCODE_BASE_URL")
243
+ or "https://api.talocode.site"
244
+ ).rstrip("/")
245
+ self.prefer_local = prefer_local
246
+ self.timeout = timeout
247
+
248
+ def _resolve_path(self, path: str) -> str:
249
+ if self.base_url.endswith("/calclane") and (
250
+ path == "/v1/calclane" or path.startswith("/v1/calclane/")
251
+ ):
252
+ rest = path[len("/v1/calclane") :] or "/health"
253
+ return rest if rest.startswith("/") else f"/{rest}"
254
+ return path
255
+
256
+ def _headers(self) -> dict[str, str]:
257
+ headers = {"Content-Type": "application/json"}
258
+ if self.api_key:
259
+ headers["Authorization"] = f"Bearer {self.api_key}"
260
+ headers["X-Api-Key"] = self.api_key
261
+ return headers
262
+
263
+ def _request(
264
+ self,
265
+ method: str,
266
+ path: str,
267
+ body: dict[str, Any] | None = None,
268
+ ) -> dict[str, Any]:
269
+ url = f"{self.base_url}{self._resolve_path(path)}"
270
+ data = json.dumps(body).encode("utf-8") if body is not None else None
271
+ req = urllib.request.Request(
272
+ url,
273
+ data=data,
274
+ headers=self._headers(),
275
+ method=method,
276
+ )
277
+ try:
278
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
279
+ raw = resp.read().decode("utf-8")
280
+ if not raw:
281
+ return {}
282
+ return json.loads(raw)
283
+ except urllib.error.HTTPError as e:
284
+ code: str | None = None
285
+ msg = str(e)
286
+ try:
287
+ detail = json.loads(e.read().decode("utf-8"))
288
+ if isinstance(detail, dict):
289
+ err = detail.get("error")
290
+ if isinstance(err, dict):
291
+ msg = err.get("message") or msg
292
+ code = err.get("code")
293
+ elif isinstance(err, str):
294
+ msg = err
295
+ code = code or detail.get("code")
296
+ except Exception:
297
+ pass
298
+ raise CalcLaneError(msg, code=code, status=e.code) from e
299
+ except urllib.error.URLError as e:
300
+ raise CalcLaneError(f"Request failed: {e.reason}") from e
301
+
302
+ def evaluate_local(
303
+ self,
304
+ expression: str,
305
+ *,
306
+ mode: str = "scientific",
307
+ angle: str = "deg",
308
+ ) -> dict[str, Any]:
309
+ return evaluate_local(expression, mode=mode, angle=angle)
310
+
311
+ def evaluate(
312
+ self,
313
+ expression: str,
314
+ *,
315
+ mode: str = "scientific",
316
+ angle: str = "deg",
317
+ remote: bool = False,
318
+ fe: bool | None = None,
319
+ ) -> dict[str, Any]:
320
+ if remote or (not self.prefer_local and self.api_key):
321
+ return self.evaluate_remote(expression, mode=mode, angle=angle, fe=fe)
322
+ return self.evaluate_local(expression, mode=mode, angle=angle)
323
+
324
+ def evaluate_remote(
325
+ self,
326
+ expression: str,
327
+ *,
328
+ mode: str = "scientific",
329
+ angle: str = "deg",
330
+ fe: bool | None = None,
331
+ ) -> dict[str, Any]:
332
+ if not self.api_key:
333
+ raise CalcLaneError(
334
+ "TALOCODE_API_KEY required for hosted evaluate",
335
+ code="MISSING_API_KEY",
336
+ status=401,
337
+ )
338
+ body: dict[str, Any] = {
339
+ "expression": expression,
340
+ "mode": mode,
341
+ "angle": angle,
342
+ }
343
+ if fe is not None:
344
+ body["fe"] = fe
345
+ return self._request("POST", "/v1/calclane/evaluate", body)
346
+
347
+ def dispatch(
348
+ self,
349
+ commands: list[dict[str, Any]],
350
+ *,
351
+ mode: str | None = None,
352
+ angle: str | None = None,
353
+ remote: bool = False,
354
+ ) -> dict[str, Any]:
355
+ if remote or (not self.prefer_local and self.api_key):
356
+ if not self.api_key:
357
+ raise CalcLaneError(
358
+ "TALOCODE_API_KEY required for hosted dispatch",
359
+ code="MISSING_API_KEY",
360
+ status=401,
361
+ )
362
+ body: dict[str, Any] = {"commands": commands}
363
+ if mode:
364
+ body["mode"] = mode
365
+ if angle:
366
+ body["angle"] = angle
367
+ return self._request("POST", "/v1/calclane/dispatch", body)
368
+ raise CalcLaneError(
369
+ "Local dispatch requires the Node engine; use evaluate_local or remote=True",
370
+ code="LOCAL_DISPATCH_UNSUPPORTED",
371
+ )
372
+
373
+ def health(self, *, remote: bool = False) -> dict[str, Any]:
374
+ if remote or (not self.prefer_local and self.api_key):
375
+ return self._request("GET", "/v1/calclane/health")
376
+ return {"ok": True, "service": "calclane", "version": "0.3.0", "mode": "local"}
377
+
378
+ def pricing(self, *, remote: bool = False) -> dict[str, Any]:
379
+ if remote or (not self.prefer_local and self.api_key):
380
+ return self._request("GET", "/v1/calclane/pricing")
381
+ return {
382
+ "product": "calclane",
383
+ "currency": "credits",
384
+ "credits": {"calclane.evaluate": 1, "calclane.dispatch": 1},
385
+ "version": "0.3.0",
386
+ }
387
+
388
+ def capabilities(self, *, remote: bool = False) -> dict[str, Any]:
389
+ if remote or (not self.prefer_local and self.api_key):
390
+ return self._request("GET", "/v1/calclane/capabilities")
391
+ return {
392
+ "product": "calclane",
393
+ "version": "0.3.0",
394
+ "modes": ["standard", "scientific"],
395
+ "endpoints": [
396
+ "GET /v1/calclane/health",
397
+ "GET /v1/calclane/pricing",
398
+ "GET /v1/calclane/capabilities",
399
+ "POST /v1/calclane/evaluate",
400
+ "POST /v1/calclane/dispatch",
401
+ ],
402
+ }
403
+
404
+
405
+ def create_calclane_client(
406
+ api_key: str | None = None,
407
+ base_url: str | None = None,
408
+ **kwargs: Any,
409
+ ) -> CalcLaneClient:
410
+ return CalcLaneClient(api_key=api_key, base_url=base_url, **kwargs)
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: talocode-calclane
3
+ Version: 0.3.0
4
+ Summary: CalcLane — open calculator engine for agents. Local evaluate + hosted Talocode API SDK/CLI.
5
+ Author: Talocode
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/talocode/calclane
8
+ Project-URL: Documentation, https://docs.talocode.site
9
+ Project-URL: Repository, https://github.com/talocode/calclane
10
+ Project-URL: Issues, https://github.com/talocode/calclane/issues
11
+ Project-URL: PyPI, https://pypi.org/project/talocode-calclane/
12
+ Keywords: calculator,calclane,scientific-calculator,talocode,agents,mcp,sdk
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+
25
+ # CalcLane (Python)
26
+
27
+ **Open calculator for agents** — local expression evaluation plus hosted Talocode API client.
28
+
29
+ Part of **[Talocode](https://talocode.site)** — open tools people trust; hosted power under [docs.talocode.site](https://docs.talocode.site).
30
+
31
+ | | |
32
+ |--|--|
33
+ | **Cloud API** | `https://api.talocode.site` → `/v1/calclane/*` |
34
+ | **Python** | `pip install talocode-calclane` (this package) |
35
+ | **Node** | `npm install @talocode/calclane` |
36
+ | **Repo** | [github.com/talocode/calclane](https://github.com/talocode/calclane) |
37
+ | **License** | MIT |
38
+
39
+ ---
40
+
41
+ ## Why CalcLane?
42
+
43
+ Agents need **deterministic math** as a tool, not a free-form LLM guess:
44
+
45
+ - **Structured JSON** — `ok`, `result`, `display`, `mode`, `error`
46
+ - **Local free path** — stdlib evaluator, no network
47
+ - **Hosted path** — same contract on Talocode Cloud (**1 credit** per evaluate)
48
+ - **CLI** — `calclane eval "2+3*4"`
49
+
50
+ ---
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ pip install talocode-calclane
56
+ ```
57
+
58
+ Requires **Python 3.10+**. Stdlib only (no extra runtime deps).
59
+
60
+ ---
61
+
62
+ ## Quickstart
63
+
64
+ ```python
65
+ import os
66
+ from calclane import CalcLaneClient
67
+
68
+ client = CalcLaneClient()
69
+
70
+ # Local (free)
71
+ print(client.evaluate("2 + 3 * 4"))
72
+ print(client.evaluate("sin(90)", angle="deg"))
73
+ print(client.evaluate("5!"))
74
+
75
+ # Hosted API (1 credit) — needs TALOCODE_API_KEY
76
+ cloud = CalcLaneClient(
77
+ api_key=os.environ["TALOCODE_API_KEY"],
78
+ prefer_local=False,
79
+ )
80
+ print(cloud.evaluate_remote("sqrt(144)"))
81
+ ```
82
+
83
+ ### Environment
84
+
85
+ | Variable | Default | Purpose |
86
+ |----------|---------|---------|
87
+ | `TALOCODE_API_KEY` | — | Bearer token for hosted routes |
88
+ | `TALOCODE_BASE_URL` | `https://api.talocode.site` | API host |
89
+
90
+ ---
91
+
92
+ ## CLI
93
+
94
+ ```bash
95
+ calclane eval "2^10"
96
+ calclane eval "sin(90)" --angle deg
97
+ calclane health
98
+ calclane pricing
99
+ calclane eval "pi*2" --remote # hosted, 1 credit
100
+ ```
101
+
102
+ Or: `python -m calclane eval "..."`.
103
+
104
+ ---
105
+
106
+ ## API surface
107
+
108
+ | Method | Path | Credits |
109
+ |--------|------|---------|
110
+ | GET | `/v1/calclane/health` | free* |
111
+ | GET | `/v1/calclane/pricing` | free* |
112
+ | GET | `/v1/calclane/capabilities` | free* |
113
+ | POST | `/v1/calclane/evaluate` | **1** |
114
+ | POST | `/v1/calclane/dispatch` | **1** |
115
+
116
+ \*Valid API key required; no credit charge.
117
+
118
+ Auth: `Authorization: Bearer $TALOCODE_API_KEY` or `X-Api-Key`.
119
+
120
+ ---
121
+
122
+ ## Related packages
123
+
124
+ | Package | Install |
125
+ |---------|---------|
126
+ | This package | `pip install talocode-calclane` |
127
+ | Node SDK / CLI | `npm i @talocode/calclane` |
128
+ | SearchLane | `pip install talocode-searchlane` |
129
+ | StackLane | `pip install talocode` |
130
+ | Tera | `pip install talocode-tera` |
131
+
132
+ ---
133
+
134
+ ## Talocode ecosystem
135
+
136
+ | Project | What it is |
137
+ |---------|------------|
138
+ | [Codra](https://github.com/talocode/codra) | Local-first agentic coding · `pip install talocode-codra` |
139
+ | [Tera](https://github.com/talocode/tera) | AI learning companion · `pip install talocode-tera` |
140
+ | [Stacklane](https://github.com/talocode/stacklane) | Cloud control plane · `pip install talocode` |
141
+ | [SearchLane](https://github.com/talocode/searchlane) | Agent web search · `pip install talocode-searchlane` |
142
+ | [DocuLane](https://github.com/talocode/doculane) | Office documents · `pip install talocode-doculane` |
143
+ | [ContextLane](https://github.com/talocode/contextlane) | Context tooling · `pip install contextlane` |
144
+ | [Tradia](https://github.com/talocode/tradia) | Trading agents · `pip install tradia` |
145
+ | **[CalcLane](https://github.com/talocode/calclane)** (this package) | Open calculator · `pip install talocode-calclane` |
146
+
147
+ More: [github.com/talocode](https://github.com/talocode) · [talocode.site](https://talocode.site) · [docs.talocode.site](https://docs.talocode.site)
148
+
149
+ ---
150
+
151
+ ## License
152
+
153
+ MIT © Talocode
@@ -0,0 +1,8 @@
1
+ calclane/__init__.py,sha256=SIYjNATR9ufXlPR8i5OO_Am0XQJpImlCNoxvtjCr3Dg,272
2
+ calclane/__main__.py,sha256=wS9uoeTWBYdb-x9No1xW2rxFILItVVkyvD_Kyudf3mc,2577
3
+ calclane/client.py,sha256=RhmPWBlpWPftEt7_qahlaME-h968isUldsSorG2pb9g,13236
4
+ talocode_calclane-0.3.0.dist-info/METADATA,sha256=dUdqPfhx9-aKpBV9XCdPVSWj-KHV48EPjV4AhN_cEv4,4777
5
+ talocode_calclane-0.3.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
6
+ talocode_calclane-0.3.0.dist-info/entry_points.txt,sha256=6JEjQuPa1TkTJUNwF5igVyiAjalORUOK3BYuAXmsoYM,52
7
+ talocode_calclane-0.3.0.dist-info/top_level.txt,sha256=68ogV2tw8XBNY9eLPWdWam9yg8R1weqGth9wICX84lY,9
8
+ talocode_calclane-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ calclane = calclane.__main__:main
@@ -0,0 +1 @@
1
+ calclane