@@ -206,7 +206,7 @@ def count_tokens(prompt):
206 """fm's exact count (under 0.1 s), or None when it can't give one."""
206 """fm's exact count (under 0.1 s), or None when it can't give one."""
208 p = subprocess.run([FM, "count-tokens", "-q", "-i", prompt.instructions], input=prompt.text,
208 p = subprocess.run([FM, "count-tokens", "-q", "-i", prompt.instructions], input=prompt.text,
209− capture_output=True, text=True, timeout=10, env=fm_env())
209+ capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=10, env=fm_env())
210 except (OSError, subprocess.TimeoutExpired):
210 except (OSError, subprocess.TimeoutExpired):
211 return None
211 return None
212 out = p.stdout.strip()
212 out = p.stdout.strip()
@@ -285,7 +285,7 @@ def _run_fm(prompt, schema, opts, extra):
285 args = [FM, "respond", "--model", opts["model"], "--no-stream", *greedy, "--schema", schema, "-i", prompt.instructions, *extra]
285 args = [FM, "respond", "--model", opts["model"], "--no-stream", *greedy, "--schema", schema, "-i", prompt.instructions, *extra]
286 timeout_s = max(1.0, time_left(opts))
286 timeout_s = max(1.0, time_left(opts))
288− p = subprocess.run(args, input=prompt.text, capture_output=True, text=True, timeout=timeout_s, env=fm_env())
288+ p = subprocess.run(args, input=prompt.text, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout_s, env=fm_env())
289 except FileNotFoundError:
289 except FileNotFoundError:
290 raise Fail("MODEL_UNAVAILABLE", f"fm not found at {FM} — requires macOS 27")
290 raise Fail("MODEL_UNAVAILABLE", f"fm not found at {FM} — requires macOS 27")
291 except subprocess.TimeoutExpired:
291 except subprocess.TimeoutExpired:
@@ -312,7 +312,7 @@ def op_ping(_payload, _opts):
312 "fmMessage": f"fm not found at {FM} — requires macOS 27", "schemasOk": False, "organizeMode": organize_mode()}
312 "fmMessage": f"fm not found at {FM} — requires macOS 27", "schemasOk": False, "organizeMode": organize_mode()}
313 license_required = False
313 license_required = False
315− avail = subprocess.run([FM, "available", "--model", "system"], capture_output=True, text=True, timeout=15, env=fm_env())
315+ avail = subprocess.run([FM, "available", "--model", "system"], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=15, env=fm_env())
316 ok, msg = avail.returncode == 0, (avail.stdout + avail.stderr).strip()[:300]
316 ok, msg = avail.returncode == 0, (avail.stdout + avail.stderr).strip()[:300]
317 if not ok and license_needed(avail.returncode, avail.stdout + avail.stderr):
317 if not ok and license_needed(avail.returncode, avail.stdout + avail.stderr):
318 license_required, msg = True, LICENSE_MESSAGE
318 license_required, msg = True, LICENSE_MESSAGE
@@ -499,7 +499,7 @@ def install_schemas():
499 if "{%s}" % key not in args:
499 if "{%s}" % key not in args:
502− p = subprocess.run([FM, *sub_args], capture_output=True, text=True, timeout=30, env=fm_env())
502+ p = subprocess.run([FM, *sub_args], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=30, env=fm_env())
503 except (FileNotFoundError, subprocess.TimeoutExpired) as e:
503 except (FileNotFoundError, subprocess.TimeoutExpired) as e:
504 print(f" {name}: fm failed ({e})", file=sys.stderr)
504 print(f" {name}: fm failed ({e})", file=sys.stderr)
505 return "error"
505 return "error"
@@ -511,7 +511,7 @@ def install_schemas():
511 subs["{%s}" % key] = p.stdout.strip()
511 subs["{%s}" % key] = p.stdout.strip()
512 args = [subs.get(a, a) for a in args]
512 args = [subs.get(a, a) for a in args]
514− p = subprocess.run([FM, *args], capture_output=True, text=True, timeout=30, env=fm_env())
514+ p = subprocess.run([FM, *args], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=30, env=fm_env())
515 except (FileNotFoundError, subprocess.TimeoutExpired) as e:
515 except (FileNotFoundError, subprocess.TimeoutExpired) as e:
516 print(f" {name}: fm failed ({e})", file=sys.stderr)
516 print(f" {name}: fm failed ({e})", file=sys.stderr)
517 return "error"
517 return "error"