Skip to content

Instantly share code, notes, and snippets.

@rajatscode
Last active September 15, 2026 15:32
Show Gist options
  • Select an option

  • Save rajatscode/eef68b493161cf13c4cf60a1b6518338 to your computer and use it in GitHub Desktop.

Select an option

Save rajatscode/eef68b493161cf13c4cf60a1b6518338 to your computer and use it in GitHub Desktop.
local-code: dynamic local model discovery and Claude/Codex launcher

local-code

An interactive Ollama launcher for people who do not want to learn model names, quantization suffixes, or provider configuration first.

Run local-code and get four choices:

  1. Install new models — see detected memory headroom, then the five best benchmark-matched local variants at or below your comfortable threshold, ranked by the live Arena text-overall leaderboard. Each row shows the exact Ollama tag, download/weight size, plain-English fit, and Arena rank/rating. Select one to inspect it, install it, and return to the Install menu. A sixth option browses families and variants with the same evidence beside each result.
  2. Use installed models — launch an installed model in Claudish, Aider, OpenCode, Goose, Ollama chat, or Ollama's maintained Claude/Codex adapters; the chooser explains model/harness compatibility and can remove a selected local model after confirmation.
  3. Metrics and diagnostics — inspect host memory/VRAM, disk, swap, Ollama, agent versions, and running models.
  4. Settings — tune the comfortable-fit threshold (40% by default).

The recommendation is deliberately evidence-based rather than a permanent model shortlist: Arena provides a current public preference score, while Ollama provides local size, context, tool badges, and cloud/local metadata. Only benchmark rows whose family and parameter count can be matched are shown; unmatched models are not assigned invented scores. Fit is shown as comfortable, near limit, too large, or hosted; the default comfortable cutoff is 40% of the conservative weight budget and can be changed with local-code settings or --comfortable-percent. Arena measures model behavior, not the speed or memory use of a quantized local build. Models above the comfortable cutoff never appear in the top five; models up to the full weight budget remain available through Browse as near limit.

Install

Python 3.9+, Ollama, and optional fzf are enough:

brew install fzf ollama
brew services start ollama
mkdir -p "$HOME/.local/bin"
install -m 755 local-code local_catalog.py local_explore.py local_hardware.py "$HOME/.local/bin/"

Run:

uv tool install --python 3.12 aider-chat  # optional, enables the coding-agent choice
# Optional multi-agent harnesses:
npm install -g opencode-ai
curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh | bash
local-code

The catalog is cached for 24 hours under ~/.cache/local-code; --refresh forces current public metadata. Discovery never downloads model weights until you select Install.

local-code settings persists the comfortable-fit cutoff under ~/.config/local-code/settings.json. A lower value is useful on unified-memory machines where a model that fits by weight alone still causes swap or makes other applications sluggish. This remains a heuristic; context/KV cache and runtime behavior are not measured.

Direct commands and advanced use

local-code --install
local-code settings
local-code --use -- --dangerously-skip-permissions
local-code --model granite4.1:8b-q8_0 --agent claudish -- -y
local-code --model granite4.1:8b-q8_0 --agent aider
local-code --model granite4.1:8b-q8_0 --agent opencode
local-code --model granite4.1:8b-q8_0 --agent goose
local-code --model granite4.1:8b-q8_0 --agent chat
local-code --model qwen3.8:27b --agent claude -- --dangerously-skip-permissions
local-code --model gpt-oss:20b --agent codex -- --yolo
local-code --dry-run --model qwen3.8:27b --agent claude
local-code --comfortable-percent 25 --install
local-code --doctor
local-code --list
local-code --scan --fit-only --limit 20
local-code --variants gemma4

Arguments after -- are passed through to the selected agent. The launcher only blocks arguments that directly replace the local model/provider routing; normal safety, permission, prompt, and workflow flags remain untouched.

The launcher uses the installed Ollama tag directly and never creates synthetic local-code-* model aliases. Claudish runs the model as ollama@TAG inside the Claude Code harness; Aider talks to Ollama directly. OpenCode is launched through Ollama's maintained OpenCode integration and supports primary agents plus child subagents; Goose receives its Ollama provider/model through environment variables. Claude and Codex are launched through Ollama's maintained adapters. Codex requires a thinking-capable model (for example gpt-oss); a completion-only model such as Granite is rejected with a clear message instead of silently falling back to a hosted account. The launcher refuses cloud-only models, checks disk space before a download, and asks for consent unless --yes is supplied to a direct pull.

Ollama's tools capability is a transport prerequisite, not proof of reliable agent behavior. Coding harnesses are omitted for models that do not advertise tools, and compatible rows explicitly warn that reliability varies by model.

OpenCode requires a 64k-or-larger context window. On small machines, an 8B model may therefore load slowly or fail to respond under OpenCode even though the same model works in Goose or a direct Ollama request. Treat that as a model/context fit issue, not proof that the harness is using a hosted model.

Free hosted models

local-code --free

This lists models currently advertising zero token prices through OpenRouter, with context/tool metadata and a source link. It is discovery only: this local launcher does not route prompts to hosted providers, manage API keys, or claim that free access is private, unlimited, or available without rate limits. Review the provider's current terms before using a hosted model for source code.

Fit and benchmark caveats

Hardware is detected at runtime. Apple Silicon reports unified memory, NVIDIA systems report free VRAM on the GPU with the most free memory, and other hosts fall back to available system RAM. A conservative weight budget is shown; fit does not include KV cache, context overhead, other applications, thermal limits, or tokens/second. “Arena rank” is a public text-preference signal, not a coding-agent guarantee. The detail view says when a benchmark match is unavailable.

Verification

python3 -m unittest -v test_local_code.py test_local_catalog.py
python3 -m py_compile local-code local_catalog.py local_explore.py local_hardware.py
#!/usr/bin/env python3
"""Small, local-only Ollama launcher for coding agents."""
import argparse
from concurrent.futures import ThreadPoolExecutor, as_completed
import hashlib
import json
import os
import re
import shlex
import shutil
import subprocess
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
import webbrowser
sys.path.insert(0, str(Path(__file__).resolve().parent))
import local_catalog as catalog
import local_hardware as hardware
import local_explore as explore
ENDPOINT = "http://127.0.0.1:11434"
CONTEXTS = (8192, 16384, 32768, 65536, 131072)
DEFAULT_SETTINGS = {"comfortable_percent": 40.0}
def settings_path():
return Path(os.environ.get("LOCAL_CODE_SETTINGS", "~/.config/local-code/settings.json")).expanduser()
def load_settings():
path = settings_path()
try:
data = json.loads(path.read_text())
value = float(data.get("comfortable_percent", DEFAULT_SETTINGS["comfortable_percent"]))
if not 1 <= value < 100:
raise ValueError
return {"comfortable_percent": value}
except (OSError, ValueError, TypeError, json.JSONDecodeError):
return dict(DEFAULT_SETTINGS)
def save_settings(settings):
path = settings_path()
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(settings, indent=2) + "\n")
def settings_flow():
"""Interactively edit persistent launcher heuristics without touching models."""
settings = load_settings()
while True:
current = settings["comfortable_percent"]
print(f"\nlocal-code settings ({settings_path()})")
print(f"Comfortable threshold: {current:.0f}% of conservative weight budget")
action = choose("Settings", [("threshold", "Change comfortable threshold"),
("reset", "Reset defaults (40%)"),
("done", "Done")])
if action in (None, "done"):
return 0
if action == "reset":
settings = dict(DEFAULT_SETTINGS)
save_settings(settings)
hardware.set_comfortable_fraction(settings["comfortable_percent"] / 100)
print("Reset and saved.")
continue
try:
value = float(input("Comfortable threshold percent (1–99): ").strip())
if not 1 <= value < 100:
raise ValueError
except (EOFError, ValueError):
print("Enter a number from 1 to 99.")
continue
settings = {"comfortable_percent": value}
save_settings(settings)
hardware.set_comfortable_fraction(value / 100)
print(f"Saved {value:.0f}%. This affects fit labels and recommendations, not model weights.")
def api(path, method="GET", payload=None, timeout=2):
data = None if payload is None else json.dumps(payload).encode()
req = urllib.request.Request(ENDPOINT + path, data=data, method=method,
headers={"Content-Type": "application/json"})
# Never let ambient proxy settings route the loopback daemon externally.
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
with opener.open(req, timeout=timeout) as response:
raw = response.read()
result = json.loads(raw or b"{}")
if not isinstance(result, dict) or result.get("error"):
raise RuntimeError(f"Ollama returned an error for {path}: {result}")
return result
def version_tuple(v):
m = re.search(r"(\d+)\.(\d+)\.(\d+)", str(v))
return tuple(map(int, m.groups())) if m else (0, 0, 0)
def server_info():
# Ollama can keep the TCP listener open while it is loading a large model.
# A single short request during that window is not evidence that the daemon
# is stopped (and telling users to run `ollama serve` then produces a
# confusing "address already in use" error).
last = None
for attempt in range(3):
try:
version = api("/api/version", timeout=5).get("version", "unknown")
tags = api("/api/tags", timeout=5).get("models", [])
return version, tags, None
except (urllib.error.URLError, OSError, ValueError, RuntimeError) as exc:
last = exc
if attempt < 2:
time.sleep(0.5)
return None, [], last
def model_names(tags):
return {m.get("name") or m.get("model") for m in tags if m.get("name") or m.get("model")}
def model_dir():
configured = os.environ.get("OLLAMA_MODELS")
if configured:
return Path(configured).expanduser()
return Path.home() / ".ollama" / "models"
def free_bytes(path):
path = Path(path).expanduser()
while not path.exists() and path != path.parent:
path = path.parent
return shutil.disk_usage(path).free
def executable(name):
"""Find a user-installed harness even when the shell omitted its bin dir."""
found = shutil.which(name)
if found:
return found
roots = [Path.home() / ".local" / "bin",
Path.home() / ".npm-global" / "bin",
Path.home() / ".nvm" / "versions" / "node"]
candidates = []
for root in roots:
if root.name == "node":
candidates.extend(root.glob("*/bin/" + name))
else:
candidates.append(root / name)
for path in candidates:
if path.is_file() and os.access(path, os.X_OK):
return str(path)
return None
def harness_env(name):
path = executable(name)
if not path:
return {}
directory = str(Path(path).parent)
current = os.environ.get("PATH", "")
return {"PATH": directory + (os.pathsep + current if current else "")}
def need_space(size_bytes, path=None):
required = int(size_bytes + 3 * 1024**3)
p = path or model_dir()
return free_bytes(p), required
def pull(tag):
# Keep Ollama's progress display and let a large download take its time.
subprocess.run(["ollama", "pull", tag], env=child_env({}), check=True)
def agent_command(agent, alias, context, extras):
forbidden = {"--model", "--local-provider", "--oss", "--base-url", "--api-key",
"--provider", "--endpoint", "--fallback-model", "--settings",
"--setting-sources", "--model-provider", "--config", "--agents",
"--profile", "--remote", "--remote-auth-token-env"}
if any(x.split("=", 1)[0] in forbidden for x in extras):
raise ValueError("extra arguments cannot override local provider/model routing")
if agent == "codex":
# Let Ollama's maintained integration own provider setup. Codex's
# native OSS adapter is intentionally limited to compatible reasoning
# models; arbitrary completion/tool models can look launched but then
# reconnect or fall back to the user's hosted account.
return (["ollama", "launch", "codex", "--model", alias, "--"] + extras, {})
if agent == "claude":
# Ollama ships and maintains the Claude Code adapter. It configures
# the Anthropic-compatible endpoint and avoids Claude Code's private
# model-catalog assumptions about synthetic local names.
return (["ollama", "launch", "claude", "--model", alias, "--"] + extras, {})
if agent == "aider":
return (["aider", "--model", f"ollama_chat/{alias}"] + extras,
{"OLLAMA_API_BASE": ENDPOINT, "OLLAMA_API_KEY": "ollama"})
if agent == "claudish":
# Claudish adapts Ollama's OpenAI-compatible interface to Claude
# Code's harness. Transport support does not make every model a good
# tool user; use_flow labels that distinction before launch.
return (["claudish", "--model", f"ollama@{alias}"] + extras,
harness_env("claudish"))
if agent == "opencode":
return (["ollama", "launch", "opencode", "--model", alias, "--"] + extras,
harness_env("opencode"))
if agent == "goose":
return (["goose"] + extras, {**harness_env("goose"), "GOOSE_PROVIDER": "ollama",
"GOOSE_MODEL": alias, "GOOSE_PROVIDER__HOST": ENDPOINT,
"GOOSE_PROVIDER__API_KEY": "ollama"})
return (["ollama", "run", alias] + extras, {})
def child_env(changes):
env = os.environ.copy()
env.update(changes)
env["OLLAMA_HOST"] = ENDPOINT
for key in ("NO_PROXY", "no_proxy"):
values = [x for x in env.get(key, "").split(",") if x]
env[key] = ",".join(dict.fromkeys(values + ["127.0.0.1", "localhost"]))
return env
def check_ready(version):
if not version:
raise RuntimeError("Ollama is unavailable at 127.0.0.1:11434. Start it with: ollama serve")
def _align_table(options, header):
"""Pad compact pipe-delimited labels so fzf and fallback menus line up."""
lines = (header or "").splitlines()
if not lines or "|" not in lines[0]:
return options, header
header_cols = [part.strip() for part in lines[0].split("|")]
cells = [[str(key)] + [part.strip() for part in str(label).split("|")]
for key, label in options]
count = max([len(header_cols)] + [len(row) for row in cells])
header_cols += [""] * (count - len(header_cols))
cells = [row + [""] * (count - len(row)) for row in cells]
widths = [max([len(header_cols[i])] + [len(row[i]) for row in cells]) for i in range(count)]
limits = [4, 27, 11, 20, 14, 20] + [20] * count
widths = [min(widths[i], limits[i] if i < len(limits) else 20) for i in range(count)]
available = max(60, shutil.get_terminal_size((100, 20)).columns - 8)
while sum(widths) + 3 * (count - 1) > available and max(widths[1:], default=0) > 8:
index = max(range(1, count), key=lambda i: widths[i])
widths[index] -= 1
def trim(value, width):
value = str(value)
return value if len(value) <= width else value[:max(1, width - 1)] + "…"
def line(parts, start=0):
return " | ".join(trim(parts[i], widths[i]).ljust(widths[i])
for i in range(start, count)).rstrip()
aligned = [(row[0], line(row, 1)) for row in cells]
return aligned, "\n".join([line(header_cols)] + lines[1:])
def choose(prompt, options, header=None, table=False):
"""Choose from (stable key, visible description) rows without interpreting shell text."""
if not sys.stdin.isatty():
raise RuntimeError("menus need a terminal; use --list, --scan, or --model NAME:TAG --agent claude")
header = header or "Enter selects / Esc cancels"
if table:
options, header = _align_table(options, header)
key_width = max((len(str(key)) for key, _ in options), default=1) if table else 0
rows = [f"{str(key).ljust(key_width)}\t{label}" if table else f"{key}\t{label}"
for key, label in options]
if shutil.which("fzf"):
env = os.environ.copy()
for key in ("FZF_DEFAULT_OPTS", "FZF_DEFAULT_COMMAND", "FZF_DEFAULT_OPTS_FILE"):
env.pop(key, None)
result = subprocess.run(
["fzf", "--no-multi", "--no-sort", "--height=70%", "--layout=reverse",
"--border", "--header", header, "--prompt", prompt + ": "],
input="\n".join(rows) + "\n", text=True, stdout=subprocess.PIPE, env=env)
if result.returncode in (1, 130):
return None
if result.returncode:
raise RuntimeError("fzf could not open its menu; check that this is an interactive terminal")
selected = result.stdout.strip().split("\t", 1)[0].strip()
return selected if selected in {key for key, _ in options} else None
print(header)
print(prompt)
for i, (key, label) in enumerate(options, 1):
display_key = str(key).ljust(key_width) if table else str(key)
print(f" {i}) {display_key} | {label}" if table else f" {i}) {key}: {label}")
try:
n = int(input("Choose (blank cancels): ").strip() or "0")
except (EOFError, ValueError):
return None
return options[n - 1][0] if 1 <= n <= len(options) else None
def command_output(command):
try:
result = subprocess.run(command, capture_output=True, text=True, timeout=5,
env=child_env({}))
return (result.stdout or result.stderr).strip() if result.returncode == 0 else "unavailable"
except (OSError, subprocess.TimeoutExpired):
return "unavailable"
def hardware_snapshot(override=None):
device = hardware.host()
weight_budget = hardware.budget(device, override)
print(hardware.describe(device, weight_budget))
if device.get("swap"):
print("Swap: " + device["swap"])
try:
print(f"Disk free ({model_dir()}): {free_bytes(model_dir()) / 1024**3:.1f} GiB")
except OSError as exc:
print(f"Disk free: unavailable ({exc})")
return device, weight_budget
def doctor(device=None, weight_budget=None):
version, tags, err = server_info()
print("Ollama endpoint: 127.0.0.1:11434")
print("Server: " + (version if version else f"unavailable ({err})"))
print("Installed models: " + (", ".join(sorted(model_names(tags))) or "none"))
if device is None:
hardware_snapshot()
for binary in ("ollama", "claudish", "opencode", "goose", "aider", "claude", "codex", "fzf"):
location = executable(binary)
print(f"{binary}: {command_output([location, '--version']) if location else 'not installed'}")
if version:
try:
running = api("/api/ps").get("models", [])
print("Running models: " + (", ".join(sorted(model_names(running))) or "none"))
except (OSError, urllib.error.URLError, ValueError, RuntimeError):
print("Running models: unavailable")
print("Runtime updates: https://github.com/ollama/ollama/releases/latest")
print("New model architectures may require updating Ollama and restarting its server.")
def validate_tag(tag):
if not re.fullmatch(r"(?:[A-Za-z0-9][A-Za-z0-9_.-]*/)?[A-Za-z0-9][A-Za-z0-9_.-]*(?::[A-Za-z0-9][A-Za-z0-9_.-]*)?", tag):
raise ValueError("use an Ollama model name or namespace/name:tag; URLs and file paths are not model names")
if "cloud" in tag.rsplit(":", 1)[-1].lower():
raise ValueError("this launcher runs local models; use --free to explore hosted alternatives")
return tag if ":" in tag else tag + ":latest"
def reject_remote(details):
if details.get("remote") or details.get("remote_model") or details.get("remote_host"):
raise RuntimeError("this model delegates inference to a remote service; local launch refused")
def launch(args, extras, budget):
tag = validate_tag(args.model)
context = args.context or 32768
agent = args.agent or "claude"
cmd, changes = agent_command(agent, tag, context, extras)
if args.dry_run:
print("DRY RUN: no network, cache writes, downloads, alias creation, or launch.")
print(f"Would inspect {tag}, verify local weights, and check disk space before a missing-model download.")
print("argv:", shlex.join(["ollama", "pull", tag] if args.pull else cmd))
if not args.pull:
print("Working directory:", os.getcwd())
print("env overrides:", shlex.join([f"{key}={value}" for key, value in {"OLLAMA_HOST": ENDPOINT, **changes}.items()]))
return 0
for binary in ({"ollama"} if args.pull else {"ollama", cmd[0]}):
if not executable(binary):
raise RuntimeError(f"{binary} is not installed/on PATH; use --doctor or --dry-run")
version, tags, _ = server_info()
check_ready(version)
installed = next((row for row in tags if (row.get("name") or row.get("model")) == tag), None)
if installed:
details = api("/api/show", "POST", {"model": tag})
reject_remote(details)
size = installed.get("size")
else:
details = catalog.manifest(tag, refresh=True)
reject_remote(details)
size = details.get("size_bytes")
if not size or not details.get("weight_bytes"):
raise RuntimeError("manifest does not identify downloadable local weights; download manually and inspect the model")
print(f"Model: {tag} | {size / 10**9:.2f} GB | {hardware.fit(size, budget)}" if size else f"Model: {tag} | size unknown")
if not args.pull:
print(f"Context: {context:,} tokens; actual context/runtime overhead must be checked with ollama ps.")
if not installed:
free, required = need_space(size)
print(f"Disk now: {free / hardware.GIB:.1f} GiB free on {model_dir()} "
f"(need {required / hardware.GIB:.1f} GiB including 3 GiB buffer)")
if free < required:
raise RuntimeError(f"not enough disk space right now: need {required / hardware.GIB:.1f} GiB including buffer; "
f"have {free / hardware.GIB:.1f} GiB on {model_dir()}")
if not args.yes:
if not sys.stdin.isatty():
raise RuntimeError("model is missing; rerun interactively or use --yes to consent to download")
if input(f"Download {tag} (~{size / 10**9:.2f} GB)? [y/N] ").strip().lower() not in ("y", "yes"):
return 0
pull(tag)
try:
print(f"Disk after download: {free_bytes(model_dir()) / hardware.GIB:.1f} GiB free")
except OSError:
pass
if args.pull:
print(f"Ready: {tag}. Already-installed tags were left unchanged; use ollama pull explicitly to update weights.")
return 0
details = api("/api/show", "POST", {"model": tag})
reject_remote(details)
caps = details.get("capabilities")
if agent != "chat" and caps and "tools" not in caps:
raise RuntimeError("this installed model does not advertise tool support; choose --agent chat or another model")
maxima = [value for key, value in details.get("model_info", {}).items()
if key.endswith(".context_length") and isinstance(value, int)]
if maxima and context > max(maxima):
raise RuntimeError(f"requested context exceeds advertised limit {max(maxima):,}; pass a smaller --context")
# Use the installed tag directly. Synthetic wrappers previously caused
# local-code-local-code-* aliases to accumulate on every launch.
if agent == "codex" and "thinking" not in (details.get("capabilities") or []):
raise RuntimeError("Codex's Ollama adapter requires a thinking-capable model; "
"use gpt-oss with Codex, or choose Aider/Chat for this model")
cmd, changes = agent_command(agent, tag, context, extras)
print(f"Launching {agent} in {os.getcwd()} using {tag}", flush=True)
os.execvpe(cmd[0], cmd, child_env(changes))
return 0
def print_external(items, kind):
for item in items:
if kind == "free":
print(f"{item['id']} | ctx {item.get('context')} | tools {item.get('tools')} | {item['url']}")
else:
print(f"{item['id']} | downloads {item.get('downloads')} | updated {item.get('updated')} | {item['url']}")
print("Discovery only: popularity and zero listed token prices do not establish coding quality, availability, or unlimited access.")
def install_flow(args, device, budget):
"""Recommend, explain, install, and return to this menu."""
while True:
print("\nInstall new models")
print("This host: " + hardware.describe(device, budget))
print("Recommendations use Arena text-overall ratings plus Ollama local-size/tool metadata.")
comfort = hardware.comfortable_percent()
print(f"Fit labels: comfortable (<= {comfort:.0f}% of conservative budget), "
f"near limit ({comfort:.0f}–100%), too large (>100%).")
live_disk = free_bytes(model_dir())
print(f"Disk available right now: {live_disk / hardware.GIB:.1f} GiB (model download requires a 3 GiB buffer).")
print("Checking the current Ollama catalog and benchmark source...", flush=True)
items = explore.recommend(budget, device, limit=5, refresh=args.refresh,
disk_available=live_disk)
if items:
print("\nFive highest-rated benchmark matches within your comfortable threshold:")
else:
print("\nNo benchmark-matched local candidates were found; browse the live catalog instead.")
options = explore.recommendation_rows(items, budget)
options.append(("browse", "Browse all local models | — | — | — | search by family or capability"))
selected = choose("Install", options,
header=("# | Model | Download | Benchmark | Fit | Role\n"
f"Fit: comfortable <= {comfort:.0f}% budget; near limit {comfort:.0f}-100%; too large >100%"),
table=True)
if selected is None:
return 0
if selected == "browse":
item = explore.helpful_browse(choose, budget, args.refresh, live_disk)
if not item:
continue
else:
item = items[int(selected) - 1]
explore.show_model(item, budget, device)
action = choose("Model", [("install", "Install this model and return to Install"),
("back", "Back to the five recommendations")])
if action != "install":
continue
if item.get("cloud"):
print("That is a hosted variant, not a local download. Pick another variant.")
continue
args.model, args.pull = item["tag"], True
launch(args, [], budget)
args.model, args.pull = None, False
print(f"\nInstalled {item['tag']}. Returning to Install.")
def _installed_items(tags, budget):
return [{"tag": row.get("name") or row.get("model"), "size_bytes": row.get("size"),
"context": None, "format": "installed"} for row in tags
if row.get("name") or row.get("model")]
def _launch_items(tags, budget):
"""Hide legacy profiles made by older local-code versions from Use."""
items = _installed_items(tags, budget)
base = [item for item in items if not item["tag"].lower().startswith("local-code-")]
return base or items
def remove_flow(budget):
"""Interactively remove one explicitly selected local Ollama model."""
while True:
version, tags, _ = server_info()
check_ready(version)
items = _installed_items(tags, budget)
if not items:
print("No local models installed.")
return 0
selected = choose("Remove", explore.variant_rows(items, budget),
header="# | Model | Download | Fit | Context | Format | Updated", table=True)
if selected is None:
return 0
item = items[int(selected)]
size = item.get("size_bytes")
size_text = f"{size / 10**9:.1f} GB" if size else "unknown size"
if input(f"Remove {item['tag']} ({size_text}) permanently from Ollama? [y/N] ").strip().lower() not in ("y", "yes"):
continue
if not executable("ollama"):
raise RuntimeError("ollama is not installed/on PATH")
subprocess.run(["ollama", "rm", item["tag"]], env=child_env({}), check=True)
try:
print(f"Removed {item['tag']}. Disk now: {free_bytes(model_dir()) / hardware.GIB:.1f} GiB free.")
except OSError:
print(f"Removed {item['tag']}.")
def use_flow(args, extras, budget):
"""Launch or remove installed models, preserving arbitrary agent flags."""
version, tags, _ = server_info()
check_ready(version)
while True:
mode = choose("Use", [("launch", "Launch an installed model"),
("remove", "Remove an installed model")])
if mode is None:
return 0
if mode == "remove":
if extras:
raise ValueError("agent flags cannot be used with Remove")
remove_flow(budget)
continue
version, tags, _ = server_info()
check_ready(version)
items = _launch_items(tags, budget)
if not items:
print("No local models installed yet. Choose Install new models first.")
continue
selected = choose("Use", explore.variant_rows(items, budget),
header="# | Model | Download | Fit | Context | Format | Updated", table=True)
if selected is None:
continue
item = items[int(selected)]
details = api("/api/show", "POST", {"model": item["tag"]})
caps = details.get("capabilities") or []
tools = "tools" in caps
tool_note = "advertises tools; reliability varies by model" if tools else "not offered: model does not advertise tools"
available = [
("claudish", f"Claudish / Claude Code | {tool_note}") if executable("claudish") and tools else None,
("opencode", f"OpenCode | {tool_note}") if executable("opencode") and tools else None,
("goose", f"Goose | {tool_note}") if executable("goose") and tools else None,
("aider", f"Aider | {tool_note}") if executable("aider") and tools else None,
("claude", f"Claude Code via Ollama | {tool_note}") if executable("ollama") and tools else None,
("codex", "Codex via Ollama | thinking + tools advertised")
if executable("ollama") and tools and "thinking" in caps else None,
("chat", "Plain Ollama chat | compatible; no coding tools"),
]
agent = choose("Launch in", [row for row in available if row])
if not agent:
continue
selected_extras = list(extras)
if not selected_extras:
example = ("-y" if agent == "claudish"
else "--dangerously-skip-permissions" if agent == "claude"
else "--yolo" if agent == "codex" else "--help")
typed = input(f"Extra {agent} flags (blank for defaults; e.g. {example}): ").strip()
if typed:
try:
selected_extras = shlex.split(typed)
except ValueError as exc:
print(f"Could not parse flags: {exc}. Try again.")
continue
break
args.model, args.agent, args.pull = item["tag"], agent, False
args.context = args.context or 32768
if selected_extras:
print("Passing through: " + shlex.join(selected_extras))
return launch(args, selected_extras, budget)
def interactive(args, extras, device, budget):
while True:
action = choose("local-code", [("install", "Install new models"),
("use", "Use installed models"),
("metrics", "Metrics and diagnostics"),
("settings", "Settings")])
if action is None:
return 0
if action == "install":
if extras:
raise ValueError("agent flags after -- belong in Use installed models, not Install")
install_flow(args, device, budget)
elif action == "use":
use_flow(args, extras, budget)
elif action == "metrics":
doctor(device, budget)
elif action == "settings":
settings_flow()
def main(argv=None):
raw = list(sys.argv[1:] if argv is None else argv)
extras = []
if "--" in raw:
split = raw.index("--")
raw, extras = raw[:split], raw[split + 1:]
ap = argparse.ArgumentParser(description="Explore current local models, compare hardware fit, and launch local coding harnesses.",
epilog="Pass agent arguments after --. No model names or device presets are baked in.")
actions = ap.add_mutually_exclusive_group()
actions.add_argument("--list", action="store_true", help="discover current Ollama families")
actions.add_argument("--scan", action="store_true", help="compare variants across current model families")
actions.add_argument("--variants", metavar="FAMILY", help="all available tags, sizes, and fit labels")
actions.add_argument("--inspect", metavar="MODEL", help="inspect exact registry manifest sizes")
actions.add_argument("--free", action="store_true", help="live free hosted catalog; reference only")
actions.add_argument("--hf", metavar="QUERY", help="search trending GGUF repositories; use '' for all")
actions.add_argument("--installed", action="store_true", help="list all installed local models")
actions.add_argument("--doctor", action="store_true", help="detect host and runtime setup")
actions.add_argument("--install", action="store_true", help="interactive install flow")
actions.add_argument("--use", action="store_true", help="interactive installed-model launcher")
actions.add_argument("--metrics", action="store_true", help="host/runtime metrics and diagnostics")
actions.add_argument("--pull", action="store_true", help="download --model without launching")
ap.add_argument("--query", default="", help="search text for family discovery")
ap.add_argument("--sort", choices=("newest", "popular"), default="newest")
ap.add_argument("--limit", type=int, default=20, help="maximum families inspected by --scan (default 20)")
ap.add_argument("--include-nontools", action="store_true", help="scan even without an advertised tools badge")
ap.add_argument("--fit-only", action="store_true", help="hide hosted/over-budget/unknown-size variants")
ap.add_argument("--refresh", action="store_true", help="force current catalog fetch; never update model weights")
ap.add_argument("--budget-gib", type=float, help="override heuristic model-weight budget for this host")
ap.add_argument("--comfortable-percent", type=float,
help="one-off comfortable-fit cutoff (1-99); persistent value: local-code settings")
ap.add_argument("--dry-run", action="store_true", help="preview --model launch without network or writes")
ap.add_argument("--model", help="any Ollama model or namespace/name:tag; no fixed shortlist")
ap.add_argument("--agent", choices=("aider", "claudish", "opencode", "goose", "claude", "codex", "chat"), default=None)
ap.add_argument("--context", type=int, help="positive context token count; default 32768")
ap.add_argument("--yes", action="store_true", help="consent to a missing-model download")
ap.add_argument("command", nargs="?", choices=("settings",), help="edit persistent launcher settings")
args = ap.parse_args(raw)
if args.limit < 1 or args.limit > 500:
ap.error("--limit must be between 1 and 500")
if args.budget_gib is not None and not (0 < args.budget_gib < 100000):
ap.error("--budget-gib must be a finite positive number")
if args.comfortable_percent is not None and not 1 <= args.comfortable_percent < 100:
ap.error("--comfortable-percent must be between 1 and 99")
settings = load_settings()
if os.environ.get("LOCAL_CODE_COMFORTABLE_PERCENT") is None:
hardware.set_comfortable_fraction(settings["comfortable_percent"] / 100)
if args.comfortable_percent is not None:
hardware.set_comfortable_fraction(args.comfortable_percent / 100)
if args.command == "settings":
return settings_flow()
if args.context is not None and not 512 <= args.context <= 2097152:
ap.error("--context must be between 512 and 2097152")
if args.pull and (not args.model or args.agent or extras):
ap.error("--pull requires --model and cannot be combined with agent arguments")
if args.dry_run:
if not args.model:
ap.error("--dry-run needs --model NAME:TAG; choose a model with --list or --scan")
return launch(args, extras, None)
if args.doctor:
doctor()
return 0
if args.free:
print_external(catalog.free_models(refresh=args.refresh), "free")
return 0
if args.hf is not None:
print_external(catalog.hf_models(query=args.hf, refresh=args.refresh), "hf")
return 0
device, budget = hardware_snapshot(args.budget_gib)
if args.model:
return launch(args, extras, budget)
if (extras or args.agent or args.context) and not args.use:
ap.error("launch options require --model")
if args.inspect:
explore.inspect(args.inspect, args.refresh)
elif args.installed:
version, tags, _ = server_info()
check_ready(version)
for row in tags:
size = row.get("size")
print(f"{row.get('name') or row.get('model')} | "
f"{size / 10**9:.2f} GB | {hardware.fit(size, budget)}" if size else str(row))
return 0
elif args.list:
for _, row in explore.family_rows(catalog.families(args.query, args.sort, args.refresh)):
print(row)
elif args.scan or args.variants:
items = (catalog.variants(args.variants, args.refresh) if args.variants else
explore.scan(args.query, args.sort, args.limit, budget, args.refresh, args.include_nontools))
if args.fit_only:
items = [row for row in items if not row.get("cloud") and
hardware.fit(row.get("size_bytes"), budget) in ("headroom", "tight")]
for _, row in explore.variant_rows(items, budget):
print(row)
elif args.install:
return install_flow(args, device, budget)
elif args.use:
return use_flow(args, extras, budget)
elif args.metrics:
doctor(device, budget)
return 0
else:
return interactive(args, extras, device, budget)
explore.status()
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (KeyboardInterrupt, EOFError):
print("\nCancelled.", file=sys.stderr)
raise SystemExit(130)
except (RuntimeError, ValueError, OSError, urllib.error.URLError, subprocess.SubprocessError) as exc:
print(f"error: {exc}", file=sys.stderr)
raise SystemExit(2)
"""Read-only, cache-backed discovery for current Ollama models.
This module deliberately reports what the registry says; it does not select or
launch a model. Network results are cached for one day so callers can use it
without making discovery a prerequisite for launching.
"""
from __future__ import annotations
import hashlib
import html
import json
import os
import re
import tempfile
import subprocess
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timezone
from html.parser import HTMLParser
from pathlib import Path
TTL = 86400
MAX_BYTES = 4 * 1024 * 1024
CACHE_DIR = Path(os.environ.get("LOCAL_CODE_CACHE", Path.home() / ".cache/local-code"))
FETCH_STATUS: dict[str, dict] = {}
def _clean(value: str) -> str:
return re.sub(r"\s+", " ", re.sub(r"[\x00-\x1f\x7f\x1b]", " ", html.unescape(value or ""))).strip()
def _url_key(url: str) -> Path:
return CACHE_DIR / (hashlib.sha256(url.encode()).hexdigest() + ".json")
def _read_cache(url):
path = _url_key(url)
try:
item = json.loads(path.read_text())
age = datetime.now(timezone.utc).timestamp() - float(item["fetched_at_epoch"])
return item["body"], age, item.get("fetched_at")
except (OSError, ValueError, KeyError, TypeError):
return None, None, None
def _write_cache(url, body):
CACHE_DIR.mkdir(parents=True, exist_ok=True)
now = datetime.now(timezone.utc)
payload = {"url": url, "fetched_at": now.isoformat(),
"fetched_at_epoch": now.timestamp(), "body": body}
fd, name = tempfile.mkstemp(prefix="catalog-", dir=CACHE_DIR)
try:
with os.fdopen(fd, "w") as stream: json.dump(payload, stream)
os.replace(name, _url_key(url))
finally:
try: os.unlink(name)
except FileNotFoundError: pass
def _fetch(url, refresh=False):
cached, age, fetched_at = _read_cache(url)
if cached is not None and not refresh and age <= TTL:
FETCH_STATUS[url] = {"source": "cache", "stale": False, "fetched_at": fetched_at}
return cached
try:
req = urllib.request.Request(url, headers={"User-Agent": "local-code-catalog/1"})
with urllib.request.urlopen(req, timeout=15) as response:
data = response.read(MAX_BYTES + 1)
if len(data) > MAX_BYTES: raise RuntimeError("response exceeded 4 MiB safety limit")
body = data.decode("utf-8", "replace")
_write_cache(url, body)
FETCH_STATUS[url] = {"source": "network", "stale": False,
"fetched_at": datetime.now(timezone.utc).isoformat()}
return body
except Exception as exc:
# Some macOS environments have a urllib proxy/TLS path that stalls;
# curl is used only for these fixed public registry hosts.
if urllib.parse.urlparse(url).netloc in {"ollama.com", "registry.ollama.ai", "openrouter.ai", "huggingface.co", "datasets-server.huggingface.co"}:
try:
proc = subprocess.run(["curl", "--fail", "--silent", "--show-error", "--location", "--max-time", "20", url], capture_output=True, timeout=25)
if proc.returncode == 0 and len(proc.stdout) <= MAX_BYTES:
body = proc.stdout.decode("utf-8", "replace"); _write_cache(url, body)
FETCH_STATUS[url] = {"source": "network-curl", "stale": False, "fetched_at": datetime.now(timezone.utc).isoformat()}
return body
except (OSError, subprocess.SubprocessError):
pass
if cached is not None:
FETCH_STATUS[url] = {"source": "cache", "stale": True, "warning": str(exc),
"fetched_at": fetched_at}
return cached
FETCH_STATUS[url] = {"source": "error", "stale": False, "warning": str(exc)}
raise RuntimeError(f"catalog fetch failed for {url}: {exc}") from exc
class _Links(HTMLParser):
def __init__(self):
super().__init__(convert_charrefs=True); self.links = []; self.current = None
def handle_starttag(self, tag, attrs):
if tag == "a":
href = dict(attrs).get("href", "")
if href: self.current = [href, []]
def handle_data(self, data):
if self.current: self.current[1].append(data)
def handle_endtag(self, tag):
if tag == "a" and self.current:
self.links.append((self.current[0], _clean(" ".join(self.current[1])))); self.current = None
def _links(body):
parser = _Links(); parser.feed(body); return parser.links
def _abs(path): return urllib.parse.urljoin("https://ollama.com", path)
def _size(text):
m = re.search(r"([\d.]+)\s*(TB|GB|MB)\b", text, re.I)
if not m: return None
return int(float(m.group(1)) * {"TB": 10**12, "GB": 10**9, "MB": 10**6}[m.group(2).upper()])
def _context(text):
m = re.search(r"([\d,.]+)\s*([KM])\s*(?:context(?:\s+window)?|tokens?)\b", text, re.I)
if not m: m = re.search(r"\b([\d,.]+)\s*([KM])\b", text, re.I)
if not m: return None
return int(float(m.group(1).replace(",", "")) * (1000 if m.group(2).upper() == "K" else 1000000))
def _family_from_link(href, text):
path = urllib.parse.urlparse(href).path
m = re.fullmatch(r"/library/([^/:?#]+)", path)
if not m: return None
name = m.group(1)
capabilities = [x for x in ("tools", "thinking", "vision", "cloud") if re.search(rf"\b{x}\b", text, re.I)]
return {"name": name, "description": text, "capabilities": capabilities,
"updated": _relative(text), "summary": text, "url": _abs(href)}
def _relative(text):
m = re.search(r"\b(?:\d+\s+)?(?:minutes?|hours?|days?|weeks?|months?|years?)\s+ago\b", text, re.I)
return m.group(0) if m else None
def families(query="", sort="newest", refresh=False):
url = ("https://ollama.com/search?q=" + urllib.parse.quote(query, safe="") if query
else "https://ollama.com/library")
url += ("&" if "?" in url else "?") + "sort=" + urllib.parse.quote(sort, safe="")
found = {}
for href, text in _links(_fetch(url, refresh)):
item = _family_from_link(href, text)
if item: found[item["name"]] = item
if not found: raise RuntimeError("Ollama catalog layout changed: no /library/{name} family links found")
return list(found.values())
def variants(family, refresh=False):
if not re.fullmatch(r"[A-Za-z0-9_.-]+", family): raise ValueError("invalid Ollama family name")
url = f"https://ollama.com/library/{family}/tags"
found = {}
for href, text in _links(_fetch(url, refresh)):
path = urllib.parse.unquote(urllib.parse.urlparse(href).path)
m = re.fullmatch(rf"/library/{re.escape(family)}:([^/?#]+)", path)
if not m: continue
tag = f"{family}:{m.group(1)}"
candidate = {"tag": tag, "size_bytes": _size(text), "context": _context(text),
"updated": _relative(text), "url": _abs(href),
"cloud": bool(re.search(r"\bcloud\b", text, re.I)),
"format": "MLX" if re.search(r"\bMLX\b", text) else ("GGUF" if re.search(r"\bGGUF\b", text, re.I) else "unknown"),
"digest": (re.search(r"\b([0-9a-f]{12,64})\b", text, re.I).group(1) if re.search(r"\b([0-9a-f]{12,64})\b", text, re.I) else None)}
# Responsive markup can repeat the same tag; retain the richest card.
if tag not in found or sum(x is not None for x in candidate.values()) > sum(x is not None for x in found[tag].values()):
found[tag] = candidate
if not found: raise RuntimeError(f"Ollama catalog layout changed: no variants for {family}")
return list(found.values())
def manifest(tag, refresh=False):
if not re.fullmatch(r"(?:[A-Za-z0-9_.-]+/)?[A-Za-z0-9_.-]+(?::[A-Za-z0-9_.-]+)?", tag):
raise ValueError("invalid Ollama manifest tag")
if ":" in tag.rsplit("/", 1)[-1]: name, version = tag.rsplit(":", 1)
else: name, version = tag, "latest"
if "/" not in name: name = "library/" + name
url = f"https://registry.ollama.ai/v2/{name}/manifests/{version}"
try: raw = json.loads(_fetch(url, refresh))
except json.JSONDecodeError as exc: raise RuntimeError("invalid registry manifest JSON") from exc
layers = raw.get("layers") or []
sizes = [x.get("size") for x in layers] + [(raw.get("config") or {}).get("size")]
if any(not isinstance(x, int) or x < 0 for x in sizes): raise RuntimeError("registry manifest has missing or invalid layer sizes")
total = sum(sizes)
weights = sum(int(x["size"]) for x in layers if "model" in x.get("mediaType", "") or "projector" in x.get("mediaType", ""))
config = raw.get("config") or {}
config_url = None
if config.get("digest"):
config_url = f"https://registry.ollama.ai/v2/{name}/blobs/{config['digest']}"
remote = bool(raw.get("remote") or config.get("remote") or re.search(r"cloud", tag, re.I))
return {"tag": tag, "size_bytes": total, "weight_bytes": weights or None,
"remote": remote, "config_url": config_url, "source_url": url, "raw": raw}
def free_models(refresh=False):
url = "https://openrouter.ai/api/v1/models"
data = json.loads(_fetch(url, refresh)); out = []
for item in data.get("data", []):
p = item.get("pricing", {})
try: free = float(p.get("prompt", 1)) == 0 and float(p.get("completion", 1)) == 0
except (TypeError, ValueError): free = False
if free:
out.append({"id": _clean(item.get("id")), "name": _clean(item.get("name")), "created": item.get("created"), "pricing": p, "context": item.get("context_length"), "tools": "tools" in item.get("supported_parameters", []), "url": "https://openrouter.ai/" + str(item.get("id"))})
return out
def hf_models(query="", refresh=False):
url = "https://huggingface.co/api/models?filter=gguf&sort=trendingScore&direction=-1&limit=40"
if query: url += "&search=" + urllib.parse.quote(query, safe="")
return [{"id": x.get("id"), "downloads": x.get("downloads"), "updated": x.get("lastModified"), "url": "https://huggingface.co/" + str(x.get("id"))} for x in json.loads(_fetch(url, refresh))]
def arena_scores(refresh=False, max_rows=600):
"""Fetch the current Arena text leaderboard's overall scores.
This is intentionally a separate enrichment source: Arena rates model
behavior, while Ollama supplies local download size and capability facts.
A score is never inferred for an unmatched model.
"""
rows = []
for offset in range(0, max_rows, 100):
params = urllib.parse.urlencode({"dataset": "lmarena-ai/leaderboard-dataset",
"config": "text", "split": "latest",
"offset": offset, "length": 100})
url = "https://datasets-server.huggingface.co/rows?" + params
data = json.loads(_fetch(url, refresh))
chunk = data.get("rows", [])
if not chunk:
break
for entry in chunk:
row = entry.get("row", {})
if row.get("category") != "overall":
continue
try:
rows.append({"model_name": _clean(row.get("model_name")),
"rating": float(row["rating"]),
"rank": int(row["rank"]),
"published": row.get("leaderboard_publish_date"),
"url": "https://arena.ai/leaderboard"})
except (KeyError, TypeError, ValueError):
continue
if len(chunk) < 100:
break
# The dataset contains category snapshots in some releases. Keep the
# strongest row for a model name, which is useful and deterministic.
best = {}
for row in rows:
if row["model_name"] not in best or row["rating"] > best[row["model_name"]]["rating"]:
best[row["model_name"]] = row
return sorted(best.values(), key=lambda row: row["rating"], reverse=True)
"""Browse current model metadata; fit and popularity are not intelligence benchmarks."""
from concurrent.futures import ThreadPoolExecutor, as_completed
import re
import sys
import local_catalog as catalog
import local_hardware as hardware
def fit_label(item, budget):
if item.get("cloud"):
return "hosted"
return {"headroom": "comfortable", "tight": "near limit",
"over budget": "too large", "unknown": "unknown"}.get(
hardware.fit(item.get("size_bytes"), budget), "unknown")
def status():
items = list(catalog.FETCH_STATUS.items())
for url, info in items[:6]:
state = "STALE CACHE" if info.get("stale") else info.get("source", "unknown")
print(f"Source: {url}\n {state}; fetched {info.get('fetched_at') or 'unknown'}")
if len(items) > 6:
print(f"Plus {len(items) - 6} source fetches in the catalog cache.")
for url, info in items[6:]:
if info.get("stale") or info.get("source") == "error":
print(f"Warning: {url}: {info.get('warning')}; fetched {info.get('fetched_at')}")
def variant_label(item, budget):
size = item.get("size_bytes")
size_text = f"{size / 10**9:.2f} GB" if size else "size unknown"
context = item.get("context")
context_text = f"{context:,} max ctx" if context else "ctx unknown"
fit = fit_label(item, budget)
return (f"{size_text} | {fit} | {context_text} | {item.get('format', 'unknown')} | "
f"updated {item.get('updated') or 'unknown'}")
def family_rows(items):
return [(str(i), f"{item['name']} | {','.join(item.get('capabilities', [])) or 'capabilities unknown'} | "
f"{item.get('updated') or 'date unknown'} | {item.get('description', '')[:160]}")
for i, item in enumerate(items)]
def variant_rows(items, budget):
return [(str(i), f"{item['tag']} | {variant_label(item, budget)}")
for i, item in enumerate(items)]
def scan(query, sort, limit, budget, refresh=False, include_nontools=False):
families = catalog.families(query=query, sort=sort, refresh=refresh)
candidates = [item for item in families if include_nontools or "tools" in item.get("capabilities", [])]
selected = candidates[:limit]
print(f"Inspecting variants for {len(selected)} of {len(candidates)} candidate families "
f"from {len(families)} discovered, ordered by {sort}. "
"This is a bounded catalog scan, not a quality leaderboard.", flush=True)
if not selected:
print("No candidates advertise tools in these results. Try --include-nontools or another search.")
return []
rows, errors = [], []
with ThreadPoolExecutor(max_workers=6) as pool:
futures = {pool.submit(catalog.variants, item["name"], refresh=refresh): (i, item)
for i, item in enumerate(selected)}
for future in as_completed(futures):
order, family = futures[future]
try:
variants = future.result()
# Model aliases often share a weight digest. Show one representative
# per digest here; --variants always displays the full list.
seen = set()
for item in variants:
identity = item.get("digest") or item["tag"]
if identity in seen:
continue
seen.add(identity)
rows.append(dict(item, family_order=order, capabilities=family.get("capabilities", [])))
except (RuntimeError, ValueError, OSError) as exc:
errors.append(f"{family['name']}: {exc}")
order = {"headroom": 0, "tight": 1, "unknown": 2, "over budget": 3}
rows.sort(key=lambda item: (4 if item.get("cloud") else order[hardware.fit(item.get("size_bytes"), budget)],
item["family_order"], item.get("size_bytes") or float("inf")))
print(f"Found {len(rows)} variants after alias deduplication; {len(errors)} families failed.")
for message in errors:
print("Catalog warning: " + message, file=sys.stderr)
return rows
def _numbers_before_b(text):
return [float(value) for value in re.findall(r"(?<![a-z])\d+(?:\.\d+)?(?=b)", (text or "").lower())]
def _benchmark_match(family, item, scores):
"""Match an Ollama family/tag to Arena only when the identity is plausible."""
family_tokens = set(re.findall(r"[a-z]+|\d+(?:\.\d+)?", family.lower()))
item_params = _numbers_before_b(item.get("tag", ""))
# A bare `:latest` tag is not enough to identify the benchmarked parameter
# count; never attach a hosted/max score to it.
if not item_params:
return None
matches = []
for score in scores:
name = score.get("model_name", "").lower()
tokens = set(re.findall(r"[a-z]+|\d+(?:\.\d+)?", name))
if not family_tokens.issubset(tokens):
continue
score_params = _numbers_before_b(name)
# Never attach a 480B benchmark score to a 19GB model. Parameterized
# variants must agree; unparameterized tags may only use unparameterized
# benchmark names.
if item_params and (not score_params or item_params[0] != score_params[0]
or not all(p in score_params for p in item_params[1:])):
continue
if not item_params and score_params:
continue
matches.append(score)
return max(matches, key=lambda row: row["rating"]) if matches else None
def recommend(budget, device=None, limit=5, refresh=False, disk_available=None):
"""Return the highest-Arena-rated local variants within the comfortable cutoff."""
# Inspect every tool-capable family returned by the live catalog so a new
# frontier family cannot disappear merely because it was added later.
families = catalog.families(sort="popular", refresh=refresh)
families = [family for family in families if "tools" in family.get("capabilities", [])]
scores = catalog.arena_scores(refresh=refresh)
rows, errors = [], []
with ThreadPoolExecutor(max_workers=12) as pool:
futures = {pool.submit(catalog.variants, family["name"], refresh=refresh): family
for family in families}
for future in as_completed(futures):
family = futures[future]
try:
for item in future.result():
fit = hardware.fit(item.get("size_bytes"), budget)
# The shortlist is intentionally stricter than Browse: it
# only recommends models inside the user-selected
# comfortable threshold. Near-limit models remain
# discoverable in Browse, but never displace a comfortable
# candidate in the top five.
if item.get("cloud") or fit != "headroom":
continue
if (disk_available is not None and item.get("size_bytes")
and item["size_bytes"] + 3 * hardware.GIB > disk_available):
continue
benchmark = _benchmark_match(family["name"], item, scores)
if not benchmark:
continue
rows.append(dict(item, family=family["name"], benchmark=benchmark,
profile=("coding specialist" if re.search(r"coder|coding|code", item["tag"], re.I)
else "general coding")))
except (RuntimeError, ValueError, OSError) as exc:
errors.append(f"{family['name']}: {exc}")
# Keep the largest comfortable representation per family.
best = {}
for item in rows:
item["fit_rank"] = 0
old = best.get(item["family"])
if old is None or (item["fit_rank"], -(item.get("size_bytes") or 0)) < (old["fit_rank"], -(old.get("size_bytes") or 0)):
best[item["family"]] = item
rows = list(best.values())
rows.sort(key=lambda item: (-item["benchmark"]["rating"], item["fit_rank"], item.get("size_bytes") or 0))
out = []
for item in rows[:limit]:
item["reason"] = (f"Arena #{item['benchmark']['rank']} ({item['benchmark']['rating']:.0f}) · "
f"{fit_label(item, budget)} · {item['profile']}")
out.append(item)
if errors:
for message in errors:
print("Recommendation warning: " + message, file=sys.stderr)
return out
def recommendation_rows(items, budget):
rows = []
for rank, item in enumerate(items, 1):
size = item.get("size_bytes")
size_text = f"{size / 10**9:.1f} GB" if size else "size unknown"
benchmark = item.get("benchmark")
benchmark_text = (f"Arena #{benchmark['rank']} ({benchmark['rating']:.0f})"
if benchmark else "Arena unranked")
rows.append((str(rank), f"{item['tag']} | {size_text} | {benchmark_text} | "
f"{fit_label(item, budget)} | {item['profile']}"))
return rows
def show_model(item, budget, device=None):
"""Explain one candidate in terms a non-specialist can act on."""
size = item.get("size_bytes")
print(f"\n{item['tag']}")
print(f"Download: {size / 10**9:.1f} GB | approximate weight-memory floor: {size / hardware.GIB:.1f} GiB"
if size else "Size: unknown")
print(f"Fit for this machine: {fit_label(item, budget)} (weight budget {budget / hardware.GIB:.1f} GiB)"
if budget else "Fit for this machine: unknown")
if item.get("context"):
print(f"Published context window: {item['context']:,} tokens")
print(f"Use case: {item.get('profile', 'coding model')}")
benchmark = item.get("benchmark")
if benchmark:
print(f"Arena text overall: rank #{benchmark['rank']}, rating {benchmark['rating']:.0f} "
f"({benchmark.get('published') or 'date unknown'})")
print(f"Benchmark source: {benchmark['url']}")
else:
print("Arena text overall: no confidently matched score")
print("Note: fit is a weight-footprint estimate; context/KV cache and speed can change actual memory use.")
def helpful_browse(choose, budget, refresh=False, disk_available=None):
"""Browse a flat, fit-first shortlist rather than a taxonomy of families."""
query = input("Filter models (blank = all; e.g. gemma, qwen, coder): ").strip()
families = catalog.families(query=query, sort="popular", refresh=refresh)
scores = catalog.arena_scores(refresh=refresh)
candidates, errors = [], []
with ThreadPoolExecutor(max_workers=12) as pool:
futures = {pool.submit(catalog.variants, family["name"], refresh=refresh): family
for family in families if "tools" in family.get("capabilities", [])}
for future in as_completed(futures):
family = futures[future]
try:
variants = future.result()
local = []
for item in variants:
fit = hardware.fit(item.get("size_bytes"), budget)
if item.get("cloud") or fit not in ("headroom", "tight"):
continue
if (disk_available is not None and item.get("size_bytes")
and item["size_bytes"] + 3 * hardware.GIB > disk_available):
continue
item = dict(item, family=family["name"], profile=(
"coding specialist" if re.search(r"coder|coding|code", item["tag"], re.I)
else "general coding"))
benchmark = _benchmark_match(family["name"], item, scores)
if benchmark:
item["benchmark"] = benchmark
local.append(item)
if not local:
continue
# One representative per family keeps Browse useful while still
# exposing the exact tag that will be installed.
local.sort(key=lambda item: (
0 if item.get("benchmark") else 1,
-(item.get("benchmark") or {}).get("rating", 0),
0 if hardware.fit(item.get("size_bytes"), budget) == "headroom" else 1,
-(item.get("size_bytes") or 0)))
candidates.append(local[0])
except (RuntimeError, ValueError, OSError) as exc:
errors.append(f"{family['name']}: {exc}")
fit_order = {"headroom": 0, "tight": 1}
candidates.sort(key=lambda item: (
fit_order[hardware.fit(item.get("size_bytes"), budget)],
-(item.get("benchmark") or {}).get("rating", 0),
item.get("family", "")))
rows = []
for i, item in enumerate(candidates):
size = item.get("size_bytes")
size_text = f"{size / 10**9:.1f} GB" if size else "size unknown"
benchmark = item.get("benchmark")
benchmark_text = (f"Arena #{benchmark['rank']} ({benchmark['rating']:.0f})"
if benchmark else "Arena unranked")
rows.append((str(i), f"{item['tag']} | {size_text} | {benchmark_text} | "
f"{fit_label(item, budget)} | {item['profile']}"))
if errors:
for message in errors:
print("Browse warning: " + message, file=sys.stderr)
if not rows:
print("No local tool-capable models with known sizes fit this host.", file=sys.stderr)
return None
comfort = hardware.comfortable_percent()
print(f"Showing {len(rows)} local representatives; comfortable fits are listed first "
f"(<= {comfort:.0f}% of budget).")
picked = choose("Browse", rows,
header=("# | Model | Download | Benchmark | Fit | Role\n"
f"Fit: comfortable <= {comfort:.0f}% budget; near limit {comfort:.0f}-100%; "
"too large >100%; hosted = remote"),
table=True)
return candidates[int(picked)] if picked is not None else None
def inspect(tag, refresh=False):
details = catalog.manifest(tag, refresh=refresh)
print(f"Model: {tag}")
for key, label in (("size_bytes", "Manifest download bytes"), ("weight_bytes", "Model/projector weight bytes")):
value = details.get(key)
print(f"{label}: {value:,} ({value / 10**9:.2f} GB)" if value else f"{label}: unknown")
print("Hosted/remote:", "yes" if details.get("remote") else "not indicated by manifest")
print("Model page: https://ollama.com/" + ("library/" if "/" not in tag else "") + tag)
print("These are registry facts, not measurements of runtime memory or coding quality.")
return details
def browse(choose, budget, query="", sort="newest", refresh=False):
items = catalog.families(query=query, sort=sort, refresh=refresh)
status()
print("Descriptions and capability badges are publisher metadata, not verified benchmarks.")
selected = choose("Family", family_rows(items))
if selected is None:
return None
family = items[int(selected)]
print(f"{family['name']}: {family.get('description', '')}\nSource: {family['url']}")
variants = catalog.variants(family["name"], refresh=refresh)
status()
selected = choose("Variant", variant_rows(variants, budget))
return variants[int(selected)] if selected is not None else None
"""Host discovery and explicitly approximate model-weight budgets, without device presets."""
import csv
import io
import os
from pathlib import Path
import platform
import subprocess
GIB = 1024 ** 3
DEFAULT_COMFORTABLE_FRACTION = 0.40
def _env_comfortable_fraction():
raw = os.environ.get("LOCAL_CODE_COMFORTABLE_PERCENT")
try:
value = float(raw) / 100 if raw is not None else DEFAULT_COMFORTABLE_FRACTION
except (TypeError, ValueError):
return DEFAULT_COMFORTABLE_FRACTION
return value if 0.01 <= value < 1 else DEFAULT_COMFORTABLE_FRACTION
COMFORTABLE_FRACTION = _env_comfortable_fraction()
def set_comfortable_fraction(value):
"""Set the human-facing comfortable-fit cutoff for this invocation."""
value = float(value)
if not 0.01 <= value < 1:
raise ValueError("comfortable threshold must be between 1 and 99 percent")
global COMFORTABLE_FRACTION
COMFORTABLE_FRACTION = value
def comfortable_percent():
return COMFORTABLE_FRACTION * 100
def output(argv):
try:
result = subprocess.run(argv, capture_output=True, text=True, timeout=5)
return result.stdout.strip() if result.returncode == 0 else ""
except (OSError, subprocess.TimeoutExpired):
return ""
def host():
system = platform.system()
memory = available = None
if system == "Darwin":
raw = output(["sysctl", "-n", "hw.memsize"])
memory = int(raw) if raw.isdigit() else None
name = output(["sysctl", "-n", "machdep.cpu.brand_string"]) or platform.machine()
if platform.machine() == "arm64":
return {"name": name, "kind": "Apple unified memory", "capacity": memory,
"basis": "total unified memory (also used by the OS and applications)",
"ram": memory, "swap": output(["sysctl", "-n", "vm.swapusage"])}
if system == "Linux":
try:
rows = {line.split(":", 1)[0]: int(line.split()[1]) * 1024
for line in Path("/proc/meminfo").read_text().splitlines()
if line.startswith(("MemTotal:", "MemAvailable:"))}
memory, available = rows.get("MemTotal"), rows.get("MemAvailable")
except (OSError, ValueError):
pass
if memory is None and hasattr(os, "sysconf"):
try:
memory = os.sysconf("SC_PHYS_PAGES") * os.sysconf("SC_PAGE_SIZE")
except (OSError, ValueError):
pass
if system == "Windows":
raw = output(["powershell", "-NoProfile", "-Command",
"(Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory"])
memory = int(raw) if raw.isdigit() else None
gpus = []
raw = output(["nvidia-smi", "--query-gpu=name,memory.total,memory.free",
"--format=csv,noheader,nounits"])
for row in csv.reader(io.StringIO(raw)):
if len(row) == 3:
try:
gpus.append({"name": row[0].strip(), "total": int(row[1]) * 1024**2,
"free": int(row[2]) * 1024**2})
except ValueError:
pass
if gpus:
chosen = max(gpus, key=lambda gpu: gpu["free"])
return {"name": chosen["name"], "kind": "NVIDIA GPU", "capacity": chosen["free"],
"basis": "free VRAM on the single GPU with most free memory; GPUs are not summed",
"ram": memory, "gpus": gpus}
return {"name": platform.processor() or platform.machine(), "kind": "CPU / unrecognized accelerator",
"capacity": available or memory, "ram": memory,
"basis": "available system RAM" if available else "total system RAM"}
def budget(device, override=None, fraction=0.75):
if override is not None:
return override * GIB
return device["capacity"] * fraction if device.get("capacity") else None
def fit(size, weight_budget, comfortable_fraction=None):
if not size or not weight_budget:
return "unknown"
threshold = COMFORTABLE_FRACTION if comfortable_fraction is None else comfortable_fraction
if size <= weight_budget * threshold:
return "headroom"
if size <= weight_budget:
return "tight"
return "over budget"
def describe(device, weight_budget):
capacity = f"{device['capacity'] / GIB:.1f} GiB" if device.get("capacity") else "unknown"
allocated = f"{weight_budget / GIB:.1f} GiB" if weight_budget else "unknown (set --budget-gib)"
return (f"{device['name']} | {device['kind']} | detected capacity {capacity}\n"
f"Basis: {device['basis']}\nWeight budget: {allocated}. "
f"Comfortable threshold: {comfortable_percent():.0f}% of weight budget. "
"Fit labels are heuristics; context/cache and runtime overhead are not measured.")
import json, tempfile, unittest
from pathlib import Path
from unittest import mock
import local_catalog as c
class CatalogTests(unittest.TestCase):
def test_families_and_variants_parse_and_dedupe(self):
html = '<a href="/library/foo" class="group"><span>Foo</span><p>tools vision 2 months ago</p></a><a href="/library/foo/">bad</a>'
with mock.patch.object(c, "_fetch", return_value=html):
self.assertEqual(c.families()[0]["name"], "foo")
tags = '<a href="/library/foo%3A1b">1B 4.2GB 32K 1 month ago GGUF</a><a href="/library/foo:1b">duplicate</a>'
with mock.patch.object(c, "_fetch", return_value=tags):
got = c.variants("foo"); self.assertEqual(len(got), 1); self.assertEqual(got[0]["size_bytes"], 4200000000)
def test_cache_is_atomic_and_stale_status(self):
with tempfile.TemporaryDirectory() as d, mock.patch.object(c, "CACHE_DIR", Path(d)), mock.patch.object(c.urllib.request, "urlopen", side_effect=OSError("offline")):
url = "https://example.invalid/data"; c._write_cache(url, "cached"); self.assertEqual(c._fetch(url, True), "cached"); self.assertTrue(c.FETCH_STATUS[url]["stale"])
def test_manifest_validation_and_size(self):
raw = {"config": {"size": 10}, "layers": [{"size": 20, "mediaType": "model"}, {"size": 3, "mediaType": "projector"}]}
with mock.patch.object(c, "_fetch", return_value=json.dumps(raw)):
got = c.manifest("foo:latest"); self.assertEqual(got["size_bytes"], 33); self.assertEqual(got["weight_bytes"], 23)
with self.assertRaises(ValueError): c.manifest("https://evil.example/x")
def test_arena_scores_keeps_overall_rows(self):
payload = {"rows": [{"row": {"category": "overall", "model_name": "foo-7b",
"rating": 1234, "rank": 4,
"leaderboard_publish_date": "2026-09-14"}},
{"row": {"category": "coding", "model_name": "foo-7b",
"rating": 999, "rank": 99}}]}
with mock.patch.object(c, "_fetch", return_value=json.dumps(payload)):
got = c.arena_scores()
self.assertEqual(len(got), 1)
self.assertEqual(got[0]["rating"], 1234.0)
if __name__ == "__main__": unittest.main()
"""Launcher tests: no downloads, inference, cache writes, or agent launches."""
import contextlib
import importlib.util
from importlib.machinery import SourceFileLoader
import io
from pathlib import Path
import tempfile
import unittest
from unittest import mock
SCRIPT = Path(__file__).with_name("local-code")
SPEC = importlib.util.spec_from_loader("local_code", SourceFileLoader("local_code", str(SCRIPT)))
mod = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(mod)
class LauncherTests(unittest.TestCase):
def setUp(self):
self.out = io.StringIO()
self.redirect = contextlib.redirect_stdout(self.out)
self.redirect.__enter__()
self.addCleanup(self.redirect.__exit__, None, None, None)
def test_host_budget_is_detected_without_model_presets(self):
with mock.patch.object(mod.hardware, "host", return_value={"name": "test", "kind": "RAM", "capacity": 8 * mod.hardware.GIB, "basis": "test"}):
device, budget = mod.hardware_snapshot(override=None)
self.assertEqual(device["name"], "test")
self.assertEqual(budget, 6 * mod.hardware.GIB)
def test_comfortable_threshold_defaults_to_40_percent(self):
self.assertEqual(mod.hardware.DEFAULT_COMFORTABLE_FRACTION, 0.40)
def test_settings_flow_persists_threshold(self):
with tempfile.TemporaryDirectory() as directory, \
mock.patch.object(mod, "settings_path", return_value=Path(directory) / "settings.json"), \
mock.patch.object(mod, "choose", side_effect=["threshold", "done"]), \
mock.patch.object(mod, "input", return_value="25"):
self.assertEqual(mod.settings_flow(), 0)
self.assertEqual(mod.load_settings()["comfortable_percent"], 25)
def test_dry_run_accepts_any_model_and_does_no_network(self):
with mock.patch.object(mod, "server_info", side_effect=AssertionError("network")), \
mock.patch.object(mod.os, "execvpe", side_effect=AssertionError("launch")):
self.assertEqual(mod.main(["--dry-run", "--model", "qwen3.8:27b", "--agent", "claude", "--context", "16384"]), 0)
text = self.out.getvalue()
self.assertIn("qwen3.8:27b", text)
self.assertIn("no network", text)
def test_dry_run_preserves_quoted_agent_arguments(self):
with mock.patch.object(mod, "server_info", side_effect=AssertionError("network")):
mod.main(["--dry-run", "--model", "namespace/model:tag", "--agent", "claude", "--", "-p", 'a "quoted" prompt'])
self.assertIn('a "quoted" prompt', self.out.getvalue())
def test_forbid_cloud_urls_and_remote_provider_overrides(self):
with self.assertRaisesRegex(ValueError, "local models"):
mod.validate_tag("qwen3.8:27b-cloud")
with self.assertRaises(ValueError):
mod.agent_command("codex", "alias", 8192, ["--profile=remote"])
def test_agent_safety_flags_are_passed_through(self):
command, _ = mod.agent_command("claude", "alias", 8192, ["--dangerously-skip-permissions"])
self.assertIn("--dangerously-skip-permissions", command)
command, _ = mod.agent_command("codex", "alias", 8192, ["--yolo", "-p", "hello"])
self.assertEqual(command[-3:], ["--yolo", "-p", "hello"])
self.assertNotIn("model_providers.ollama.base_url", command)
def test_benchmark_matching_rejects_wrong_parameter_count(self):
scores = [{"model_name": "qwen3-vl-235b-a22b-instruct", "rating": 1400, "rank": 1}]
item = {"tag": "qwen3-vl:2b-instruct"}
self.assertIsNone(mod.explore._benchmark_match("qwen3-vl", item, scores))
def test_recommendations_exclude_near_limit_models(self):
families = [{"name": "comfortable-7b", "capabilities": ["tools"]},
{"name": "tight-14b", "capabilities": ["tools"]}]
variants = {
"comfortable-7b": [{"tag": "comfortable-7b:7b", "size_bytes": 3, "cloud": False}],
"tight-14b": [{"tag": "tight-14b:14b", "size_bytes": 7, "cloud": False}],
}
scores = [{"model_name": "comfortable-7b", "rating": 1000, "rank": 2},
{"model_name": "tight-14b", "rating": 2000, "rank": 1}]
with mock.patch.object(mod.explore.catalog, "families", return_value=families), \
mock.patch.object(mod.explore.catalog, "variants", side_effect=lambda name, refresh=False: variants[name]), \
mock.patch.object(mod.explore.catalog, "arena_scores", return_value=scores), \
mock.patch.object(mod.explore.hardware, "fit", side_effect=lambda size, budget: "headroom" if size <= 4 else "tight"):
items = mod.explore.recommend(10, limit=5)
self.assertEqual([item["tag"] for item in items], ["comfortable-7b:7b"])
def test_menu_cancel_does_not_write(self):
with mock.patch.object(mod, "hardware_snapshot", return_value=({}, None)), \
mock.patch.object(mod, "choose", return_value=None), \
mock.patch.object(mod, "server_info", side_effect=AssertionError("network")), \
mock.patch.object(mod.os, "execvpe", side_effect=AssertionError("launch")):
self.assertEqual(mod.main([]), 0)
def test_missing_model_requires_consent_before_pull(self):
tags = []
with mock.patch.object(mod, "server_info", return_value=("0.99.0", tags, None)), \
mock.patch.object(mod, "api", return_value={"size": 10}), \
mock.patch.object(mod.catalog, "manifest", return_value={"size_bytes": 100, "weight_bytes": 50, "remote": False}), \
mock.patch.object(mod.sys.stdin, "isatty", return_value=False), \
mock.patch.object(mod, "pull") as pull, \
mock.patch.object(mod, "free_bytes", return_value=10**12), \
mock.patch.object(mod, "hardware_snapshot", return_value=({}, 10**12)), \
mock.patch.object(mod.shutil, "which", return_value="/bin/tool"):
with self.assertRaisesRegex(RuntimeError, "--yes"):
mod.main(["--model", "qwen3.8:27b", "--agent", "chat"])
pull.assert_not_called()
def test_installed_model_uses_ollama_managed_claude_adapter(self):
tags = [{"name": "qwen3.8:27b", "size": 100}]
details = {"modelfile": "FROM /models/blob\n", "template": "x", "system": "", "parameters": ""}
with mock.patch.object(mod, "server_info", return_value=("0.99.0", tags, None)), \
mock.patch.object(mod, "api", return_value=details), \
mock.patch.object(mod.os, "execvpe") as execute, \
mock.patch.object(mod, "hardware_snapshot", return_value=({}, 10**12)), \
mock.patch.object(mod.shutil, "which", return_value="/bin/tool"):
mod.main(["--model", "qwen3.8:27b", "--agent", "claude"])
command, argv, env = execute.call_args.args
self.assertEqual(command, "ollama")
self.assertEqual(argv[0:5], ["ollama", "launch", "claude", "--model", "qwen3.8:27b"])
self.assertEqual(argv[5], "--")
self.assertEqual(env["OLLAMA_HOST"], mod.ENDPOINT)
def test_aider_uses_installed_tag_without_creating_wrapper(self):
command, env = mod.agent_command("aider", "granite4.1:8b-q8_0", 32768, [])
self.assertEqual(command, ["aider", "--model", "ollama_chat/granite4.1:8b-q8_0"])
self.assertEqual(env["OLLAMA_API_BASE"], mod.ENDPOINT)
def test_claudish_uses_local_ollama_provider_and_preserves_flags(self):
with mock.patch.object(mod.shutil, "which", return_value="/opt/homebrew/bin/claudish"):
command, env = mod.agent_command("claudish", "granite4.1:8b-q8_0", 32768, ["-y"])
self.assertEqual(command, ["claudish", "--model", "ollama@granite4.1:8b-q8_0", "-y"])
self.assertIn("/opt/homebrew/bin", env["PATH"])
def test_multi_agent_adapters_use_local_ollama(self):
command, env = mod.agent_command("opencode", "granite4.1:8b-q8_0", 32768, [])
self.assertEqual(command[:5], ["ollama", "launch", "opencode", "--model", "granite4.1:8b-q8_0"])
command, env = mod.agent_command("goose", "granite4.1:8b-q8_0", 32768, [])
self.assertEqual(command, ["goose"])
self.assertEqual(env["GOOSE_PROVIDER"], "ollama")
self.assertEqual(env["GOOSE_MODEL"], "granite4.1:8b-q8_0")
def test_use_hides_legacy_synthetic_profiles_but_remove_can_still_list_them(self):
tags = [{"name": "granite4.1:8b-q8_0", "size": 1},
{"name": "local-code-granite4-1-8b-32768:latest", "size": 1}]
self.assertEqual([row["tag"] for row in mod._launch_items(tags, 10)], ["granite4.1:8b-q8_0"])
self.assertEqual(len(mod._installed_items(tags, 10)), 2)
def test_noninteractive_diagnostics_show_host_without_serials(self):
with mock.patch.object(mod, "server_info", return_value=(None, [], RuntimeError("offline"))), \
mock.patch.object(mod, "hardware_snapshot", return_value=({}, None)), \
mock.patch.object(mod, "command_output", return_value="unavailable"):
self.assertEqual(mod.main(["--doctor"]), 0)
self.assertNotIn("Serial", self.out.getvalue())
def test_remove_flow_confirms_selected_model(self):
tags = [{"name": "old-model:7b", "size": 7_000_000_000}]
with mock.patch.object(mod, "server_info", return_value=("0.99.0", tags, None)), \
mock.patch.object(mod, "choose", side_effect=["0", None]) as choose, \
mock.patch.object(mod, "input", return_value="y"), \
mock.patch.object(mod.subprocess, "run") as run, \
mock.patch.object(mod, "free_bytes", return_value=10**12), \
mock.patch.object(mod.shutil, "which", return_value="/bin/ollama"):
self.assertEqual(mod.remove_flow(10**12), 0)
self.assertEqual(run.call_args.args[0], ["ollama", "rm", "old-model:7b"])
if __name__ == "__main__":
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment