mlx-code 0.0.1a2__tar.gz → 0.0.1a3__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.
@@ -0,0 +1,204 @@
1
+ Metadata-Version: 2.4
2
+ Name: mlx-code
3
+ Version: 0.0.1a3
4
+ Summary: Local Claude Code for Mac
5
+ Home-page: https://github.com/JosefAlbers/mlx-code
6
+ Author: J Joe
7
+ Author-email: albersj66@gmail.com
8
+ License: Apache-2.0
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: mlx-lm>=0.19.0
12
+ Requires-Dist: PyYAML
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: description
16
+ Dynamic: description-content-type
17
+ Dynamic: home-page
18
+ Dynamic: license
19
+ Dynamic: requires-dist
20
+ Dynamic: requires-python
21
+ Dynamic: summary
22
+
23
+ # mlx-code: Local Claude Code for Mac
24
+
25
+ <img width="1024" height="1536" alt="mlxcodelogo" src="https://github.com/user-attachments/assets/df605c1d-f8e7-433c-9aaf-176c73e3d628" />
26
+ ![mlxcode](https://github.com/user-attachments/assets/86004bd2-549f-44b8-9a05-a9b6d9f1c4a2)
27
+
28
+ ### Quick Start
29
+
30
+ ```bash
31
+ brew install --cask claude-code
32
+ pip install mlx-code
33
+ ```
34
+
35
+ ### Commands
36
+ | Command | What it does | Example |
37
+ |--------|--------------|--------|
38
+ | `mlx-code` | Start interactive mode | `mlx-code` |
39
+ | `mlx-code "task"` | Run a one-time task | `mlx-code "fix the build error"` |
40
+ | `mlx-code -p "query"` | Run one-off query, then exit | `mlx-code -p "explain this function"` |
41
+ | `mlx-code -c` | Continue most recent conversation in current directory | `mlx-code -c` |
42
+ | `mlx-code -r` | Resume a previous conversation | `mlx-code -r` |
43
+ | `mlx-code commit` | Create a Git commit | `mlx-code commit` |
44
+ | `/clear` | Clear conversation history | `/clear` |
45
+ | `/help` | Show available commands | `/help` |
46
+ | `exit` or `Ctrl+C` | Exit Claude Code | `exit` |
47
+
48
+ <details><summary>Click to expand</summary><pre>
49
+ ╭─── Claude Code v2.1.74 ──────────────────────────────────────────────────────────────────────────────────╮
50
+ │ │ Tips for getting started │
51
+ │ Welcome back! │ Run /init to create a CLAUDE.md file with instruct… │
52
+ │ │ ─────────────────────────────────────────────────── │
53
+ │ ▐▛███▜▌ │ Recent activity │
54
+ │ ▝▜█████▛▘ │ No recent activity │
55
+ │ ▘▘ ▝▝ │ │
56
+ │ │ │
57
+ │ mlx-community/Qwen3.5-4B-Opti… · API Usage Billing │ │
58
+ │ ~/Downloads/tmp │ │
59
+ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯
60
+
61
+ ❯ list the files in this directory and explain what each one does
62
+
63
+ ⏺ The user wants to understand the files in the current directory, so I'll use the Glob tool to list all files first.
64
+
65
+ ⏺ Searched for 1 pattern (ctrl+o to expand)
66
+
67
+ ⏺ I need to fix the Glob tool call - the parameter should be pattern, not key.
68
+
69
+ ⏺ Searched for 1 pattern (ctrl+o to expand)
70
+
71
+ ⏺ The user wants me to explain what each file does. I should organize these by project directory and explain the purpose of each file. Let me read a few key files to provide accurate explanations.
72
+
73
+ This directory contains several project folders. Let me explain each one:
74
+
75
+ Main Projects
76
+
77
+ cyberpunk/
78
+
79
+ - main.zig - Main entry point for a Zig-based cyberpunk project
80
+ - build.zig - Build configuration file for Zig
81
+ - map.c - C source file, likely for map rendering or game logic
82
+ - help.py - Python helper script, possibly for documentation or utilities
83
+
84
+ rts/
85
+
86
+ - main.zig - Main entry point for a real-time strategy (RTS) game in Zig
87
+ - build.zig - Build configuration for Zig
88
+ - battle_sim.c - C file for battle simulation logic
89
+
90
+ splat_zig_raylib/
91
+
92
+ - main.zig (two versions - one in splat_zig/, one in root) - Main entry point for a Raylib-based game
93
+ - build.zig - Build configuration
94
+ - gsplat_transform.py - Python script for splat transform calculations
95
+ - index.html - Web entry point for the game
96
+ - build_web.sh - Shell script for building web version
97
+ - README.md - Project documentation
98
+
99
+ tarot/
100
+
101
+ - tarot.py - Python script for tarot-related functionality
102
+ - index.html - Web entry point
103
+
104
+ yorha/
105
+
106
+ - index.html - Web entry point (likely a simple web page)
107
+
108
+ jina.py
109
+
110
+ - jina.py - Python script, possibly related to Jina AI or a utility
111
+
112
+ mlx-code-v1.py & mlx-code.py
113
+
114
+ - These appear to be different versions of the mlx-code agent wrapper (the local Claude Code-style agent for Apple Silicon)
115
+
116
+ tmp.md
117
+
118
+ - tmp.md - Temporary markdown file (contents unknown)
119
+
120
+ mlx_trace.log
121
+
122
+ - mlx_trace.log - Trace log file from the mlx model runtime
123
+
124
+ .DS_Store files
125
+
126
+ - macOS metadata files (can be ignored)
127
+
128
+ ---
129
+ Summary: This is a temporary download folder (/Users/jjms/Downloads/tmp) containing multiple small projects:
130
+ - Game projects in Zig (cyberpunk, RTS, Raylib-based game)
131
+ - Python scripts for various utilities
132
+ - Web entry points (HTML files)
133
+ - Build configurations (Zig build files)
134
+
135
+ The Zig projects appear to be the main focus, with game-related code and build systems.
136
+
137
+ ✻ Brewed for 42s
138
+
139
+ ❯ explain how output is parsed in mlx-code.py
140
+
141
+ ⏺ The user wants to understand how output is parsed in mlx-code.py, so I need to read that file first to understand its structure and find the output parsing logic.
142
+
143
+ ⏺ Read 1 file (ctrl+o to expand)
144
+
145
+ ⏺ I need to fix the Read tool call - it should use file_path not key.
146
+
147
+ ⏺ Read 1 file (ctrl+o to expand)
148
+
149
+ ⏺ Now I have the full file. Let me explain the output parsing logic, which is the key part the user asked about.
150
+
151
+
152
+ The output parsing logic is in the parse_output(raw: str) function (lines 233-315). Here's how it works:
153
+
154
+ Output Parsing Flow
155
+
156
+ 1. Extract Thinking Blocks (lines 237-244)
157
+
158
+ First, it extracts any `
159
+
160
+ ⏺ The output parsing logic is in the parse_output(raw: str) function (lines 233-315). Here's how it works:
161
+
162
+ Output Parsing Flow
163
+
164
+ 1. Extract Thinking Blocks (lines 237-244)
165
+
166
+ First, it extracts any `
167
+
168
+ ⏺ ` blocks by finding the earliest occurrences of both markers and splitting them out:
169
+ while "
170
+
171
+ ⏺ " in remaining:
172
+ s = remaining.index("
173
+
174
+ ⏺ ")
175
+ # Extract thinking block
176
+ remaining = remaining[e + 8:].strip()
177
+
178
+ ### 2. **Extract Tool Use Blocks** (lines 247-289)
179
+ It searches for `<tool_call>...</tool_call>` markers and tries to parse the content:
180
+ - **First attempt**: Try parsing as JSON with `name` or `tool` field
181
+ - **Fallback**: Parse as XML-like format with `<function=...>` and `<parameter=...>` tags
182
+ - Creates tool blocks with `type: "tool_use"`, unique ID, name, and input
183
+
184
+ ### 3. **Handle JSON Tool Response** (lines 291-311)
185
+ If no `<tool_call>...</tool_call>` blocks were found, it checks if the remaining output is a JSON object with `name` field (indicating a tool response).
186
+
187
+ ### 4. **Return Result** (line 315)
188
+ Returns a tuple of `(blocks, stop_reason)`:
189
+ - `blocks`: List of parsed blocks (`{"type": "text"}`, `{"type": "thinking"}`, `{"type": "tool_use"}`)
190
+ - `stop_reason`: `"tool_use"` if tools were detected, `"end_turn"` otherwise
191
+
192
+ ## Key Patterns Used
193
+
194
+ | Pattern | Purpose |
195
+ |---------|---------|
196
+ | `
197
+
198
+ ⏺ | Thinking block markers | |...| Tool invocation markers | | JSON withname/tool| Tool response format | |<function=...>/<parameter=...>` | Alternative tool format |
199
+
200
+ The parser is designed to handle both the tool invocation format (<tool_call>...</tool_call>) and tool response format (JSON with name field) used by the system.
201
+
202
+ ✻ Worked for 1m 9s
203
+ </pre></details><br>
204
+
@@ -0,0 +1,182 @@
1
+ # mlx-code: Local Claude Code for Mac
2
+
3
+ <img width="1024" height="1536" alt="mlxcodelogo" src="https://github.com/user-attachments/assets/df605c1d-f8e7-433c-9aaf-176c73e3d628" />
4
+ ![mlxcode](https://github.com/user-attachments/assets/86004bd2-549f-44b8-9a05-a9b6d9f1c4a2)
5
+
6
+ ### Quick Start
7
+
8
+ ```bash
9
+ brew install --cask claude-code
10
+ pip install mlx-code
11
+ ```
12
+
13
+ ### Commands
14
+ | Command | What it does | Example |
15
+ |--------|--------------|--------|
16
+ | `mlx-code` | Start interactive mode | `mlx-code` |
17
+ | `mlx-code "task"` | Run a one-time task | `mlx-code "fix the build error"` |
18
+ | `mlx-code -p "query"` | Run one-off query, then exit | `mlx-code -p "explain this function"` |
19
+ | `mlx-code -c` | Continue most recent conversation in current directory | `mlx-code -c` |
20
+ | `mlx-code -r` | Resume a previous conversation | `mlx-code -r` |
21
+ | `mlx-code commit` | Create a Git commit | `mlx-code commit` |
22
+ | `/clear` | Clear conversation history | `/clear` |
23
+ | `/help` | Show available commands | `/help` |
24
+ | `exit` or `Ctrl+C` | Exit Claude Code | `exit` |
25
+
26
+ <details><summary>Click to expand</summary><pre>
27
+ ╭─── Claude Code v2.1.74 ──────────────────────────────────────────────────────────────────────────────────╮
28
+ │ │ Tips for getting started │
29
+ │ Welcome back! │ Run /init to create a CLAUDE.md file with instruct… │
30
+ │ │ ─────────────────────────────────────────────────── │
31
+ │ ▐▛███▜▌ │ Recent activity │
32
+ │ ▝▜█████▛▘ │ No recent activity │
33
+ │ ▘▘ ▝▝ │ │
34
+ │ │ │
35
+ │ mlx-community/Qwen3.5-4B-Opti… · API Usage Billing │ │
36
+ │ ~/Downloads/tmp │ │
37
+ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯
38
+
39
+ ❯ list the files in this directory and explain what each one does
40
+
41
+ ⏺ The user wants to understand the files in the current directory, so I'll use the Glob tool to list all files first.
42
+
43
+ ⏺ Searched for 1 pattern (ctrl+o to expand)
44
+
45
+ ⏺ I need to fix the Glob tool call - the parameter should be pattern, not key.
46
+
47
+ ⏺ Searched for 1 pattern (ctrl+o to expand)
48
+
49
+ ⏺ The user wants me to explain what each file does. I should organize these by project directory and explain the purpose of each file. Let me read a few key files to provide accurate explanations.
50
+
51
+ This directory contains several project folders. Let me explain each one:
52
+
53
+ Main Projects
54
+
55
+ cyberpunk/
56
+
57
+ - main.zig - Main entry point for a Zig-based cyberpunk project
58
+ - build.zig - Build configuration file for Zig
59
+ - map.c - C source file, likely for map rendering or game logic
60
+ - help.py - Python helper script, possibly for documentation or utilities
61
+
62
+ rts/
63
+
64
+ - main.zig - Main entry point for a real-time strategy (RTS) game in Zig
65
+ - build.zig - Build configuration for Zig
66
+ - battle_sim.c - C file for battle simulation logic
67
+
68
+ splat_zig_raylib/
69
+
70
+ - main.zig (two versions - one in splat_zig/, one in root) - Main entry point for a Raylib-based game
71
+ - build.zig - Build configuration
72
+ - gsplat_transform.py - Python script for splat transform calculations
73
+ - index.html - Web entry point for the game
74
+ - build_web.sh - Shell script for building web version
75
+ - README.md - Project documentation
76
+
77
+ tarot/
78
+
79
+ - tarot.py - Python script for tarot-related functionality
80
+ - index.html - Web entry point
81
+
82
+ yorha/
83
+
84
+ - index.html - Web entry point (likely a simple web page)
85
+
86
+ jina.py
87
+
88
+ - jina.py - Python script, possibly related to Jina AI or a utility
89
+
90
+ mlx-code-v1.py & mlx-code.py
91
+
92
+ - These appear to be different versions of the mlx-code agent wrapper (the local Claude Code-style agent for Apple Silicon)
93
+
94
+ tmp.md
95
+
96
+ - tmp.md - Temporary markdown file (contents unknown)
97
+
98
+ mlx_trace.log
99
+
100
+ - mlx_trace.log - Trace log file from the mlx model runtime
101
+
102
+ .DS_Store files
103
+
104
+ - macOS metadata files (can be ignored)
105
+
106
+ ---
107
+ Summary: This is a temporary download folder (/Users/jjms/Downloads/tmp) containing multiple small projects:
108
+ - Game projects in Zig (cyberpunk, RTS, Raylib-based game)
109
+ - Python scripts for various utilities
110
+ - Web entry points (HTML files)
111
+ - Build configurations (Zig build files)
112
+
113
+ The Zig projects appear to be the main focus, with game-related code and build systems.
114
+
115
+ ✻ Brewed for 42s
116
+
117
+ ❯ explain how output is parsed in mlx-code.py
118
+
119
+ ⏺ The user wants to understand how output is parsed in mlx-code.py, so I need to read that file first to understand its structure and find the output parsing logic.
120
+
121
+ ⏺ Read 1 file (ctrl+o to expand)
122
+
123
+ ⏺ I need to fix the Read tool call - it should use file_path not key.
124
+
125
+ ⏺ Read 1 file (ctrl+o to expand)
126
+
127
+ ⏺ Now I have the full file. Let me explain the output parsing logic, which is the key part the user asked about.
128
+
129
+
130
+ The output parsing logic is in the parse_output(raw: str) function (lines 233-315). Here's how it works:
131
+
132
+ Output Parsing Flow
133
+
134
+ 1. Extract Thinking Blocks (lines 237-244)
135
+
136
+ First, it extracts any `
137
+
138
+ ⏺ The output parsing logic is in the parse_output(raw: str) function (lines 233-315). Here's how it works:
139
+
140
+ Output Parsing Flow
141
+
142
+ 1. Extract Thinking Blocks (lines 237-244)
143
+
144
+ First, it extracts any `
145
+
146
+ ⏺ ` blocks by finding the earliest occurrences of both markers and splitting them out:
147
+ while "
148
+
149
+ ⏺ " in remaining:
150
+ s = remaining.index("
151
+
152
+ ⏺ ")
153
+ # Extract thinking block
154
+ remaining = remaining[e + 8:].strip()
155
+
156
+ ### 2. **Extract Tool Use Blocks** (lines 247-289)
157
+ It searches for `<tool_call>...</tool_call>` markers and tries to parse the content:
158
+ - **First attempt**: Try parsing as JSON with `name` or `tool` field
159
+ - **Fallback**: Parse as XML-like format with `<function=...>` and `<parameter=...>` tags
160
+ - Creates tool blocks with `type: "tool_use"`, unique ID, name, and input
161
+
162
+ ### 3. **Handle JSON Tool Response** (lines 291-311)
163
+ If no `<tool_call>...</tool_call>` blocks were found, it checks if the remaining output is a JSON object with `name` field (indicating a tool response).
164
+
165
+ ### 4. **Return Result** (line 315)
166
+ Returns a tuple of `(blocks, stop_reason)`:
167
+ - `blocks`: List of parsed blocks (`{"type": "text"}`, `{"type": "thinking"}`, `{"type": "tool_use"}`)
168
+ - `stop_reason`: `"tool_use"` if tools were detected, `"end_turn"` otherwise
169
+
170
+ ## Key Patterns Used
171
+
172
+ | Pattern | Purpose |
173
+ |---------|---------|
174
+ | `
175
+
176
+ ⏺ | Thinking block markers | |...| Tool invocation markers | | JSON withname/tool| Tool response format | |<function=...>/<parameter=...>` | Alternative tool format |
177
+
178
+ The parser is designed to handle both the tool invocation format (<tool_call>...</tool_call>) and tool response format (JSON with name field) used by the system.
179
+
180
+ ✻ Worked for 1m 9s
181
+ </pre></details><br>
182
+
@@ -1,5 +1,6 @@
1
1
  import argparse
2
2
  import json
3
+ import logging
3
4
  import os
4
5
  import re
5
6
  import subprocess
@@ -19,27 +20,62 @@ DEFAULT_MODEL = "mlx-community/Qwen3.5-4B-OptiQ-4bit"
19
20
  DEFAULT_SKILL_DIRS = ["./skills", os.path.expanduser("~/.claude/skills")]
20
21
  LOG_FILE = "mlx_trace.log"
21
22
 
22
- model = None
23
- tokenizer = None
24
- model_id = None
25
- skills = {}
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)
41
-
42
- def parse_frontmatter(text):
23
+ logging.basicConfig(
24
+ filename=LOG_FILE,
25
+ level=logging.DEBUG,
26
+ format="%(message)s",
27
+ )
28
+ logger = logging.getLogger(__name__)
29
+
30
+ class AppState:
31
+ def __init__(self, model, tokenizer, model_id: str, skills: dict):
32
+ self.model = model
33
+ self.tokenizer = tokenizer
34
+ self.model_id = model_id
35
+ self.skills = skills
36
+ self._counter = 0
37
+ self._lock = threading.Lock()
38
+
39
+ def trace(self, prompt: str, raw: str, elapsed: float):
40
+ with self._lock:
41
+ self._counter += 1
42
+ count = self._counter
43
+ sep = "=" * 80
44
+ logger.debug(
45
+ "\n%s\nCALL %d %s (%.1fs)\n%s\n--- PROMPT ---\n%s\n--- OUTPUT ---\n%s\n",
46
+ sep, count, time.strftime("%Y-%m-%d %H:%M:%S"), elapsed, sep, prompt, raw,
47
+ )
48
+
49
+ def skill_body(self, name: str) -> str:
50
+ if name not in self.skills:
51
+ available = ", ".join(self.skills) or "none"
52
+ return f"Unknown skill '{name}'. Available: {available}"
53
+ try:
54
+ return Path(self.skills[name]["path"]).read_text()
55
+ except Exception as e:
56
+ return f"Error reading skill: {e}"
57
+
58
+ def generate(self, prompt: str, max_tokens: int, temp: float, top_p: float) -> str:
59
+ from mlx_lm import generate as mlx_gen
60
+ t0 = time.time()
61
+ raw = mlx_gen(
62
+ self.model, self.tokenizer,
63
+ prompt=prompt,
64
+ max_tokens=max_tokens,
65
+ verbose=False,
66
+ )
67
+ self.trace(prompt, raw, time.time() - t0)
68
+ return raw
69
+
70
+ def encode(self, text: str) -> list:
71
+ return self.tokenizer.encode(text)
72
+
73
+ def apply_chat_template(self, messages: list) -> str:
74
+ return self.tokenizer.apply_chat_template(
75
+ messages, tokenize=False, add_generation_prompt=True
76
+ )
77
+
78
+ def parse_frontmatter(text: str):
43
79
  m = re.match(r"^---\n(.*?)\n---\n", text, re.DOTALL)
44
80
  if not m:
45
81
  return {}, text
@@ -57,7 +93,8 @@ def parse_frontmatter(text):
57
93
  fm[k.strip()] = v.strip()
58
94
  return fm, body
59
95
 
60
- def scan_skills(dirs):
96
+
97
+ def scan_skills(dirs: list) -> dict:
61
98
  found = {}
62
99
  for d in dirs:
63
100
  p = Path(d)
@@ -75,37 +112,17 @@ def scan_skills(dirs):
75
112
  print(f" warn: {f}: {e}", flush=True)
76
113
  return found
77
114
 
78
- def skill_body(name):
79
- if name not in skills:
80
- return f"Unknown skill '{name}'. Available: {', '.join(skills) or 'none'}"
81
- try:
82
- return Path(skills[name]["path"]).read_text()
83
- except Exception as e:
84
- return f"Error reading skill: {e}"
115
+ ANTI_LOOP_INSTRUCTION = (
116
+ "After you have written your response, stop immediately. "
117
+ "Do not re-read the question, do not re-draft the answer, "
118
+ "do not explain what you just did. Output your answer once and stop."
119
+ )
85
120
 
86
- def load_model(path):
87
- global model, tokenizer, model_id
88
- from mlx_lm import load
89
- print(f"Loading {path} …", flush=True)
90
- model, tokenizer = load(path)
91
- model_id = path
92
- print("Ready.\n", flush=True)
93
121
 
94
- def generate(prompt, max_tokens, temp, top_p):
95
- from mlx_lm import generate as mlx_gen
96
- t0 = time.time()
97
- raw = mlx_gen(
98
- model, tokenizer,
99
- prompt=prompt,
100
- max_tokens=max_tokens,
101
- verbose=False,
102
- )
103
- trace(prompt, raw, time.time() - t0)
104
- return raw
105
-
106
- def skills_system_addon():
122
+ def skills_system_addon(skills: dict) -> str:
123
+ base = ANTI_LOOP_INSTRUCTION
107
124
  if not skills:
108
- return ""
125
+ return base
109
126
  entries = "\n".join(
110
127
  f"<skill><n>{n}</n><description>{s['description']}</description></skill>"
111
128
  for n, s in skills.items()
@@ -113,10 +130,12 @@ def skills_system_addon():
113
130
  return (
114
131
  "\n\n<available_skills>\n" + entries +
115
132
  "\nUse the read_skill tool to get full instructions before attempting "
116
- "any task that matches a skill.\n</available_skills>"
133
+ "any task that matches a skill.\n</available_skills>\n\n"
134
+ + base
117
135
  )
118
136
 
119
- def tools_to_text(tools):
137
+
138
+ def tools_to_text(tools: list) -> str:
120
139
  header = (
121
140
  "You have access to these tools. "
122
141
  "To call a tool output ONLY a <tool_call> block:\n"
@@ -131,7 +150,8 @@ def tools_to_text(tools):
131
150
  lines.append(f"- {name}({params}): {desc}")
132
151
  return "\n".join(lines)
133
152
 
134
- def build_messages(body, extra=None):
153
+
154
+ def build_messages(body: dict, skills: dict, extra: list = None) -> list:
135
155
  msgs = []
136
156
 
137
157
  sys_parts = []
@@ -139,15 +159,16 @@ def build_messages(body, extra=None):
139
159
  if isinstance(raw, str) and raw:
140
160
  sys_parts.append(raw)
141
161
  elif isinstance(raw, list):
142
- t = "\n".join(b.get("text","") for b in raw if b.get("type")=="text")
143
- if t: sys_parts.append(t)
144
- sys_parts.append(skills_system_addon())
162
+ t = "\n".join(b.get("text", "") for b in raw if b.get("type") == "text")
163
+ if t:
164
+ sys_parts.append(t)
165
+ sys_parts.append(skills_system_addon(skills))
145
166
 
146
167
  if skills:
147
168
  sys_parts.append(tools_to_text([{
148
169
  "name": "read_skill",
149
170
  "description": "Read the full SKILL.md instructions for a skill before using it.",
150
- "input_schema": {"type":"object","properties":{"name":{"type":"string"}},"required":["name"]},
171
+ "input_schema": {"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]},
151
172
  }]))
152
173
 
153
174
  system = "\n".join(p for p in sys_parts if p).strip()
@@ -166,7 +187,7 @@ def build_messages(body, extra=None):
166
187
  if t == "text":
167
188
  parts.append(block["text"])
168
189
  elif t == "thinking":
169
- parts.append(f"<think>\n{block.get('thinking','')}\n</think>")
190
+ parts.append(f"<think>\n{block.get('thinking', '')}\n</think>")
170
191
  elif t == "tool_use":
171
192
  args = "".join(
172
193
  f"<parameter={k}>\n{v}\n</parameter>"
@@ -176,7 +197,7 @@ def build_messages(body, extra=None):
176
197
  elif t == "tool_result":
177
198
  rc = block.get("content", "")
178
199
  if isinstance(rc, list):
179
- rc = "\n".join(c.get("text","") for c in rc if c.get("type")=="text")
200
+ rc = "\n".join(c.get("text", "") for c in rc if c.get("type") == "text")
180
201
  parts.append(f"<tool_response>\n{rc}\n</tool_response>")
181
202
  msgs.append({"role": role, "content": "\n".join(parts)})
182
203
 
@@ -184,22 +205,23 @@ def build_messages(body, extra=None):
184
205
  msgs.extend(extra)
185
206
  return msgs
186
207
 
187
- def build_prompt(body, extra=None):
188
- msgs = build_messages(body, extra=extra)
189
- return tokenizer.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
190
208
 
191
- def parse_output(raw):
209
+ def build_prompt(body: dict, state: AppState, extra: list = None) -> str:
210
+ msgs = build_messages(body, state.skills, extra=extra)
211
+ return state.apply_chat_template(msgs)
212
+
213
+ def parse_output(raw: str):
192
214
  blocks = []
193
215
  remaining = raw
194
216
 
195
217
  while "<think>" in remaining and "</think>" in remaining:
196
- s = remaining.index("<think>")
197
- e = remaining.index("</think>")
218
+ s = remaining.index("<think>")
219
+ e = remaining.index("</think>")
198
220
  before = remaining[:s].strip()
199
221
  if before:
200
222
  blocks.append({"type": "text", "text": before})
201
- blocks.append({"type": "thinking", "thinking": remaining[s+7:e].strip()})
202
- remaining = remaining[e+8:].strip()
223
+ blocks.append({"type": "thinking", "thinking": remaining[s + 7:e].strip()})
224
+ remaining = remaining[e + 8:].strip()
203
225
  remaining = remaining.replace("</think>", "").strip()
204
226
 
205
227
  tool_blocks = []
@@ -213,8 +235,10 @@ def parse_output(raw):
213
235
  name = obj.get("name") or obj.get("tool")
214
236
  args = obj.get("arguments") or obj.get("input") or {}
215
237
  if isinstance(args, str):
216
- try: args = json.loads(args)
217
- except: args = {"raw": args}
238
+ try:
239
+ args = json.loads(args)
240
+ except Exception:
241
+ args = {"raw": args}
218
242
  if name:
219
243
  parsed = {"name": name, "input": args}
220
244
  except Exception:
@@ -252,8 +276,10 @@ def parse_output(raw):
252
276
  args = obj.get("arguments") or obj.get("input") or {}
253
277
  if name:
254
278
  if isinstance(args, str):
255
- try: args = json.loads(args)
256
- except: args = {"raw": args}
279
+ try:
280
+ args = json.loads(args)
281
+ except Exception:
282
+ args = {"raw": args}
257
283
  blocks.append({
258
284
  "type": "tool_use",
259
285
  "id": f"toolu_{uuid.uuid4().hex[:8]}",
@@ -268,35 +294,44 @@ def parse_output(raw):
268
294
  blocks.append({"type": "text", "text": remaining})
269
295
  return blocks or [{"type": "text", "text": raw}], "end_turn"
270
296
 
271
- def resolve_read_skill(blocks, body, max_tokens, temp, top_p):
272
- extra = []
297
+
298
+ def resolve_read_skill(
299
+ blocks: list, body: dict, state: AppState,
300
+ max_tokens: int, temp: float, top_p: float,
301
+ ) -> tuple:
302
+ extra = []
303
+ stop_reason = "tool_use"
304
+
273
305
  for _ in range(5):
274
- skill_calls = [b for b in blocks if b.get("type")=="tool_use" and b["name"]=="read_skill"]
306
+ skill_calls = [b for b in blocks if b.get("type") == "tool_use" and b["name"] == "read_skill"]
275
307
  if not skill_calls:
276
308
  break
277
309
  for c in skill_calls:
278
- name = c["input"].get("name","")
279
- content = skill_body(name)
310
+ name = c["input"].get("name", "")
311
+ content = state.skill_body(name)
280
312
  args = f"<parameter=name>\n{name}\n</parameter>"
281
313
  extra.append({"role": "assistant", "content": f"<tool_call>\n<function=read_skill>\n{args}</function>\n</tool_call>"})
282
- extra.append({"role": "user", "content": f"<tool_response>\n{content}\n</tool_response>"})
283
- prompt = build_prompt(body, extra=extra)
284
- raw = generate(prompt, max_tokens, temp, top_p)
314
+ extra.append({"role": "user", "content": f"<tool_response>\n{content}\n</tool_response>"})
315
+ prompt = build_prompt(body, state, extra=extra)
316
+ raw = state.generate(prompt, max_tokens, temp, top_p)
285
317
  blocks, stop_reason = parse_output(raw)
286
- stop_reason = "tool_use" if any(b.get("type")=="tool_use" for b in blocks) else "end_turn"
318
+ if stop_reason == "end_turn":
319
+ break
320
+
287
321
  return blocks, stop_reason
288
322
 
289
- def sse(event, data):
323
+ def sse(event: str, data: dict) -> bytes:
290
324
  return f"event: {event}\ndata: {json.dumps(data)}\n\n".encode()
291
325
 
292
- def blocks_to_sse(blocks, msg_id, stop_reason, in_tokens, out_tokens):
326
+
327
+ def blocks_to_sse(blocks: list, msg_id: str, stop_reason: str, in_tokens: int, out_tokens: int) -> bytes:
293
328
  out = bytearray()
294
329
 
295
330
  out += sse("message_start", {
296
331
  "type": "message_start",
297
332
  "message": {
298
333
  "id": msg_id, "type": "message", "role": "assistant",
299
- "model": model_id, "content": [], "stop_reason": None,
334
+ "model": "local", "content": [], "stop_reason": None,
300
335
  "stop_sequence": None,
301
336
  "usage": {"input_tokens": in_tokens, "output_tokens": 0},
302
337
  },
@@ -349,77 +384,89 @@ def blocks_to_sse(blocks, msg_id, stop_reason, in_tokens, out_tokens):
349
384
 
350
385
  return bytes(out)
351
386
 
352
- class Handler(BaseHTTPRequestHandler):
387
+ def make_handler(state: AppState):
353
388
 
354
- def log_message(self, fmt, *args):
355
- pass
389
+ class Handler(BaseHTTPRequestHandler):
356
390
 
357
- def send_json(self, code, obj):
358
- body = json.dumps(obj).encode()
359
- self.send_response(code)
360
- self.send_header("Content-Type", "application/json")
361
- self.send_header("Content-Length", str(len(body)))
362
- self.end_headers()
363
- self.wfile.write(body)
391
+ def log_message(self, fmt, *args):
392
+ pass
364
393
 
365
- def read_json(self):
366
- n = int(self.headers.get("Content-Length", 0))
367
- return json.loads(self.rfile.read(n))
394
+ def send_json(self, code: int, obj: dict):
395
+ body = json.dumps(obj).encode()
396
+ self.send_response(code)
397
+ self.send_header("Content-Type", "application/json")
398
+ self.send_header("Content-Length", str(len(body)))
399
+ self.end_headers()
400
+ self.wfile.write(body)
368
401
 
369
- def path_base(self):
370
- return self.path.split("?")[0].rstrip("/")
402
+ def read_json(self) -> dict:
403
+ n = int(self.headers.get("Content-Length", 0))
404
+ return json.loads(self.rfile.read(n))
371
405
 
372
- def do_GET(self):
373
- if self.path_base() == "/v1/models":
374
- self.send_json(200, {"data": [
375
- {"id": model_id, "object": "model",
376
- "created": int(time.time()), "owned_by": "local"},
377
- ]})
378
- else:
379
- self.send_json(404, {"error": "not found"})
406
+ def path_base(self) -> str:
407
+ return self.path.split("?")[0].rstrip("/")
380
408
 
381
- def do_POST(self):
382
- pb = self.path_base()
409
+ def do_GET(self):
410
+ if self.path_base() == "/v1/models":
411
+ self.send_json(200, {"data": [
412
+ {"id": state.model_id, "object": "model",
413
+ "created": int(time.time()), "owned_by": "local"},
414
+ ]})
415
+ else:
416
+ self.send_json(404, {"error": "not found"})
383
417
 
384
- if pb == "/v1/messages/count_tokens":
385
- self.send_json(200, {"input_tokens": 0})
386
- return
418
+ def do_POST(self):
419
+ pb = self.path_base()
387
420
 
388
- if pb != "/v1/messages":
389
- self.send_json(404, {"error": f"unknown endpoint {pb}"})
390
- return
421
+ if pb == "/v1/messages/count_tokens":
422
+ self.send_json(200, {"input_tokens": 0})
423
+ return
391
424
 
392
- body = self.read_json()
393
- body["model"] = model_id
425
+ if pb != "/v1/messages":
426
+ self.send_json(404, {"error": f"unknown endpoint {pb}"})
427
+ return
394
428
 
395
- max_tokens = body.get("max_tokens", 8192)
396
- temp = body.get("temperature", 0.7)
397
- top_p = body.get("top_p", 0.9)
398
- msg_id = f"msg_{uuid.uuid4().hex}"
429
+ body = self.read_json()
430
+ body["model"] = state.model_id
399
431
 
400
- prompt = build_prompt(body)
401
- raw = generate(prompt, max_tokens, temp, top_p)
432
+ max_tokens = body.get("max_tokens", 8192)
433
+ temp = body.get("temperature", 0.7)
434
+ top_p = body.get("top_p", 0.9)
435
+ msg_id = f"msg_{uuid.uuid4().hex}"
402
436
 
403
- blocks, stop_reason = parse_output(raw)
437
+ prompt = build_prompt(body, state)
438
+ raw = state.generate(prompt, max_tokens, temp, top_p)
404
439
 
405
- if any(b.get("type")=="tool_use" and b["name"]=="read_skill" for b in blocks):
406
- blocks, stop_reason = resolve_read_skill(blocks, body, max_tokens, temp, top_p)
440
+ blocks, stop_reason = parse_output(raw)
407
441
 
408
- in_tokens = len(tokenizer.encode(prompt))
409
- out_tokens = len(tokenizer.encode(raw))
442
+ if any(b.get("type") == "tool_use" and b["name"] == "read_skill" for b in blocks):
443
+ blocks, stop_reason = resolve_read_skill(blocks, body, state, max_tokens, temp, top_p)
410
444
 
411
- sse_bytes = blocks_to_sse(blocks, msg_id, stop_reason, in_tokens, out_tokens)
445
+ in_tokens = len(state.encode(prompt))
446
+ out_tokens = len(state.encode(raw))
447
+
448
+ sse_bytes = blocks_to_sse(blocks, msg_id, stop_reason, in_tokens, out_tokens)
449
+
450
+ self.send_response(200)
451
+ self.send_header("Content-Type", "text/event-stream")
452
+ self.send_header("Cache-Control", "no-cache")
453
+ self.send_header("Content-Length", str(len(sse_bytes)))
454
+ self.end_headers()
455
+ try:
456
+ self.wfile.write(sse_bytes)
457
+ self.wfile.flush()
458
+ except BrokenPipeError:
459
+ pass
460
+
461
+ return Handler
462
+
463
+ def load_model(path: str):
464
+ from mlx_lm import load
465
+ print(f"Loading {path} …", flush=True)
466
+ model, tokenizer = load(path)
467
+ print("Ready.\n", flush=True)
468
+ return model, tokenizer
412
469
 
413
- self.send_response(200)
414
- self.send_header("Content-Type", "text/event-stream")
415
- self.send_header("Cache-Control", "no-cache")
416
- self.send_header("Content-Length", str(len(sse_bytes)))
417
- self.end_headers()
418
- try:
419
- self.wfile.write(sse_bytes)
420
- self.wfile.flush()
421
- except BrokenPipeError:
422
- pass
423
470
 
424
471
  def main():
425
472
  parser = argparse.ArgumentParser()
@@ -433,9 +480,10 @@ def main():
433
480
  print("Scanning skills …", flush=True)
434
481
  skills = scan_skills(skill_dirs)
435
482
 
436
- load_model(args.model)
483
+ model, tokenizer = load_model(args.model)
484
+ state = AppState(model, tokenizer, args.model, skills)
437
485
 
438
- server = HTTPServer((args.host, args.port), Handler)
486
+ server = HTTPServer((args.host, args.port), make_handler(state))
439
487
  thread = threading.Thread(target=server.serve_forever, daemon=True)
440
488
  thread.start()
441
489
 
@@ -453,5 +501,6 @@ def main():
453
501
  result = subprocess.run(["claude"] + claude_args, env=env)
454
502
  sys.exit(result.returncode)
455
503
 
504
+
456
505
  if __name__ == "__main__":
457
506
  main()
@@ -0,0 +1,204 @@
1
+ Metadata-Version: 2.4
2
+ Name: mlx-code
3
+ Version: 0.0.1a3
4
+ Summary: Local Claude Code for Mac
5
+ Home-page: https://github.com/JosefAlbers/mlx-code
6
+ Author: J Joe
7
+ Author-email: albersj66@gmail.com
8
+ License: Apache-2.0
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: mlx-lm>=0.19.0
12
+ Requires-Dist: PyYAML
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: description
16
+ Dynamic: description-content-type
17
+ Dynamic: home-page
18
+ Dynamic: license
19
+ Dynamic: requires-dist
20
+ Dynamic: requires-python
21
+ Dynamic: summary
22
+
23
+ # mlx-code: Local Claude Code for Mac
24
+
25
+ <img width="1024" height="1536" alt="mlxcodelogo" src="https://github.com/user-attachments/assets/df605c1d-f8e7-433c-9aaf-176c73e3d628" />
26
+ ![mlxcode](https://github.com/user-attachments/assets/86004bd2-549f-44b8-9a05-a9b6d9f1c4a2)
27
+
28
+ ### Quick Start
29
+
30
+ ```bash
31
+ brew install --cask claude-code
32
+ pip install mlx-code
33
+ ```
34
+
35
+ ### Commands
36
+ | Command | What it does | Example |
37
+ |--------|--------------|--------|
38
+ | `mlx-code` | Start interactive mode | `mlx-code` |
39
+ | `mlx-code "task"` | Run a one-time task | `mlx-code "fix the build error"` |
40
+ | `mlx-code -p "query"` | Run one-off query, then exit | `mlx-code -p "explain this function"` |
41
+ | `mlx-code -c` | Continue most recent conversation in current directory | `mlx-code -c` |
42
+ | `mlx-code -r` | Resume a previous conversation | `mlx-code -r` |
43
+ | `mlx-code commit` | Create a Git commit | `mlx-code commit` |
44
+ | `/clear` | Clear conversation history | `/clear` |
45
+ | `/help` | Show available commands | `/help` |
46
+ | `exit` or `Ctrl+C` | Exit Claude Code | `exit` |
47
+
48
+ <details><summary>Click to expand</summary><pre>
49
+ ╭─── Claude Code v2.1.74 ──────────────────────────────────────────────────────────────────────────────────╮
50
+ │ │ Tips for getting started │
51
+ │ Welcome back! │ Run /init to create a CLAUDE.md file with instruct… │
52
+ │ │ ─────────────────────────────────────────────────── │
53
+ │ ▐▛███▜▌ │ Recent activity │
54
+ │ ▝▜█████▛▘ │ No recent activity │
55
+ │ ▘▘ ▝▝ │ │
56
+ │ │ │
57
+ │ mlx-community/Qwen3.5-4B-Opti… · API Usage Billing │ │
58
+ │ ~/Downloads/tmp │ │
59
+ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯
60
+
61
+ ❯ list the files in this directory and explain what each one does
62
+
63
+ ⏺ The user wants to understand the files in the current directory, so I'll use the Glob tool to list all files first.
64
+
65
+ ⏺ Searched for 1 pattern (ctrl+o to expand)
66
+
67
+ ⏺ I need to fix the Glob tool call - the parameter should be pattern, not key.
68
+
69
+ ⏺ Searched for 1 pattern (ctrl+o to expand)
70
+
71
+ ⏺ The user wants me to explain what each file does. I should organize these by project directory and explain the purpose of each file. Let me read a few key files to provide accurate explanations.
72
+
73
+ This directory contains several project folders. Let me explain each one:
74
+
75
+ Main Projects
76
+
77
+ cyberpunk/
78
+
79
+ - main.zig - Main entry point for a Zig-based cyberpunk project
80
+ - build.zig - Build configuration file for Zig
81
+ - map.c - C source file, likely for map rendering or game logic
82
+ - help.py - Python helper script, possibly for documentation or utilities
83
+
84
+ rts/
85
+
86
+ - main.zig - Main entry point for a real-time strategy (RTS) game in Zig
87
+ - build.zig - Build configuration for Zig
88
+ - battle_sim.c - C file for battle simulation logic
89
+
90
+ splat_zig_raylib/
91
+
92
+ - main.zig (two versions - one in splat_zig/, one in root) - Main entry point for a Raylib-based game
93
+ - build.zig - Build configuration
94
+ - gsplat_transform.py - Python script for splat transform calculations
95
+ - index.html - Web entry point for the game
96
+ - build_web.sh - Shell script for building web version
97
+ - README.md - Project documentation
98
+
99
+ tarot/
100
+
101
+ - tarot.py - Python script for tarot-related functionality
102
+ - index.html - Web entry point
103
+
104
+ yorha/
105
+
106
+ - index.html - Web entry point (likely a simple web page)
107
+
108
+ jina.py
109
+
110
+ - jina.py - Python script, possibly related to Jina AI or a utility
111
+
112
+ mlx-code-v1.py & mlx-code.py
113
+
114
+ - These appear to be different versions of the mlx-code agent wrapper (the local Claude Code-style agent for Apple Silicon)
115
+
116
+ tmp.md
117
+
118
+ - tmp.md - Temporary markdown file (contents unknown)
119
+
120
+ mlx_trace.log
121
+
122
+ - mlx_trace.log - Trace log file from the mlx model runtime
123
+
124
+ .DS_Store files
125
+
126
+ - macOS metadata files (can be ignored)
127
+
128
+ ---
129
+ Summary: This is a temporary download folder (/Users/jjms/Downloads/tmp) containing multiple small projects:
130
+ - Game projects in Zig (cyberpunk, RTS, Raylib-based game)
131
+ - Python scripts for various utilities
132
+ - Web entry points (HTML files)
133
+ - Build configurations (Zig build files)
134
+
135
+ The Zig projects appear to be the main focus, with game-related code and build systems.
136
+
137
+ ✻ Brewed for 42s
138
+
139
+ ❯ explain how output is parsed in mlx-code.py
140
+
141
+ ⏺ The user wants to understand how output is parsed in mlx-code.py, so I need to read that file first to understand its structure and find the output parsing logic.
142
+
143
+ ⏺ Read 1 file (ctrl+o to expand)
144
+
145
+ ⏺ I need to fix the Read tool call - it should use file_path not key.
146
+
147
+ ⏺ Read 1 file (ctrl+o to expand)
148
+
149
+ ⏺ Now I have the full file. Let me explain the output parsing logic, which is the key part the user asked about.
150
+
151
+
152
+ The output parsing logic is in the parse_output(raw: str) function (lines 233-315). Here's how it works:
153
+
154
+ Output Parsing Flow
155
+
156
+ 1. Extract Thinking Blocks (lines 237-244)
157
+
158
+ First, it extracts any `
159
+
160
+ ⏺ The output parsing logic is in the parse_output(raw: str) function (lines 233-315). Here's how it works:
161
+
162
+ Output Parsing Flow
163
+
164
+ 1. Extract Thinking Blocks (lines 237-244)
165
+
166
+ First, it extracts any `
167
+
168
+ ⏺ ` blocks by finding the earliest occurrences of both markers and splitting them out:
169
+ while "
170
+
171
+ ⏺ " in remaining:
172
+ s = remaining.index("
173
+
174
+ ⏺ ")
175
+ # Extract thinking block
176
+ remaining = remaining[e + 8:].strip()
177
+
178
+ ### 2. **Extract Tool Use Blocks** (lines 247-289)
179
+ It searches for `<tool_call>...</tool_call>` markers and tries to parse the content:
180
+ - **First attempt**: Try parsing as JSON with `name` or `tool` field
181
+ - **Fallback**: Parse as XML-like format with `<function=...>` and `<parameter=...>` tags
182
+ - Creates tool blocks with `type: "tool_use"`, unique ID, name, and input
183
+
184
+ ### 3. **Handle JSON Tool Response** (lines 291-311)
185
+ If no `<tool_call>...</tool_call>` blocks were found, it checks if the remaining output is a JSON object with `name` field (indicating a tool response).
186
+
187
+ ### 4. **Return Result** (line 315)
188
+ Returns a tuple of `(blocks, stop_reason)`:
189
+ - `blocks`: List of parsed blocks (`{"type": "text"}`, `{"type": "thinking"}`, `{"type": "tool_use"}`)
190
+ - `stop_reason`: `"tool_use"` if tools were detected, `"end_turn"` otherwise
191
+
192
+ ## Key Patterns Used
193
+
194
+ | Pattern | Purpose |
195
+ |---------|---------|
196
+ | `
197
+
198
+ ⏺ | Thinking block markers | |...| Tool invocation markers | | JSON withname/tool| Tool response format | |<function=...>/<parameter=...>` | Alternative tool format |
199
+
200
+ The parser is designed to handle both the tool invocation format (<tool_call>...</tool_call>) and tool response format (JSON with name field) used by the system.
201
+
202
+ ✻ Worked for 1m 9s
203
+ </pre></details><br>
204
+
@@ -1 +1,2 @@
1
1
  mlx-lm>=0.19.0
2
+ PyYAML
@@ -6,13 +6,13 @@ setup(
6
6
  author_email="albersj66@gmail.com",
7
7
  author="J Joe",
8
8
  license="Apache-2.0",
9
- version="0.0.1a2",
9
+ version="0.0.1a3",
10
10
  readme="README.md",
11
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",
15
- install_requires=["mlx-lm>=0.19.0"],
15
+ install_requires=["mlx-lm>=0.19.0", "PyYAML"],
16
16
  py_modules=["main"],
17
17
  entry_points={"console_scripts": ["mlx-code=main:main"]},
18
18
  )
mlx_code-0.0.1a2/PKG-INFO DELETED
@@ -1,24 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: mlx-code
3
- Version: 0.0.1a2
4
- Summary: Local Claude Code for Mac
5
- Home-page: https://github.com/JosefAlbers/mlx-code
6
- Author: J Joe
7
- Author-email: albersj66@gmail.com
8
- License: Apache-2.0
9
- Requires-Python: >=3.11
10
- Description-Content-Type: text/markdown
11
- Requires-Dist: mlx-lm>=0.19.0
12
- Dynamic: author
13
- Dynamic: author-email
14
- Dynamic: description
15
- Dynamic: description-content-type
16
- Dynamic: home-page
17
- Dynamic: license
18
- Dynamic: requires-dist
19
- Dynamic: requires-python
20
- Dynamic: summary
21
-
22
- # mlx-code: Local Claude Code for Mac
23
-
24
- ![mlxcode](https://github.com/user-attachments/assets/86004bd2-549f-44b8-9a05-a9b6d9f1c4a2)
@@ -1,3 +0,0 @@
1
- # mlx-code: Local Claude Code for Mac
2
-
3
- ![mlxcode](https://github.com/user-attachments/assets/86004bd2-549f-44b8-9a05-a9b6d9f1c4a2)
@@ -1,24 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: mlx-code
3
- Version: 0.0.1a2
4
- Summary: Local Claude Code for Mac
5
- Home-page: https://github.com/JosefAlbers/mlx-code
6
- Author: J Joe
7
- Author-email: albersj66@gmail.com
8
- License: Apache-2.0
9
- Requires-Python: >=3.11
10
- Description-Content-Type: text/markdown
11
- Requires-Dist: mlx-lm>=0.19.0
12
- Dynamic: author
13
- Dynamic: author-email
14
- Dynamic: description
15
- Dynamic: description-content-type
16
- Dynamic: home-page
17
- Dynamic: license
18
- Dynamic: requires-dist
19
- Dynamic: requires-python
20
- Dynamic: summary
21
-
22
- # mlx-code: Local Claude Code for Mac
23
-
24
- ![mlxcode](https://github.com/user-attachments/assets/86004bd2-549f-44b8-9a05-a9b6d9f1c4a2)
File without changes