#!/bin/sh # Last-resort source: queries a local LLM inference server (Ollama or # LM Studio, both common on dev machines) for a fix, only when nothing # cheaper matched. Silent no-op if no local server is reachable -- this # never touches the network, never calls a paid API, and costs nothing # when you don't have a local model running. # # Numbered 70, after every deterministic/heuristic source (10-50) and # before the team-shared source (90): cheap, verified candidates should # always win first. fx.sh's FX_AUTO picks the *first* candidate in # source order, so this only gets auto-selected when it's the only # opinion offered at all -- still review it, this is a guess, not a # fact. Every other default source is either a cached human decision or # a deterministic rule; this is the one source capable of confidently # suggesting something wrong. # # Needs python3 (uses urllib, no extra dependency beyond stdlib) -- # silently no-ops without it rather than ship a fragile hand-rolled # JSON encoder in shell. cmd="$1" [ -n "$cmd" ] || exit 0 command -v python3 >/dev/null 2>&1 || exit 0 OLLAMA_URL="${FX_LLM_OLLAMA_URL:-http://127.0.0.1:11434}" LMSTUDIO_URL="${FX_LLM_LMSTUDIO_URL:-http://127.0.0.1:1234}" python3 - "$cmd" "$FX_OUTPUT" "$OLLAMA_URL" "$LMSTUDIO_URL" <<'PYEOF' import sys, json, urllib.request, urllib.error cmd, output, ollama_url, lmstudio_url = sys.argv[1:5] PROMPT = f"""You are suggesting a fix for a failed shell command, to be taught to a self-healing shell function called f(). Rules: - f() will eval your fix ONCE as a precondition, then re-run the ORIGINAL command verbatim. Your fix never replaces the command; it only sets up state so the original command then succeeds. - If the fix should generalize beyond this one invocation, define a shell function with the same name as the failing program, wrapping the real binary via `command "$@"`, and only special-case the failure inside it. - Respond with EXACTLY ONE LINE of POSIX shell and nothing else: no explanation, no markdown, no code fences, no commentary. Failing command: {cmd} Its output: {output} """ def http_json(url, payload, timeout): req = urllib.request.Request( url, data=json.dumps(payload).encode(), headers={"Content-Type": "application/json"}, method="POST", ) with urllib.request.urlopen(req, timeout=timeout) as r: return json.loads(r.read().decode()) def http_get_json(url, timeout): with urllib.request.urlopen(url, timeout=timeout) as r: return json.loads(r.read().decode()) def try_ollama(): tags = http_get_json(ollama_url.rstrip("/") + "/api/tags", 2) models = tags.get("models") or [] if not models: return None model = models[0].get("name") if not model: return None resp = http_json( ollama_url.rstrip("/") + "/api/generate", {"model": model, "prompt": PROMPT, "stream": False}, 25, ) return (resp.get("response") or "").strip() def try_lmstudio(): models = http_get_json(lmstudio_url.rstrip("/") + "/v1/models", 2) data = models.get("data") or [] if not data: return None model = data[0].get("id") if not model: return None resp = http_json( lmstudio_url.rstrip("/") + "/v1/chat/completions", { "model": model, "messages": [{"role": "user", "content": PROMPT}], "temperature": 0, }, 25, ) choices = resp.get("choices") or [] if not choices: return None return (choices[0].get("message", {}).get("content") or "").strip() for attempt in (try_ollama, try_lmstudio): try: result = attempt() except (urllib.error.URLError, TimeoutError, OSError, ValueError, KeyError): result = None if result: # Enforce the one-line contract even if the model ignores instructions. first_line = result.splitlines()[0].strip() first_line = first_line.strip("`") if first_line: print(first_line) break PYEOF