mlx-code 0.0.20__tar.gz → 0.0.23__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 (27) hide show
  1. {mlx_code-0.0.20 → mlx_code-0.0.23}/PKG-INFO +19 -22
  2. {mlx_code-0.0.20 → mlx_code-0.0.23}/README.md +18 -21
  3. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/repl.py +50 -15
  4. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code.egg-info/PKG-INFO +19 -22
  5. {mlx_code-0.0.20 → mlx_code-0.0.23}/setup.py +1 -1
  6. {mlx_code-0.0.20 → mlx_code-0.0.23}/LICENSE +0 -0
  7. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/__init__.py +0 -0
  8. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/apis.py +0 -0
  9. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/gits.py +0 -0
  10. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/lsp_tool.py +0 -0
  11. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/main.py +0 -0
  12. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/mcb.py +0 -0
  13. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/mcb_tool.py +0 -0
  14. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/ntui.py +0 -0
  15. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/stream_log.py +0 -0
  16. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/tools.py +0 -0
  17. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/util.py +0 -0
  18. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/view_git.py +0 -0
  19. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/view_log.py +0 -0
  20. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code.egg-info/SOURCES.txt +0 -0
  21. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code.egg-info/dependency_links.txt +0 -0
  22. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code.egg-info/entry_points.txt +0 -0
  23. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code.egg-info/requires.txt +0 -0
  24. {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code.egg-info/top_level.txt +0 -0
  25. {mlx_code-0.0.20 → mlx_code-0.0.23}/setup.cfg +0 -0
  26. {mlx_code-0.0.20 → mlx_code-0.0.23}/tests/__init__.py +0 -0
  27. {mlx_code-0.0.20 → mlx_code-0.0.23}/tests/test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlx-code
3
- Version: 0.0.20
3
+ Version: 0.0.23
4
4
  Summary: Coding Agent for Mac
5
5
  Home-page: https://josefalbers.github.io/mlx-code/
6
6
  Author: J Joe
@@ -38,7 +38,7 @@ Dynamic: summary
38
38
 
39
39
  A Git-native coding agent that can run entirely on your Mac. No API keys, no cloud, and no data leaving your machine. Powered by Apple MLX, it turns commits, branches, and worktrees into the agent’s state, history, and execution model
40
40
 
41
- https://github.com/user-attachments/assets/0569d101-8d0a-4e67-9e82-fce84a5ef3f0
41
+ [![Link](https://raw.githubusercontent.com/JosefAlbers/mlx-code/main/assets/mlx-code-v0.0.20.gif)](https://youtu.be/0lkY7YQCyCo)
42
42
 
43
43
  ---
44
44
 
@@ -68,19 +68,19 @@ REPL tabs (each tab = a git branch + agent) │
68
68
 
69
69
  ├────────────────────────────────────► each tab is an independent Agent
70
70
 
71
- ┌────┴────────────────────────────────┐
72
- │ Agent
73
- │ ┌──────────────┐ ┌──────────────┐
74
- │ │ API: │ │ Tools: │
75
- │ │ MLX (local) │ │ Read Write │
76
- │ │ Claude │ │ Edit Bash │
77
- │ │ Gemini │ │ Grep Find │
78
- │ │ OpenAI │ │ Ls Skill │
79
- │ └──────────────┘ │ Agent ───────┼─┼───► spawns child Agent
80
- │ └──────────────┘ │ (each with own tools + worktree + etc)
81
- │ Git worktree
82
- │ (isolation + session state)
83
- └─────────────────────────────────────┘
71
+ ┌────┴─────────────────────────────────┐
72
+ │ Agent
73
+ │ ┌──────────────┐ ┌──────────────┐
74
+ │ │ API: │ │ Tools: │
75
+ │ │ MLX (local) │ │ Read Write │
76
+ │ │ Claude │ │ Edit Bash │
77
+ │ │ Gemini │ │ Grep Find │
78
+ │ │ OpenAI │ │ Ls Skill │
79
+ │ └──────────────┘ │ Agent ───────┼──┼───► spawns child Agent
80
+ │ └──────────────┘ │ (each with own tools + worktree + etc)
81
+ │ Git worktree
82
+ │ (isolation + session state)
83
+ └──────────────────────────────────────┘
84
84
  ```
85
85
 
86
86
  Each layer is importable and composable on its own. A commit records state, a branch records an alternative path, and a tab is just a live view over an `Agent`.
@@ -106,8 +106,6 @@ mlc-run --api deepseek --model deepseek-v4-flash
106
106
 
107
107
  That's it. The first run starts a local inference server and drops you into the REPL.
108
108
 
109
- [![Link](https://raw.githubusercontent.com/JosefAlbers/mlx-code/main/assets/mlx-code-v0.0.20.gif)](https://youtu.be/0lkY7YQCyCo)
110
-
111
109
  ---
112
110
 
113
111
  ## Core ideas
@@ -312,7 +310,6 @@ echo "explain lsp.py" | mlc-run -a deepseek | cat - PLAN.md | mlc-run --url http
312
310
  mlc-run --notui
313
311
  ```
314
312
 
315
-
316
313
  ---
317
314
 
318
315
  ## Using as a Library
@@ -444,9 +441,9 @@ agent = Agent(extra_tool_classes=[LiveDBTool], tool_names=["QueryDB"])
444
441
  | `/branch [--rev N] [prompt]` | Open a new branch tab from the current (or earlier) checkpoint |
445
442
  | `/abort` | Abort the running agent |
446
443
  | `/export [path]` | Export session to JSON |
447
- | `/exit` or `/quit` | Close branch tab, or exit the app |
444
+ | `/exit [--all]` | Close branch tab, or exit the app |
448
445
  | `!command` | Run a shell command; output captured in the TUI |
449
- | `!!command` | Run an interactive command (TUI suspends, terminal handed to process) |
446
+ | `$command` | Run an interactive command (TUI suspends, terminal handed to process) |
450
447
 
451
448
  ### Key bindings
452
449
 
@@ -454,8 +451,8 @@ agent = Agent(extra_tool_classes=[LiveDBTool], tool_names=["QueryDB"])
454
451
  |---|---|
455
452
  | `Enter` | Submit |
456
453
  | `Ctrl-J` | Insert newline |
457
- | `Alt-1` … `Alt-9` | Jump to tab N |
458
- | `Tab` / `Shift-Tab` | Cycle through tabs |
454
+ | `Ctrl-1` … `Ctrl-9` | Jump to tab N |
455
+ | `Ctrl-,` / `Ctrl-.` | Cycle through tabs |
459
456
  | `Ctrl-C` | Abort running agent |
460
457
  | `Ctrl-D` | Close branch tab, or exit app |
461
458
  | `Ctrl-R` | Recall last prompt into editor |
@@ -2,7 +2,7 @@
2
2
 
3
3
  A Git-native coding agent that can run entirely on your Mac. No API keys, no cloud, and no data leaving your machine. Powered by Apple MLX, it turns commits, branches, and worktrees into the agent’s state, history, and execution model
4
4
 
5
- https://github.com/user-attachments/assets/0569d101-8d0a-4e67-9e82-fce84a5ef3f0
5
+ [![Link](https://raw.githubusercontent.com/JosefAlbers/mlx-code/main/assets/mlx-code-v0.0.20.gif)](https://youtu.be/0lkY7YQCyCo)
6
6
 
7
7
  ---
8
8
 
@@ -32,19 +32,19 @@ REPL tabs (each tab = a git branch + agent) │
32
32
 
33
33
  ├────────────────────────────────────► each tab is an independent Agent
34
34
 
35
- ┌────┴────────────────────────────────┐
36
- │ Agent
37
- │ ┌──────────────┐ ┌──────────────┐
38
- │ │ API: │ │ Tools: │
39
- │ │ MLX (local) │ │ Read Write │
40
- │ │ Claude │ │ Edit Bash │
41
- │ │ Gemini │ │ Grep Find │
42
- │ │ OpenAI │ │ Ls Skill │
43
- │ └──────────────┘ │ Agent ───────┼─┼───► spawns child Agent
44
- │ └──────────────┘ │ (each with own tools + worktree + etc)
45
- │ Git worktree
46
- │ (isolation + session state)
47
- └─────────────────────────────────────┘
35
+ ┌────┴─────────────────────────────────┐
36
+ │ Agent
37
+ │ ┌──────────────┐ ┌──────────────┐
38
+ │ │ API: │ │ Tools: │
39
+ │ │ MLX (local) │ │ Read Write │
40
+ │ │ Claude │ │ Edit Bash │
41
+ │ │ Gemini │ │ Grep Find │
42
+ │ │ OpenAI │ │ Ls Skill │
43
+ │ └──────────────┘ │ Agent ───────┼──┼───► spawns child Agent
44
+ │ └──────────────┘ │ (each with own tools + worktree + etc)
45
+ │ Git worktree
46
+ │ (isolation + session state)
47
+ └──────────────────────────────────────┘
48
48
  ```
49
49
 
50
50
  Each layer is importable and composable on its own. A commit records state, a branch records an alternative path, and a tab is just a live view over an `Agent`.
@@ -70,8 +70,6 @@ mlc-run --api deepseek --model deepseek-v4-flash
70
70
 
71
71
  That's it. The first run starts a local inference server and drops you into the REPL.
72
72
 
73
- [![Link](https://raw.githubusercontent.com/JosefAlbers/mlx-code/main/assets/mlx-code-v0.0.20.gif)](https://youtu.be/0lkY7YQCyCo)
74
-
75
73
  ---
76
74
 
77
75
  ## Core ideas
@@ -276,7 +274,6 @@ echo "explain lsp.py" | mlc-run -a deepseek | cat - PLAN.md | mlc-run --url http
276
274
  mlc-run --notui
277
275
  ```
278
276
 
279
-
280
277
  ---
281
278
 
282
279
  ## Using as a Library
@@ -408,9 +405,9 @@ agent = Agent(extra_tool_classes=[LiveDBTool], tool_names=["QueryDB"])
408
405
  | `/branch [--rev N] [prompt]` | Open a new branch tab from the current (or earlier) checkpoint |
409
406
  | `/abort` | Abort the running agent |
410
407
  | `/export [path]` | Export session to JSON |
411
- | `/exit` or `/quit` | Close branch tab, or exit the app |
408
+ | `/exit [--all]` | Close branch tab, or exit the app |
412
409
  | `!command` | Run a shell command; output captured in the TUI |
413
- | `!!command` | Run an interactive command (TUI suspends, terminal handed to process) |
410
+ | `$command` | Run an interactive command (TUI suspends, terminal handed to process) |
414
411
 
415
412
  ### Key bindings
416
413
 
@@ -418,8 +415,8 @@ agent = Agent(extra_tool_classes=[LiveDBTool], tool_names=["QueryDB"])
418
415
  |---|---|
419
416
  | `Enter` | Submit |
420
417
  | `Ctrl-J` | Insert newline |
421
- | `Alt-1` … `Alt-9` | Jump to tab N |
422
- | `Tab` / `Shift-Tab` | Cycle through tabs |
418
+ | `Ctrl-1` … `Ctrl-9` | Jump to tab N |
419
+ | `Ctrl-,` / `Ctrl-.` | Cycle through tabs |
423
420
  | `Ctrl-C` | Abort running agent |
424
421
  | `Ctrl-D` | Close branch tab, or exit app |
425
422
  | `Ctrl-R` | Recall last prompt into editor |
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
  import asyncio
3
+ import subprocess
3
4
  import copy
4
5
  import datetime
5
6
  import json
@@ -255,14 +256,14 @@ def append_to_history_table(tbl: Table, messages: list[dict]) -> None:
255
256
  if render_as_md:
256
257
  body = Markdown(text)
257
258
  else:
258
- body = RichText(escape(text), style=style)
259
+ body = RichText(text, style=style)
259
260
  tbl.add_row(RichText(prefix, style=style), body)
260
261
 
261
262
  def render_history(messages: list[dict]) -> Table:
262
263
  tbl = _make_empty_history_table()
263
264
  append_to_history_table(tbl, messages)
264
265
  return tbl
265
- REPL_HELP = '\nCommands:\n/help show this message\n/clear [--config F] clear conversation; --config reconfigures agent from YAML/JSON\n/history show full conversation transcript\n/history --raw show raw API message log (debug)\n/diff [--all] show side-by-side diff of changes\n/errors show timestamped error log for this tab\n/tools list active tools\n/branch [--rev N] [--no-worktree] [prompt]\n open a branch tab; optional prompt runs immediately\n/abort abort the running agent\n/export [path] export session to JSON\n/exit /quit close branch tab, or exit the app\n!command run shell command in worktree (output captured in TUI)\n!!command run interactive shell command (TUI suspends, terminal handed to process)\n e.g. !ls !git diff !cat file.py\n !!vim file.py !!yazi !!less log.txt\nKeys:\nEnter submit\nCtrl-J insert newline in editor\nAlt-1 … Alt-9 jump directly to tab N\nTab / Shift-Tab cycle through tabs\nCtrl-C abort running agent\nCtrl-D close branch tab, or exit app\nCtrl-R recall last prompt into editor\n'
266
+ REPL_HELP = '\nCommands:\n/help show this message\n/clear [--config F] clear conversation; --config reconfigures agent from YAML/JSON\n/history show full conversation transcript\n/history --raw show raw API message log (debug)\n/diff [--all] show side-by-side diff of changes\n/errors show timestamped error log for this tab\n/tools list active tools\n/branch [--rev N] [--no-worktree] [prompt]\n open a branch tab; optional prompt runs immediately\n/abort abort the running agent\n/export [path] export session to JSON\n/exit /quit [--all] close branch tab, or exit the app\n!command run shell command in worktree (output captured in TUI)\n$command run interactive shell command (TUI suspends, terminal handed to process)\n e.g. !ls !git diff !cat file.py\n $vim file.py $yazi $less log.txt\nKeys:\nEnter submit\nCtrl-J insert newline in editor\nCtrl-1 … Ctrl-9 jump directly to tab N\nCtrl-, / Ctrl-. cycle through tabs\nCtrl-C abort running agent\nCtrl-D close branch tab (exit app if last tab) \nCtrl-R recall last prompt into editor\n'
266
267
 
267
268
  class InputBox(TextArea):
268
269
  BINDINGS = [Binding('ctrl+j', 'insert_newline', 'New line', show=False), Binding('enter', 'submit_text', 'Submit', show=False, priority=True), Binding('ctrl+r', 'recall_last', 'Recall', show=False), Binding('ctrl+d', 'request_close', 'Exit', show=False, priority=True)]
@@ -485,7 +486,7 @@ class StatusBar(Static):
485
486
  class HelpBar(Static):
486
487
 
487
488
  def __init__(self, **kwargs):
488
- self._idle_text = RichText('/help !cmd !!interactive Ctrl-J newline Alt-1…9 tabs Ctrl-C abort Ctrl-D exit', style='dim')
489
+ self._idle_text = RichText('/help !cmd $interactive Ctrl-J newline Ctrl-,. tabs Ctrl-C abort Ctrl-D exit', style='dim')
489
490
  super().__init__(self._idle_text, **kwargs)
490
491
 
491
492
  def show_idle(self) -> None:
@@ -505,7 +506,7 @@ class HelpBar(Static):
505
506
 
506
507
  class ReplApp(App[None]):
507
508
  CSS = '\n ReplApp { layout: vertical; background: $background; }\n ContentSwitcher { height: 1fr; }\n InputBox {\n height: auto;\n min-height: 3;\n max-height: 8;\n border: none;\n border-top: tall $panel-darken-1;\n background: $panel;\n padding: 0 2;\n color: $text;\n }\n InputBox:focus { border-top: tall $accent; }\n TabBar { height: 1; background: $panel; padding: 0 1; }\n StatusBar { height: 1; background: $panel; color: $text-muted; padding: 0 1; }\n HelpBar { height: 1; color: $text-muted; padding: 0 1; }\n '
508
- BINDINGS = [Binding('ctrl+c', 'abort_agent', 'Abort', priority=True, show=False), Binding('ctrl+d', 'close_or_exit', 'Exit', show=False), Binding('tab', 'next_tab', 'Next tab', show=False), Binding('shift+tab', 'prev_tab', 'Prev tab', show=False), Binding('ctrl+1', 'switch_tab(1)', 'Tab 1', show=False), Binding('ctrl+2', 'switch_tab(2)', 'Tab 2', show=False), Binding('ctrl+3', 'switch_tab(3)', 'Tab 3', show=False), Binding('ctrl+4', 'switch_tab(4)', 'Tab 4', show=False), Binding('ctrl+5', 'switch_tab(5)', 'Tab 5', show=False), Binding('ctrl+6', 'switch_tab(6)', 'Tab 6', show=False), Binding('ctrl+7', 'switch_tab(7)', 'Tab 7', show=False), Binding('ctrl+8', 'switch_tab(8)', 'Tab 8', show=False), Binding('ctrl+9', 'switch_tab(9)', 'Tab 9', show=False)]
509
+ BINDINGS = [Binding('ctrl+c', 'abort_agent', 'Abort', priority=True, show=False), Binding('ctrl+d', 'close_or_exit', 'Exit', show=False), Binding('ctrl+full_stop', 'next_tab', 'Next tab', show=False, priority=True), Binding('ctrl+comma', 'prev_tab', 'Prev tab', show=False, priority=True), Binding('ctrl+1', 'switch_tab(1)', 'Tab 1', show=False), Binding('ctrl+2', 'switch_tab(2)', 'Tab 2', show=False), Binding('ctrl+3', 'switch_tab(3)', 'Tab 3', show=False), Binding('ctrl+4', 'switch_tab(4)', 'Tab 4', show=False), Binding('ctrl+5', 'switch_tab(5)', 'Tab 5', show=False), Binding('ctrl+6', 'switch_tab(6)', 'Tab 6', show=False), Binding('ctrl+7', 'switch_tab(7)', 'Tab 7', show=False), Binding('ctrl+8', 'switch_tab(8)', 'Tab 8', show=False), Binding('ctrl+9', 'switch_tab(9)', 'Tab 9', show=False), Binding('ctrl+z', 'suspend_app', 'Suspend', show=False, priority=True)]
509
510
 
510
511
  def __init__(self, agent: Agent, init_prompt: str | None=None) -> None:
511
512
  super().__init__()
@@ -542,6 +543,11 @@ class ReplApp(App[None]):
542
543
  except Exception:
543
544
  pass
544
545
 
546
+ def action_suspend_app(self) -> None:
547
+ import signal
548
+ with self.suspend():
549
+ os.kill(os.getpid(), signal.SIGTSTP)
550
+
545
551
  @property
546
552
  def active_tab(self) -> Tab:
547
553
  return self.tabs[self.active_index]
@@ -578,8 +584,8 @@ class ReplApp(App[None]):
578
584
  tab.errors.clear()
579
585
  tab.last_error = ''
580
586
  self.query_one('#helpbar', HelpBar).show_idle()
581
- if text.startswith('!!'):
582
- command = text[2:].strip()
587
+ if text.startswith('$'):
588
+ command = text[1:].strip()
583
589
  if command:
584
590
  await self._run_interactive_command(tab, command)
585
591
  return
@@ -592,9 +598,6 @@ class ReplApp(App[None]):
592
598
  if text.startswith('/'):
593
599
  await self._handle_command(tab, text)
594
600
  return
595
- if text.lower() in {'exit', 'quit'}:
596
- self._do_close_or_exit()
597
- return
598
601
  if tab.is_running:
599
602
  self.query_one('#helpbar', HelpBar).show_error('Agent is running — /abort first.')
600
603
  return
@@ -641,10 +644,13 @@ class ReplApp(App[None]):
641
644
  gwt = tab.agent.ctx.get('gwt')
642
645
  cwd = gwt.worktree if gwt and getattr(gwt, 'worktree', None) else tab.agent.ctx.get('cwd') or os.getcwd()
643
646
  env = tab.agent.ctx.get('env')
647
+
648
+ def _blocking_run() -> int:
649
+ return subprocess.run(command, shell=True, cwd=cwd, env={**os.environ, **env}).returncode
644
650
  with self.suspend():
645
- proc = await asyncio.create_subprocess_shell(command, cwd=cwd, stdin=None, stdout=None, stderr=None, env=env if env else None)
646
- returncode = await proc.wait()
647
- tab.show_command(f'!!{command}', f'[exited {returncode}]')
651
+ loop = asyncio.get_running_loop()
652
+ returncode = await loop.run_in_executor(None, _blocking_run)
653
+ tab.show_command(f'${command}', f'[exited {returncode}]')
648
654
 
649
655
  async def _handle_command(self, tab: Tab, text: str) -> None:
650
656
  cmd, _, arg = text.partition(' ')
@@ -704,7 +710,7 @@ class ReplApp(App[None]):
704
710
  content = m.get('content', '')
705
711
  if isinstance(content, list):
706
712
  content = ' '.join((b.get('text', '') for b in content if isinstance(b, dict) and b.get('type') == 'text'))
707
- content = re.sub('\\s+', ' ', content).strip()
713
+ content = re.sub('\\s+', ' ', content).strip()
708
714
  if len(content) > 100:
709
715
  content = content[:100] + '…'
710
716
  line = f'{i}. {content}'
@@ -870,11 +876,19 @@ class ReplApp(App[None]):
870
876
  except OSError as exc:
871
877
  self.query_one('#helpbar', HelpBar).show_error(f'Export failed: {exc}')
872
878
  elif cmd in {'/exit', '/quit'}:
873
- self._exit_with_summary(tab)
879
+ if arg == '--all':
880
+ self._exit_with_summary(tab)
881
+ else:
882
+ self._do_close_or_exit()
874
883
  else:
875
884
  self.query_one('#helpbar', HelpBar).show_error(f'Unknown command: {cmd!r} — try /help')
876
885
 
877
886
  def _exit_with_summary(self, exit_tab: Tab) -> None:
887
+ for t in self.tabs:
888
+ if t.is_running:
889
+ t.agent.abort()
890
+ if t.running_task:
891
+ t.running_task.cancel()
878
892
  summary = []
879
893
  for t in self.tabs:
880
894
  gwt = t.agent.ctx.get('gwt')
@@ -935,7 +949,18 @@ class ReplApp(App[None]):
935
949
  if tab.running_task:
936
950
  tab.running_task.cancel()
937
951
  self._confirm_close = False
938
- self._exit_with_summary(tab)
952
+ if len(self.tabs) > 1:
953
+ key = id(tab.agent)
954
+ if key in self._unsubscribers:
955
+ self._unsubscribers[key]()
956
+ del self._unsubscribers[key]
957
+ tab.remove()
958
+ self.tabs.pop(self.active_index)
959
+ if self.active_index >= len(self.tabs):
960
+ self.active_index = len(self.tabs) - 1
961
+ self._switch_to(self.active_index)
962
+ else:
963
+ self._exit_with_summary(tab)
939
964
 
940
965
  def action_abort_agent(self) -> None:
941
966
  input_box = self.query_one(InputBox)
@@ -1043,6 +1068,16 @@ def run_repl(*, base_url=None, model=None, api: Literal['claude', 'codex', 'gemi
1043
1068
  finally:
1044
1069
  if log_fp:
1045
1070
  log_fp.close()
1071
+ if app_instance:
1072
+ cleaned = set()
1073
+ for t in app_instance.tabs:
1074
+ gwt = t.agent.ctx.get('gwt')
1075
+ if gwt and getattr(gwt, 'worktree', None) and (gwt.worktree not in cleaned):
1076
+ cleaned.add(gwt.worktree)
1077
+ try:
1078
+ cleanup_worktree(gwt)
1079
+ except Exception:
1080
+ pass
1046
1081
  if app_instance and hasattr(app_instance, '_exit_summary') and app_instance._exit_summary:
1047
1082
  print('\n--- Session Exit Summary ---')
1048
1083
  for item in app_instance._exit_summary:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlx-code
3
- Version: 0.0.20
3
+ Version: 0.0.23
4
4
  Summary: Coding Agent for Mac
5
5
  Home-page: https://josefalbers.github.io/mlx-code/
6
6
  Author: J Joe
@@ -38,7 +38,7 @@ Dynamic: summary
38
38
 
39
39
  A Git-native coding agent that can run entirely on your Mac. No API keys, no cloud, and no data leaving your machine. Powered by Apple MLX, it turns commits, branches, and worktrees into the agent’s state, history, and execution model
40
40
 
41
- https://github.com/user-attachments/assets/0569d101-8d0a-4e67-9e82-fce84a5ef3f0
41
+ [![Link](https://raw.githubusercontent.com/JosefAlbers/mlx-code/main/assets/mlx-code-v0.0.20.gif)](https://youtu.be/0lkY7YQCyCo)
42
42
 
43
43
  ---
44
44
 
@@ -68,19 +68,19 @@ REPL tabs (each tab = a git branch + agent) │
68
68
 
69
69
  ├────────────────────────────────────► each tab is an independent Agent
70
70
 
71
- ┌────┴────────────────────────────────┐
72
- │ Agent
73
- │ ┌──────────────┐ ┌──────────────┐
74
- │ │ API: │ │ Tools: │
75
- │ │ MLX (local) │ │ Read Write │
76
- │ │ Claude │ │ Edit Bash │
77
- │ │ Gemini │ │ Grep Find │
78
- │ │ OpenAI │ │ Ls Skill │
79
- │ └──────────────┘ │ Agent ───────┼─┼───► spawns child Agent
80
- │ └──────────────┘ │ (each with own tools + worktree + etc)
81
- │ Git worktree
82
- │ (isolation + session state)
83
- └─────────────────────────────────────┘
71
+ ┌────┴─────────────────────────────────┐
72
+ │ Agent
73
+ │ ┌──────────────┐ ┌──────────────┐
74
+ │ │ API: │ │ Tools: │
75
+ │ │ MLX (local) │ │ Read Write │
76
+ │ │ Claude │ │ Edit Bash │
77
+ │ │ Gemini │ │ Grep Find │
78
+ │ │ OpenAI │ │ Ls Skill │
79
+ │ └──────────────┘ │ Agent ───────┼──┼───► spawns child Agent
80
+ │ └──────────────┘ │ (each with own tools + worktree + etc)
81
+ │ Git worktree
82
+ │ (isolation + session state)
83
+ └──────────────────────────────────────┘
84
84
  ```
85
85
 
86
86
  Each layer is importable and composable on its own. A commit records state, a branch records an alternative path, and a tab is just a live view over an `Agent`.
@@ -106,8 +106,6 @@ mlc-run --api deepseek --model deepseek-v4-flash
106
106
 
107
107
  That's it. The first run starts a local inference server and drops you into the REPL.
108
108
 
109
- [![Link](https://raw.githubusercontent.com/JosefAlbers/mlx-code/main/assets/mlx-code-v0.0.20.gif)](https://youtu.be/0lkY7YQCyCo)
110
-
111
109
  ---
112
110
 
113
111
  ## Core ideas
@@ -312,7 +310,6 @@ echo "explain lsp.py" | mlc-run -a deepseek | cat - PLAN.md | mlc-run --url http
312
310
  mlc-run --notui
313
311
  ```
314
312
 
315
-
316
313
  ---
317
314
 
318
315
  ## Using as a Library
@@ -444,9 +441,9 @@ agent = Agent(extra_tool_classes=[LiveDBTool], tool_names=["QueryDB"])
444
441
  | `/branch [--rev N] [prompt]` | Open a new branch tab from the current (or earlier) checkpoint |
445
442
  | `/abort` | Abort the running agent |
446
443
  | `/export [path]` | Export session to JSON |
447
- | `/exit` or `/quit` | Close branch tab, or exit the app |
444
+ | `/exit [--all]` | Close branch tab, or exit the app |
448
445
  | `!command` | Run a shell command; output captured in the TUI |
449
- | `!!command` | Run an interactive command (TUI suspends, terminal handed to process) |
446
+ | `$command` | Run an interactive command (TUI suspends, terminal handed to process) |
450
447
 
451
448
  ### Key bindings
452
449
 
@@ -454,8 +451,8 @@ agent = Agent(extra_tool_classes=[LiveDBTool], tool_names=["QueryDB"])
454
451
  |---|---|
455
452
  | `Enter` | Submit |
456
453
  | `Ctrl-J` | Insert newline |
457
- | `Alt-1` … `Alt-9` | Jump to tab N |
458
- | `Tab` / `Shift-Tab` | Cycle through tabs |
454
+ | `Ctrl-1` … `Ctrl-9` | Jump to tab N |
455
+ | `Ctrl-,` / `Ctrl-.` | Cycle through tabs |
459
456
  | `Ctrl-C` | Abort running agent |
460
457
  | `Ctrl-D` | Close branch tab, or exit app |
461
458
  | `Ctrl-R` | Recall last prompt into editor |
@@ -11,7 +11,7 @@ setup(
11
11
  author_email="albersj66@gmail.com",
12
12
  author="J Joe",
13
13
  license="Apache-2.0",
14
- version="0.0.20",
14
+ version="0.0.23",
15
15
  readme="README.md",
16
16
  description="Coding Agent for Mac",
17
17
  long_description=open("README.md").read(),
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