mlx-code 0.0.9__tar.gz → 0.0.11__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.9 → mlx_code-0.0.11}/PKG-INFO +30 -29
- {mlx_code-0.0.9 → mlx_code-0.0.11}/README.md +24 -27
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/main.py +20 -17
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/mcb.py +1 -1
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/mcb_tool.py +4 -4
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/repl.py +39 -19
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/stream_log.py +4 -1
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/tools.py +13 -12
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code.egg-info/PKG-INFO +30 -29
- {mlx_code-0.0.9 → mlx_code-0.0.11}/setup.py +11 -9
- {mlx_code-0.0.9 → mlx_code-0.0.11}/LICENSE +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/__init__.py +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/apis.py +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/gits.py +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/lsp_tool.py +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/util.py +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/view_git.py +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/view_log.py +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code.egg-info/SOURCES.txt +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code.egg-info/dependency_links.txt +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code.egg-info/entry_points.txt +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code.egg-info/requires.txt +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code.egg-info/top_level.txt +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/setup.cfg +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/tests/__init__.py +0 -0
- {mlx_code-0.0.9 → mlx_code-0.0.11}/tests/test.py +0 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mlx-code
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.11
|
|
4
4
|
Summary: Coding Agent for Mac
|
|
5
|
-
Home-page: https://github.
|
|
5
|
+
Home-page: https://josefalbers.github.io/mlx-code/
|
|
6
6
|
Author: J Joe
|
|
7
7
|
Author-email: albersj66@gmail.com
|
|
8
8
|
License: Apache-2.0
|
|
9
|
+
Project-URL: Source, https://github.com/JosefAlbers/mlx-code
|
|
10
|
+
Project-URL: Issues, https://github.com/JosefAlbers/mlx-code/issues
|
|
11
|
+
Project-URL: Documentation, https://josefalbers.github.io/mlx-code/
|
|
9
12
|
Requires-Python: >=3.12.8
|
|
10
13
|
Description-Content-Type: text/markdown
|
|
11
14
|
License-File: LICENSE
|
|
@@ -22,16 +25,17 @@ Dynamic: description-content-type
|
|
|
22
25
|
Dynamic: home-page
|
|
23
26
|
Dynamic: license
|
|
24
27
|
Dynamic: license-file
|
|
28
|
+
Dynamic: project-url
|
|
25
29
|
Dynamic: provides-extra
|
|
26
30
|
Dynamic: requires-dist
|
|
27
31
|
Dynamic: requires-python
|
|
28
32
|
Dynamic: summary
|
|
29
33
|
|
|
30
|
-
# mlx-code
|
|
34
|
+
# [mlx-code](https://josefalbers.github.io/mlx-code)
|
|
31
35
|
|
|
32
36
|
A lightweight coding agent built on Apple's MLX framework.
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
https://github.com/user-attachments/assets/0569d101-8d0a-4e67-9e82-fce84a5ef3f0
|
|
35
39
|
|
|
36
40
|
---
|
|
37
41
|
|
|
@@ -48,68 +52,65 @@ A lightweight coding agent built on Apple's MLX framework.
|
|
|
48
52
|
## Quick Start
|
|
49
53
|
|
|
50
54
|
```bash
|
|
51
|
-
pip install mlx-code
|
|
52
|
-
|
|
55
|
+
pip install mlx-code
|
|
56
|
+
mlc
|
|
53
57
|
```
|
|
54
58
|
|
|
55
59
|
---
|
|
56
60
|
|
|
57
61
|
## Command Line
|
|
58
62
|
|
|
59
|
-
### `
|
|
63
|
+
### `mlc`: local server + harness
|
|
60
64
|
|
|
61
65
|
Starts the MLX inference server and launches a harness against it.
|
|
62
66
|
|
|
63
67
|
```bash
|
|
64
68
|
# Default: local MLX server + built-in REPL harness
|
|
65
|
-
|
|
69
|
+
mlc
|
|
66
70
|
|
|
67
71
|
# Use a different harness (routes traffic through the local server)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
mlc --leash claude
|
|
73
|
+
mlc --leash gemini
|
|
74
|
+
mlc --leash codex
|
|
71
75
|
|
|
72
76
|
# Server only, no harness
|
|
73
|
-
|
|
77
|
+
mlc --leash none
|
|
74
78
|
|
|
75
79
|
# Specify a model
|
|
76
|
-
|
|
80
|
+
mlc --model mlx-community/Qwen3.5-4B-OptiQ-4bit
|
|
77
81
|
|
|
78
82
|
# Restrict the tools available to the agent
|
|
79
|
-
|
|
83
|
+
mlc --tools Read Write Bash
|
|
80
84
|
|
|
81
85
|
# Custom system prompt
|
|
82
|
-
|
|
86
|
+
mlc --system "You are a helpful assistant."
|
|
83
87
|
|
|
84
88
|
# Load skills from a directory (scans recursively for SKILL.md files)
|
|
85
|
-
|
|
89
|
+
mlc --skill ./my-skills
|
|
86
90
|
|
|
87
91
|
# Resume a previous session from a git commit hash
|
|
88
|
-
|
|
92
|
+
mlc --resume <commit-hash>
|
|
89
93
|
|
|
90
|
-
# Because `
|
|
91
|
-
echo "
|
|
94
|
+
# Because `mlc` reads from stdin when it isn't a TTY, it composes naturally with shell pipes:
|
|
95
|
+
echo "Here's the solution you proposed: <excerpt>$(mlc -p "write code for a chrome extension to play youtube x5 speed")</excerpt> Now argue against it. What are the edge cases this doesn't handle? What assumptions did you make that might not hold in a production system? What would you change if you knew this code would be read by a senior engineer in a security audit?" | mlc
|
|
92
96
|
```
|
|
93
97
|
|
|
94
|
-
### `
|
|
98
|
+
### `mlc-run`: harness only
|
|
95
99
|
|
|
96
100
|
Runs the agent harness against an already-running server or a remote provider.
|
|
97
101
|
|
|
98
102
|
```bash
|
|
99
103
|
# Connect to a local server at 127.0.0.1:8000 (default)
|
|
100
|
-
|
|
104
|
+
mlc-run
|
|
101
105
|
|
|
102
106
|
# Remote providers
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
mlc-run --api claude
|
|
108
|
+
mlc-run --api gemini
|
|
109
|
+
mlc-run --api deepseek --model deepseek-v4-pro
|
|
110
|
+
mlc-run --api codex
|
|
107
111
|
|
|
108
112
|
# Custom endpoint
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# With skills
|
|
112
|
-
mlxc-run --skill ./my-skills
|
|
113
|
+
echo "explain lsp.py" | mlc-run -a deepseek | cat - PLAN.md | mlc-run --url http://localhost:9000
|
|
113
114
|
```
|
|
114
115
|
|
|
115
116
|
---
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# mlx-code
|
|
1
|
+
# [mlx-code](https://josefalbers.github.io/mlx-code)
|
|
2
2
|
|
|
3
3
|
A lightweight coding agent built on Apple's MLX framework.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
https://github.com/user-attachments/assets/0569d101-8d0a-4e67-9e82-fce84a5ef3f0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -19,68 +19,65 @@ A lightweight coding agent built on Apple's MLX framework.
|
|
|
19
19
|
## Quick Start
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
pip install mlx-code
|
|
23
|
-
|
|
22
|
+
pip install mlx-code
|
|
23
|
+
mlc
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
28
|
## Command Line
|
|
29
29
|
|
|
30
|
-
### `
|
|
30
|
+
### `mlc`: local server + harness
|
|
31
31
|
|
|
32
32
|
Starts the MLX inference server and launches a harness against it.
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
# Default: local MLX server + built-in REPL harness
|
|
36
|
-
|
|
36
|
+
mlc
|
|
37
37
|
|
|
38
38
|
# Use a different harness (routes traffic through the local server)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
mlc --leash claude
|
|
40
|
+
mlc --leash gemini
|
|
41
|
+
mlc --leash codex
|
|
42
42
|
|
|
43
43
|
# Server only, no harness
|
|
44
|
-
|
|
44
|
+
mlc --leash none
|
|
45
45
|
|
|
46
46
|
# Specify a model
|
|
47
|
-
|
|
47
|
+
mlc --model mlx-community/Qwen3.5-4B-OptiQ-4bit
|
|
48
48
|
|
|
49
49
|
# Restrict the tools available to the agent
|
|
50
|
-
|
|
50
|
+
mlc --tools Read Write Bash
|
|
51
51
|
|
|
52
52
|
# Custom system prompt
|
|
53
|
-
|
|
53
|
+
mlc --system "You are a helpful assistant."
|
|
54
54
|
|
|
55
55
|
# Load skills from a directory (scans recursively for SKILL.md files)
|
|
56
|
-
|
|
56
|
+
mlc --skill ./my-skills
|
|
57
57
|
|
|
58
58
|
# Resume a previous session from a git commit hash
|
|
59
|
-
|
|
59
|
+
mlc --resume <commit-hash>
|
|
60
60
|
|
|
61
|
-
# Because `
|
|
62
|
-
echo "
|
|
61
|
+
# Because `mlc` reads from stdin when it isn't a TTY, it composes naturally with shell pipes:
|
|
62
|
+
echo "Here's the solution you proposed: <excerpt>$(mlc -p "write code for a chrome extension to play youtube x5 speed")</excerpt> Now argue against it. What are the edge cases this doesn't handle? What assumptions did you make that might not hold in a production system? What would you change if you knew this code would be read by a senior engineer in a security audit?" | mlc
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
### `
|
|
65
|
+
### `mlc-run`: harness only
|
|
66
66
|
|
|
67
67
|
Runs the agent harness against an already-running server or a remote provider.
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
70
|
# Connect to a local server at 127.0.0.1:8000 (default)
|
|
71
|
-
|
|
71
|
+
mlc-run
|
|
72
72
|
|
|
73
73
|
# Remote providers
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
mlc-run --api claude
|
|
75
|
+
mlc-run --api gemini
|
|
76
|
+
mlc-run --api deepseek --model deepseek-v4-pro
|
|
77
|
+
mlc-run --api codex
|
|
78
78
|
|
|
79
79
|
# Custom endpoint
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# With skills
|
|
83
|
-
mlxc-run --skill ./my-skills
|
|
80
|
+
echo "explain lsp.py" | mlc-run -a deepseek | cat - PLAN.md | mlc-run --url http://localhost:9000
|
|
84
81
|
```
|
|
85
82
|
|
|
86
83
|
---
|
|
@@ -861,7 +861,7 @@ def generate_step(
|
|
|
861
861
|
y, logprobs = _step(input_tokens=prompt, input_embeddings=input_embeddings)
|
|
862
862
|
mx.async_eval(y, logprobs)
|
|
863
863
|
n = 0
|
|
864
|
-
_tl = int(max_tokens * 0.
|
|
864
|
+
_tl = int(max_tokens * 0.9)
|
|
865
865
|
_td = []
|
|
866
866
|
while True:
|
|
867
867
|
if n != max_tokens:
|
|
@@ -879,7 +879,7 @@ def generate_step(
|
|
|
879
879
|
y, logprobs = (next_y, next_logprobs)
|
|
880
880
|
if n <= _tl:
|
|
881
881
|
_td.append(_yi)
|
|
882
|
-
if is_stuck(_td) or
|
|
882
|
+
if (is_stuck(_td) or n == _tl) and _te not in _td:
|
|
883
883
|
y = mx.array([_te])
|
|
884
884
|
_tl = -1
|
|
885
885
|
n += 1
|
|
@@ -1487,7 +1487,7 @@ def serve(
|
|
|
1487
1487
|
fixed_port: bool = False,
|
|
1488
1488
|
gwt=None,
|
|
1489
1489
|
) -> tuple[HTTPServer, str]:
|
|
1490
|
-
handler = make_handler(model,
|
|
1490
|
+
handler = make_handler(model, cache, system, tools, skips, gwt)
|
|
1491
1491
|
while True:
|
|
1492
1492
|
try:
|
|
1493
1493
|
server = HTTPServer((host, port), handler)
|
|
@@ -1505,13 +1505,26 @@ def serve(
|
|
|
1505
1505
|
|
|
1506
1506
|
|
|
1507
1507
|
def main():
|
|
1508
|
-
parser = argparse.ArgumentParser(
|
|
1509
|
-
|
|
1508
|
+
parser = argparse.ArgumentParser(description="mlx-code MAIN")
|
|
1509
|
+
parser.add_argument(
|
|
1510
|
+
"-p",
|
|
1511
|
+
"--prompt",
|
|
1512
|
+
default=None,
|
|
1513
|
+
help="Initial prompt sent automatically when the REPL starts",
|
|
1514
|
+
)
|
|
1515
|
+
parser.add_argument(
|
|
1516
|
+
"-r",
|
|
1517
|
+
"--resume",
|
|
1518
|
+
default=None,
|
|
1519
|
+
metavar="COMMIT",
|
|
1520
|
+
help="Resume a previous session from the given git commit hash",
|
|
1510
1521
|
)
|
|
1511
1522
|
parser.add_argument(
|
|
1512
1523
|
"-m",
|
|
1513
1524
|
"--model",
|
|
1514
1525
|
default="mlx-community/Qwen3.5-4B-OptiQ-4bit",
|
|
1526
|
+
# default="mlx-community/Qwen3.6-27B-OptiQ-4bit",
|
|
1527
|
+
# default="mlx-community/Qwen3.6-35B-A3B-OptiQ-4bit",
|
|
1515
1528
|
help="MLX model path or HuggingFace repo ID (default: Qwen3.5-4B-OptiQ-4bit)",
|
|
1516
1529
|
)
|
|
1517
1530
|
parser.add_argument(
|
|
@@ -1567,20 +1580,10 @@ def main():
|
|
|
1567
1580
|
],
|
|
1568
1581
|
help="Regex patterns stripped from model output before it is returned to the client",
|
|
1569
1582
|
)
|
|
1570
|
-
parser.add_argument(
|
|
1571
|
-
"--prompt",
|
|
1572
|
-
default=None,
|
|
1573
|
-
help="Initial prompt sent automatically when the REPL starts",
|
|
1574
|
-
)
|
|
1575
|
-
parser.add_argument(
|
|
1576
|
-
"--resume",
|
|
1577
|
-
default=None,
|
|
1578
|
-
metavar="COMMIT",
|
|
1579
|
-
help="Resume a previous session from the given git commit hash",
|
|
1580
|
-
)
|
|
1581
1583
|
parser.add_argument("--stream", default=None, help="File to stream log into")
|
|
1582
1584
|
args, leash_args = parser.parse_known_args()
|
|
1583
1585
|
logger.debug(f"args={args!r} leash_args={leash_args!r}")
|
|
1586
|
+
cache = os.path.abspath(args.cache)
|
|
1584
1587
|
with tempfile.TemporaryDirectory(dir="/tmp") as _home:
|
|
1585
1588
|
env = os.environ.copy()
|
|
1586
1589
|
home = Path(_home)
|
|
@@ -1597,7 +1600,7 @@ def main():
|
|
|
1597
1600
|
host=args.host,
|
|
1598
1601
|
port=args.port if args.port is not None else 8000,
|
|
1599
1602
|
model=args.model,
|
|
1600
|
-
cache=
|
|
1603
|
+
cache=cache,
|
|
1601
1604
|
system=None if args.leash in ("none", "noapi") else args.system,
|
|
1602
1605
|
tools=args.tools,
|
|
1603
1606
|
skips=args.skips,
|
|
@@ -38,9 +38,9 @@ class CommentKBTool(Tool):
|
|
|
38
38
|
dt = self.ctx.get("dt")
|
|
39
39
|
if dt is None:
|
|
40
40
|
return tout("No knowledge base is available", True)
|
|
41
|
-
if params.
|
|
42
|
-
return tout(f"Comment not found: {params.
|
|
43
|
-
return tout(json.dumps({"id": dt.comment(params.content, to=params.
|
|
41
|
+
if params.parent not in dt.kb:
|
|
42
|
+
return tout(f"Comment not found: {params.parent}", True)
|
|
43
|
+
return tout(json.dumps({"id": dt.comment(params.content, to=params.parent)}))
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class SubmitKBParams(BaseModel):
|
|
@@ -70,4 +70,4 @@ def system_prompt(dt):
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
ALL_TOOLS = [ReadKBTool, CommentKBTool, SubmitKBTool]
|
|
73
|
-
ALL_NAMES = [i.name for i in ALL_TOOLS]
|
|
73
|
+
ALL_NAMES = [i.name for i in ALL_TOOLS] + ["Agent"]
|
|
@@ -234,13 +234,13 @@ class Agent:
|
|
|
234
234
|
return msg
|
|
235
235
|
|
|
236
236
|
|
|
237
|
-
_REPL_HELP = "Commands:\n /help — show this message\n /clear — clear conversation history\n /history — print message history\n /tools — list active tools\n /branch — spawn a branched sub-agent and run a one-shot prompt\n /abort — signal abort after next tool call\n
|
|
237
|
+
_REPL_HELP = "Commands:\n /help — show this message\n /clear — clear conversation history\n /history — print message history\n /tools — list active tools\n /branch — spawn a branched sub-agent and run a one-shot prompt\n /abort — signal abort after next tool call\n /export [path] — export session to a timestamped JSON file\n /quit / exit — end the session\n"
|
|
238
238
|
import sys
|
|
239
239
|
import os
|
|
240
240
|
from contextlib import contextmanager
|
|
241
241
|
|
|
242
242
|
|
|
243
|
-
def read_input(prompt: str = "\x1b[32m
|
|
243
|
+
def read_input(prompt: str = "\x1b[32m≫ \x1b[0m") -> str:
|
|
244
244
|
if sys.platform == "win32":
|
|
245
245
|
return _read_input_win(prompt)
|
|
246
246
|
else:
|
|
@@ -416,7 +416,7 @@ def _read_input_win(prompt: str) -> str:
|
|
|
416
416
|
|
|
417
417
|
|
|
418
418
|
async def repl(agent, init_prompt=None) -> None:
|
|
419
|
-
is_tty = sys.stdin.isatty()
|
|
419
|
+
is_tty = sys.stdin.isatty() and sys.stdout.isatty()
|
|
420
420
|
loop = asyncio.get_running_loop()
|
|
421
421
|
_suppress = False
|
|
422
422
|
last_block = ""
|
|
@@ -467,9 +467,7 @@ async def repl(agent, init_prompt=None) -> None:
|
|
|
467
467
|
elif et == "tool_start":
|
|
468
468
|
name_part = "\x1b[1;43;30;1m" + p["name"] + "\x1b[0m"
|
|
469
469
|
args_part = (
|
|
470
|
-
" \x1b[
|
|
471
|
-
if p["args"]
|
|
472
|
-
else ""
|
|
470
|
+
" \x1b[33m" + json.dumps(p["args"]) + "\x1b[0m" if p["args"] else ""
|
|
473
471
|
)
|
|
474
472
|
emit(name_part + args_part + "\n", "tool")
|
|
475
473
|
elif et == "tool_end":
|
|
@@ -504,6 +502,8 @@ async def repl(agent, init_prompt=None) -> None:
|
|
|
504
502
|
else:
|
|
505
503
|
user_input = sys.stdin.read()
|
|
506
504
|
if not user_input:
|
|
505
|
+
if not is_tty:
|
|
506
|
+
break
|
|
507
507
|
continue
|
|
508
508
|
last_block = ""
|
|
509
509
|
logger.debug(user_input)
|
|
@@ -552,11 +552,29 @@ async def repl(agent, init_prompt=None) -> None:
|
|
|
552
552
|
elif cmd == "/abort":
|
|
553
553
|
agent.abort()
|
|
554
554
|
print("[abort signalled]")
|
|
555
|
+
elif cmd == "/export":
|
|
556
|
+
import datetime
|
|
557
|
+
|
|
558
|
+
ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
559
|
+
default_path = os.path.join(os.getcwd(), f"session_{ts}.json")
|
|
560
|
+
out_path = arg.strip() or default_path
|
|
561
|
+
payload = {
|
|
562
|
+
"version": 1,
|
|
563
|
+
"exported_at": ts,
|
|
564
|
+
"system": agent.system,
|
|
565
|
+
"messages": agent.messages,
|
|
566
|
+
}
|
|
567
|
+
try:
|
|
568
|
+
with open(out_path, "w", encoding="utf-8") as f:
|
|
569
|
+
json.dump(payload, f, indent=2, ensure_ascii=False)
|
|
570
|
+
print(f"[exported {len(agent.messages)} messages → {out_path}]")
|
|
571
|
+
except OSError as e:
|
|
572
|
+
print(f"[export failed: {e}]")
|
|
555
573
|
else:
|
|
556
574
|
print(f"Unknown command: {cmd} (try /help)")
|
|
557
575
|
continue
|
|
558
576
|
if is_tty:
|
|
559
|
-
if user_input.lower() in {"exit", "quit"}:
|
|
577
|
+
if user_input.lower() in {"exit", "/quit"}:
|
|
560
578
|
print("Bye!")
|
|
561
579
|
break
|
|
562
580
|
print("\x1b[34mπ\x1b[0m ", end="", flush=True)
|
|
@@ -682,6 +700,19 @@ def main():
|
|
|
682
700
|
|
|
683
701
|
setup_logger(log_file=".log.json")
|
|
684
702
|
parser = argparse.ArgumentParser(description="mlx-code REPL")
|
|
703
|
+
parser.add_argument(
|
|
704
|
+
"-p",
|
|
705
|
+
"--prompt",
|
|
706
|
+
default=None,
|
|
707
|
+
help="Initial prompt sent automatically when the REPL starts",
|
|
708
|
+
)
|
|
709
|
+
parser.add_argument(
|
|
710
|
+
"-r",
|
|
711
|
+
"--resume",
|
|
712
|
+
default=None,
|
|
713
|
+
metavar="COMMIT",
|
|
714
|
+
help="Resume a previous session from the given git commit hash",
|
|
715
|
+
)
|
|
685
716
|
parser.add_argument(
|
|
686
717
|
"-a",
|
|
687
718
|
"--api",
|
|
@@ -720,17 +751,6 @@ def main():
|
|
|
720
751
|
default=None,
|
|
721
752
|
help="API key; falls back to the relevant *_API_KEY env var",
|
|
722
753
|
)
|
|
723
|
-
parser.add_argument(
|
|
724
|
-
"--prompt",
|
|
725
|
-
default=None,
|
|
726
|
-
help="Initial prompt sent automatically when the REPL starts",
|
|
727
|
-
)
|
|
728
|
-
parser.add_argument(
|
|
729
|
-
"--resume",
|
|
730
|
-
default=None,
|
|
731
|
-
metavar="COMMIT",
|
|
732
|
-
help="Resume a previous session from the given git commit hash",
|
|
733
|
-
)
|
|
734
754
|
parser.add_argument("--stream", default=None, help="File to stream log into")
|
|
735
755
|
args = parser.parse_args()
|
|
736
756
|
logger.debug(args)
|
|
@@ -743,7 +763,7 @@ def main():
|
|
|
743
763
|
elif args.api == "gemini":
|
|
744
764
|
api_key = os.environ.get("GEMINI_API_KEY") if api_key is None else api_key
|
|
745
765
|
url = "https://generativelanguage.googleapis.com" if api_key else url
|
|
746
|
-
model = "gemini-3.1-flash-lite
|
|
766
|
+
model = "gemini-3.1-flash-lite" if model is None else model
|
|
747
767
|
tool_names = [] if tool_names is None else tool_names
|
|
748
768
|
run_repl(
|
|
749
769
|
api=args.api,
|
|
@@ -45,7 +45,10 @@ class StreamLogger:
|
|
|
45
45
|
self.fp.write(self._line_prefix())
|
|
46
46
|
self._at_line_start = False
|
|
47
47
|
self.fp.write(part)
|
|
48
|
-
|
|
48
|
+
try:
|
|
49
|
+
self.fp.flush()
|
|
50
|
+
except:
|
|
51
|
+
pass
|
|
49
52
|
|
|
50
53
|
@classmethod
|
|
51
54
|
def attach_to_child(cls, child_agent, parent_ctx, tool_name="sub"):
|
|
@@ -11,7 +11,7 @@ import logging
|
|
|
11
11
|
import random
|
|
12
12
|
from abc import ABC, abstractmethod
|
|
13
13
|
from typing import Any, Literal
|
|
14
|
-
from pydantic import BaseModel, Field, ValidationError
|
|
14
|
+
from pydantic import BaseModel, Field, ValidationError, field_validator
|
|
15
15
|
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
17
17
|
|
|
@@ -438,11 +438,19 @@ class AgentParams(BaseModel):
|
|
|
438
438
|
system: str | None = Field(
|
|
439
439
|
default=None, description="System prompt override. Defaults to parent."
|
|
440
440
|
)
|
|
441
|
-
tools: list[str]
|
|
442
|
-
|
|
443
|
-
description="Restrict tools for the sub-agent, e.g. ['Read', 'Bash']. Defaults to all parent tools.",
|
|
441
|
+
tools: list[str] = Field(
|
|
442
|
+
description="Tools available to the sub-agent. Must be an explicit subset of the parent's tools."
|
|
444
443
|
)
|
|
445
444
|
|
|
445
|
+
@field_validator("tools", mode="before")
|
|
446
|
+
@classmethod
|
|
447
|
+
def coerce_tools(cls, v):
|
|
448
|
+
if isinstance(v, str):
|
|
449
|
+
v = json.loads(v)
|
|
450
|
+
if not isinstance(v, list):
|
|
451
|
+
raise ValueError("tools must be a list of strings")
|
|
452
|
+
return v
|
|
453
|
+
|
|
446
454
|
|
|
447
455
|
class AgentTool(Tool):
|
|
448
456
|
name = "Agent"
|
|
@@ -451,19 +459,12 @@ class AgentTool(Tool):
|
|
|
451
459
|
|
|
452
460
|
async def execute(self, params: AgentParams, signal=None) -> dict:
|
|
453
461
|
parent = self.ctx["agent"]
|
|
454
|
-
tool_names: list[str] | None = None
|
|
455
|
-
if params.tools is not None:
|
|
456
|
-
raw = params.tools
|
|
457
|
-
if isinstance(raw, str):
|
|
458
|
-
raw = json.loads(raw)
|
|
459
|
-
tool_names = [t for t in raw if isinstance(t, str)]
|
|
460
462
|
overrides = {}
|
|
461
463
|
if params.api is not None:
|
|
462
464
|
overrides["api"] = params.api
|
|
463
465
|
if params.system is not None:
|
|
464
466
|
overrides["system"] = params.system
|
|
465
|
-
|
|
466
|
-
overrides["tool_names"] = tool_names
|
|
467
|
+
overrides["tool_names"] = params.tools
|
|
467
468
|
child = parent.spawn(**overrides)
|
|
468
469
|
if "_stream_log_fp" in parent.ctx:
|
|
469
470
|
from .stream_log import StreamLogger
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mlx-code
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.11
|
|
4
4
|
Summary: Coding Agent for Mac
|
|
5
|
-
Home-page: https://github.
|
|
5
|
+
Home-page: https://josefalbers.github.io/mlx-code/
|
|
6
6
|
Author: J Joe
|
|
7
7
|
Author-email: albersj66@gmail.com
|
|
8
8
|
License: Apache-2.0
|
|
9
|
+
Project-URL: Source, https://github.com/JosefAlbers/mlx-code
|
|
10
|
+
Project-URL: Issues, https://github.com/JosefAlbers/mlx-code/issues
|
|
11
|
+
Project-URL: Documentation, https://josefalbers.github.io/mlx-code/
|
|
9
12
|
Requires-Python: >=3.12.8
|
|
10
13
|
Description-Content-Type: text/markdown
|
|
11
14
|
License-File: LICENSE
|
|
@@ -22,16 +25,17 @@ Dynamic: description-content-type
|
|
|
22
25
|
Dynamic: home-page
|
|
23
26
|
Dynamic: license
|
|
24
27
|
Dynamic: license-file
|
|
28
|
+
Dynamic: project-url
|
|
25
29
|
Dynamic: provides-extra
|
|
26
30
|
Dynamic: requires-dist
|
|
27
31
|
Dynamic: requires-python
|
|
28
32
|
Dynamic: summary
|
|
29
33
|
|
|
30
|
-
# mlx-code
|
|
34
|
+
# [mlx-code](https://josefalbers.github.io/mlx-code)
|
|
31
35
|
|
|
32
36
|
A lightweight coding agent built on Apple's MLX framework.
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
https://github.com/user-attachments/assets/0569d101-8d0a-4e67-9e82-fce84a5ef3f0
|
|
35
39
|
|
|
36
40
|
---
|
|
37
41
|
|
|
@@ -48,68 +52,65 @@ A lightweight coding agent built on Apple's MLX framework.
|
|
|
48
52
|
## Quick Start
|
|
49
53
|
|
|
50
54
|
```bash
|
|
51
|
-
pip install mlx-code
|
|
52
|
-
|
|
55
|
+
pip install mlx-code
|
|
56
|
+
mlc
|
|
53
57
|
```
|
|
54
58
|
|
|
55
59
|
---
|
|
56
60
|
|
|
57
61
|
## Command Line
|
|
58
62
|
|
|
59
|
-
### `
|
|
63
|
+
### `mlc`: local server + harness
|
|
60
64
|
|
|
61
65
|
Starts the MLX inference server and launches a harness against it.
|
|
62
66
|
|
|
63
67
|
```bash
|
|
64
68
|
# Default: local MLX server + built-in REPL harness
|
|
65
|
-
|
|
69
|
+
mlc
|
|
66
70
|
|
|
67
71
|
# Use a different harness (routes traffic through the local server)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
mlc --leash claude
|
|
73
|
+
mlc --leash gemini
|
|
74
|
+
mlc --leash codex
|
|
71
75
|
|
|
72
76
|
# Server only, no harness
|
|
73
|
-
|
|
77
|
+
mlc --leash none
|
|
74
78
|
|
|
75
79
|
# Specify a model
|
|
76
|
-
|
|
80
|
+
mlc --model mlx-community/Qwen3.5-4B-OptiQ-4bit
|
|
77
81
|
|
|
78
82
|
# Restrict the tools available to the agent
|
|
79
|
-
|
|
83
|
+
mlc --tools Read Write Bash
|
|
80
84
|
|
|
81
85
|
# Custom system prompt
|
|
82
|
-
|
|
86
|
+
mlc --system "You are a helpful assistant."
|
|
83
87
|
|
|
84
88
|
# Load skills from a directory (scans recursively for SKILL.md files)
|
|
85
|
-
|
|
89
|
+
mlc --skill ./my-skills
|
|
86
90
|
|
|
87
91
|
# Resume a previous session from a git commit hash
|
|
88
|
-
|
|
92
|
+
mlc --resume <commit-hash>
|
|
89
93
|
|
|
90
|
-
# Because `
|
|
91
|
-
echo "
|
|
94
|
+
# Because `mlc` reads from stdin when it isn't a TTY, it composes naturally with shell pipes:
|
|
95
|
+
echo "Here's the solution you proposed: <excerpt>$(mlc -p "write code for a chrome extension to play youtube x5 speed")</excerpt> Now argue against it. What are the edge cases this doesn't handle? What assumptions did you make that might not hold in a production system? What would you change if you knew this code would be read by a senior engineer in a security audit?" | mlc
|
|
92
96
|
```
|
|
93
97
|
|
|
94
|
-
### `
|
|
98
|
+
### `mlc-run`: harness only
|
|
95
99
|
|
|
96
100
|
Runs the agent harness against an already-running server or a remote provider.
|
|
97
101
|
|
|
98
102
|
```bash
|
|
99
103
|
# Connect to a local server at 127.0.0.1:8000 (default)
|
|
100
|
-
|
|
104
|
+
mlc-run
|
|
101
105
|
|
|
102
106
|
# Remote providers
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
mlc-run --api claude
|
|
108
|
+
mlc-run --api gemini
|
|
109
|
+
mlc-run --api deepseek --model deepseek-v4-pro
|
|
110
|
+
mlc-run --api codex
|
|
107
111
|
|
|
108
112
|
# Custom endpoint
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# With skills
|
|
112
|
-
mlxc-run --skill ./my-skills
|
|
113
|
+
echo "explain lsp.py" | mlc-run -a deepseek | cat - PLAN.md | mlc-run --url http://localhost:9000
|
|
113
114
|
```
|
|
114
115
|
|
|
115
116
|
---
|
|
@@ -2,11 +2,16 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="mlx-code",
|
|
5
|
-
url=
|
|
5
|
+
url="https://josefalbers.github.io/mlx-code/",
|
|
6
|
+
project_urls={
|
|
7
|
+
"Source": "https://github.com/JosefAlbers/mlx-code",
|
|
8
|
+
"Issues": "https://github.com/JosefAlbers/mlx-code/issues",
|
|
9
|
+
"Documentation": "https://josefalbers.github.io/mlx-code/",
|
|
10
|
+
},
|
|
6
11
|
author_email="albersj66@gmail.com",
|
|
7
12
|
author="J Joe",
|
|
8
13
|
license="Apache-2.0",
|
|
9
|
-
version="0.0.
|
|
14
|
+
version="0.0.11",
|
|
10
15
|
readme="README.md",
|
|
11
16
|
description="Coding Agent for Mac",
|
|
12
17
|
long_description=open("README.md").read(),
|
|
@@ -14,14 +19,11 @@ setup(
|
|
|
14
19
|
python_requires=">=3.12.8",
|
|
15
20
|
install_requires=[
|
|
16
21
|
"mlx-lm>=0.31.3; platform_system=='Darwin'",
|
|
17
|
-
"httpx",
|
|
22
|
+
"httpx",
|
|
23
|
+
"pydantic",
|
|
24
|
+
"GitPython",
|
|
18
25
|
],
|
|
19
|
-
extras_require={
|
|
20
|
-
"all": [
|
|
21
|
-
"python-lsp-server[all]",
|
|
22
|
-
# "tree-sitter>=0.23.0", "tree-sitter-python", # "tree-sitter-javascript", ‥
|
|
23
|
-
],
|
|
24
|
-
},
|
|
26
|
+
extras_require={"all": ["python-lsp-server[all]"]},
|
|
25
27
|
packages=find_packages(),
|
|
26
28
|
entry_points={
|
|
27
29
|
"console_scripts": [
|
|
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
|