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.
- {mlx_code-0.0.20 → mlx_code-0.0.23}/PKG-INFO +19 -22
- {mlx_code-0.0.20 → mlx_code-0.0.23}/README.md +18 -21
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/repl.py +50 -15
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code.egg-info/PKG-INFO +19 -22
- {mlx_code-0.0.20 → mlx_code-0.0.23}/setup.py +1 -1
- {mlx_code-0.0.20 → mlx_code-0.0.23}/LICENSE +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/__init__.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/apis.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/gits.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/lsp_tool.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/main.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/mcb.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/mcb_tool.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/ntui.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/stream_log.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/tools.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/util.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/view_git.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code/view_log.py +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code.egg-info/SOURCES.txt +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code.egg-info/dependency_links.txt +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code.egg-info/entry_points.txt +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code.egg-info/requires.txt +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/mlx_code.egg-info/top_level.txt +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/setup.cfg +0 -0
- {mlx_code-0.0.20 → mlx_code-0.0.23}/tests/__init__.py +0 -0
- {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.
|
|
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://
|
|
41
|
+
[](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
|
|
80
|
-
│ └──────────────┘
|
|
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
|
-
[](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
|
|
444
|
+
| `/exit [--all]` | Close branch tab, or exit the app |
|
|
448
445
|
| `!command` | Run a shell command; output captured in the TUI |
|
|
449
|
-
|
|
|
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
|
-
| `
|
|
458
|
-
| `
|
|
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://
|
|
5
|
+
[](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
|
|
44
|
-
│ └──────────────┘
|
|
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
|
-
[](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
|
|
408
|
+
| `/exit [--all]` | Close branch tab, or exit the app |
|
|
412
409
|
| `!command` | Run a shell command; output captured in the TUI |
|
|
413
|
-
|
|
|
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
|
-
| `
|
|
422
|
-
| `
|
|
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(
|
|
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
|
|
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
|
|
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('
|
|
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[
|
|
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
|
-
|
|
646
|
-
returncode = await
|
|
647
|
-
tab.show_command(f'
|
|
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+', '
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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://
|
|
41
|
+
[](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
|
|
80
|
-
│ └──────────────┘
|
|
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
|
-
[](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
|
|
444
|
+
| `/exit [--all]` | Close branch tab, or exit the app |
|
|
448
445
|
| `!command` | Run a shell command; output captured in the TUI |
|
|
449
|
-
|
|
|
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
|
-
| `
|
|
458
|
-
| `
|
|
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 |
|
|
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
|