Compare commits

...

4 Commits

Author SHA1 Message Date
Sigrid Jin (ง'̀-'́)ง oO
4ea31c1bc9 Merge pull request #3253 from EmreCelenli/docs/mlx-compatibility
docs: document mlx-lm backend for Apple Silicon and known gotchas
2026-06-27 01:17:10 +09:00
Sigrid Jin (ง'̀-'́)ง oO
4e0cba76cc Merge pull request #3263 from hiisandog/fix/claw-small-cleanup-c6b25
Improve command lookup normalization
2026-06-27 01:16:51 +09:00
陈家名
5babda196b Improve command lookup normalization 2026-06-25 15:43:30 +08:00
EmreCelenli
3ae922508c docs: document mlx-lm backend and known model-resolution/EOS gotchas 2026-06-18 18:36:39 +02:00
3 changed files with 40 additions and 2 deletions

View File

@@ -97,6 +97,27 @@ export OPENAI_API_KEY="local-dev-token"
claw --model "Qwen/Qwen2.5-Coder-7B-Instruct" prompt "Reply exactly HELLO_WORLD_123"
```
## mlx-lm (Apple Silicon)
On Apple Silicon, [mlx-lm](https://github.com/ml-explore/mlx-lm) gives meaningfully faster inference than llama.cpp-based backends for models under roughly 14B parameters.
Install and start the server:
```bash
pipx install mlx-lm
mlx_lm.server --model mlx-community/Qwen2.5-Coder-7B-Instruct-4bit --port 8080
```
Then route Claw to it:
```bash
export OPENAI_BASE_URL="http://127.0.0.1:8080/v1"
export OPENAI_API_KEY="local-dev-token"
claw --model "mlx-community/Qwen2.5-Coder-7B-Instruct-4bit" prompt "Reply exactly HELLO_WORLD_123"
```
mlx-lm serves models under their full Hugging Face repo ID. Use the exact `id` field from `curl $OPENAI_BASE_URL/models` for `--model`. A bare name like `qwen2.5-coder-7b-instruct` will fail model resolution before the request ever reaches the server.
## Local skills install from disk
Skills are discovered from Claw skill roots such as `.claw/skills/` in a workspace and `~/.claw/skills/` for user-level installs. Legacy `.codex/skills/` roots may also be scanned for compatibility, but new local Claw projects should prefer `.claw/skills/`.
@@ -149,3 +170,5 @@ Offline install checklist:
| Plain prompt works but tools fail | Confirm the model/server supports OpenAI-compatible tool calls and response shapes. |
| Skill says installed but `/skills <name>` fails | Check `/skills list` for the discovered name and source; verify provider credentials separately with `claw doctor`. |
| A local docs/log file contains secrets | Redact it before using `@path` file context or attaching it to an issue. |
| `404 Repository Not Found` from huggingface.co when running `claw` | The `--model` value isn't a full Hugging Face repo ID. Use the exact `id` field from `curl $OPENAI_BASE_URL/models`, not a bare model name. |
| mlx-lm output includes a trailing `<|im_end|>`, or generation runs long | Unfixed mlx-lm bug ([#973](https://github.com/ml-explore/mlx-lm/issues/973), closed without a merge). Set `eos_token_id` in the cached `generation_config.json` (or `config.json`) to the real end-of-turn token. |

View File

@@ -55,7 +55,8 @@ def command_names() -> list[str]:
def get_command(name: str) -> PortingModule | None:
needle = COMMAND_ALIASES.get(name.lower(), name.lower())
normalized = name.strip().lower()
needle = COMMAND_ALIASES.get(normalized, normalized)
for module in PORTED_COMMANDS:
if module.name.lower() == needle:
return module
@@ -72,8 +73,15 @@ def get_commands(cwd: str | None = None, include_plugin_commands: bool = True, i
def find_commands(query: str, limit: int = 20) -> list[PortingModule]:
needle = query.lower()
needle = query.strip().lower()
matches = [module for module in PORTED_COMMANDS if needle in module.name.lower() or needle in module.source_hint.lower()]
matches.sort(
key=lambda module: (
module.name.lower() != needle,
not module.name.lower().startswith(needle),
needle not in module.name.lower(),
)
)
return matches[:limit]

View File

@@ -183,6 +183,13 @@ class PortingWorkspaceTests(unittest.TestCase):
self.assertIn("Mirrored command 'plugin'", result.stdout)
self.assertNotIn('Unknown mirrored command', result.stdout)
def test_command_lookup_normalizes_user_input_whitespace(self) -> None:
from src.commands import execute_command, find_commands, get_command
self.assertEqual('plugin', get_command(' PLUGINS ').name)
self.assertEqual('review', find_commands(' review ', limit=1)[0].name)
self.assertIn("Mirrored command 'plugin'", execute_command(' marketplace ', 'browse').message)
def test_route_plugin_slash_commands_match_commands(self) -> None:
prompts = ('/plugin list', '/plugins list', '/marketplace browse', '/reload-plugins')
for prompt in prompts: