DeepSeek Harness Remote
DeepSeek Harness Remote: DeepSeek Harness 실행 PC와 모바일·PC를 안전하게 연결해 어디서나 작업 이어받기
LLM 프롬프트를 검사·평가·재작성하는 데스크톱 앱, 로컬 모델 내장, 오프라인 작동
A desktop app that lints, scores and rewrites LLM prompts with a built-in local model. Works offline; nothing you type leaves your machine.
생산성JavaScript · TypeScriptGitHub @Osman19702온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
댓글을 불러오는 중입니다.
로그인 상태를 확인하는 중입니다.
Lint, score and rewrite LLM prompts - a desktop app with a built-in local model; cloud providers optional
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Formerly PromptFixer: the same app, renamed in 0.5.0. Settings, the library and the downloaded model carry over on their own (see Upgrading from PromptFixer).
A desktop app for Windows and macOS that lints, scores and rewrites LLM prompts — with a built-in local model, so it works offline and nothing you type leaves your machine. That last claim is checked on every push, not just asserted: see docs/PRIVACY.md for what does and does not leave, and how the check works.
Website: www.whetprompt.com — the latest download, and what changed in every release.
Paste a prompt. It's scored instantly against ~30 prompt-engineering rules. Press Fix prompt and the local model rewrites it; the rewrite is re-scored so you can see whether it actually got better, with a word-level diff and a change log explaining every edit.
Not quite what you wanted? Select any passage of the rewrite and mark it Keep it — I loved it or Change it — I didn't like it, then press Fix again with my marks. The model revises its own rewrite instead of starting over — what you kept comes back word for word, what you disliked gets reworded — and the result is still scored against the prompt you wrote, so every attempt stays comparable.
Cloud providers (Anthropic, OpenAI, OpenRouter, Gemini, Ollama, any OpenAI-compatible endpoint) remain available as an optional switch — but nothing requires them. A key goes in Settings, in the top bar, and works at once.
Whetprompt-0.7.0-win-x64.exe (339.2 MiB, Windows 10/11 64-bit) from the v0.7.0 release. The 2.5 GB model downloads on first run; after that the app works offline. Installed over an earlier version, or over PromptFixer, it keeps your library and the model (Upgrading from PromptFixer).
c11bff46a8298a16cb26ea1b9c307e0327e3be82f1fe2994b137575bf8319e41, also in the release's
SHA256SUMS.txt.
Check it in PowerShell with (Get-FileHash .\Whetprompt-0.7.0-win-x64.exe).Hash.ToLower().Or get the same app from the Microsoft Store: signed by the Store, so there is no SmartScreen warning, and it updates itself. It is free to try for 30 days and then a one-off purchase (£0.79 in the UK), and it keeps its files a little differently (The Microsoft Store version). The download above stays free.
There is no macOS build of Whetprompt yet. The newest is 0.4.0, from before the rename, so the disk images and the app they install are still called PromptFixer, without what 0.5.0 and later brought. Two disk images on the v0.4.0 release, for macOS 13 or later. Pick the one for your Mac (Apple menu, About This Mac):
5950705c72d5e19e6b2abd7d8d334db50dabcee3c595b3248488303c814c17fa.6a5d3963ee807391be127626bee786bddf24a403a65de8546bf4feb4e7ea4b0c.Both hashes are in the release's SHA256SUMS.txt; check one in Terminal
with shasum -a 256 ~/Downloads/PromptFixer-0.4.0-mac-arm64.dmg. The app is ad-hoc signed and not
notarized, so macOS refuses the first launch: click Done, then System Settings → Privacy &
Security → Open Anyway. docs/INSTALL.md walks through it. The model
downloads on first run as on Windows; on Apple Silicon it runs on the GPU through Metal, on an
Intel Mac on the CPU.
Three commands. Needs Node 20+ and ~3 GB of disk.
npm install
npm run setup
npm run desktop
npm run setup downloads the model once (2.5 GB, resumable if interrupted). After that the app
runs with no internet connection. You can skip setup and click Download inside the app
instead — same thing, with a progress bar.
If
npm installwarns about install scripts "not yet covered by allowScripts" (npm 11+), the approvals are already recorded inpackage.json. If they're somehow missing, runnpm install-scripts approve node-llama-cpp electron electron-winstaller, thennode node_modules/electron/install.jsifnode_modules/electron/distis empty.
Whetprompt/
├── .github/
│ ├── ISSUE_TEMPLATE/ report-output.yml: the form the Report link beside a rewrite opens
│ └── workflows/ ci.yml: every push runs the checks, the tests, the acceptance suite and a visual comparison
│ with the commit before on a Windows runner, and keeps both reports
│ release.yml: a pushed tag v<version> builds the installer onto a draft GitHub release,
│ and the Microsoft Store package as a workflow artifact; started by hand it is a
│ rehearsal that releases nothing
│ pages.yml: a push to master that touches site/, a published release, or a run by hand
│ writes site/releases.json from the GitHub Releases API and publishes site/ to GitHub Pages
├── build/ icon.svg and icon.png, the app icon; appx/, the Store package images drawn from it
├── electron/
│ ├── main.js Electron main process: starts the server in-process, opens the window
│ ├── env.js Desktop .env loading (user-data dir) and desktop defaults
│ └── handlers.js Navigation guard, the deterministic quit/unload handler, a window size that fits the screen
├── server/
│ ├── index.js Express routes; exports start() for Electron
│ ├── analyze.js The linter: rules, categories, scoring (no model involved)
│ ├── examples.js The eight sample prompts, served at GET /api/examples
│ ├── metaprompt.js Rewrite instructions, presets, JSON schema for the output
│ ├── local-llm.js node-llama-cpp wrapper: download → load → grammar-constrained inference
│ ├── models.js Model catalog (3 tiers), RAM-based selection, on-disk resolution
│ ├── vcruntime.js Why llama.cpp cannot start on Windows: the Visual C++ runtime check and its message
│ ├── providers.js Provider registry: local + cloud adapters, plain fetch, no SDKs
│ ├── keys.js API keys from Settings: written to the .env file, one line at a time
│ ├── store.js Library persistence (atomic JSON writes)
│ ├── e2e.test.js End-to-end tests against a stub provider (no key, no model needed)
│ ├── ui.test.js Unit tests for src/lib/ui.ts, the marks arithmetic included
│ └── cli.test.js Tests for the prompt-linting CLI
├── scripts/
│ ├── setup-model.js CLI model download (npm run setup)
│ ├── bundle-vcruntime.js electron-builder afterPack hook: the Visual C++ runtime next to the llama.cpp binaries
│ ├── appx-assets.mjs Draws build/appx/, the Microsoft Store package images, from build/icon.svg
│ ├── lint-prompts.js Lint prompt files in CI (npm run lint:prompts)
│ ├── check-release.mjs The release gate: version, lock file, changelog and sources agree (npm run check:release)
│ ├── changelog-section.mjs Prints one version's CHANGELOG.md section; the release notes are cut from it
│ ├── site-releases.mjs Writes site/releases.json, the website's release list, from GitHub (npm run site:releases)
│ ├── visual.mjs Visual check runner: build, start stub + server, run Elastishot (npm run visual)
│ └── visual.test.js Tests for the runner's arguments, approve step and exit codes, and the config's shape
├── src/ React UI (Vite + TypeScript)
│ ├── App.tsx State, layout, local-model banner and status
│ ├── components/ ScorePanel, IssueList, DiffView, Library, MarkableOutput (the rewrite and its marks),
│ │ Settings (the API keys)
│ └── lib/ api.ts (client), diff.ts (word-level LCS diff), ui.ts (view logic and marks, no React)
├── acceptance/ Acceptance scenarios, Groups A–N (npm run test:acceptance; see docs/ATDD.md)
│ ├── features/ The scenarios as Gherkin, one file per group
│ ├── M-feedback.acceptance.test.js Group M: marking a rewrite and fixing it again (API and browser)
│ ├── N-appearance.acceptance.test.js Group N: the visual check, run through the real Elastishot CLI
│ ├── support/ Harness: real server, scripted stub provider, Playwright helpers, visual.js (runs Elastishot)
│ └── trace.js Traceability matrix (npm run test:trace)
├── docs/
│ ├── INSTALL.md End-user install guide for the Windows download (no Node or npm needed)
│ ├── RELEASING.md Release checklist: changelog, version bump, tag, the Release workflow, verifying the draft,
│ │ and the Microsoft Store
│ ├── STORE.md The Microsoft Store listing: every page of a submission, ready to paste
│ ├── releases/ The release page of each version: download, hash, what is new
│ ├── ATDD.md Acceptance test-driven development: strategy and the acceptance scenarios
│ ├── PRIVACY.md What does and does not leave the machine, and how scenario F11 checks it
│ └── LOCAL-MODEL.md Model choice, measured performance, and the train/fine-tune analysis
├── site/ The website (https://www.whetprompt.com/): index.html, styles.css, site.js,
│ icon.svg, whetprompt.png, README.md; releases.json is written by npm run site:releases
│ and git-ignored
├── dist/ Built frontend (npm run build) — served by the server
├── elastishot.config.mjs Visual check targets: eight screens, each at two window sizes
├── .elastishot/ Approved pictures and run reports of the visual check (per machine, git-ignored)
├── CHANGELOG.md What changed in each version, for users (Keep a Changelog)
├── .env.example Optional configuration
└── package.json Scripts + electron-builder config
Model files live outside the project at ~/.whetprompt/models/ so they survive reinstalls and
never end up in a build. The prompt library lives in Electron's per-user data directory whenever
the app runs as a desktop app — npm run desktop, npm run desktop:dev or the installed build —
for example %APPDATA%\Whetprompt\data\library.json on Windows (the folder is named whetprompt,
after the package name, when run from source). Only browser mode (npm run dev, npm start) uses
server/data/library.json. Override either with WHETPROMPT_DATA_DIR. The Microsoft Store version
is the exception to "survive reinstalls": on a PC without the installer's version it keeps both in
the storage Windows gives a Store app, which uninstalling empties
(docs/INSTALL.md).
To move a library between machines, GET /api/library/export downloads it as
whetprompt-library-YYYY-MM-DD.json and POST /api/library/import with that file's body merges it
in: entries whose id already exists are skipped, never overwritten, malformed entries are dropped,
and the reply says how many were imported and skipped.
Default is Qwen3-4B-Instruct-2507 (Q4_K_M, 2.5 GB). Two other tiers are one command away:
| Tier | Model | Size | Needs |
|---|---|---|---|
lite | Qwen2.5-1.5B-Instruct | 1.0 GB | 4 GB RAM |
default | Qwen3-4B-Instruct-2507 | 2.5 GB | 8 GB RAM, or a 4 GB+ GPU |
quality | Qwen2.5-7B-Instruct | 4.7 GB | 12 GB RAM, or a 6 GB+ GPU |
npm run setup -- quality
Or pick a tier from the model dropdown in the app (it shows which are downloaded).
GPU is detected automatically: CUDA, Vulkan or Metal, with a CPU fallback. On the development machine (GTX 1660 Ti, Vulkan) the 4B model does ~21 tokens/s — a typical fix takes 15–35 s, plus ~15 s to load the model the first time, which the app now shows as a real progress bar. The rewrite is streamed into the Fixed tab as the model writes it, so the wait is readable rather than blank, and the model stays loaded until the app quits, so only the first fix waits for it. Output is grammar-constrained to the result schema, so the small model can't produce malformed JSON.
Why this model, what it measured, and whether it's worth fine-tuning a smaller one for this job: docs/LOCAL-MODEL.md. Short version: don't train; if you ever must, it's a LoRA on data this app already collects, and dynamic few-shot from the library gets you most of the way for free.
| Command | What it does |
|---|---|
npm run setup | Download the local model (-- lite|default|quality to choose) |
npm run desktop | Build the UI and open the desktop app |
npm run desktop:dev | Open the desktop app without rebuilding |
npm run dist:win | Build a Windows installer into release/ (dist:mac, dist:linux likewise) |
npm run dist:store | Build the Microsoft Store package, release/Whetprompt-<version>-win-x64.appx (see docs/RELEASING.md) |
npm run release:checksums | Write release/SHA256SUMS.txt for the built installers (-- --check verifies it; -- --dir <folder> targets another folder) |
npm run check:release | The gate a release must pass: package.json, package-lock.json and CHANGELOG.md name the same version and no source file repeats it (-- --tag v0.2.0, the form a release uses, also compares the tag name and refuses entries left under [Unreleased]) — see docs/RELEASING.md |
npm run site:releases | Write site/releases.json for the website from the GitHub Releases API: every published release with its tag, date, notes, installer name, size and SHA-256. The Pages workflow (.github/workflows/pages.yml) runs it before uploading site/; the file is git-ignored |
npm run dev | Browser mode: API (watch) + Vite dev server at http://localhost:5173 |
npm start | Browser mode: serve the built app + API from one port (8787) |
npm test | Unit and component tests — no key, no model download needed |
Whetprompt follows Semantic Versioning and keeps a
changelog. The version is written down once, in package.json: the installer's file
name and GET /api/health read it, and npm run check:release fails when the lock file, the
changelog or a source file disagrees. Every push to any branch is tested on a Windows runner: the
CI workflow runs that check, the typecheck, the build and the unit and component tests in one job
and the acceptance suite in another. A release is cut by pushing a tag v<version>; the Release
workflow builds the installer and SHA256SUMS.txt onto a draft GitHub release, which is published
by hand once the download has been checked. Started by hand, the same workflow is a rehearsal: it
builds everything on the runner and creates no release. Publishing a release also redeploys the
website with that release's download and notes; the Pages workflow reads both from the release, so
nothing on the site has to be updated by hand. The checklist, and what counts as a patch, a minor
or a breaking change, is in docs/RELEASING.md.
scripts/lint-prompts.js runs the same deterministic rules as the app over a set of prompt files and exits non-zero when one falls below the bar. No server, no model, no API key.
npm run lint:prompts -- <path...> [--intent <id>] [--min-score <n>] [--fail-on high|medium|low|none] [--json] [--quiet]
<path> may be files or directories; directories are searched recursively for *.md, *.txt and *.prompt. Each file is one prompt.<!-- whetprompt: intent=code -->. --intent is the default for files without one (general if unset).--json prints one JSON object with every file's score, categories and issues; --quiet prints only the summary line.Exit codes:
| Code | Meaning |
|---|---|
0 | Every file passed |
1 | At least one file scored below --min-score (default 0) or has an issue at or above --fail-on (default high) |
2 | Usage error: no paths, unknown flag or intent, path not found, or no prompt files found |
A minimal GitHub Actions job:
name: lint-prompts
on: [push, pull_request]
jobs:
lint-prompts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run lint:prompts -- prompts/ --min-score 70 --fail-on high
Elastishot compares screenshots of the interface with pictures you approved earlier, and names the element behind every difference — by test id, or by role and name — instead of just painting pixels red. The acceptance suite proves the interface works; this proves a change to the CSS or a component did not move, recolour or resize something you never meant to touch. It is a developer tool: nothing in the app depends on it.
npm run visual:approve
npm run visual
Approve first, compare afterwards. npm run visual:approve captures every screen and stores it as
the approved picture; npm run visual captures them again and fails on any difference. Both build
the UI (-- --no-build reuses dist/), start the scripted stub provider and the real server from
acceptance/support/ on OS-assigned ports with throwaway data and model folders, run Elastishot
over the targets in elastishot.config.mjs, and shut everything down again — so no key, no model
and no network are needed, and the rewrite on screen is the same one on every run. A full run of
the sixteen pictures takes about 20 s on the development machine.
Every screen is reached by driving the page the way a user would, never by sleeping, and is
captured at two window sizes: desktop (1360×880, the size the browser scenarios run at) and
small (1024×720, the small window scenario H13 guards).
| Screen | What is on it |
|---|---|
empty | The app as it opens: empty editor, example gallery |
issues | The sample blog prompt typed in; live score and the Issues tab |
fixed | After Fix prompt: the Fixed tab with the rewrite |
marked | The Fixed tab with one passage marked to keep and one marked to change |
diff | The Diff tab |
changes | The Changes tab |
library | The result saved, and the Library drawer open on that one entry |
settings | The Settings drawer, every cloud provider without a key |
A screen fails when Elastishot finds a changed, added, removed or moved region on it, or when its
overall similarity drops below 0.98. After changing the look on purpose, run
npm run visual:approve again: it writes the pictures that are missing and promotes the ones that
changed.
One screen. Name it after --: npm run visual -- fixed checks that screen at both sizes,
npm run visual -- marked/small one size of it, and npm run visual:approve -- fixed marked
approves only those two. A mistyped name is an error (exit 2) that lists the screens there are.
Against a commit, with no approved pictures. npm run visual -- --against master answers
"what did my change do to the interface?". The ref (a branch, a tag, a commit) is checked out into
.elastishot/against/<sha>, built there and served by its own server and stub; its screens become
the pictures of this one run, and the working tree is then captured and compared with them. Both
sides are captured on the same machine in the same run, so fonts and the browser cancel out and
nothing has to be approved or committed first; the checkout, its server and its pictures are gone
afterwards, and your approved pictures are neither read nor written. Screen names work as usual
(npm run visual -- --against v0.2.0 fixed/small), and a screen the ref cannot be driven to,
because it did not exist yet, is reported as new. The checkout sits inside the project so that it
finds the project's node_modules: a ref that needs other dependencies to build or to serve, or
that is older than the hooks elastishot.config.mjs drives the page by (v0.1.0 is), cannot be
compared like this, and the runner says so. This is the form CI uses.
What CI keeps. Every push runs three jobs on a Windows runner (.github/workflows/ci.yml):
the checks and the unit and component tests; the acceptance suite; and this comparison, between the
pushed commit and the one it was pushed onto (a pull request: its target branch). A run keeps two
artifacts for 14 days whatever its outcome. elastishot-report is the comparison's report, a page
per screen and size with the slider, the changed regions and the elements behind them, and its
table is that job's summary page; differences do not fail the job, since a change to the interface
is usually the point of a push, and only a comparison that could not be made is red.
acceptance-report is docs/ATDD-RESULTS.pdf as that run printed it, with the JSON it was
printed from, and the traceability table is that job's summary page.
Where things live. Approved pictures are in .elastishot/baselines/<screen>/<size>/
(baseline.png plus the element map that lets a difference be named). Every run writes a folder
under .elastishot/runs/ holding index.html (one card per picture — the last line of the output
is its path), a detail page per picture under pairs/, report.json and junit.xml for a CI job;
the text file .elastishot/runs/latest names the newest run folder. The whole of .elastishot/ is
git-ignored, and the baselines are deliberately per machine and not committed: fonts and
antialiasing render differently from one machine, GPU and browser build to the next, so another
machine's pictures fail on yours without anything having changed. On a fresh clone there is
nothing to compare with, so npm run visual exits 1 with every screen reported as new until you
approve. This is also why npm run test:all does not include it. (npm run test:acceptance does
cover the machinery: Group N approves its own pictures into .elastishot/runs/acceptance-* and
never reads yours.)
Exit codes are Elastishot's, passed through:
| Code | Meaning |
|---|---|
0 | Every picture matches (for visual:approve: everything is approved) |
1 | At least one screen differs, or has no approved picture yet |
2 | Error: an unknown screen or flag, no build in dist/ with --no-build, a capture that failed, or the config used without the runner |
130 | Interrupted with Ctrl+C; the server and its temp folders are still cleaned up |
What is hidden or masked, and why. Anything that honestly differs between two runs is kept off
the picture, or every run would fail. Toasts are hidden: they carry elapsed seconds ("Fixed in
1.2s") and leave on a timer. The date on a Library entry is hidden, and so is the settings file's
folder in the Settings drawer, a new temp folder for every server the harness starts. The Run
details strip of the Changes tab — elapsed time and token counts — is masked, so it shows as a
solid block in the pictures and its contents are not checked. Every capture also ends at rest:
mouse parked, nothing focused or selected, panes scrolled to the top, no spinner on screen;
Elastishot itself stops animations and hides the text caret. And each capture starts by emptying
the server's library, so a prompt saved for the library screen cannot turn up in the next one or
be learned from by a later fix. That is the one destructive thing the check does, so the config
refuses any server that was not started by the acceptance harness (it recognises the throwaway
model folder) — it cannot empty a library somebody is using.
Adding a screen. A screen is a name and a list of steps in elastishot.config.mjs. Copy a line
of targets and name the steps that lead to it — the Diff tab is
screen('diff', typeSample, fix, openTab('Diff')) — and write a new step where none fits: a step is
an async function of the Playwright page that waits on a real condition, never on a timer. If the
screen shows something that varies, give it a data-testid and add it to hide or mask. Then
add the name to the target list in scripts/visual.test.js and to SCREENS in
acceptance/support/visual.js, and approve it with npm run visual:approve -- <name>.
Three environment variables belong to the check, none of them to the app:
| Variable | Purpose |
|---|---|
WHETPROMPT_VISUAL_URL | The server to photograph. npm run visual sets it; without it the config refuses to load and says to use the npm script |
WHETPROMPT_VISUAL_BASELINES | Another folder for the approved pictures (default .elastishot/baselines) — Group N uses it to keep away from yours |
WHETPROMPT_VISUAL_VARIANT | Restyle the page before capture to prove the check notices: keep-colour (the keep highlight loses its green), marks-plain (both highlights lose all their paint and keep their place), rewrite-type (the rewrite in larger type), fix-button (a red Fix prompt button). Any other name is an error |
Everything has a working default. Configuration is a plain .env file (see .env.example); create
it only if you want to change something, and restart the app afterwards. API keys need neither:
Settings, in the top bar, saves a key to this file, and the provider works at once. Where the
file lives depends on how you run Whetprompt:
| How you run it | .env location |
|---|---|
| Installed desktop app | Electron's per-user data directory: %APPDATA%\Whetprompt\.env on Windows, ~/Library/Application Support/Whetprompt/.env on macOS, ~/.config/Whetprompt/.env on Linux |
npm run desktop / npm run desktop:dev | The project .env (the user-data file is read as a fallback; from source that folder is whetprompt) |
npm run dev / npm start / npm run setup | The project .env |
| Variable | Purpose |
|---|---|
WHETPROMPT_MODEL | Force a tier: lite, default, quality (also honoured by npm run setup) |
WHETPROMPT_MODEL_DIR | Where model files are stored (default ~/.whetprompt/models) |
WHETPROMPT_DATA_DIR | Where the prompt library is stored (see above for the defaults) |
WHETPROMPT_PRELOAD | 1 to load the model at startup (the desktop default; 0 to load on first fix) |
WHETPROMPT_IDLE_UNLOAD_MS | How long the model may sit unused before it is released (default: never, until the app quits; 900000 is fifteen minutes) |
WHETPROMPT_FIX_TIMEOUT_MS | How long a local fix may produce nothing before it is stopped (default 5 minutes; 0 for no limit). A stall, not a total time limit: a slow machine is never cut off mid-answer |
DEFAULT_PROVIDER | Provider selected on first load (default local) |
DEFAULT_MODEL | Force a specific model id instead of the provider's default |
PORT | Browser-mode port (default 8787; npm run dev proxies to it; the desktop app picks a free one) |
ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, GOOGLE_API_KEY | Enable a cloud provider; Settings writes these |
WHETPROMPT_ENV_FILE | The .env file Settings saves keys to. The desktop app sets it to its user-data .env; unset, it is the .env in the working folder, the one the server read |
OLLAMA_BASE_URL, COMPATIBLE_BASE_URL, COMPATIBLE_API_KEY, COMPATIBLE_LABEL | Other local/compatible endpoints (and the name the UI shows for the compatible one) |
Keys stay on the server side and are never sent to the UI: Settings shows whether a key is saved,
never what it is. Settings changes the key's own line and keeps every other line of the file as it
was, and it refuses anything that is not a key (a space, a line break, a quote, a backslash, a #,
fewer than eight characters). A key set in the system environment, or in a .env read before the
user-data one (the project's, in npm run desktop), wins over the file, so Settings shows it as
set outside Whetprompt and leaves it alone. Test beside a key asks the provider whether it
accepts it — one request, the only one Settings ever makes, and only when pressed.
prompt
│
├─► analyze.js ──────────► score + issues (local, deterministic, instant)
│ │
│ ▼
└─► metaprompt.js ─► local-llm.js ─► rewrite ─► analyze.js again ─► before/after
(GBNF grammar from the JSON schema)
The linter runs first and its findings go into the rewrite prompt, so the model fixes the same problems the UI shows you. The rewrite is re-linted with identical rules, which is what makes the before/after comparison meaningful.
Two rewrites of the same prompt, with the same settings, are not guaranteed to match — and the reason is not the temperature. Before each fix the app looks through your saved library and passes the model up to two of your own earlier rewrites as examples, so the rewrites you keep teach it what "enough change" looks like for you. That is the point of the feature, but it means the input to the model changes as your library grows.
For a run you intend to compare against another — measuring a rule change, timing the model, checking whether a prompt got better — remove that variable first:
DELETE /api/library/all, or Clear in the Library panel).The result line says learned from N saved example(s) whenever examples were used, and
meta.examplesUsed carries the same number, so you can tell after the fact which runs are
comparable. Everything else about a fix is already fixed: the same lint options score the prompt
before and after, and a retry sees the same examples the first attempt did.
| Strength | What the model may do | Must keep |
|---|---|---|
| Light touch | Spelling, grammar, and only findings fixable from your own text (vague terms, conflicts, missing verb). It never sees "add context / audience / format / limit" findings, so it cannot invent them. | 60% of your words |
| Balanced | Fix the findings and add the missing essentials, using [placeholders] for anything it cannot know. | 30% |
| Full rebuild | Restructure freely. | no floor |
Two linter rules were reworked after real use: a length limit is only a defect for prose tasks
(for code, agents and extraction it is a low-priority scope nicety), and placeholders the rewrite
adds deliberately are scored as a reminder, not a defect, so an honest rewrite is never punished
for saying [product name] instead of making one up.
Small models don't reliably obey "change as little as possible", so the server checks every rewrite: how much of your original vocabulary survived, and whether the model pasted its own instructions into the output. A rewrite that fails is retried once with the reason spelled out; if the retry fails too, you get the better attempt plus a yellow warning above the result. The Diff tab always shows exactly what changed.
Learn from your library. Every fix also looks through the prompts you saved for up to two whose
original resembles the one being fixed (shared vocabulary, same task type preferred) and whose rewrite
scored higher than its original, and shows them to the model as before/after examples of how much
change you like — their style, not their content. It runs on the library alone, so it costs nothing
and works offline; meta.examplesUsed in the response says how many were used, and sending
options.fewShot: false turns it off for a request.
Fix again with your marks. A fix can also start from an earlier rewrite instead of from scratch.
POST /api/fix takes an optional feedback: { previous, keep, change } — the rewrite that was
marked, and the passages of it to keep and to change — while prompt stays the original, so the
before score, the diff and the strength guard still measure against what you wrote. The model is
shown the marked rewrite with both lists, and the guard gains two checks: every kept passage must
come back word for word, and every passage marked for change must occur fewer times than it did
in the marked rewrite (compared case-sensitively, but blind to how the whitespace falls). Marks
travel as text, not as positions, which is why a change is counted rather than looked for: the
same words may occur three times with one marked, or stand inside a sentence you asked to keep.
The model is told what the guard will accept there: the kept sentence wins and the words are
changed where they stand on their own — or, where they stand nowhere else because the kept
passage itself occurs twice, one copy of it is left word for word and the other reworked. A miss
is retried once with the missed
passages quoted, like any other failure; if the retry misses too, you get the attempt that ignored
fewer marks and a warning of kind feedback (meta.warningKind) — unless a leak, retention or
growth warning applies, which takes precedence. Marks are coerced rather than refused: passages
that are not in previous are dropped, duplicates collapse, a kept passage gives way to one
marked for change only when the two cannot be told apart (the same words where they occur once,
a keep that exists nowhere but inside the passage to change, or — from a direct API call only,
the interface's marks never overlap — kept passages that stand nowhere else and between them
hold every place the words to change stand in, where the fewest that make room give way), each
list stops at 20 passages
and each passage at 2,000 characters, and feedback with nothing usable left is an ordinary fix,
byte for byte. Only a
previous over the 60,000-character prompt limit is refused (413). meta.feedback in the
response — present only when marks were applied — carries keep and change, the number of
passages of each kind the model was shown, and missingKeep and unchangedChange, the passages
it did not honour in the text you were given — every one of them when the reply held no rewrite
at all (the whole message pasted back, or a saved example) and your original is shown in its
place, whatever that original happens to contain.
GET /api/examples."Rewrote more than the strength allows" warning — the model ignored the strength twice. Try Balanced, or add an instruction (the + Instructions field) naming what to keep.
"Did not follow all of your marks" warning — after Fix again with my marks, the model ignored at least one mark on both attempts; the sentence under the headline says how many kept passages are missing and how many passages to change are still there, and you are shown the better attempt. Mark the result again and fix again, or say what you want instead in the + Instructions field. Very short marks cause this most often: a passage is matched as plain text and counted, not read word by word, so a change mark on "the" is honoured only once the rewrite holds those letters fewer times than before ("other" included), and a model that rewords the marked one but writes "the" somewhere new leaves the count where it was. Mark the phrase or the sentence.
Download interrupted — run npm run setup again; it resumes.
"Prompt is too long for the local model" — the 4B has an 8k-token context shared between input and output. Shorten the prompt, or switch to a cloud provider for that one.
Slow on CPU — expected at 5–10 tok/s. Use lite, or check the model dropdown shows a GPU
backend in the status chip once loaded.
Model loads on Vulkan but you have an NVIDIA card — the prebuilt CUDA binary needs a compatible CUDA runtime; Vulkan is the fallback and is close in speed for a 4B model.
"Local model problem" naming the Microsoft Visual C++ runtime, or NoBinaryFoundError — the
prebuilt llama.cpp binaries for Windows need the Visual C++ Redistributable, which a fresh Windows
install does not have and Electron does not bring. The installer ships it next to them; from
source, or with 0.4.0, install it from https://aka.ms/vc14/vc_redist.x64.exe and press
Retry. On such a machine npm ci cannot finish Electron's own download either (its unzipper
is a native module that needs the same DLLs): install the Redistributable first.
npm run visual reports a change you did not make — the approved pictures are from one
machine at one moment. Fonts, antialiasing, the GPU, display scaling, a Playwright browser update
or baselines copied from another machine all move pixels without any code changing. Open the
report (the last line of the output is its path), check that the element it names is one you did
not touch, and approve on this machine: , or
for one screen. If the same screen fails on every run with
nothing changed in between, something on it varies between captures: give it a and
add it to or in .
WHETPROMPT_FIX_TIMEOUT_MS).WHETPROMPT_IDLE_UNLOAD_MS releases it when idle.The code is MIT-licensed; see LICENSE. Two things are not under it:
msvcp140.dll, vcruntime140.dll, vcruntime140_1.dll)
that the Windows installer and the Microsoft Store package carry next to the llama.cpp binaries.
They are Microsoft's files, redistributed under the Visual Studio licence terms that allow it
(docs/RELEASING.md), and the MIT licence
does not apply to them.npm run test:acceptance | The acceptance scenarios in acceptance/ (see docs/ATDD.md) — API, CLI, browser (Playwright), Electron and the visual check (Elastishot, with pictures it approves for itself); needs npm run build first and takes about four minutes |
npm run test:trace | Traceability matrix: every scenario in acceptance/features/ against its automated test |
npm run test:report | Run the acceptance suite and print the results to docs/ATDD-RESULTS.pdf |
npm run visual | Visual check with Elastishot: build the UI, start the stub provider and the real server, capture eight screens at two window sizes and compare them with the approved pictures (-- fixed for one screen, -- --no-build to reuse dist/) — see Visual checks with Elastishot |
npm run visual:approve | Approve the current look as the new baseline — every screen, or only the named ones |
npm run typecheck | TypeScript, no emit |
WHETPROMPT_IDLE_UNLOAD_MS~/.whetprompt/modelsDELETE /api/local/model/:tiernpm run visual:approvenpm run visual:approve -- <screen>data-testidhidemaskelastishot.config.mjsElastishot run by hand stops with "WHETPROMPT_VISUAL_URL is not set" or "was not started by the
acceptance harness" — neither comes from npm run visual. The first means Elastishot was run
directly (npx elastishot …), the second that WHETPROMPT_VISUAL_URL points at a server you are
using, whose library the check will not empty. Go through npm run visual, which starts a
throwaway server for it.
Packaging — npm run dist:win produces an NSIS installer under release/, and
npm run dist:mac (on a Mac) a .dmg for each of Apple Silicon and Intel. The model is
not bundled (it's per-user in ~/.whetprompt); the installer is still ~350 MB because it
ships the prebuilt CPU, Vulkan and CUDA llama.cpp backends so the right one is picked at runtime.
Binaries for other CPU architectures and llama.cpp's build-from-source bundle are excluded. On
Windows the Visual C++ runtime goes in next to the backends, taken from Visual Studio or from
WHETPROMPT_VCRUNTIME_DIR; a build that has neither fails (docs/RELEASING.md).
Packaging fails with EPERM … rename 'release\win-unpacked.tmp' — the project is inside a
synced folder (OneDrive, Dropbox). The sync agent locks the freshly written tree mid-rename; it
is timing-dependent, so it can work one time and fail the next. Build to an unsynced folder and
copy the installer back:
npm run build && npx electron-builder --win --config.directories.output="$LOCALAPPDATA/Whetprompt/release"
Then copy Whetprompt-*-win-x64.exe (and its .blockmap) from that folder into release/,
or move the project out of the synced folder. Before publishing, write the checksum file with
npm run release:checksums (add -- --dir <folder> to hash the unsynced folder directly) and
attach SHA256SUMS.txt to the GitHub release next to the installer.