Hidden Solver
A stealthy screen-capture QA tool powered by LLMs. It captures a selected screen region, extracts the question/options, optionally augments with a local knowledge base, and returns a compact answer.
中文说明请见 README.zh-CN.md.
- Features
- Requirements
- Quick Start
- Hotkeys
- Runtime Pipeline
- Glossary
- Configuration Loading
- Configuration Reference
- RAG Document Formats & Cleaning
- Example Configurations
- Advanced Usage
- Best Practices
- Performance & Cost
- Security & Privacy
- Troubleshooting
- FAQ
- Project Structure
- License
- Screen region capture (Wayland and X11)
- Fixed and dynamic selection modes
- Visual or silent overlay
- OpenAI-compatible local backend or Cherry Studio API backend
- Optional local KB (RAG via LlamaIndex)
- Separate text-solve mode (vision for extraction, text-only for final answer)
- Debug logs for inspection
- Python >= 3.10
- Wayland:
grimis required for screenshots - X11: no extra system tool required
-
Install dependencies:
cd hidden_solver uv sync -
Create your
.env:cp .env.example .env
-
Fill in API keys and model settings in
.env. -
Run the app:
cd hidden_solver uv run solve
Wayland note: keyboard hooks and screenshots may require elevated privileges:
sudo uv run solveOr add your user to the input group (reboot required):
sudo usermod -aG input $USERDefault hotkeys (customizable via .env):
HOTKEY_SETUP(<home>): enter setup mode to select regionsHOTKEY_TRIGGER_A(<page_up>): solve with prompt A (single-choice)HOTKEY_TRIGGER_B(<page_down>): solve with prompt B (multi-choice)HOTKEY_TOGGLE_ANSWER(<end>): show/hide answer overlayHOTKEY_QUIT(<ctrl>+<alt>+]): quit application
- Setup: select the question region (and answer overlay region for fixed mode).
- Capture: take a screenshot of the question region.
- Extract (optional):
- If
SOLVE_TEXT_SEPARATE=trueorKB_QUERY_MODE=llm, the app extracts text using the vision backend andEXTRACT_PROMPT.
- If
- RAG (optional):
- If
KB_ENABLED=true, the app retrieves related snippets (RAG) or injects the full KB.
- If
- Solve:
- If
SOLVE_TEXT_SEPARATE=true, solve with a text-only backend using extracted text. - Otherwise solve with the vision backend using the screenshot.
- If
- Post-process: filter output to allowed option letters.
- Vision backend: the model used for screenshot understanding (local OpenAI-compatible or Cherry).
- Text backend: the model used for final solving in separate text mode.
- Extraction: turning screenshot into question/options text via
EXTRACT_PROMPT. - RAG: retrieving snippets from a local KB using embeddings.
- Full injection: attaching the entire KB to the prompt (no retrieval).
- The app loads
.envfrom the current working directory. - Shell environment variables override
.env. - Recommended: run from
hidden_solverso.envis discovered. - Boolean values:
true/false,1/0,yes/no,on/offare accepted.
Defaults shown below come from hidden_solver/src/hidden_solver/config.py.
DEBUG(default:false): print debug logs to terminal.VISION_BACKEND(default:local):localorcherry.SOLVE_TEXT_SEPARATE(default:false): enable text-only solve after extraction.SOLVE_TEXT_BACKEND(default:local):localorcherry(empty => fallback toVISION_BACKEND).
OPENAI_API_KEY(default: empty): API key.OPENAI_BASE_URL(default:https://api.siliconflow.cn/v1): OpenAI-compatible base URL.OPENAI_MODEL(default:Qwen/Qwen3-VL-235B-A22B-Thinking): vision model.OPENAI_TEMPERATURE(default:0.0)OPENAI_TOP_P(default:1.0)OPENAI_MAX_TOKENS(default:3000)OPENAI_TIMEOUT(default:60)OPENAI_THINKING_TYPE(default: empty): thinking switch for Volcengine Ark / Doubao and compatible OpenAI-style backends. Supportsenabled|disabled|auto; empty means the field is not sent.
CHERRY_BASE_URL(default:http://127.0.0.1:23333)CHERRY_API_KEY(default: empty)CHERRY_AUTH_MODE(default:bearer):bearer|x-api-key|noneCHERRY_AUTH_HEADER(default: empty): custom auth header name (overridesCHERRY_AUTH_MODE)CHERRY_MODEL(default: empty): model id asmodelfield (copy from/v1/models)CHERRY_EXTRA_HEADERS(default: empty): JSON orKey=Value;Key2=Value2CHERRY_TEMPERATURE(default:0.0)CHERRY_TOP_P(default:1.0)CHERRY_MAX_TOKENS(default:3000)CHERRY_TIMEOUT(default:60)
OpenAI-compatible:
SOLVE_TEXT_OPENAI_API_KEY(default: empty)SOLVE_TEXT_OPENAI_BASE_URL(default: empty)SOLVE_TEXT_OPENAI_MODEL(default: empty)SOLVE_TEXT_OPENAI_TEMPERATURE(default: empty, fallback toOPENAI_TEMPERATURE)SOLVE_TEXT_OPENAI_TOP_P(default: empty, fallback toOPENAI_TOP_P)SOLVE_TEXT_OPENAI_MAX_TOKENS(default: empty, fallback toOPENAI_MAX_TOKENS)SOLVE_TEXT_OPENAI_TIMEOUT(default: empty, fallback toOPENAI_TIMEOUT)SOLVE_TEXT_OPENAI_THINKING_TYPE(default: empty, fallback toOPENAI_THINKING_TYPE)
Cherry:
SOLVE_TEXT_CHERRY_BASE_URL(default: empty)SOLVE_TEXT_CHERRY_API_KEY(default: empty)SOLVE_TEXT_CHERRY_AUTH_MODE(default: empty)SOLVE_TEXT_CHERRY_AUTH_HEADER(default: empty)SOLVE_TEXT_CHERRY_MODEL(default: empty)SOLVE_TEXT_CHERRY_EXTRA_HEADERS(default: empty)SOLVE_TEXT_CHERRY_TEMPERATURE(default:0.0)SOLVE_TEXT_CHERRY_TOP_P(default:1.0)SOLVE_TEXT_CHERRY_MAX_TOKENS(default:3000)SOLVE_TEXT_CHERRY_TIMEOUT(default:60)
PROMPT_A: single-choice prompt.PROMPT_B: multi-choice prompt.EXTRACT_PROMPT: extraction prompt (used when text extraction is needed).
ANSWER_ALLOWED_OPTIONS(default:ABCDEF)ANSWER_NO_MATCH(default:?)
APP_MODE(default:fixed):fixed|dynamicVISUAL_MODE(default:visible):visible|silentAUTO_POLL_ENABLED(default:false)AUTO_POLL_INTERVAL(default:60)QUESTION_ARCHIVE_ENABLED(default:false): save the exact question-region image sent to the LLM.QUESTION_ARCHIVE_DIR(default:question_archive): archive directory; relative paths are resolved from the current working directory.
KB_ENABLED(default:false)KB_REBUILD_ON_START(default:false)KB_MODE(default:rag):rag|fullKB_DOCS_DIR(default:kb)KB_INDEX_DIR(default:.kb_index)KB_TOP_K(default:4)KB_MAX_CHARS(default:4000)KB_FULL_MAX_CHARS(default:KB_MAX_CHARS)KB_QUERY_MODE(default:llm):llm|prompt|noneKB_EMBED_BASE_URL(default: empty)KB_EMBED_API_KEY(default: empty)KB_EMBED_MODEL(default: empty)KB_EMBED_TIMEOUT(default: empty, fallback toOPENAI_TIMEOUT)KB_EMBED_REBUILD_TIMEOUT(default: empty, used whenKB_REBUILD_ON_START=true)
RAG rules:
KB_MODE=fulldoes not use embeddings.KB_MODE=ragrequiresKB_EMBED_*.KB_REBUILD_ON_START=truedeletesKB_INDEX_DIRonce on the first RAG call.- When rebuilding, the embed timeout prefers
KB_EMBED_REBUILD_TIMEOUTif set.
Supported formats (via LlamaIndex SimpleDirectoryReader):
- Plain text (
.txt) and Markdown (.md) are the most reliable. - PDFs often work, but scanned PDFs may produce empty text. Use OCR before indexing.
- For other formats (docx/html), convert to
.txtor.mdif parsing is unreliable.
Cleaning strategies (recommended before indexing):
- Remove headers, footers, page numbers, and navigation noise.
- Normalize whitespace and remove duplicates.
- Split large documents by topic (1k-5k characters per file is a good starting point).
- Convert tables into bullet lists or key-value lines for better retrieval.
- Use meaningful file names (they appear in retrieval snippets as the source).
- Keep a stable directory structure to avoid accidental re-index churn.
Example KB layout:
kb/
math/
algebra_basics.md
policy/
onboarding.txt
VISION_BACKEND=local
OPENAI_BASE_URL=https://api.siliconflow.cn/v1
OPENAI_MODEL=Qwen/Qwen3-VL-235B-A22B-Instruct
OPENAI_API_KEY=sk-xxxx
SOLVE_TEXT_SEPARATE=false
KB_ENABLED=falseVISION_BACKEND=cherry
CHERRY_BASE_URL=http://127.0.0.1:23333
CHERRY_API_KEY=cs-sk-xxxx
CHERRY_AUTH_MODE=bearer
CHERRY_MODEL=my-openai:gpt-4
SOLVE_TEXT_SEPARATE=true
SOLVE_TEXT_BACKEND=local
SOLVE_TEXT_OPENAI_BASE_URL=https://api.siliconflow.cn/v1
SOLVE_TEXT_OPENAI_MODEL=deepseek-ai/DeepSeek-V3.1-Terminus
SOLVE_TEXT_OPENAI_API_KEY=sk-xxxx
SOLVE_TEXT_OPENAI_MAX_TOKENS=30
KB_ENABLED=true
KB_MODE=rag
KB_QUERY_MODE=llm
KB_EMBED_BASE_URL=https://api.siliconflow.cn/v1
KB_EMBED_MODEL=Qwen/Qwen3-Embedding-8B
KB_EMBED_API_KEY=sk-xxxxKB_ENABLED=true
KB_MODE=full
KB_FULL_MAX_CHARS=20000KB_ENABLED=true
KB_MODE=rag
KB_QUERY_MODE=promptVISION_BACKEND=local
SOLVE_TEXT_SEPARATE=true
SOLVE_TEXT_BACKEND=local
SOLVE_TEXT_OPENAI_MODEL=deepseek-ai/DeepSeek-V3.1-Terminus
SOLVE_TEXT_OPENAI_MAX_TOKENS=20- Vision-only with strict output: use low temperature and low max tokens.
- Text solve for cheaper inference: keep extraction on vision model, solve with a smaller text model.
- RAG for domain data: store PDFs/markdown in
KB_DOCS_DIR, setKB_MODE=rag. - Full injection for tiny KB:
KB_MODE=fullgives stable prompt prefixes for caching. - Use prompt-based retrieval:
KB_QUERY_MODE=promptif extraction is unreliable.
- Keep extraction deterministic:
EXTRACT_PROMPT+temperature=0. - Limit output length for text solve:
SOLVE_TEXT_OPENAI_MAX_TOKENS=10~50. - Use
ANSWER_ALLOWED_OPTIONSto reduce noise. - For small KBs,
KB_MODE=fullimproves caching and avoids embedding costs. - For large KBs, keep
KB_TOP_Klow and tuneKB_MAX_CHARS. - Prefer running from
hidden_solverto ensure.envis loaded. - Ignore
.kb_indexin git (index is regenerable).
- Use smaller capture regions to reduce token usage and latency.
- Keep
OPENAI_MAX_TOKENSsmall when you only need a letter. - Prefer
SOLVE_TEXT_SEPARATE=truewith a lightweight text model for cheaper inference. - Reduce RAG overhead with smaller
KB_TOP_KandKB_MAX_CHARS. - Disable
DEBUGin normal use to avoid I/O overhead.
- Do not commit
.envor real API keys. Use.env.examplefor sharing. - Rotate keys if they were exposed.
- Be cautious when sending sensitive content to third-party APIs.
- RAG says embedding not configured: ensure
KB_EMBED_*is set and reachable. - RAG still uses old docs: delete
KB_INDEX_DIRor setKB_REBUILD_ON_START=true. - Wayland screenshots are black: check
grimand permissions. - Hotkeys not working: try running with sudo or add user to
inputgroup. - Cherry API 401/403: verify
CHERRY_API_KEYand auth headers. - Multi-monitor offset: ensure primary monitor scaling is correct.
- Embedding request fails: check
KB_EMBED_BASE_URLand model name support.
Q: Do I need to re-embed the entire KB on every query?
A: No. The KB is embedded once and stored in KB_INDEX_DIR. Later queries only embed the query text.
Q: What is the difference between KB_QUERY_MODE=llm and prompt?
A: llm uses extracted text from the screenshot as the retrieval query. prompt uses the raw prompt string.
Q: Why is the answer only a letter?
A: Output is filtered by ANSWER_ALLOWED_OPTIONS to keep results compact and reliable.
Q: Can I use Cherry for vision and local embeddings for RAG?
A: Yes. RAG uses KB_EMBED_* settings and is independent of VISION_BACKEND.
Q: How do I reset the KB index?
A: Delete KB_INDEX_DIR or set KB_REBUILD_ON_START=true before the next run.
src/hidden_solver/core/: app controller and worker threadsservices/: LLM, KB, screenshot, hotkeyui/: overlay widgetsconfig.py: configuration loadermain.py: entry point
MIT