lcode-cli 0.2.0__tar.gz → 0.2.1__tar.gz

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.
Files changed (67) hide show
  1. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/CHANGELOG.md +11 -1
  2. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/PKG-INFO +10 -8
  3. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/README.md +9 -7
  4. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/index.md +2 -2
  5. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/models.md +5 -2
  6. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/quickstart.md +1 -1
  7. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/__init__.py +1 -1
  8. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/agent.py +41 -12
  9. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/permissions.py +1 -1
  10. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/tests/test_agent.py +12 -0
  11. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/tests/test_permissions.py +9 -0
  12. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/tests/test_tools.py +11 -0
  13. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.editorconfig +0 -0
  14. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.github/CODEOWNERS +0 -0
  15. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  16. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  17. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  18. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
  19. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  20. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.github/dependabot.yml +0 -0
  21. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.github/workflows/ci.yml +0 -0
  22. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.github/workflows/docs.yml +0 -0
  23. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.github/workflows/release.yml +0 -0
  24. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.gitignore +0 -0
  25. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/.pre-commit-config.yaml +0 -0
  26. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/AGENTS.md +0 -0
  27. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/CODE_OF_CONDUCT.md +0 -0
  28. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/CONTRIBUTING.md +0 -0
  29. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/LICENSE +0 -0
  30. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/SECURITY.md +0 -0
  31. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/assets/demo.svg +0 -0
  32. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/assets/extra.css +0 -0
  33. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/assets/logo.svg +0 -0
  34. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/assets/social-preview.png +0 -0
  35. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/changelog.md +0 -0
  36. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/configuration.md +0 -0
  37. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/contributing.md +0 -0
  38. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/how-it-works.md +0 -0
  39. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/installation.md +0 -0
  40. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/troubleshooting.md +0 -0
  41. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/docs/usage.md +0 -0
  42. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/install.sh +0 -0
  43. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/mkdocs.yml +0 -0
  44. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/overrides/main.html +0 -0
  45. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/pyproject.toml +0 -0
  46. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/scripts/record_demo.py +0 -0
  47. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/scripts/social-preview.html +0 -0
  48. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/__main__.py +0 -0
  49. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/catalog.py +0 -0
  50. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/cli.py +0 -0
  51. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/config.py +0 -0
  52. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/hardware.py +0 -0
  53. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/limits.py +0 -0
  54. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/models.toml +0 -0
  55. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/ollama.py +0 -0
  56. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/render.py +0 -0
  57. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/repl.py +0 -0
  58. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/sessions.py +0 -0
  59. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/tools.py +0 -0
  60. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/src/lcode/web.py +0 -0
  61. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/tests/conftest.py +0 -0
  62. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/tests/test_catalog.py +0 -0
  63. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/tests/test_cli.py +0 -0
  64. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/tests/test_config.py +0 -0
  65. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/tests/test_sessions.py +0 -0
  66. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/tests/test_web.py +0 -0
  67. {lcode_cli-0.2.0 → lcode_cli-0.2.1}/uv.lock +0 -0
@@ -6,6 +6,15 @@ All notable changes to lcode are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.1] - 2026-09-29
10
+
11
+ ### Fixed
12
+
13
+ - File edits and writes failed with "IndexError: list index out of range" in the default `ask`
14
+ permission mode, a regression in 0.2.0. `auto-edit` and `yolo` modes were not affected.
15
+ - Slow models no longer look stuck: the status line keeps counting the elapsed time while the model
16
+ thinks, answers or silently writes a long file into a tool call, and reminds you that Ctrl+C stops it.
17
+
9
18
  ## [0.2.0] - 2026-09-29
10
19
 
11
20
  ### Added
@@ -98,7 +107,8 @@ First public release.
98
107
  - `AGENTS.md` project instructions and `/init` to generate them.
99
108
  - One-line installer for Linux and macOS.
100
109
 
101
- [Unreleased]: https://github.com/nasser1941/lcode/compare/v0.2.0...HEAD
110
+ [Unreleased]: https://github.com/nasser1941/lcode/compare/v0.2.1...HEAD
111
+ [0.2.1]: https://github.com/nasser1941/lcode/compare/v0.2.0...v0.2.1
102
112
  [0.2.0]: https://github.com/nasser1941/lcode/compare/v0.1.2...v0.2.0
103
113
  [0.1.2]: https://github.com/nasser1941/lcode/compare/v0.1.1...v0.1.2
104
114
  [0.1.1]: https://github.com/nasser1941/lcode/compare/v0.1.0...v0.1.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: lcode-cli
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A local-first terminal coding agent powered by open-weight models on your own GPU or Mac, via Ollama.
5
5
  Project-URL: Homepage, https://nasser1941.github.io/lcode/
6
6
  Project-URL: Documentation, https://nasser1941.github.io/lcode/
@@ -150,8 +150,8 @@ What `lcode setup` picks for common machines:
150
150
  | NVIDIA 8–24 GB + 32 GB RAM | qwen3.6-35b | 256K |
151
151
  | NVIDIA 8 GB + 16 GB RAM | gpt-oss-20b | 64K |
152
152
 
153
- On an RTX 4080 Laptop GPU (12 GB) the default model generates 50–60 tokens/s at 256K context, and
154
- qwen3.5-9b 64 tokens/s at 128K. See
153
+ At 256K context the default model generates 50–55 tokens/s on an RTX 4080 Laptop GPU (12 GB) and
154
+ 45 tokens/s on an M4 Pro Mac with 48 GB. See
155
155
  [Models & context windows](https://nasser1941.github.io/lcode/models/) for memory estimates and
156
156
  tuning.
157
157
 
@@ -165,11 +165,13 @@ fills up. Details: [How it works](https://nasser1941.github.io/lcode/how-it-work
165
165
 
166
166
  ## Roadmap
167
167
 
168
- - More models in the catalog, with community test reports ([request one](https://github.com/nasser1941/lcode/issues/new?template=model_request.yml))
169
- - Measured Apple Silicon performance numbers
170
- - MCP (Model Context Protocol) tool servers
171
- - Web fetch and search tools
172
- - Packaging on PyPI and Homebrew
168
+ See the pinned [Roadmap issue](https://github.com/nasser1941/lcode/issues/10). Next up:
169
+
170
+ - [`/undo` and automatic checkpoints](https://github.com/nasser1941/lcode/issues/4)
171
+ - [`lcode bench`: score models on your own hardware](https://github.com/nasser1941/lcode/issues/5)
172
+ - [MCP tool servers](https://github.com/nasser1941/lcode/issues/6)
173
+ - [Homebrew formula](https://github.com/nasser1941/lcode/issues/8) and an [optional sandbox for shell commands](https://github.com/nasser1941/lcode/issues/9)
174
+ - More models in the catalog, and test reports from your hardware ([report one](https://github.com/nasser1941/lcode/issues/new?template=model_request.yml))
173
175
 
174
176
  ## Contributing
175
177
 
@@ -117,8 +117,8 @@ What `lcode setup` picks for common machines:
117
117
  | NVIDIA 8–24 GB + 32 GB RAM | qwen3.6-35b | 256K |
118
118
  | NVIDIA 8 GB + 16 GB RAM | gpt-oss-20b | 64K |
119
119
 
120
- On an RTX 4080 Laptop GPU (12 GB) the default model generates 50–60 tokens/s at 256K context, and
121
- qwen3.5-9b 64 tokens/s at 128K. See
120
+ At 256K context the default model generates 50–55 tokens/s on an RTX 4080 Laptop GPU (12 GB) and
121
+ 45 tokens/s on an M4 Pro Mac with 48 GB. See
122
122
  [Models & context windows](https://nasser1941.github.io/lcode/models/) for memory estimates and
123
123
  tuning.
124
124
 
@@ -132,11 +132,13 @@ fills up. Details: [How it works](https://nasser1941.github.io/lcode/how-it-work
132
132
 
133
133
  ## Roadmap
134
134
 
135
- - More models in the catalog, with community test reports ([request one](https://github.com/nasser1941/lcode/issues/new?template=model_request.yml))
136
- - Measured Apple Silicon performance numbers
137
- - MCP (Model Context Protocol) tool servers
138
- - Web fetch and search tools
139
- - Packaging on PyPI and Homebrew
135
+ See the pinned [Roadmap issue](https://github.com/nasser1941/lcode/issues/10). Next up:
136
+
137
+ - [`/undo` and automatic checkpoints](https://github.com/nasser1941/lcode/issues/4)
138
+ - [`lcode bench`: score models on your own hardware](https://github.com/nasser1941/lcode/issues/5)
139
+ - [MCP tool servers](https://github.com/nasser1941/lcode/issues/6)
140
+ - [Homebrew formula](https://github.com/nasser1941/lcode/issues/8) and an [optional sandbox for shell commands](https://github.com/nasser1941/lcode/issues/9)
141
+ - More models in the catalog, and test reports from your hardware ([report one](https://github.com/nasser1941/lcode/issues/new?template=model_request.yml))
140
142
 
141
143
  ## Contributing
142
144
 
@@ -82,8 +82,8 @@ until the task is done, asking before it changes anything.
82
82
 
83
83
  | Platform | Recommended hardware | Default choice |
84
84
  |---|---|---|
85
- | **Ubuntu / Linux** with NVIDIA GPU | 12 GB+ VRAM and 32 GB RAM | Qwen3.6 35B-A3B at 256K context |
86
- | **macOS on Apple Silicon** (M1–M4) | 48 GB+ unified memory | Qwen3.6 35B-A3B at 256K context |
85
+ | **Ubuntu / Linux** with NVIDIA GPU | 12 GB+ VRAM and 32 GB RAM | Qwen3.6 35B-A3B at 256K context (50–55 tok/s on 12 GB) |
86
+ | **macOS on Apple Silicon** (M1–M4) | 48 GB+ unified memory | Qwen3.6 35B-A3B at 256K context (45 tok/s on an M4 Pro) |
87
87
  | Smaller machines (16–24 GB Mac, 8 GB GPU) | — | Qwen3.5 9B / 4B with a smaller context |
88
88
 
89
89
  See [Models & context windows](models.md) for the full catalog and how lcode sizes models to your
@@ -138,7 +138,7 @@ results on your machine are welcome in the
138
138
  | Mac with M4, 16 GB | ~11 GB | qwen3.5-9b | 64K | fast |
139
139
  | Mac with M4 / M4 Pro, 24 GB | ~16 GB | qwen3.5-9b | 256K | fast |
140
140
  | Mac with M4 Pro / M4 Max, 36 GB | ~24 GB | qwen3.6-35b | 64K | fast |
141
- | Mac with M4 Pro, 48 GB | ~36 GB | qwen3.6-35b | 256K | fast |
141
+ | Mac with M4 Pro, 48 GB | ~36 GB | qwen3.6-35b | 256K | fast (measured: 45 tok/s) |
142
142
  | Mac with M4 Max, 64–128 GB | 48–96 GB | qwen3.6-35b | 256K | fast |
143
143
  | NVIDIA 8 GB + 16 GB RAM | ~16 GB | gpt-oss-20b | 64K | good (experts in RAM) |
144
144
  | NVIDIA 8–16 GB + 32 GB RAM | 32–40 GB | qwen3.6-35b | 256K | good (experts in RAM) |
@@ -200,7 +200,10 @@ The small models on the same GPU (a bug-fix task: read, run the failing tests, e
200
200
  Dense models slow down about 3x once they no longer fit in VRAM, which is why lcode sizes their
201
201
  context to stay on the GPU.
202
202
 
203
- Apple Silicon numbers are not measured yet; please share yours.
203
+ On a Mac with an **Apple M4 Pro and 48 GB** of unified memory (reported by a user, Ollama 0.34), lcode
204
+ picks the same `qwen3.6-35b` at 256K context and generates **45 tokens/s**, running entirely on the
205
+ GPU's share of unified memory. Results from other Macs are very welcome in the
206
+ [model reports](https://github.com/nasser1941/lcode/issues/new?template=model_request.yml).
204
207
 
205
208
  ### Tips for Macs
206
209
 
@@ -12,7 +12,7 @@ lcode
12
12
 
13
13
  ```text
14
14
  ╭──────────────────────────────────────────────╮
15
- │ lcode v0.2.0 — local coding agent │
15
+ │ lcode v0.2.1 — local coding agent │
16
16
  │ │
17
17
  │ model lcode-qwen3.6-35b │
18
18
  │ context 256K tokens │
@@ -1,3 +1,3 @@
1
1
  """lcode — a local-first terminal coding agent powered by open-weight models via Ollama."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.1"
@@ -101,6 +101,29 @@ def git_info(cwd: Path) -> str:
101
101
  return "unknown"
102
102
 
103
103
 
104
+ class Progress:
105
+ """The status line under a running request.
106
+
107
+ It is re-rendered on every spinner frame, so the elapsed time keeps moving even when no text
108
+ arrives, for example while the model writes a long file into a tool call.
109
+ """
110
+
111
+ def __init__(self) -> None:
112
+ self.started = self.last_chunk = time.time()
113
+ self.phase = "Thinking"
114
+ self.chars = 0
115
+
116
+ def update(self, phase: str, text: str) -> None:
117
+ self.phase, self.chars, self.last_chunk = phase, self.chars + len(text), time.time()
118
+
119
+ def __rich__(self) -> Text:
120
+ now = time.time()
121
+ phase = self.phase if now - self.last_chunk < 3 else "Working"
122
+ return Text.from_markup(
123
+ f"[cyan]{phase}…[/] [dim]({self.chars // 4} tokens, {now - self.started:.0f}s · Ctrl+C to stop)[/]"
124
+ )
125
+
126
+
104
127
  @dataclass
105
128
  class Settings:
106
129
  model: str # Ollama model name to call
@@ -301,35 +324,41 @@ class Agent:
301
324
  """Stream one model response, rendering thinking/content live. Returns the assistant message."""
302
325
  content, thinking, tool_calls, final = "", "", [], {}
303
326
  md = MarkdownStreamer(self.console)
304
- status = self.console.status("[cyan]Thinking…[/]", spinner="dots")
305
- status.start()
306
- spinning, printed_thinking, t0 = True, False, time.time()
327
+ progress = Progress()
328
+ status = None
329
+ printed_thinking = False
330
+
331
+ def start_spinner() -> None:
332
+ nonlocal status
333
+ if status is None:
334
+ status = self.console.status(progress, spinner="dots")
335
+ status.start()
307
336
 
308
337
  def stop_spinner() -> None:
309
- nonlocal spinning
310
- if spinning:
338
+ nonlocal status
339
+ if status is not None:
311
340
  status.stop()
312
- spinning = False
341
+ status = None
313
342
 
343
+ start_spinner()
314
344
  try:
315
345
  for chunk in self.chat(self.messages, self.tool_schemas(), self.settings.think):
316
346
  msg = chunk.get("message", {})
317
347
  if msg.get("thinking"):
318
348
  thinking += msg["thinking"]
349
+ progress.update("Thinking", msg["thinking"])
319
350
  if self.settings.show_thinking:
320
- stop_spinner()
351
+ stop_spinner() # raw text is printed as it streams
321
352
  self.console.print(Text(msg["thinking"], style="dim italic"), end="")
322
353
  printed_thinking = True
323
- else:
324
- status.update(
325
- f"[cyan]Thinking…[/] [dim]({len(thinking) // 4} tokens, {time.time() - t0:.0f}s)[/]"
326
- )
327
354
  if msg.get("content"):
328
- stop_spinner()
329
355
  if printed_thinking:
330
356
  self.console.print("\n")
331
357
  printed_thinking = False
332
358
  content += msg["content"]
359
+ progress.update("Writing", msg["content"])
360
+ # Finished markdown blocks print above the spinner, which keeps running meanwhile.
361
+ start_spinner()
333
362
  md.feed(msg["content"])
334
363
  if msg.get("tool_calls"):
335
364
  tool_calls.extend(msg["tool_calls"])
@@ -71,7 +71,7 @@ class Permissions:
71
71
  if self.mode == "yolo" or key in self.always or (kind == "edit" and self.mode == "auto-edit"):
72
72
  return True, ""
73
73
  self.console.print(Panel(body, title=title, title_align="left", border_style="yellow"))
74
- scope = {"edit": "file edits", "bash": key.split(":", 1)[1], "web": key.split(":", 1)[1]}[kind]
74
+ scope = "file edits" if kind == "edit" else key.split(":", 1)[-1]
75
75
  try:
76
76
  answer = input(f" Allow? [y]es / [a]lways for '{scope}' this session / [n]o (+ optional reason): ")
77
77
  except EOFError:
@@ -149,6 +149,18 @@ def test_malformed_tool_calls_give_up_eventually(make_agent):
149
149
  agent.run_turn("write a.py")
150
150
 
151
151
 
152
+ def test_progress_line_keeps_counting_without_chunks(monkeypatch):
153
+ from lcode.agent import Progress
154
+
155
+ clock = [1000.0]
156
+ monkeypatch.setattr("lcode.agent.time.time", lambda: clock[0])
157
+ progress = Progress()
158
+ progress.update("Writing", "x" * 400)
159
+ assert progress.__rich__().plain.startswith("Writing… (100 tokens, 0s")
160
+ clock[0] += 125 # the model is silently writing a tool call
161
+ assert progress.__rich__().plain.startswith("Working… (100 tokens, 125s")
162
+
163
+
152
164
  def test_mentions_attach_files(agent):
153
165
  text = agent.expand_mentions("explain @src/pkg/math.py please, mail me@example.com")
154
166
  assert '<file path="src/pkg/math.py">' in text
@@ -59,3 +59,12 @@ def test_always_and_modes(monkeypatch):
59
59
  assert perms.request("edit", "edit", "Edit", "diff")[0]
60
60
  perms.cycle()
61
61
  assert perms.mode == "yolo"
62
+
63
+
64
+ @pytest.mark.parametrize(("kind", "key"), [("edit", "edit"), ("bash", "bash:rm"), ("web", "web:search")])
65
+ def test_every_kind_can_ask_and_remember(monkeypatch, kind, key):
66
+ perms = Permissions(Console(file=io.StringIO()), "ask")
67
+ monkeypatch.setattr("builtins.input", lambda _: "a")
68
+ assert perms.request(key, kind, "Title", "body") == (True, "")
69
+ monkeypatch.setattr("builtins.input", lambda _: pytest.fail("should not ask again"))
70
+ assert perms.request(key, kind, "Title", "body") == (True, "")
@@ -127,3 +127,14 @@ def test_truncate_keeps_head_and_tail():
127
127
  text = "a" * 50 + "b" * 50
128
128
  out = truncate(text, limit=30)
129
129
  assert out.startswith("a" * 20) and out.endswith("b" * 10) and "truncated" in out
130
+
131
+
132
+ def test_edits_in_ask_mode(make_agent, repo: Path, monkeypatch):
133
+ """Regression: file edits used to crash with IndexError whenever lcode asked for permission."""
134
+ agent = make_agent(mode="ask")
135
+ monkeypatch.setattr("builtins.input", lambda _: "y")
136
+ assert agent.tools.run("write_file", {"path": "NOTES.md", "content": "# Notes\n"}).startswith("Created")
137
+ agent.tools.run("read_file", {"path": "src/pkg/math.py"})
138
+ out = agent.tools.run("edit_file", {"path": "src/pkg/math.py", "old_string": "a - b", "new_string": "b - a"})
139
+ assert out.startswith("Edited src/pkg/math.py")
140
+ assert "return b - a" in (repo / "src/pkg/math.py").read_text()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes