mlx-code 0.0.1a1__tar.gz → 0.0.1a2__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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlx-code
3
- Version: 0.0.1a1
4
- Summary: Local Claude Code-style coding agent via mlx-lm
3
+ Version: 0.0.1a2
4
+ Summary: Local Claude Code for Mac
5
5
  Home-page: https://github.com/JosefAlbers/mlx-code
6
6
  Author: J Joe
7
7
  Author-email: albersj66@gmail.com
@@ -2,6 +2,9 @@ import argparse
2
2
  import json
3
3
  import os
4
4
  import re
5
+ import subprocess
6
+ import sys
7
+ import threading
5
8
  import time
6
9
  import uuid
7
10
  from http.server import BaseHTTPRequestHandler, HTTPServer
@@ -14,18 +17,27 @@ except ImportError:
14
17
 
15
18
  DEFAULT_MODEL = "mlx-community/Qwen3.5-4B-OptiQ-4bit"
16
19
  DEFAULT_SKILL_DIRS = ["./skills", os.path.expanduser("~/.claude/skills")]
17
- LOG_PROMPT_TAIL = 1000
20
+ LOG_FILE = "mlx_trace.log"
18
21
 
19
22
  model = None
20
23
  tokenizer = None
21
24
  model_id = None
22
25
  skills = {}
23
-
24
-
25
- def log(tag, text):
26
- bar = "─" * 60
27
- print(f"\n{bar}\n{tag}\n{bar}\n{text}\n", flush=True)
28
-
26
+ _call_counter = 0
27
+
28
+ def trace(prompt: str, raw: str, elapsed: float):
29
+ global _call_counter
30
+ _call_counter += 1
31
+ sep = "=" * 80
32
+ entry = (
33
+ f"\n{sep}\n"
34
+ f"CALL {_call_counter} {time.strftime('%Y-%m-%d %H:%M:%S')} ({elapsed:.1f}s)\n"
35
+ f"{sep}\n"
36
+ f"--- PROMPT ---\n{prompt}\n"
37
+ f"--- OUTPUT ---\n{raw}\n"
38
+ )
39
+ with open(LOG_FILE, "a") as f:
40
+ f.write(entry)
29
41
 
30
42
  def parse_frontmatter(text):
31
43
  m = re.match(r"^---\n(.*?)\n---\n", text, re.DOTALL)
@@ -71,7 +83,6 @@ def skill_body(name):
71
83
  except Exception as e:
72
84
  return f"Error reading skill: {e}"
73
85
 
74
-
75
86
  def load_model(path):
76
87
  global model, tokenizer, model_id
77
88
  from mlx_lm import load
@@ -82,37 +93,21 @@ def load_model(path):
82
93
 
83
94
  def generate(prompt, max_tokens, temp, top_p):
84
95
  from mlx_lm import generate as mlx_gen
85
- return mlx_gen(
96
+ t0 = time.time()
97
+ raw = mlx_gen(
86
98
  model, tokenizer,
87
99
  prompt=prompt,
88
100
  max_tokens=max_tokens,
89
101
  verbose=False,
90
102
  )
91
-
92
-
93
- READ_SKILL_TOOL = {
94
- "type": "function",
95
- "function": {
96
- "name": "read_skill",
97
- "description": (
98
- "Read the full SKILL.md for a skill before using it. "
99
- "Call this whenever a task matches a skill description."
100
- ),
101
- "parameters": {
102
- "type": "object",
103
- "properties": {
104
- "name": {"type": "string", "description": "Skill name from available_skills"}
105
- },
106
- "required": ["name"],
107
- },
108
- },
109
- }
103
+ trace(prompt, raw, time.time() - t0)
104
+ return raw
110
105
 
111
106
  def skills_system_addon():
112
107
  if not skills:
113
108
  return ""
114
109
  entries = "\n".join(
115
- f"<skill><name>{n}</name><description>{s['description']}</description></skill>"
110
+ f"<skill><n>{n}</n><description>{s['description']}</description></skill>"
116
111
  for n, s in skills.items()
117
112
  )
118
113
  return (
@@ -189,12 +184,10 @@ def build_messages(body, extra=None):
189
184
  msgs.extend(extra)
190
185
  return msgs
191
186
 
192
-
193
187
  def build_prompt(body, extra=None):
194
188
  msgs = build_messages(body, extra=extra)
195
189
  return tokenizer.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
196
190
 
197
-
198
191
  def parse_output(raw):
199
192
  blocks = []
200
193
  remaining = raw
@@ -212,7 +205,7 @@ def parse_output(raw):
212
205
  tool_blocks = []
213
206
  leftover = remaining
214
207
  for m in re.finditer(r"<tool_call>(.*?)</tool_call>", remaining, re.DOTALL):
215
- inner = m.group(1).strip()
208
+ inner = m.group(1).strip()
216
209
  parsed = None
217
210
 
218
211
  try:
@@ -275,27 +268,21 @@ def parse_output(raw):
275
268
  blocks.append({"type": "text", "text": remaining})
276
269
  return blocks or [{"type": "text", "text": raw}], "end_turn"
277
270
 
278
-
279
271
  def resolve_read_skill(blocks, body, max_tokens, temp, top_p):
280
272
  extra = []
281
273
  for _ in range(5):
282
274
  skill_calls = [b for b in blocks if b.get("type")=="tool_use" and b["name"]=="read_skill"]
283
275
  if not skill_calls:
284
276
  break
285
- log("↻ SKILL TOOL", "\n".join(f" read_skill({c['input']})" for c in skill_calls))
286
277
  for c in skill_calls:
287
278
  name = c["input"].get("name","")
288
279
  content = skill_body(name)
289
280
  args = f"<parameter=name>\n{name}\n</parameter>"
290
281
  extra.append({"role": "assistant", "content": f"<tool_call>\n<function=read_skill>\n{args}</function>\n</tool_call>"})
291
- log(f"↻ SKILL BODY [{name}]", content[:500] + ("…" if len(content)>500 else ""))
292
282
  extra.append({"role": "user", "content": f"<tool_response>\n{content}\n</tool_response>"})
293
283
  prompt = build_prompt(body, extra=extra)
294
- log("→ MLX (re-prompt tail)", prompt[-LOG_PROMPT_TAIL:])
295
- raw = generate(prompt, max_tokens, temp, top_p)
296
- log("← MLX RAW", raw)
284
+ raw = generate(prompt, max_tokens, temp, top_p)
297
285
  blocks, stop_reason = parse_output(raw)
298
- log("← PARSED", json.dumps(blocks, indent=2))
299
286
  stop_reason = "tool_use" if any(b.get("type")=="tool_use" for b in blocks) else "end_turn"
300
287
  return blocks, stop_reason
301
288
 
@@ -362,11 +349,10 @@ def blocks_to_sse(blocks, msg_id, stop_reason, in_tokens, out_tokens):
362
349
 
363
350
  return bytes(out)
364
351
 
365
-
366
352
  class Handler(BaseHTTPRequestHandler):
367
353
 
368
354
  def log_message(self, fmt, *args):
369
- print(f" HTTP {fmt % args}", flush=True)
355
+ pass
370
356
 
371
357
  def send_json(self, code, obj):
372
358
  body = json.dumps(obj).encode()
@@ -404,44 +390,17 @@ class Handler(BaseHTTPRequestHandler):
404
390
  return
405
391
 
406
392
  body = self.read_json()
407
- body["model"] = model_id
393
+ body["model"] = model_id
408
394
 
409
395
  max_tokens = body.get("max_tokens", 8192)
410
396
  temp = body.get("temperature", 0.7)
411
397
  top_p = body.get("top_p", 0.9)
412
398
  msg_id = f"msg_{uuid.uuid4().hex}"
413
399
 
414
- tools_requested = [t["name"] for t in body.get("tools", [])]
415
- last_msg = body.get("messages", [{}])[-1]
416
- last_content = last_msg.get("content", "")
417
- if isinstance(last_content, list):
418
- last_content = " | ".join(
419
- b.get("text", b.get("type","?"))[:80]
420
- for b in last_content
421
- )
422
- tool_schemas = body.get("tools", [])
423
- if len(body.get("messages", [])) <= 1:
424
- tools_summary = json.dumps(tool_schemas, indent=2)
425
- else:
426
- tools_summary = str(tools_requested)
427
-
428
- log("→ CC REQUEST", (
429
- f"tools: {tools_summary}\n"
430
- f"msgs: {len(body.get('messages',[]))}\n"
431
- f"last: {str(last_content)[:200]}"
432
- ))
433
-
434
400
  prompt = build_prompt(body)
435
- log("→ MLX PROMPT (tail)", prompt[-LOG_PROMPT_TAIL:])
436
-
437
- t0 = time.time()
438
- raw = generate(prompt, max_tokens, temp, top_p)
439
- dt = time.time() - t0
440
-
441
- log(f"← MLX RAW ({dt:.1f}s)", raw)
401
+ raw = generate(prompt, max_tokens, temp, top_p)
442
402
 
443
403
  blocks, stop_reason = parse_output(raw)
444
- log("← PARSED", json.dumps(blocks, indent=2))
445
404
 
446
405
  if any(b.get("type")=="tool_use" and b["name"]=="read_skill" for b in blocks):
447
406
  blocks, stop_reason = resolve_read_skill(blocks, body, max_tokens, temp, top_p)
@@ -451,13 +410,6 @@ class Handler(BaseHTTPRequestHandler):
451
410
 
452
411
  sse_bytes = blocks_to_sse(blocks, msg_id, stop_reason, in_tokens, out_tokens)
453
412
 
454
- log("→ CC RESPONSE", (
455
- f"stop_reason: {stop_reason}\n"
456
- f"blocks: {[b['type'] for b in blocks]}\n"
457
- f"tokens: {in_tokens} in / {out_tokens} out\n"
458
- f"sse_bytes: {len(sse_bytes)}"
459
- ))
460
-
461
413
  self.send_response(200)
462
414
  self.send_header("Content-Type", "text/event-stream")
463
415
  self.send_header("Cache-Control", "no-cache")
@@ -475,32 +427,31 @@ def main():
475
427
  parser.add_argument("--port", type=int, default=8000)
476
428
  parser.add_argument("--host", default="127.0.0.1")
477
429
  parser.add_argument("--skills", action="append", metavar="DIR")
478
- args = parser.parse_args()
430
+ args, claude_args = parser.parse_known_args()
479
431
 
480
432
  skill_dirs = args.skills or DEFAULT_SKILL_DIRS
481
433
  print("Scanning skills …", flush=True)
482
434
  skills = scan_skills(skill_dirs)
483
- print(f"Found {len(skills)} skill(s).\n", flush=True)
484
435
 
485
436
  load_model(args.model)
486
437
 
438
+ server = HTTPServer((args.host, args.port), Handler)
439
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
440
+ thread.start()
441
+
487
442
  print(f"🍎 mlx-claude-server http://{args.host}:{args.port}")
488
- print(f" model : {args.model}")
489
- print(f" skills : {len(skills)}")
490
- print()
491
- print(" Other terminal:")
492
- print(f" export ANTHROPIC_BASE_URL=http://{args.host}:{args.port}")
493
- print( " export ANTHROPIC_AUTH_TOKEN=local")
494
- print(f" export ANTHROPIC_MODEL={args.model}")
495
- print(f" export ANTHROPIC_SMALL_FAST_MODEL={args.model}")
496
- print( " claude")
443
+ print(f" model : {args.model}")
444
+ print(f" trace : {LOG_FILE}")
497
445
  print()
498
446
 
499
- server = HTTPServer((args.host, args.port), Handler)
500
- try:
501
- server.serve_forever()
502
- except KeyboardInterrupt:
503
- print("\nShutting down.")
447
+ env = os.environ.copy()
448
+ env["ANTHROPIC_BASE_URL"] = f"http://{args.host}:{args.port}"
449
+ env["ANTHROPIC_AUTH_TOKEN"] = "local"
450
+ env["ANTHROPIC_MODEL"] = args.model
451
+ env["ANTHROPIC_SMALL_FAST_MODEL"] = args.model
452
+
453
+ result = subprocess.run(["claude"] + claude_args, env=env)
454
+ sys.exit(result.returncode)
504
455
 
505
456
  if __name__ == "__main__":
506
457
  main()
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlx-code
3
- Version: 0.0.1a1
4
- Summary: Local Claude Code-style coding agent via mlx-lm
3
+ Version: 0.0.1a2
4
+ Summary: Local Claude Code for Mac
5
5
  Home-page: https://github.com/JosefAlbers/mlx-code
6
6
  Author: J Joe
7
7
  Author-email: albersj66@gmail.com
@@ -6,9 +6,9 @@ setup(
6
6
  author_email="albersj66@gmail.com",
7
7
  author="J Joe",
8
8
  license="Apache-2.0",
9
- version="0.0.1a1",
9
+ version="0.0.1a2",
10
10
  readme="README.md",
11
- description="Local Claude Code-style coding agent via mlx-lm",
11
+ description="Local Claude Code for Mac",
12
12
  long_description=open("README.md").read(),
13
13
  long_description_content_type="text/markdown",
14
14
  python_requires=">=3.11",
File without changes
File without changes