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.
Add to your opencode.json:
{
"plugin": ["opencode-preserve-format"]
}OpenCode installs the package automatically at startup.
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.
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.
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.tsGlobal:
cp index.ts ~/.config/opencode/plugins/preserve-format.tsThe 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.
Install dependencies, then run:
npm testOther useful checks:
npm run typecheck
npm run build| 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 |