opencode-preserve-formatOpenCode plugin that preserves original BOM and line endings when editing files
1
20
3 in 7 days
25.3
Multi-signal model
3 months ago
2026-05-10
Install and configure
opencode.jsonWrites to this project's opencode.json — applies to this repository only.
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-preserve-format@2026.5.10"]
}Writes to ~/.config/opencode/opencode.json — applies to every project.
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-preserve-format@2026.5.10"]
}If you want to modify the plugin locally, install it into the project and reference the local path.
shell
pnpm add -D opencode-preserve-formatopencode loads npm dependencies through its embedded runtime on startup and caches them locally — no manual global install needed.
opencode-preserve-format
An OpenCode plugin that preserves a file's original BOM and line endings whenever OpenCode modifies it. Every file keeps exactly what it had before — no configuration needed.
Installation
Add to your opencode.json:
{
"plugin": ["opencode-preserve-format"]
}
OpenCode installs the package automatically at startup.
How detection works
Line endings: The plugin counts \r\n (CRLF) and lone \n (LF) occurrences in the file. Whichever is more frequent is used as the target. Equal counts → CRLF. No newlines → LF.
BOM: The first bytes of the file are inspected for:
| Bytes | Encoding |
|---|---|
EF BB BF |
UTF-8 BOM |
FF FE |
UTF-16 LE |
FE FF |
UTF-16 BE |
If found, the BOM is re-prepended after every edit.
New files: When the write tool creates a file that doesn't exist yet, the target line ending is detected from the AI-generated content using the same majority algorithm. No BOM is added to new files.
Mixed endings: Dominant wins. A file with 70 CRLF and 30 LF is treated as a CRLF file.
How it works
The plugin hooks into OpenCode's tool execution pipeline at two points:
Before tool execution (
tool.execute.before): Reads the original file, detects its format, and convertscontent/oldString/newStringin the tool args before they reach disk. NormalizingoldStringensures the edit tool's string-matching succeeds even when the LLM generates it with incorrect line endings.After file edits (
file.editedevent): Re-reads the file as raw bytes, applies the full format pipeline (strip BOM → decode → normalize endings → encode → prepend BOM), and writes back only if something changed. This is the safety net for partial edits that produce mixed line endings.
Installing as a local file
Copy index.ts into your .opencode/plugins/ directory. No additional dependencies are needed (only Node.js built-ins are used).
Project-level:
cp index.ts <project>/.opencode/plugins/preserve-format.ts
Global:
cp index.ts ~/.config/opencode/plugins/preserve-format.ts
Debugging
The plugin logs through ctx.client.app.log() with the service name preserve-format.
For the fastest debug loop:
- Install the plugin as a local file from
index.ts. - Start OpenCode with the plugin enabled for the project.
- Edit a file with known line endings or BOM.
- Check the OpenCode logs for entries from
service=preserve-format.
Useful log messages include:
plugin loadedtool: write/tool: edit/tool: multiedit/tool: apply_patchwrite -> crlforwrite -> lfnormalized filecould not read formatcould not normalize file
To exercise the full pipeline manually, test with an existing CRLF or BOM-marked file, make an edit through OpenCode, then confirm the file still has the same BOM and dominant line ending after the write completes.
Running tests
Install dependencies, then run:
npm test
Other useful checks:
npm run typecheck
npm run build
Manual test matrix
| Scenario | Expected behaviour |
|---|---|
| UTF-8, CRLF, no BOM | All line endings remain CRLF |
| UTF-8, LF, no BOM | All line endings remain LF |
| UTF-8, CRLF, with BOM | BOM preserved, all CRLF |
| UTF-8, LF, with BOM | BOM preserved, all LF |
| UTF-16 LE with BOM | BOM preserved, correct encoding, endings preserved |
| UTF-16 BE with BOM | BOM preserved, correct encoding, endings preserved |
| New file (write) | Ending detected from AI content; no BOM added |
| Binary file (.png, .zip, etc.) | No-op |
| Mixed endings (70 CRLF / 30 LF) | Normalized to CRLF |
| Unreadable file | Warning logged; file passes through unmodified |