Search and replace by intent — a local LLM writes the regex, you review every match before it runs.
  • TypeScript 94.4%
  • CSS 3.4%
  • JavaScript 1.7%
  • Shell 0.5%
Find a file
Johannes Kaindl 7b48ef0567
docs(readme): den Backtracking-Schutz beschreiben, wie er wirklich wirkt
Das README versprach 'a runaway pattern can't hang the editor' mit Verweis
auf das Zeitbudget. Das Budget wird aber ZWISCHEN Zeilen geprueft — ein
einzelner entgleister Match auf einer Zeile lief davon, und genau das ist im
GUI-Durchlauf passiert.

Jetzt steht da, was gilt: generierte Muster werden abgelehnt, handgeschriebene
erst nach einer Messung freigegeben, und das Budget kann eine einzelne Zeile
nicht retten. Bremse, keine Garantie.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 21:16:40 +02:00
.github/workflows docs: README (EN/DE), Lizenzen, SECURITY, Diataxis-Manual, Release-Infra 2026-07-25 14:44:44 +02:00
docs docs: README (EN/DE), Lizenzen, SECURITY, Diataxis-Manual, Release-Infra 2026-07-25 14:44:44 +02:00
scripts feat: Transmute MVP — NL zu Regex mit Trefferliste und bestaetigter Anwendung 2026-07-25 14:33:50 +02:00
src fix(regex): Kanarienvogel-Lauf vor jeder Risiko-Freigabe 2026-07-27 20:15:07 +02:00
tests fix(regex): Kanarienvogel-Lauf vor jeder Risiko-Freigabe 2026-07-27 20:15:07 +02:00
tools feat: Transmute MVP — NL zu Regex mit Trefferliste und bestaetigter Anwendung 2026-07-25 14:33:50 +02:00
.editorconfig feat: Transmute MVP — NL zu Regex mit Trefferliste und bestaetigter Anwendung 2026-07-25 14:33:50 +02:00
.gitignore feat: Transmute MVP — NL zu Regex mit Trefferliste und bestaetigter Anwendung 2026-07-25 14:33:50 +02:00
AGENTS.md fix(regex): Kanarienvogel-Lauf vor jeder Risiko-Freigabe 2026-07-27 20:15:07 +02:00
CHANGELOG.md chore(release): 0.3.0 2026-07-27 21:07:58 +02:00
CONTRIBUTING.md docs: README (EN/DE), Lizenzen, SECURITY, Diataxis-Manual, Release-Infra 2026-07-25 14:44:44 +02:00
esbuild.config.mjs feat: Transmute MVP — NL zu Regex mit Trefferliste und bestaetigter Anwendung 2026-07-25 14:33:50 +02:00
eslint.config.mjs feat: Transmute MVP — NL zu Regex mit Trefferliste und bestaetigter Anwendung 2026-07-25 14:33:50 +02:00
eslint.portal.config.mjs feat: Transmute MVP — NL zu Regex mit Trefferliste und bestaetigter Anwendung 2026-07-25 14:33:50 +02:00
LICENSE docs: README (EN/DE), Lizenzen, SECURITY, Diataxis-Manual, Release-Infra 2026-07-25 14:44:44 +02:00
LICENSE-DOCS docs: README (EN/DE), Lizenzen, SECURITY, Diataxis-Manual, Release-Infra 2026-07-25 14:44:44 +02:00
manifest.json chore(release): 0.3.0 2026-07-27 21:07:58 +02:00
package-lock.json feat: Transmute MVP — NL zu Regex mit Trefferliste und bestaetigter Anwendung 2026-07-25 14:33:50 +02:00
package.json chore(release): 0.3.0 2026-07-27 21:07:58 +02:00
README.de.md docs: README (EN/DE), Lizenzen, SECURITY, Diataxis-Manual, Release-Infra 2026-07-25 14:44:44 +02:00
README.md docs(readme): den Backtracking-Schutz beschreiben, wie er wirklich wirkt 2026-07-27 21:16:40 +02:00
SECURITY.md docs: README (EN/DE), Lizenzen, SECURITY, Diataxis-Manual, Release-Infra 2026-07-25 14:44:44 +02:00
styles.css fix(regex): Kanarienvogel-Lauf vor jeder Risiko-Freigabe 2026-07-27 20:15:07 +02:00
tsconfig.json feat: Transmute MVP — NL zu Regex mit Trefferliste und bestaetigter Anwendung 2026-07-25 14:33:50 +02:00
tsconfig.test.json feat: Transmute MVP — NL zu Regex mit Trefferliste und bestaetigter Anwendung 2026-07-25 14:33:50 +02:00
versions.json chore(release): 0.3.0 2026-07-27 21:07:58 +02:00
vitest.config.ts feat: Transmute MVP — NL zu Regex mit Trefferliste und bestaetigter Anwendung 2026-07-25 14:33:50 +02:00

Transmute

🇬🇧 English · 🇩🇪 Deutsch

License: AGPL-3.0 Docs: CC BY-SA 4.0 Release Platform

Describe what to replace in plain language — a local LLM writes the regular expression, and you review every match before anything is written.

Features

  • Natural-language search & replace. Type what should change (e.g. "turn dates from DD.MM.YYYY into YYYY-MM-DD") and a local OpenAI-compatible LLM turns it into a JavaScript regular expression, a replacement pattern, and a one-sentence plain-language explanation of what the pattern matches.
  • Preview before anything is written. Every match is shown before/after, line by line, with its own checkbox. Nothing changes in your note until you click "Apply" — and only the checked matches are written.
  • No separate undo system needed. Applying writes through the editor, so the change lands in Obsidian's own undo stack — Cmd+Z reverts it in one step, exactly like any other edit.
  • Iterative refinement. Not quite right? Type a follow-up such as "but not inside code blocks" and click "Refine" — the plugin sends the conversation history and the actual matches found so far back to the model, so it can see what it got wrong.
  • Scope control. Run on the whole note or on your current selection; the default is configurable in settings.
  • A history you can go back through. Refining is trial and error, and the third attempt can be worse than the first. Every round is kept: from the second one on, the panel lists them with their instruction and match count, and one click returns to any earlier version — the later ones stay. Refining from an earlier version continues that one.
  • The rule sticks to its note. The note is pinned when the preview is generated, and the panel names it. Refining and applying go to that note no matter which tab has focus — and applying re-runs the rule first, so an edit elsewhere in the note (or a linter plugin rewriting your frontmatter on save) doesn't matter, while an edit to a matched span stops the write instead of silently replacing the wrong text.
  • Model and thinking switchable from the panel. Both also live in settings, but noticing mid-try that another model fits better shouldn't cost two dialogs. A model the endpoint no longer offers is reported rather than silently swapped, and a model that always thinks (gpt-oss, harmony) shows a locked toggle instead of pretending it can be turned off.
  • Edit the pattern yourself. Regex, replacement and flags are editable right in the panel, and the preview recomputes after a short typing pause. An edited rule becomes its own history entry ("Edited by hand"), so the model's version stays and remains one click away. g is shown but not offered — it is always on, and a switch that switches nothing would be a lie.
  • A way in without the model at all. "or write the pattern yourself" opens the same preview with an empty pattern, for when you already know what you want and don't feel like waiting for a model. No separate mode, no second tab.
  • A cheat sheet where you're typing. A collapsible regex reference sits right under the fields — no model, no network — and stays open while you work.
  • You decide about risky patterns you wrote yourself — with a measurement to back it up. A hand-written pattern that trips the backtracking heuristic is a warning with a reason, not a refusal: "Run it anyway" releases it for exactly that pattern. Change one character and the warning is back — a release for (a+)+b says nothing about (a+)+bc. Before the release actually runs, the pattern is timed against a 20-character sample of the longest line: backtracking cost grows exponentially with input length, so a pattern that is already slow there would effectively never finish on a long line, and it is stopped with the measurement shown. This is a brake, not a guarantee — a pattern that passes the sample can still blow up on a much longer line.
  • An optional "replace with" field. Off by default — many instructions have no target pattern at all — but switchable on in settings if you think in classic find/replace terms.
  • Model-agnostic by design. No model name is ever hard-coded. The model list is read live from the endpoint's GET /v1/models; leaving the setting empty lets the server pick whichever model is loaded.
  • Ordered endpoint fallback list. Configure several OpenAI-compatible servers; the plugin tries them in order and uses the first one that answers, with one-click presets for LM Studio and Ollama and a per-row reachability status.
  • Reasoning models handled correctly. <think> blocks are stripped before the JSON answer is parsed, and — where the server supports it — reasoning is actively suppressed so thinking models answer faster and don't leak their chain of thought into the note.
  • A runaway pattern is caught before it runs. Every generated pattern is screened by a static heuristic (nested quantifiers, quantified alternation, unbounded backreferences) before it is ever executed, and a generated pattern that trips it is rejected outright. Execution is additionally bounded by a time budget — but be aware of what that budget can and cannot do: it is checked between lines, so it stops a slow run over many lines, and it cannot stop a single match that has gone exponential on one very long line. Obsidian does not allow web workers, so a running match cannot be cancelled. That is why a hand-written pattern that trips the heuristic is released only after Transmute has measured it on a short sample first (see below).
  • Tolerant of imperfect model output. The model's answer is parsed leniently — code fences are stripped, the first balanced JSON object is extracted — and a single retry with the concrete parse or validation error is sent back to the model before giving up with a clear message.
  • Bilingual interface. English is canonical; the UI follows Obsidian's display language and ships a full German translation.

In detail

Transmute solves a gap Obsidian leaves open: the built-in search is read-only, and the existing regex plugins (Regex Find/Replace, Search and Replace Regex, Apply Patterns) all assume you already know how to write a regular expression. Transmute lets you describe the edit in your own words instead. A ribbon icon (replace, label "Transmute") and the command "Open panel" open a sidebar panel: pick a scope, describe the change, click "Preview", and the model's pattern is compiled, screened for catastrophic-backtracking risk, and run against your text — never blindly. The resulting hits are shown with highlighting; select or deselect individual matches (or use "Select all" / "Select none"), refine the instruction as many times as you like, and only write the note once you are satisfied.

Requirements

  • Obsidian 1.8.7+ (desktop or mobile).
  • An OpenAI-compatible local server (e.g. LM Studio or Ollama) with a chat-capable model loaded. New to local LLMs? The local LLM setup guide walks you through server, model and mobile access end to end. The endpoint and model are configured in the plugin settings — nothing leaves your machine.

Install

Search for Transmute in Settings → Community plugins → Browse, then click Install and Enable.

Manual

Download main.js, manifest.json, and styles.css from the latest release and place them in <vault>/.obsidian/plugins/transmute/, then enable the plugin under Settings → Community plugins.

From source

git clone https://codeberg.org/jkaindl/obsidian-transmute
cd obsidian-transmute
npm install
npm run build   # produces main.js

Then copy main.js, manifest.json, and styles.css into <vault>/.obsidian/plugins/transmute/ and reload Obsidian.

Usage

  1. Point the plugin at your local server (see Configuration below) and make sure a model is loaded.
  2. Click the ribbon icon "Transmute" (or run the command "Open panel") to open the panel in the sidebar.
  3. Pick a scope: "Whole note" or "Selection".
  4. Describe the change in the instruction box, e.g. "turn dates from DD.MM.YYYY into YYYY-MM-DD", and click "Preview".
  5. Review the preview: the generated pattern, its plain-language explanation, and every match with a before/after line and a checkbox. Deselect anything you don't want, or use "Select all" / "Select none".
  6. Not quite right? Type a follow-up in the refine box (e.g. "but not inside code blocks") and click "Refine" — the model sees the previous rounds and the actual matches it produced.
  7. Refined more than once? The panel now shows a history above the pattern — click any earlier step to go back to it. Refining from there continues that version; the later ones stay in the list.
  8. Click "Apply". Only the checked matches are written, in a single editor transaction — so Cmd+Z reverts the whole thing in one step, and your cursor and scroll position survive. "Discard" throws the result away and leaves your instruction standing.

Configuration

Open Settings → Community plugins → Transmute. Settings are grouped under "Connection" and "Behaviour".

Setting Default Effect
Endpoints ["http://127.0.0.1:1234"] Ordered list of OpenAI-compatible servers, tried in order; the first reachable one is used. One-click presets for LM Studio and Ollama, plus a per-row reachability status and a "Test connections" button.
Model "" (empty) Which model to request. Empty lets the server choose whichever is loaded. A dropdown is filled from the active endpoint's /v1/models; use the reload button to refresh it.
Request timeout (ms) 120000 How long to wait for the model to answer before giving up.
Default scope file (Whole note) Which scope a new rule starts with — "Whole note" or "Selection". Switchable per run in the panel.
Text sample sent to the model 2000 (characters) How much of the scope text is sent along with the instruction, so the model can see what it is matching against.
Time budget for running the pattern (ms) 2000 A pattern that runs longer than this on a line-by-line scan is stopped, and the matches found so far are shown with a notice.
Show a separate "replace with" field false Adds a second, optional input for the target pattern. Off by default — many instructions ("strip trailing whitespace", "make headings one level deeper") have no target pattern at all.
Ask reasoning models to skip thinking true Sends reasoning-suppression parameters to the endpoint (and strips <think> blocks from the answer either way) so reasoning-capable local models answer faster and more reliably. Measured on this task: ~1.7 s and 5/5 correct with suppression, 2656 s and 4/5 without. Also switchable from the panel.

Endpoint tip: enter the base URL without a trailing /v1 — the client appends /v1 itself (a trailing /v1 is stripped automatically either way, so both forms work).

How it works

Transmute sends your instruction, plus a sample of the scope text, to the configured endpoint's /v1/chat/completions and asks for a single JSON object — regex, flags, replacement, explanation — never prose. The answer is parsed leniently (code fences and any <think> block are stripped, the first balanced JSON object is extracted), and one retry with the concrete error is sent back to the model if the first answer doesn't parse or validate.

Before the generated pattern ever runs, a static heuristic screens it for constructs known to cause catastrophic backtracking — nested quantifiers, quantified alternation over identical branches, unbounded backreferences — and rejects it with a plain-language reason if it looks dangerous. A pattern that passes is then executed line by line against the scope text under a configurable time budget; multi-line patterns (an s/m flag, or a literal \n in the pattern) run once against the full text instead, since a time budget only makes sense between discrete steps.

Nothing is written to your note until you click "Apply". At that point, only the checked matches are applied — in reverse order, so earlier replacements never shift the offsets of later ones — and the result is written through Obsidian's editor API (editor.replaceRange), which is what puts the change on the normal undo stack.

Manual

The full documentation follows the Diátaxis framework — see docs/manual/index.md:

  • Tutorial — get from zero to your first applied replacement.
  • How-to guides — task-focused recipes (multiple endpoints, refining a rule, undoing, handling an unsafe-pattern error).
  • Reference — settings, commands, error messages, the JSON contract.
  • Explanation — why preview-before-apply is the core of the design, why there is no separate snapshot system, and why the safety guard has no web worker to lean on.

See the changelog for release notes.

ksawl/obsidian-alchemist shares the alchemy/transmutation imagery but a different job: it is a general vault-hygiene toolkit. Transmute is narrowly about turning a plain-language instruction into a reviewed, applied regex replacement — it does not aim to cover vault hygiene more broadly.

Contributing

Contributions are welcome. Please read CONTRIBUTING.md for the workflow (test-driven, main always green, feature work in feat/<name>, Conventional Commits) and AGENTS.md for the architecture and module conventions. The canonical repository lives on Codeberg; GitHub (johannes-kaindl/obsidian-transmute) is a mirror.

License

  • Code: AGPL-3.0-or-later. A commercial dual-license is available on request if the AGPL copyleft does not fit your use case.
  • Documentation and text: CC BY-SA 4.0.

Copyright © 2026 Johannes Kaindl.