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.
Files changed (26) hide show
  1. {mlx_code-0.0.9 → mlx_code-0.0.11}/PKG-INFO +30 -29
  2. {mlx_code-0.0.9 → mlx_code-0.0.11}/README.md +24 -27
  3. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/main.py +20 -17
  4. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/mcb.py +1 -1
  5. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/mcb_tool.py +4 -4
  6. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/repl.py +39 -19
  7. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/stream_log.py +4 -1
  8. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/tools.py +13 -12
  9. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code.egg-info/PKG-INFO +30 -29
  10. {mlx_code-0.0.9 → mlx_code-0.0.11}/setup.py +11 -9
  11. {mlx_code-0.0.9 → mlx_code-0.0.11}/LICENSE +0 -0
  12. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/__init__.py +0 -0
  13. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/apis.py +0 -0
  14. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/gits.py +0 -0
  15. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/lsp_tool.py +0 -0
  16. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/util.py +0 -0
  17. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/view_git.py +0 -0
  18. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code/view_log.py +0 -0
  19. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code.egg-info/SOURCES.txt +0 -0
  20. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code.egg-info/dependency_links.txt +0 -0
  21. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code.egg-info/entry_points.txt +0 -0
  22. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code.egg-info/requires.txt +0 -0
  23. {mlx_code-0.0.9 → mlx_code-0.0.11}/mlx_code.egg-info/top_level.txt +0 -0
  24. {mlx_code-0.0.9 → mlx_code-0.0.11}/setup.cfg +0 -0
  25. {mlx_code-0.0.9 → mlx_code-0.0.11}/tests/__init__.py +0 -0
  26. {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.9
3
+ Version: 0.0.11
4
4
  Summary: Coding Agent for Mac
5
- Home-page: https://github.com/JosefAlbers/mlx-code
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
- ![demo](https://raw.githubusercontent.com/JosefAlbers/mlx-code/main/assets/mlx-code.gif)
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[all]
52
- mlxc
55
+ pip install mlx-code
56
+ mlc
53
57
  ```
54
58
 
55
59
  ---
56
60
 
57
61
  ## Command Line
58
62
 
59
- ### `mlxc`: local server + harness
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
- mlxc
69
+ mlc
66
70
 
67
71
  # Use a different harness (routes traffic through the local server)
68
- mlxc --leash claude
69
- mlxc --leash gemini
70
- mlxc --leash codex
72
+ mlc --leash claude
73
+ mlc --leash gemini
74
+ mlc --leash codex
71
75
 
72
76
  # Server only, no harness
73
- mlxc --leash none
77
+ mlc --leash none
74
78
 
75
79
  # Specify a model
76
- mlxc --model mlx-community/Qwen3.5-4B-OptiQ-4bit
80
+ mlc --model mlx-community/Qwen3.5-4B-OptiQ-4bit
77
81
 
78
82
  # Restrict the tools available to the agent
79
- mlxc --tools Read Write Bash
83
+ mlc --tools Read Write Bash
80
84
 
81
85
  # Custom system prompt
82
- mlxc --system "You are a helpful assistant."
86
+ mlc --system "You are a helpful assistant."
83
87
 
84
88
  # Load skills from a directory (scans recursively for SKILL.md files)
85
- mlxc --skill ./my-skills
89
+ mlc --skill ./my-skills
86
90
 
87
91
  # Resume a previous session from a git commit hash
88
- mlxc --resume <commit-hash>
92
+ mlc --resume <commit-hash>
89
93
 
90
- # Because `mlxc` reads from stdin when it isn't a TTY, it composes naturally with shell pipes:
91
- echo "explain lsp.py" | mlxc -d | cat - PLAN.md | mlxc
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
- ### `mlxc-run`: harness only
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
- mlxc-run
104
+ mlc-run
101
105
 
102
106
  # Remote providers
103
- mlxc-run --api claude
104
- mlxc-run --api gemini
105
- mlxc-run --api deepseek --model deepseek-v4-pro
106
- mlxc-run --api codex
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
- mlxc-run --url http://localhost:9000
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
- ![demo](https://raw.githubusercontent.com/JosefAlbers/mlx-code/main/assets/mlx-code.gif)
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[all]
23
- mlxc
22
+ pip install mlx-code
23
+ mlc
24
24
  ```
25
25
 
26
26
  ---
27
27
 
28
28
  ## Command Line
29
29
 
30
- ### `mlxc`: local server + harness
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
- mlxc
36
+ mlc
37
37
 
38
38
  # Use a different harness (routes traffic through the local server)
39
- mlxc --leash claude
40
- mlxc --leash gemini
41
- mlxc --leash codex
39
+ mlc --leash claude
40
+ mlc --leash gemini
41
+ mlc --leash codex
42
42
 
43
43
  # Server only, no harness
44
- mlxc --leash none
44
+ mlc --leash none
45
45
 
46
46
  # Specify a model
47
- mlxc --model mlx-community/Qwen3.5-4B-OptiQ-4bit
47
+ mlc --model mlx-community/Qwen3.5-4B-OptiQ-4bit
48
48
 
49
49
  # Restrict the tools available to the agent
50
- mlxc --tools Read Write Bash
50
+ mlc --tools Read Write Bash
51
51
 
52
52
  # Custom system prompt
53
- mlxc --system "You are a helpful assistant."
53
+ mlc --system "You are a helpful assistant."
54
54
 
55
55
  # Load skills from a directory (scans recursively for SKILL.md files)
56
- mlxc --skill ./my-skills
56
+ mlc --skill ./my-skills
57
57
 
58
58
  # Resume a previous session from a git commit hash
59
- mlxc --resume <commit-hash>
59
+ mlc --resume <commit-hash>
60
60
 
61
- # Because `mlxc` reads from stdin when it isn't a TTY, it composes naturally with shell pipes:
62
- echo "explain lsp.py" | mlxc -d | cat - PLAN.md | mlxc
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
- ### `mlxc-run`: harness only
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
- mlxc-run
71
+ mlc-run
72
72
 
73
73
  # Remote providers
74
- mlxc-run --api claude
75
- mlxc-run --api gemini
76
- mlxc-run --api deepseek --model deepseek-v4-pro
77
- mlxc-run --api codex
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
- mlxc-run --url http://localhost:9000
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.2)
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 (n == _tl and _te not in _td):
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, os.path.abspath(cache), system, tools, skips, gwt)
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
- description="Serve a local MLX model with an OpenAI-compatible API."
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=args.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,
@@ -183,7 +183,7 @@ class KB:
183
183
 
184
184
  class DocThread:
185
185
  def __init__(self, kb: KB | None = None):
186
- self.kb = KB("mcb.json") if kb is None else kb
186
+ self.kb = KB(db_path="mcb.json") if kb is None else kb
187
187
  self.submissions = []
188
188
 
189
189
  def submit(
@@ -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.to not in dt.kb:
42
- return tout(f"Comment not found: {params.to}", True)
43
- return tout(json.dumps({"id": dt.comment(params.content, to=params.to)}))
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 exit / quit — end the session\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≫\x1b[0m ") -> str:
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[2;33m" + json.dumps(p["args"]) + "\x1b[0m"
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-preview" if model is None else model
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
- self.fp.flush()
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] | str | None = Field(
442
- default=None,
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
- if tool_names is not None:
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.9
3
+ Version: 0.0.11
4
4
  Summary: Coding Agent for Mac
5
- Home-page: https://github.com/JosefAlbers/mlx-code
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
- ![demo](https://raw.githubusercontent.com/JosefAlbers/mlx-code/main/assets/mlx-code.gif)
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[all]
52
- mlxc
55
+ pip install mlx-code
56
+ mlc
53
57
  ```
54
58
 
55
59
  ---
56
60
 
57
61
  ## Command Line
58
62
 
59
- ### `mlxc`: local server + harness
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
- mlxc
69
+ mlc
66
70
 
67
71
  # Use a different harness (routes traffic through the local server)
68
- mlxc --leash claude
69
- mlxc --leash gemini
70
- mlxc --leash codex
72
+ mlc --leash claude
73
+ mlc --leash gemini
74
+ mlc --leash codex
71
75
 
72
76
  # Server only, no harness
73
- mlxc --leash none
77
+ mlc --leash none
74
78
 
75
79
  # Specify a model
76
- mlxc --model mlx-community/Qwen3.5-4B-OptiQ-4bit
80
+ mlc --model mlx-community/Qwen3.5-4B-OptiQ-4bit
77
81
 
78
82
  # Restrict the tools available to the agent
79
- mlxc --tools Read Write Bash
83
+ mlc --tools Read Write Bash
80
84
 
81
85
  # Custom system prompt
82
- mlxc --system "You are a helpful assistant."
86
+ mlc --system "You are a helpful assistant."
83
87
 
84
88
  # Load skills from a directory (scans recursively for SKILL.md files)
85
- mlxc --skill ./my-skills
89
+ mlc --skill ./my-skills
86
90
 
87
91
  # Resume a previous session from a git commit hash
88
- mlxc --resume <commit-hash>
92
+ mlc --resume <commit-hash>
89
93
 
90
- # Because `mlxc` reads from stdin when it isn't a TTY, it composes naturally with shell pipes:
91
- echo "explain lsp.py" | mlxc -d | cat - PLAN.md | mlxc
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
- ### `mlxc-run`: harness only
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
- mlxc-run
104
+ mlc-run
101
105
 
102
106
  # Remote providers
103
- mlxc-run --api claude
104
- mlxc-run --api gemini
105
- mlxc-run --api deepseek --model deepseek-v4-pro
106
- mlxc-run --api codex
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
- mlxc-run --url http://localhost:9000
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='https://github.com/JosefAlbers/mlx-code',
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.9",
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", "pydantic", "GitPython",
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