Text Generation
GGUF
English
uncensored
lower-refusal
coding
reasoning
tool-use
cybersecurity
local-ai
ollama
conversational
Instructions to use studiobrn/modHacker with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use studiobrn/modHacker with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf studiobrn/modHacker:F16 # Run inference directly in the terminal: llama cli -hf studiobrn/modHacker:F16
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf studiobrn/modHacker:F16 # Run inference directly in the terminal: llama cli -hf studiobrn/modHacker:F16
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf studiobrn/modHacker:F16 # Run inference directly in the terminal: ./llama-cli -hf studiobrn/modHacker:F16
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf studiobrn/modHacker:F16 # Run inference directly in the terminal: ./build/bin/llama-cli -hf studiobrn/modHacker:F16
Use Docker
docker model run hf.co/studiobrn/modHacker:F16
- LM Studio
- Jan
- vLLM
How to use studiobrn/modHacker with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "studiobrn/modHacker" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "studiobrn/modHacker", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/studiobrn/modHacker:F16
- Ollama
How to use studiobrn/modHacker with Ollama:
ollama run hf.co/studiobrn/modHacker:F16
- Unsloth Desktop
- Pi
How to use studiobrn/modHacker with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf studiobrn/modHacker:F16
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "studiobrn/modHacker:F16" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use studiobrn/modHacker with Docker Model Runner:
docker model run hf.co/studiobrn/modHacker:F16
- Lemonade
How to use studiobrn/modHacker with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull studiobrn/modHacker:F16
Run and chat with the model
lemonade run user.modHacker-F16
List all available models
lemonade list
- Hermes Agent
How to use studiobrn/modHacker with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf studiobrn/modHacker:F16
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default studiobrn/modHacker:F16
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use studiobrn/modHacker with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf studiobrn/modHacker:F16
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "studiobrn/modHacker:F16" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
| from __future__ import annotations | |
| import json | |
| import logging | |
| import shutil | |
| import subprocess | |
| import threading | |
| from pathlib import Path | |
| from typing import Any, Dict, Optional, Tuple | |
| from flask import Flask, jsonify, render_template, request | |
| logger = logging.getLogger("modInteractive.admin") | |
| BASE_DIR = Path(__file__).resolve().parents[1] | |
| app = Flask( | |
| __name__, | |
| template_folder=str(Path(__file__).resolve().parent / "templates"), | |
| static_folder=str(Path(__file__).resolve().parent / "static"), | |
| ) | |
| _config: Optional[Any] = None | |
| _config_data: Dict[str, Any] = {} | |
| _config_path: Path = BASE_DIR / "config.json" | |
| def init(config: Any, config_path: str) -> None: | |
| global _config, _config_data, _config_path | |
| _config = config | |
| _config_path = Path(config_path).expanduser().resolve() | |
| try: | |
| if hasattr(config, "reload"): | |
| config.reload() | |
| elif hasattr(config, "load"): | |
| config.load() | |
| data = getattr(config, "data", {}) | |
| _config_data = dict(data) if isinstance(data, dict) else {} | |
| except Exception: | |
| logger.exception("Admin config initialization failed") | |
| _config_data = _read_config_file() | |
| def index() -> str: | |
| return render_template("index.html") | |
| def favicon() -> tuple[str, int]: | |
| return "", 204 | |
| def get_config() -> Any: | |
| data = _read_config_file() | |
| if not data: | |
| return jsonify({"error": "Config not found or empty"}), 404 | |
| return jsonify(data) | |
| def update_config() -> Any: | |
| global _config_data | |
| data = request.get_json(silent=True) | |
| if not isinstance(data, dict): | |
| return jsonify({"error": "JSON object required"}), 400 | |
| try: | |
| if _config is not None and hasattr(_config, "update"): | |
| _config.update(data, save=True) | |
| _config_data = dict(_config.data) | |
| else: | |
| current = _read_config_file() | |
| merged = _deep_merge(current, data) | |
| _write_config_file(merged) | |
| _config_data = merged | |
| return jsonify( | |
| { | |
| "status": "ok", | |
| "message": "Configuration updated", | |
| "config": _config_data, | |
| } | |
| ) | |
| except Exception as exc: | |
| logger.exception("Config update failed") | |
| return jsonify({"error": str(exc)}), 500 | |
| def reload_config() -> Any: | |
| global _config_data | |
| try: | |
| if _config is not None and hasattr(_config, "reload"): | |
| _config.reload() | |
| _config_data = dict(_config.data) | |
| else: | |
| _config_data = _read_config_file() | |
| return jsonify( | |
| { | |
| "status": "ok", | |
| "message": "Configuration reloaded", | |
| "config": _config_data, | |
| } | |
| ) | |
| except Exception as exc: | |
| logger.exception("Config reload failed") | |
| return jsonify({"error": str(exc)}), 500 | |
| def get_status() -> Any: | |
| data = _current_config() | |
| trigger_source = str(_nested_get(data, "trigger.source", "camera")).lower().strip() | |
| status: Dict[str, Any] = { | |
| "admin": "running", | |
| "project_root": str(BASE_DIR), | |
| "config_path": str(_config_path), | |
| "trigger_source": trigger_source, | |
| "opencv_available": False, | |
| "opencv_version": None, | |
| "camera_available": False, | |
| "camera_resolution": None, | |
| "pir_available": False, | |
| "pir_gpio_pin": _safe_int(_nested_get(data, "pir.gpio_pin", 17), 17, 0, 27), | |
| "pir_state": None, | |
| "video_exists": False, | |
| "video_path": None, | |
| "mpv_available": shutil.which("mpv") is not None, | |
| "mpv_path": shutil.which("mpv"), | |
| } | |
| if trigger_source == "camera": | |
| _fill_camera_status(status, data) | |
| elif trigger_source == "pir": | |
| _fill_pir_status(status, data) | |
| video_path = _resolve_video_path(str(_nested_get(data, "video.path", "videos/selamlama.mp4"))) | |
| status["video_path"] = str(video_path) | |
| status["video_exists"] = video_path.is_file() | |
| if video_path.is_file(): | |
| status["video_size_mb"] = round(video_path.stat().st_size / (1024 * 1024), 2) | |
| return jsonify(status) | |
| def test_video() -> Any: | |
| data = _current_config() | |
| mpv_path = shutil.which(str(_nested_get(data, "video.player", "mpv"))) | |
| if not mpv_path: | |
| return jsonify({"error": "mpv not installed. Install: sudo apt install mpv"}), 500 | |
| requested_video = None | |
| body = request.get_json(silent=True) | |
| if isinstance(body, dict): | |
| requested_video = body.get("path") | |
| video_value = str(requested_video or _nested_get(data, "video.path", "videos/selamlama.mp4")) | |
| video_path = _resolve_video_path(video_value) | |
| allowed, reason = _is_allowed_video_path(video_path) | |
| if not allowed: | |
| return jsonify({"error": reason}), 403 | |
| if not video_path.is_file(): | |
| return jsonify({"error": f"Video not found: {video_path}"}), 404 | |
| fullscreen = bool(_nested_get(data, "video.fullscreen", True)) | |
| volume = _safe_int(_nested_get(data, "video.volume", 90), 90, 0, 100) | |
| command = [ | |
| mpv_path, | |
| "--no-terminal", | |
| "--really-quiet", | |
| "--keep-open=no", | |
| "--osd-level=0", | |
| f"--volume={volume}", | |
| ] | |
| if fullscreen: | |
| command.extend(["--fs", "--no-border", "--ontop", "--cursor-autohide=always"]) | |
| command.append(str(video_path)) | |
| try: | |
| subprocess.Popen( | |
| command, | |
| stdin=subprocess.DEVNULL, | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL, | |
| shell=False, | |
| start_new_session=True, | |
| ) | |
| return jsonify( | |
| { | |
| "status": "ok", | |
| "message": "Video playback started", | |
| "video": str(video_path), | |
| } | |
| ) | |
| except Exception as exc: | |
| logger.exception("Test video failed") | |
| return jsonify({"error": str(exc)}), 500 | |
| def get_logs() -> Any: | |
| limit = _safe_int(request.args.get("limit", 100), 100, 1, 1000) | |
| log_file = BASE_DIR / "logs" / "modinteractive.log" | |
| if not log_file.exists(): | |
| return jsonify({"logs": []}) | |
| try: | |
| lines = log_file.read_text(encoding="utf-8", errors="replace").splitlines() | |
| return jsonify({"logs": lines[-limit:]}) | |
| except Exception as exc: | |
| logger.exception("Reading logs failed") | |
| return jsonify({"error": str(exc)}), 500 | |
| def run_server(host: str = "0.0.0.0", port: int = 8080) -> None: | |
| try: | |
| logger.info("Admin panel starting on http://%s:%d", host, port) | |
| app.run( | |
| host=host, | |
| port=int(port), | |
| debug=False, | |
| use_reloader=False, | |
| threaded=True, | |
| ) | |
| except Exception: | |
| logger.exception("Admin panel failed to start") | |
| def start_admin_thread(config: Any, config_path: str) -> threading.Thread: | |
| init(config, config_path) | |
| host = str(config.get("admin.host", "0.0.0.0")) | |
| port = _safe_int(config.get("admin.port", 8080), 8080, 1, 65535) | |
| thread = threading.Thread( | |
| target=run_server, | |
| args=(host, port), | |
| daemon=True, | |
| name="modinteractive-admin-server", | |
| ) | |
| thread.start() | |
| logger.info("Admin panel thread started on port %d", port) | |
| return thread | |
| def _fill_camera_status(status: Dict[str, Any], data: Dict[str, Any]) -> None: | |
| try: | |
| import cv2 | |
| status["opencv_available"] = True | |
| status["opencv_version"] = cv2.__version__ | |
| camera_index = _normalize_camera_index( | |
| _nested_get(data, "camera.index", 0) | |
| ) | |
| camera_width = _safe_int(_nested_get(data, "camera.width", 640), 640, 1) | |
| camera_height = _safe_int(_nested_get(data, "camera.height", 480), 480, 1) | |
| camera_fps = _safe_int(_nested_get(data, "camera.fps", 15), 15, 1) | |
| camera_backend = str(_nested_get(data, "camera.backend", "v4l2")).lower() | |
| if camera_backend == "v4l2" and hasattr(cv2, "CAP_V4L2"): | |
| cap = cv2.VideoCapture(camera_index, cv2.CAP_V4L2) | |
| else: | |
| cap = cv2.VideoCapture(camera_index) | |
| try: | |
| if cap.isOpened(): | |
| cap.set(cv2.CAP_PROP_FRAME_WIDTH, camera_width) | |
| cap.set(cv2.CAP_PROP_FRAME_HEIGHT, camera_height) | |
| cap.set(cv2.CAP_PROP_FPS, camera_fps) | |
| ok, frame = cap.read() | |
| if ok and frame is not None: | |
| status["camera_available"] = True | |
| status["camera_resolution"] = f"{frame.shape[1]}x{frame.shape[0]}" | |
| else: | |
| status["camera_available"] = True | |
| status["camera_resolution"] = "opened but no frame" | |
| finally: | |
| cap.release() | |
| except Exception as exc: | |
| status["camera_error"] = str(exc) | |
| def _fill_pir_status(status: Dict[str, Any], data: Dict[str, Any]) -> None: | |
| """Report PIR status without trying to claim GPIO already owned by the app. | |
| The admin panel runs inside the same application process. When PIR mode is active, | |
| the main loop already owns the GPIO line, so opening the same BCM pin again from | |
| the status endpoint may produce "GPIO busy". For the dashboard we report the | |
| configured pin and that the GPIO is controlled by the running app. The standalone | |
| health check and tools/test_pir.py still perform real GPIO read tests. | |
| """ | |
| status["pir_available"] = True | |
| status["pir_state"] = "managed_by_application" | |
| status["pir_note"] = "GPIO is controlled by the running modInteractive process" | |
| def _current_config() -> Dict[str, Any]: | |
| if _config is not None: | |
| try: | |
| data = getattr(_config, "data", None) | |
| if isinstance(data, dict): | |
| return dict(data) | |
| except Exception: | |
| logger.debug("Could not read live config object", exc_info=True) | |
| return _read_config_file() | |
| def _read_config_file() -> Dict[str, Any]: | |
| try: | |
| if not _config_path.exists(): | |
| return {} | |
| data = json.loads(_config_path.read_text(encoding="utf-8")) | |
| if isinstance(data, dict): | |
| return data | |
| return {} | |
| except Exception: | |
| logger.exception("Could not read config file") | |
| return {} | |
| def _write_config_file(data: Dict[str, Any]) -> None: | |
| _config_path.parent.mkdir(parents=True, exist_ok=True) | |
| _config_path.write_text( | |
| json.dumps(data, indent=2, ensure_ascii=False) + "\n", | |
| encoding="utf-8", | |
| ) | |
| def _deep_merge(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]: | |
| result = dict(base) | |
| for key, value in override.items(): | |
| if isinstance(result.get(key), dict) and isinstance(value, dict): | |
| result[key] = _deep_merge(result[key], value) | |
| else: | |
| result[key] = value | |
| return result | |
| def _nested_get(data: Dict[str, Any], key_path: str, default: Any = None) -> Any: | |
| value: Any = data | |
| for key in key_path.split("."): | |
| if isinstance(value, dict) and key in value: | |
| value = value[key] | |
| else: | |
| return default | |
| return value | |
| def _resolve_video_path(value: str) -> Path: | |
| path = Path(value).expanduser() | |
| if not path.is_absolute(): | |
| path = BASE_DIR / path | |
| return path.resolve() | |
| def _is_allowed_video_path(path: Path) -> Tuple[bool, str]: | |
| videos_dir = (BASE_DIR / "videos").resolve() | |
| try: | |
| path.relative_to(videos_dir) | |
| return True, "" | |
| except ValueError: | |
| return False, f"Only files under videos directory are allowed: {videos_dir}" | |
| def _normalize_camera_index(value: Any) -> Any: | |
| if isinstance(value, int): | |
| return value | |
| if isinstance(value, str): | |
| stripped = value.strip() | |
| if stripped.isdigit(): | |
| return int(stripped) | |
| return stripped | |
| try: | |
| return int(value) | |
| except (TypeError, ValueError): | |
| return 0 | |
| def _safe_int( | |
| value: Any, | |
| default: int, | |
| minimum: Optional[int] = None, | |
| maximum: Optional[int] = None, | |
| ) -> int: | |
| try: | |
| number = int(value) | |
| except (TypeError, ValueError): | |
| number = default | |
| if minimum is not None and number < minimum: | |
| number = minimum | |
| if maximum is not None and number > maximum: | |
| number = maximum | |
| return number | |