lcode-cli 0.2.2__tar.gz → 0.3.0__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 (70) hide show
  1. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/CHANGELOG.md +14 -1
  2. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/PKG-INFO +4 -2
  3. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/README.md +3 -1
  4. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/SECURITY.md +2 -0
  5. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/configuration.md +2 -0
  6. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/how-it-works.md +1 -0
  7. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/index.md +1 -1
  8. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/quickstart.md +1 -1
  9. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/usage.md +35 -0
  10. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/__init__.py +1 -1
  11. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/agent.py +17 -0
  12. lcode_cli-0.3.0/src/lcode/checkpoints.py +449 -0
  13. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/cli.py +1 -0
  14. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/config.py +1 -0
  15. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/repl.py +163 -1
  16. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/tools.py +3 -0
  17. lcode_cli-0.3.0/tests/test_checkpoints.py +226 -0
  18. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.editorconfig +0 -0
  19. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.github/CODEOWNERS +0 -0
  20. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  21. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  22. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  23. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
  24. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  25. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.github/dependabot.yml +0 -0
  26. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.github/workflows/ci.yml +0 -0
  27. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.github/workflows/docs.yml +0 -0
  28. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.github/workflows/release.yml +0 -0
  29. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.gitignore +0 -0
  30. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/.pre-commit-config.yaml +0 -0
  31. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/AGENTS.md +0 -0
  32. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  33. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/CONTRIBUTING.md +0 -0
  34. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/LICENSE +0 -0
  35. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/assets/demo.svg +0 -0
  36. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/assets/extra.css +0 -0
  37. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/assets/logo.svg +0 -0
  38. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/assets/social-preview.png +0 -0
  39. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/changelog.md +0 -0
  40. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/contributing.md +0 -0
  41. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/installation.md +0 -0
  42. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/models.md +0 -0
  43. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/docs/troubleshooting.md +0 -0
  44. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/install.sh +0 -0
  45. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/mkdocs.yml +0 -0
  46. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/overrides/main.html +0 -0
  47. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/pyproject.toml +0 -0
  48. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/scripts/record_demo.py +0 -0
  49. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/scripts/social-preview.html +0 -0
  50. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/__main__.py +0 -0
  51. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/catalog.py +0 -0
  52. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/hardware.py +0 -0
  53. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/limits.py +0 -0
  54. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/models.toml +0 -0
  55. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/ollama.py +0 -0
  56. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/permissions.py +0 -0
  57. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/render.py +0 -0
  58. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/sessions.py +0 -0
  59. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/src/lcode/web.py +0 -0
  60. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/tests/conftest.py +0 -0
  61. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/tests/test_agent.py +0 -0
  62. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/tests/test_catalog.py +0 -0
  63. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/tests/test_cli.py +0 -0
  64. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/tests/test_config.py +0 -0
  65. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/tests/test_context_picker.py +0 -0
  66. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/tests/test_permissions.py +0 -0
  67. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/tests/test_sessions.py +0 -0
  68. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/tests/test_tools.py +0 -0
  69. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/tests/test_web.py +0 -0
  70. {lcode_cli-0.2.2 → lcode_cli-0.3.0}/uv.lock +0 -0
@@ -6,6 +6,18 @@ All notable changes to lcode are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.0] - 2026-09-30
10
+
11
+ ### Added
12
+
13
+ - `/undo` takes back the file changes of the last request: lcode saves a checkpoint before the model
14
+ first changes files in a request, and restores edited and deleted files and removes new ones,
15
+ including changes made by shell commands. `/undo` again goes further back, `/rewind N` goes back
16
+ to before request N (optionally removing those requests from the conversation too), and
17
+ `/checkpoints` lists them. Checkpoints live in a separate git repository in lcode's state folder,
18
+ so your own repository is never touched, and work in folders that aren't git repositories.
19
+ Turn them off with `lcode config set checkpoints false`.
20
+
9
21
  ## [0.2.2] - 2026-09-30
10
22
 
11
23
  ### Added
@@ -116,7 +128,8 @@ First public release.
116
128
  - `AGENTS.md` project instructions and `/init` to generate them.
117
129
  - One-line installer for Linux and macOS.
118
130
 
119
- [Unreleased]: https://github.com/nasser1941/lcode/compare/v0.2.2...HEAD
131
+ [Unreleased]: https://github.com/nasser1941/lcode/compare/v0.3.0...HEAD
132
+ [0.3.0]: https://github.com/nasser1941/lcode/compare/v0.2.2...v0.3.0
120
133
  [0.2.2]: https://github.com/nasser1941/lcode/compare/v0.2.1...v0.2.2
121
134
  [0.2.1]: https://github.com/nasser1941/lcode/compare/v0.2.0...v0.2.1
122
135
  [0.2.0]: https://github.com/nasser1941/lcode/compare/v0.1.2...v0.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: lcode-cli
3
- Version: 0.2.2
3
+ Version: 0.3.0
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/
@@ -76,6 +76,8 @@ through [Ollama](https://ollama.com), and when it needs current information it c
76
76
  largest context that fits: `lcode setup` does it in one step.
77
77
  - **Safe by default.** Every edit is shown as a diff and every command that isn't read-only needs
78
78
  your approval. `auto-edit` and `yolo` modes when you want speed.
79
+ - **Undo.** lcode saves a checkpoint before the model changes files; `/undo` takes back the last
80
+ request's edits, new files and shell-command changes, without touching your git history.
79
81
  - **Bring your own model.** Eight curated open-weight models, all tested end to end, or any Ollama model
80
82
  with tool calling.
81
83
  - **Web search when needed.** Looks up the latest versions, docs and error messages with Ollama web
@@ -123,6 +125,7 @@ lcode
123
125
  | `lcode -c` / `lcode --resume` | continue the last session here / pick a saved session from a list |
124
126
  | `lcode --model qwen3.5-9b --context 128k` | pick a model and context window for this session |
125
127
  | `lcode models` / `lcode doctor` | what fits this machine / check the installation |
128
+ | `/undo`, `/rewind` | take back the last request's file changes, or go back further |
126
129
  | `/rename`, `/resume` | name the current session, resume a saved one |
127
130
  | `/model`, `/context`, `/compact`, `/help` | switch model, resize the context window (pick from a list), summarize, list commands |
128
131
 
@@ -167,7 +170,6 @@ fills up. Details: [How it works](https://nasser1941.github.io/lcode/how-it-work
167
170
 
168
171
  See the pinned [Roadmap issue](https://github.com/nasser1941/lcode/issues/10). Next up:
169
172
 
170
- - [`/undo` and automatic checkpoints](https://github.com/nasser1941/lcode/issues/4)
171
173
  - [`lcode bench`: score models on your own hardware](https://github.com/nasser1941/lcode/issues/5)
172
174
  - [MCP tool servers](https://github.com/nasser1941/lcode/issues/6)
173
175
  - [Homebrew formula](https://github.com/nasser1941/lcode/issues/8) and an [optional sandbox for shell commands](https://github.com/nasser1941/lcode/issues/9)
@@ -43,6 +43,8 @@ through [Ollama](https://ollama.com), and when it needs current information it c
43
43
  largest context that fits: `lcode setup` does it in one step.
44
44
  - **Safe by default.** Every edit is shown as a diff and every command that isn't read-only needs
45
45
  your approval. `auto-edit` and `yolo` modes when you want speed.
46
+ - **Undo.** lcode saves a checkpoint before the model changes files; `/undo` takes back the last
47
+ request's edits, new files and shell-command changes, without touching your git history.
46
48
  - **Bring your own model.** Eight curated open-weight models, all tested end to end, or any Ollama model
47
49
  with tool calling.
48
50
  - **Web search when needed.** Looks up the latest versions, docs and error messages with Ollama web
@@ -90,6 +92,7 @@ lcode
90
92
  | `lcode -c` / `lcode --resume` | continue the last session here / pick a saved session from a list |
91
93
  | `lcode --model qwen3.5-9b --context 128k` | pick a model and context window for this session |
92
94
  | `lcode models` / `lcode doctor` | what fits this machine / check the installation |
95
+ | `/undo`, `/rewind` | take back the last request's file changes, or go back further |
93
96
  | `/rename`, `/resume` | name the current session, resume a saved one |
94
97
  | `/model`, `/context`, `/compact`, `/help` | switch model, resize the context window (pick from a list), summarize, list commands |
95
98
 
@@ -134,7 +137,6 @@ fills up. Details: [How it works](https://nasser1941.github.io/lcode/how-it-work
134
137
 
135
138
  See the pinned [Roadmap issue](https://github.com/nasser1941/lcode/issues/10). Next up:
136
139
 
137
- - [`/undo` and automatic checkpoints](https://github.com/nasser1941/lcode/issues/4)
138
140
  - [`lcode bench`: score models on your own hardware](https://github.com/nasser1941/lcode/issues/5)
139
141
  - [MCP tool servers](https://github.com/nasser1941/lcode/issues/6)
140
142
  - [Homebrew formula](https://github.com/nasser1941/lcode/issues/8) and an [optional sandbox for shell commands](https://github.com/nasser1941/lcode/issues/9)
@@ -37,6 +37,8 @@ user, in your working directory**. It is not a sandbox. Keep in mind:
37
37
  snippets from your code. Use `web = ask` to approve each search, or `web = off` / `--no-web` to
38
38
  keep everything local.
39
39
  - Sessions (including file contents the model read) are stored in `~/.local/state/lcode/sessions`.
40
+ Checkpoints for `/undo` keep copies of your project's files (except ignored ones) in
41
+ `~/.local/state/lcode/checkpoints` for 14 days; turn them off with `checkpoints = false`.
40
42
 
41
43
  Reports about bypassing the permission prompts, the read-only allowlist, or data leaving the
42
44
  machine unexpectedly are especially welcome.
@@ -25,6 +25,7 @@ lcode config path # print the file location
25
25
  | `web` | `on` | Web search and page fetching: `on`, `ask` (before each search/website) or `off` |
26
26
  | `search_backend` | `auto` | `auto`, `ollama`, `brave`, `tavily` or `searxng`; `auto` uses the first one configured |
27
27
  | `searxng_url` | | Your SearXNG instance, e.g. `http://localhost:8888` |
28
+ | `checkpoints` | `true` | Save a checkpoint before the model changes files, so [`/undo`](usage.md#undo-and-checkpoints) can restore them |
28
29
 
29
30
  Example file:
30
31
 
@@ -58,6 +59,7 @@ Environment variables override the file, which is useful for one-off runs and CI
58
59
  | `~/.config/lcode/config.toml` | Settings |
59
60
  | `~/.local/state/lcode/sessions/` | Saved conversations (for `lcode -c`) |
60
61
  | `~/.local/state/lcode/history` | Prompt history (++up++ in the prompt) |
62
+ | `~/.local/state/lcode/checkpoints/` | Checkpoints for `/undo` (copies of your project's files; deleted after 14 days) |
61
63
  | `~/.local/state/lcode/limits.json` | Context sizes that ran out of GPU memory on this machine (safe to delete) |
62
64
 
63
65
  Sessions contain everything the model read, including file contents. Delete the folder to clear them.
@@ -75,6 +75,7 @@ the Ollama server you configure, it only contacts the web when the model searche
75
75
  | `agent.py` | System prompt, streaming, tool loop, compaction, sessions |
76
76
  | `tools.py` | Tool schemas and implementations |
77
77
  | `permissions.py` | Approval prompts and the read-only allowlist |
78
+ | `checkpoints.py` | Snapshots before the model changes files, for `/undo` and `/rewind` |
78
79
  | `catalog.py`, `models.toml` | Model catalog and memory estimates |
79
80
  | `hardware.py` | NVIDIA / Apple Silicon detection |
80
81
  | `ollama.py` | Ollama HTTP client |
@@ -67,7 +67,7 @@ until the task is done, asking before it changes anything.
67
67
  ---
68
68
 
69
69
  Every edit is shown as a diff and every non-read-only command needs your approval, unless you
70
- choose otherwise.
70
+ choose otherwise. `/undo` takes back a request's file changes.
71
71
 
72
72
  - :material-swap-horizontal:{ .lg .middle } **Bring your own model**
73
73
 
@@ -12,7 +12,7 @@ lcode
12
12
 
13
13
  ```text
14
14
  ╭──────────────────────────────────────────────╮
15
- │ lcode v0.2.2 — local coding agent │
15
+ │ lcode v0.3.0 — local coding agent │
16
16
  │ │
17
17
  │ model lcode-qwen3.6-35b │
18
18
  │ context 256K tokens │
@@ -49,6 +49,9 @@ reasoning is on.
49
49
  | `/clear` | Start a new conversation (the current one stays saved) |
50
50
  | `/rename NAME` | Name the current session so it's easy to find later |
51
51
  | `/resume [SESSION]` | Resume a saved session: pick from a list, or give its number or name; `/resume all` lists every folder |
52
+ | `/undo` | Undo the file changes of the last request ([details](#undo-and-checkpoints)) |
53
+ | `/rewind [N]` | Go back to before request N: its files, and optionally the conversation |
54
+ | `/checkpoints` | List the requests that changed files, and which files |
52
55
  | `/compact [focus]` | Summarize the conversation to free context |
53
56
  | `/context [size]` | Show how full the context window is and change its size: pick from a list with memory estimates, or give a size like `/context 128k` (`/ctx` is a shortcut) |
54
57
  | `/model [name]`, `/models` | Switch model, list models |
@@ -97,6 +100,38 @@ or ++n++. Text after ++n++ goes to the model as instructions: `n run the tests w
97
100
  In `yolo` mode the model can run any command as your user. Use it in a container, a VM or a
98
101
  throwaway clone, not on a machine with data you care about.
99
102
 
103
+ ## Undo and checkpoints
104
+
105
+ Before the model first changes anything in a request (an edit, a new file, or a shell command that
106
+ isn't read-only), lcode saves a checkpoint of your project. If the result isn't what you wanted,
107
+ `/undo` puts the files back:
108
+
109
+ ```text
110
+ ❯ /undo
111
+ Undoing request 3: switch the parser to the new tokenizer
112
+ restore src/parser.py
113
+ restore tests/test_parser.py
114
+ remove src/tokenizer_v2.py
115
+ Done: restored src/parser.py, tests/test_parser.py; removed src/tokenizer_v2.py.
116
+ ```
117
+
118
+ - `/undo` again goes one more request back. The model is told which files were restored, so it
119
+ reads them again instead of assuming its changes are still there.
120
+ - `/checkpoints` lists the requests that changed files. `/rewind 2` goes back to before request 2
121
+ in one step (undoing every request after it too) and offers to remove those requests from the
122
+ conversation as well.
123
+ - Changes made by shell commands are covered too (`sed -i`, generated files, `rm`), in any folder,
124
+ whether or not it's a git repository.
125
+ - If you edited one of those files yourself after the model did, lcode lists it and asks before
126
+ going back, since that would lose your edit. Files the model didn't touch are never changed.
127
+
128
+ Checkpoints are stored in a separate git repository under `~/.local/state/lcode/checkpoints/`. Your
129
+ own repository (its index, branches, stash and history) is never touched. Ignored files (per
130
+ `.gitignore`), `node_modules`, virtual environments and files over 10 MB aren't included. Folders
131
+ with more than 20,000 files, or your home folder itself, aren't checkpointed; lcode says so once and
132
+ carries on. Checkpoints are deleted after 14 days. To turn them off:
133
+ `lcode config set checkpoints false`.
134
+
100
135
  ## Web search
101
136
 
102
137
  When the answer depends on information newer than the model's training data, such as the latest
@@ -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.2"
3
+ __version__ = "0.3.0"
@@ -19,6 +19,7 @@ from rich.panel import Panel
19
19
  from rich.text import Text
20
20
 
21
21
  from lcode import catalog, limits, sessions, web
22
+ from lcode.checkpoints import Checkpoints
22
23
  from lcode.config import format_tokens
23
24
  from lcode.ollama import Ollama, OllamaError
24
25
  from lcode.permissions import Permissions
@@ -136,6 +137,7 @@ class Settings:
136
137
  web: str = "on" # on | ask | off
137
138
  search_backend: str = "auto"
138
139
  searxng_url: str | None = None
140
+ checkpoints: bool = True # snapshot files before the model changes them, for /undo
139
141
 
140
142
 
141
143
  class Agent:
@@ -147,6 +149,7 @@ class Agent:
147
149
  self.perms = Permissions(self.console, settings.permission_mode)
148
150
  self.tools = Toolbox(self)
149
151
  self.session_id = self.new_session_id()
152
+ self.checkpoints = Checkpoints(self.console, settings.checkpoints)
150
153
  self.session_name = ""
151
154
  self.session_title = ""
152
155
  self.ctx_used = 0
@@ -220,6 +223,7 @@ class Agent:
220
223
  "name": self.session_name,
221
224
  "title": self.session_title,
222
225
  "messages": self.messages,
226
+ "checkpoints": self.checkpoints.to_json(),
223
227
  }
224
228
  f.write_text(json.dumps(data))
225
229
 
@@ -228,6 +232,7 @@ class Agent:
228
232
  self.session_id = self.new_session_id()
229
233
  self.session_name = ""
230
234
  self.session_title = ""
235
+ self.checkpoints.reset(self.session_id)
231
236
 
232
237
  def rename(self, name: str) -> None:
233
238
  self.session_name = " ".join(name.split())
@@ -249,6 +254,7 @@ class Agent:
249
254
  note = f"The session's directory {saved_cwd} no longer exists; staying in {self.cwd}"
250
255
  self.messages = [{"role": "system", "content": self.system_prompt()}, *data.get("messages", [])[1:]]
251
256
  self.session_id = info.id
257
+ self.checkpoints.load(info.id, data.get("checkpoints") or [])
252
258
  self.session_name = data.get("name", "")
253
259
  self.session_title = data.get("title") or sessions.title_from(self.messages)
254
260
  self.tools.read_mtimes.clear() # files may have changed since; the model must read them again
@@ -406,6 +412,17 @@ class Agent:
406
412
  return name
407
413
 
408
414
  def run_turn(self, user_text: str) -> None:
415
+ self.checkpoints.begin_turn(self.session_id, user_text, len(self.messages))
416
+ try:
417
+ self._run_turn(user_text)
418
+ finally:
419
+ self.checkpoints.end_turn()
420
+
421
+ def checkpoint(self) -> None:
422
+ """Called before the model changes files: snapshot them once per request, for /undo."""
423
+ self.checkpoints.before_change(self.cwd)
424
+
425
+ def _run_turn(self, user_text: str) -> None:
409
426
  self.messages.append({"role": "user", "content": self.expand_mentions(user_text)})
410
427
  malformed = 0
411
428
  for _ in range(MAX_STEPS_PER_TURN):