<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: imper</title>
    <description>The latest articles on DEV Community by imper (@imper_7cde72b79d2529291ec).</description>
    <link>https://dev.to/imper_7cde72b79d2529291ec</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3812341%2F4a3e352b-d42f-4efe-bb2c-23e8703e87bb.jpg</url>
      <title>DEV Community: imper</title>
      <link>https://dev.to/imper_7cde72b79d2529291ec</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/imper_7cde72b79d2529291ec"/>
    <language>en</language>
    <item>
      <title>I built a local-first memory service so my coding agent stops forgetting between sessions</title>
      <dc:creator>imper</dc:creator>
      <pubDate>Sun, 30 Aug 2026 03:23:32 +0000</pubDate>
      <link>https://dev.to/imper_7cde72b79d2529291ec/i-built-a-local-first-memory-service-so-my-coding-agent-stops-forgetting-between-sessions-l99</link>
      <guid>https://dev.to/imper_7cde72b79d2529291ec/i-built-a-local-first-memory-service-so-my-coding-agent-stops-forgetting-between-sessions-l99</guid>
      <description>&lt;p&gt;My coding agent was very good at solving a problem and very bad at remembering that it had. Every session started clean. Which port the local service listens on, why we went with one crate instead of another, the fix for a race we had already chased twice: I typed all of it again, in a slightly different order, most days.&lt;/p&gt;

&lt;p&gt;Each harness has some answer to this. pi has its own store, Claude Code has another, Codex keeps transcripts. None of them hand anything to the next one, so the memory lives wherever you happened to be working that week.&lt;/p&gt;

&lt;p&gt;I wanted one place to keep it, on my machine, that any of them could ask. That turned into &lt;a href="https://github.com/Blue-B/memnest" rel="noopener noreferrer"&gt;memnest&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F526zticd7iloaf0el2ep.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F526zticd7iloaf0el2ep.png" alt="memnest architecture" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One service, several front doors
&lt;/h2&gt;

&lt;p&gt;memnest is a single Rust binary listening on &lt;code&gt;127.0.0.1:3111&lt;/code&gt;. One address serves both an HTTP API and a Streamable HTTP MCP endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:3111        HTTP API
http://127.0.0.1:3111/mcp    MCP endpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each harness exposes different extension points, so the wiring differs while the store and the tool contract stay identical:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Harness&lt;/th&gt;
&lt;th&gt;Prompt-time recall&lt;/th&gt;
&lt;th&gt;Memory tools&lt;/th&gt;
&lt;th&gt;Transcript capture&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pi&lt;/td&gt;
&lt;td&gt;Autocontext, from the extension&lt;/td&gt;
&lt;td&gt;Registered by the extension&lt;/td&gt;
&lt;td&gt;&lt;code&gt;memnest watch&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;memnest hook&lt;/code&gt; on &lt;code&gt;UserPromptSubmit&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;memnest watch&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;memnest hook&lt;/code&gt; on &lt;code&gt;UserPromptSubmit&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;memnest watch&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other MCP clients&lt;/td&gt;
&lt;td&gt;Depends on the client&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Point any MCP client at it and you are done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"memnest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:3111/mcp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Streamable HTTP is the recommended transport because every client then shares one server and one data directory. stdio works, but only when that single process owns the store. A second writer on the same data directory is rejected rather than allowed to race the indexes.&lt;/p&gt;

&lt;h2&gt;
  
  
  No LLM anywhere in the loop
&lt;/h2&gt;

&lt;p&gt;Nothing in memnest calls a model API. Embeddings run locally with &lt;code&gt;intfloat/multilingual-e5-base&lt;/code&gt;, and there is no summarization step between what you saved and what comes back.&lt;/p&gt;

&lt;p&gt;That was a deliberate choice, and it cost something. LLM summarization would compress a long transcript into something denser and probably rank better. It would also mean that reading my own notes depends on someone else's uptime, someone else's pricing, and a paraphrase I never reviewed. A memory that quietly rewrites what you told it is worse than a memory that occasionally returns too much.&lt;/p&gt;

&lt;p&gt;So there are two kinds of records, kept apart. Things you save on purpose (decisions, preferences, corrections) and redacted conversation text, kept verbatim. Both are searchable, neither is rewritten.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQLite is the truth, the indexes are opinions
&lt;/h2&gt;

&lt;p&gt;The store is SQLite. Next to it sit a Tantivy BM25 index and an HNSW vector index, and both are derived state that can be thrown away and rebuilt.&lt;/p&gt;

&lt;p&gt;That distinction is what makes the write path recoverable. A write inserts the record and an index job in the same transaction, then builds both indexes, then clears the job. If the process dies halfway, the job is still there at startup and the write is replayed. The failure mode is a few seconds of extra work on boot, not a memory that exists in the database and cannot be found by search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why two indexes and not one
&lt;/h2&gt;

&lt;p&gt;This is the part I would argue about with someone.&lt;/p&gt;

&lt;p&gt;BM25 finds an exact token. Ask it for &lt;code&gt;3111&lt;/code&gt; or a crate name and it lands on the right record. Ask it for the same idea in different words and it shrugs.&lt;/p&gt;

&lt;p&gt;Vector similarity does the opposite. It handles the paraphrase and drifts away from the literal string you actually typed, which is exactly the case where you knew the answer and just wanted it back.&lt;/p&gt;

&lt;p&gt;You cannot tell which one you need until the query arrives, and by then the write is long done. So a write pays for both, and a read merges the two rankings with reciprocal rank fusion at k=60, then reranks with MMR at lambda=0.5 so five near-identical memories do not fill the whole result.&lt;/p&gt;

&lt;p&gt;Scope narrows before any of that runs: the current directory's workspace plus a shared &lt;code&gt;playbook&lt;/code&gt; scope for rules that hold everywhere. Project memory does not leak sideways.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I measured, including what I threw away
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/Blue-B/memnest/blob/main/docs/retrieval-benchmarks.md" rel="noopener noreferrer"&gt;benchmarks doc&lt;/a&gt; has a public fixture of 22 documents and 46 hand-labelled Korean queries, 41 with a relevant document and 5 deliberately with none.&lt;/p&gt;

&lt;p&gt;Current hybrid search gets recall@1 of 0.976 on it. Two ideas that looked good did not ship.&lt;/p&gt;

&lt;p&gt;A CJK 2-to-3-character ngram tokenizer took partial-term lexical recall from 0.167 to 1.000 on an isolated 5,000-document probe. On the real 46-query fixture it changed nothing, at 2.94 times the index size and roughly 50 times the lexical query cost. Every language pays that, and the measured gain on representative queries was zero, so it stays off behind a flag.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;jina-reranker-v2-base-multilingual&lt;/code&gt; cross-encoder took Precision@1 from 0.200 to 1.000 on five known rank errors. It also wanted 1.1 GB of model cache and 35 seconds of warm initialization for a service meant to answer a prompt hook in milliseconds. Also off.&lt;/p&gt;

&lt;p&gt;Both are written down with their numbers rather than quietly dropped, because "we tried it and here is what it cost" is the part I always want from someone else's repo and rarely get.&lt;/p&gt;

&lt;p&gt;The honest weak spot is candidate rejection. Recall@1 is 0.976, but no-result accuracy is 0.200: four of the five queries that should return nothing still return a low-confidence memory. Good retrieval and bad abstention is a real failure mode for an agent, because a confidently irrelevant memory in the prompt is worse than an empty one. That is what I am working on next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secrets do not go in the searchable store
&lt;/h2&gt;

&lt;p&gt;Credentials live in an AES-256-GCM vault behind separate tools, not in anything the search path can reach. Credential-shaped text is redacted on the way in, so a token pasted into a conversation does not quietly become a searchable record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;Linux x86_64 and aarch64 can install a release without a Rust toolchain. The script verifies the archive checksum, installs the binary, registers a user systemd service, and checks its health.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Blue-B/memnest/main/core/scripts/install.sh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/memnest-install.sh
bash /tmp/memnest-install.sh &lt;span class="nt"&gt;--user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the script before you run it. Windows and WSL have their own scripts in the same directory, and building from source needs Git and a 2024-edition Rust toolchain.&lt;/p&gt;

&lt;p&gt;Starting the service downloads nothing. The embedding model arrives on the first operation that needs it, so the first write or first search is slower than the rest. &lt;code&gt;memnest --warmup-embedding&lt;/code&gt; pays that up front.&lt;/p&gt;

&lt;p&gt;MIT licensed. The repo is at &lt;a href="https://github.com/Blue-B/memnest" rel="noopener noreferrer"&gt;github.com/Blue-B/memnest&lt;/a&gt;, and I would rather hear where the design is wrong than where it is fine.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I stopped spawning a second Pi just to switch sessions</title>
      <dc:creator>imper</dc:creator>
      <pubDate>Sun, 23 Aug 2026 14:23:00 +0000</pubDate>
      <link>https://dev.to/imper_7cde72b79d2529291ec/i-stopped-spawning-a-second-pi-just-to-switch-sessions-9cb</link>
      <guid>https://dev.to/imper_7cde72b79d2529291ec/i-stopped-spawning-a-second-pi-just-to-switch-sessions-9cb</guid>
      <description>&lt;p&gt;I use the Pi coding agent from several project folders. After a while, &lt;code&gt;pi --resume&lt;/code&gt; became a long list of sessions with similar first prompts. I kept reopening the wrong one.&lt;/p&gt;

&lt;p&gt;I made &lt;a href="https://github.com/Blue-B/pisesh" rel="noopener noreferrer"&gt;pisesh&lt;/a&gt; to add the things I wanted in that list: favorites, search, custom names, a current-project view, and optional title generation. It is a single Node script with no runtime dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first &lt;code&gt;/sesh&lt;/code&gt; implementation
&lt;/h2&gt;

&lt;p&gt;The first version took a direct route. It paused Pi's TUI, opened the pisesh picker, then started another Pi process for the selected session.&lt;/p&gt;

&lt;p&gt;That was useful because leaving the child process returned me to the original session. It also meant the process tree looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pi session A
  pisesh
    Pi session B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The nested process eventually became the problem. Tools were running in session B, but anything watching the outer Pi process could still see session A as idle. The terminal looked right while the runtime lifecycle was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching the existing runtime
&lt;/h2&gt;

&lt;p&gt;Pi already exposes &lt;code&gt;ctx.switchSession()&lt;/code&gt; to extensions. In pisesh 0.3.0, the picker no longer starts Pi when it was opened through &lt;code&gt;/sesh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The picker writes one small JSON selection to a private file descriptor. It includes the absolute session path and the selected model behavior. The extension reads that result and asks Pi to switch the current runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pi session A
  /sesh picker
  ctx.switchSession(sessionB)
Pi session B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standalone &lt;code&gt;pisesh&lt;/code&gt; still starts &lt;code&gt;pi --session&lt;/code&gt;, so the shell command keeps its old behavior. Only the extension path changed.&lt;/p&gt;

&lt;p&gt;There were two details I did not want to lose. &lt;code&gt;Enter&lt;/code&gt; still resumes with the current default model and thinking level, while &lt;code&gt;o&lt;/code&gt; restores the values recorded in the selected session. Interrupted tool calls are also repaired before the handoff when Pi would otherwise load an incomplete transcript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pi &lt;span class="nb"&gt;install &lt;/span&gt;npm:pisesh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;/sesh&lt;/code&gt; inside Pi. The package is MIT licensed and works on Node 18 or newer.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Blue-B/pisesh" rel="noopener noreferrer"&gt;https://github.com/Blue-B/pisesh&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cli</category>
      <category>opensource</category>
      <category>node</category>
    </item>
    <item>
      <title>영상 하나 던지면 번역된 자막이 생기는 오픈소스 앱 WhisperSubTranslate v2.0 (whisper.cpp + 로컬 LLM)</title>
      <dc:creator>imper</dc:creator>
      <pubDate>Sat, 23 May 2026 14:09:21 +0000</pubDate>
      <link>https://dev.to/imper_7cde72b79d2529291ec/yeongsang-hana-deonjimyeon-beonyeogdoen-jamagi-saenggineun-opeunsoseu-aeb-whispersubtranslate-v20-whispercpp-rokeol-llm-3bh7</link>
      <guid>https://dev.to/imper_7cde72b79d2529291ec/yeongsang-hana-deonjimyeon-beonyeogdoen-jamagi-saenggineun-opeunsoseu-aeb-whispersubtranslate-v20-whispercpp-rokeol-llm-3bh7</guid>
      <description>&lt;p&gt;외국어 영상 볼 때 자막 없으면 답답하죠. 저도 그랬습니다.&lt;/p&gt;

&lt;p&gt;그래서 WhisperSubTranslate 만들었습니다. 영상 파일 하나 던지면 whisper.cpp가 음성 추출하고, 번역 엔진이 SRT 자막으로 만들어줍니다. 클라우드 업로드 없고, 계정도 필요 없습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;엔진 선택 가능:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MyMemory (무료, 키 불필요, 일일 제한 있음)&lt;/li&gt;
&lt;li&gt;DeepL / OpenAI / Gemini (본인 키 사용)&lt;/li&gt;
&lt;li&gt;로컬 LLM (HY-MT GGUF 모델, 완전 오프라인) — v2.0 주요 기능&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;실제로 잘 되는 것들:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;긴 영상도 배치 큐로 처리&lt;/li&gt;
&lt;li&gt;폴더 드래그 앤 드롭&lt;/li&gt;
&lt;li&gt;GPU 있으면 CUDA 가속, 없으면 CPU 폴백&lt;/li&gt;
&lt;li&gt;SRT 출력 → VLC, mpv, Plex 등에서 바로 사용&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;안 되는 것 (솔직히):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;영상에 박힌 자막 추출은 안 됨. 음성에서 새로 만드는 방식입니다&lt;/li&gt;
&lt;li&gt;실시간 마이크 입력은 아직 지원 안 함&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;한국어 포함 14개 언어, whisper.cpp 기준 100+ 소스 언어 지원합니다. Windows 포터블 빌드 + Linux/macOS 소스 빌드.&lt;/p&gt;

&lt;p&gt;오픈소스 (GPL-3.0): &lt;a href="https://github.com/Blue-B/WhisperSubTranslate" rel="noopener noreferrer"&gt;https://github.com/Blue-B/WhisperSubTranslate&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>electron</category>
      <category>whisper</category>
      <category>ai</category>
    </item>
    <item>
      <title>/disconnect for opencode — a tiny TUI plugin I wish existed before I made it</title>
      <dc:creator>imper</dc:creator>
      <pubDate>Mon, 18 May 2026 17:23:30 +0000</pubDate>
      <link>https://dev.to/imper_7cde72b79d2529291ec/disconnect-for-opencode-a-tiny-tui-plugin-i-wish-existed-before-i-made-it-20j9</link>
      <guid>https://dev.to/imper_7cde72b79d2529291ec/disconnect-for-opencode-a-tiny-tui-plugin-i-wish-existed-before-i-made-it-20j9</guid>
      <description>&lt;p&gt;I use &lt;a href="https://opencode.ai" rel="noopener noreferrer"&gt;opencode&lt;/a&gt; as my daily TUI coding agent. It's good. But there's one thing that kept biting me.&lt;/p&gt;

&lt;p&gt;When you want to remove a single provider — say you rotated a key and want the old entry gone — opencode doesn't ship a clean way to do it. The choices are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Re-auth every provider from scratch&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;~/.local/share/opencode/auth.json&lt;/code&gt; in vim and pray the trailing comma survives&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I did option 2 maybe four times before I got annoyed enough to write a plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;opencode plugin opencode-tui-utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then inside the TUI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/disconnect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It opens opencode's own provider picker — the same dialog component the rest of the TUI already uses — so it doesn't feel like a bolted-on script. You pick the provider you want gone, confirm, done.&lt;/p&gt;

&lt;p&gt;Token values are never printed. Only provider names and auth types are shown, so you can run it in a recorded session without paranoia.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other commands
&lt;/h2&gt;

&lt;p&gt;Since I was writing the plugin loader anyway, I added the three other slash commands I kept wanting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/lsp-toggle&lt;/code&gt; — flip LSP on/off without restarting the TUI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/websearch-toggle&lt;/code&gt; — same for web search&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/tool-status&lt;/code&gt; — print what's currently enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The toggle commands update your shell profile and prompt for a restart, which matches opencode's existing pattern for launch-gated flags.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a plugin instead of a fork
&lt;/h2&gt;

&lt;p&gt;opencode's plugin system is just JSON: drop the package into &lt;code&gt;~/.config/opencode/tui.json&lt;/code&gt; under &lt;code&gt;"plugin"&lt;/code&gt;, restart, the slash command shows up in the palette next to the built-ins. No fork to maintain, no rebase pain when opencode updates.&lt;/p&gt;

&lt;p&gt;This also means adding command #5 is mechanical — there's a shared API wrapper and the loader handles registration. If you've got a slash command you keep missing in opencode, &lt;a href="https://github.com/Blue-B/opencode-tui-utils/blob/main/CONTRIBUTING.md#command-ideas-up-for-grabs" rel="noopener noreferrer"&gt;there's a "command ideas up for grabs" section in CONTRIBUTING.md&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to find it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/Blue-B/opencode-tui-utils" rel="noopener noreferrer"&gt;https://github.com/Blue-B/opencode-tui-utils&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;code&gt;opencode-tui-utils&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;License: MIT&lt;/li&gt;
&lt;li&gt;Inspired by &lt;a href="https://github.com/anomalyco/opencode/issues/10494" rel="noopener noreferrer"&gt;opencode issue #10494&lt;/a&gt;, where someone had asked for the disconnect flow&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Built by &lt;strong&gt;&lt;a href="https://github.com/Blue-B" rel="noopener noreferrer"&gt;@Blue-B&lt;/a&gt;&lt;/strong&gt; — happy to take feedback, especially from anyone using opencode in a workflow I haven't thought of.&lt;/p&gt;

</description>
      <category>opencode</category>
      <category>cli</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>One install command wires 8 CLI coding agents to a shared stealth Chromium</title>
      <dc:creator>imper</dc:creator>
      <pubDate>Mon, 18 May 2026 16:58:37 +0000</pubDate>
      <link>https://dev.to/imper_7cde72b79d2529291ec/one-install-command-wires-8-cli-coding-agents-to-a-shared-stealth-chromium-3e50</link>
      <guid>https://dev.to/imper_7cde72b79d2529291ec/one-install-command-wires-8-cli-coding-agents-to-a-shared-stealth-chromium-3e50</guid>
      <description>&lt;p&gt;I use a bunch of CLI coding agents — Claude Code, Codex CLI, pi, opencode, Gemini CLI, Kiro, Amp, Crush — and every one of them shipped its own headless Chromium that gets blocked by basic bot checks (&lt;code&gt;navigator.webdriver&lt;/code&gt;, missing plugins, the usual headless fingerprints).&lt;/p&gt;

&lt;p&gt;The fix itself isn't novel. Patched stealth Chromium has existed for years. The annoying part is wiring it into each agent — every one wants its config in a different file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code → &lt;code&gt;CLAUDE.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Codex CLI / opencode → &lt;code&gt;AGENTS.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Gemini CLI → &lt;code&gt;GEMINI.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Kiro → &lt;code&gt;.kiro/steering/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Amp / Crush → their own dotfiles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and all of them need to point at the same browser binary.&lt;/p&gt;

&lt;p&gt;So I spent a weekend writing a one-command installer that does the wiring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Blue-B/browser-harness-kit
&lt;span class="nb"&gt;cd &lt;/span&gt;browser-harness-kit
bash install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It detects which of the 8 agents you have installed, writes the right rule file for each, and creates a shared &lt;code&gt;~/.playwright/cli.config.json&lt;/code&gt; so they all hit the same stealth Chromium through &lt;a href="https://www.npmjs.com/package/pi-playwright" rel="noopener noreferrer"&gt;pi-playwright&lt;/a&gt; as the CLI runner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;Every agent drives a real headed Chromium that passes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;navigator.webdriver&lt;/code&gt; check&lt;/li&gt;
&lt;li&gt;plugin / mimeType length checks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;window.chrome&lt;/code&gt; shape&lt;/li&gt;
&lt;li&gt;full &lt;a href="https://bot.sannysoft.com" rel="noopener noreferrer"&gt;bot.sannysoft.com&lt;/a&gt; matrix&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Screenshot of the pass matrix and verify output are in the &lt;a href="https://github.com/Blue-B/browser-harness-kit" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it isn't
&lt;/h2&gt;

&lt;p&gt;This repo is integration glue only. It does NOT bundle or redistribute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;[CloakBrowser]&lt;a href="https://www.reddit.com/r/SideProject/comments/1tgrmo3(https://github.com/CloakHQ/CloakBrowser)" rel="noopener noreferrer"&gt;https://www.reddit.com/r/SideProject/comments/1tgrmo3(https://github.com/CloakHQ/CloakBrowser)&lt;/a&gt;&lt;/strong&gt; — the actual stealth Chromium build (linked, not bundled)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playwright + &lt;a href="https://www.npmjs.com/package/pi-playwright" rel="noopener noreferrer"&gt;pi-playwright&lt;/a&gt;&lt;/strong&gt; — the runner&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 8 agents themselves&lt;/strong&gt; — each lives at its own home&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just the wiring between them, so I don't have to redo it on every fresh box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm sharing it
&lt;/h2&gt;

&lt;p&gt;I wrote this for my own setup. After the third time pasting the same config snippets into a new machine I figured someone else would save a weekend.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/Blue-B/browser-harness-kit" rel="noopener noreferrer"&gt;https://github.com/Blue-B/browser-harness-kit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Tested on WSL2 + Ubuntu 24.04 + Node 22&lt;/li&gt;
&lt;li&gt;License: MIT&lt;/li&gt;
&lt;li&gt;Reddit discussion: &lt;a href="https://www.reddit.com/r/SideProject/comments/1tgrmo3" rel="noopener noreferrer"&gt;https://www.reddit.com/r/SideProject/comments/1tgrmo3&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy to take feedback, or PRs adding adapters for CLI agents I haven't covered. If your agent reads from yet another rule file, drop an issue.&lt;/p&gt;




&lt;p&gt;Built by &lt;strong&gt;&lt;a href="https://github.com/Blue-B" rel="noopener noreferrer"&gt;@Blue-B&lt;/a&gt;&lt;/strong&gt; — find more side projects on my GitHub.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cli</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building an offline subtitle extractor with whisper.cpp and Electron</title>
      <dc:creator>imper</dc:creator>
      <pubDate>Sun, 08 Mar 2026 02:37:46 +0000</pubDate>
      <link>https://dev.to/imper_7cde72b79d2529291ec/building-an-offline-subtitle-extractor-with-whispercpp-and-electron-44k2</link>
      <guid>https://dev.to/imper_7cde72b79d2529291ec/building-an-offline-subtitle-extractor-with-whispercpp-and-electron-44k2</guid>
      <description>&lt;p&gt;I watch a lot of foreign language content - anime, K-dramas, tech talks - and getting subtitles was always a pain. Upload to a random website, hit the daily limit, try another one, or install Python and figure out whisper's CLI.&lt;/p&gt;

&lt;p&gt;So over the past few months I've been building a desktop app that handles the whole pipeline locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;You drop a video file in, pick a whisper model size, and it spits out an SRT subtitle file. Optionally you can translate the subtitles using one of several engines.&lt;/p&gt;

&lt;p&gt;The speech-to-text runs via &lt;strong&gt;whisper.cpp&lt;/strong&gt; so everything stays on your machine. No uploads, no API calls for the transcription part. If you have an NVIDIA GPU it automatically uses CUDA, otherwise it falls back to CPU - this was one of the trickier parts to get right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bixenk40upft6zx7vbk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bixenk40upft6zx7vbk.png" alt="App Screenshot" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech decisions
&lt;/h2&gt;

&lt;p&gt;I went with &lt;strong&gt;Electron + Node.js&lt;/strong&gt; because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cross-platform (though I'm mainly targeting Windows right now)&lt;/li&gt;
&lt;li&gt;Easy to bundle whisper.cpp binaries and ffmpeg&lt;/li&gt;
&lt;li&gt;The UI is just HTML/CSS/JS so iteration is fast&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For whisper.cpp integration, the app spawns it as a child process with the right flags depending on whether CUDA is available. Model files (GGML format) auto-download on first run into a local &lt;code&gt;_models/&lt;/code&gt; folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translation engines
&lt;/h2&gt;

&lt;p&gt;Translation is optional. Currently supported:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MyMemory&lt;/strong&gt; - free, no API key, ~50K chars/day&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeepL&lt;/strong&gt; - free tier 500K chars/month, needs API key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI GPT&lt;/strong&gt; - paid, good quality for nuanced translations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini&lt;/strong&gt; - Google's API, generous free tier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app chunks subtitle text and sends it in batches to avoid rate limits. Each engine has its own quirks with language codes so there's a mapping layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  v1.4.0 changes
&lt;/h2&gt;

&lt;p&gt;Just pushed the latest update which adds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic GPU/CPU fallback detection&lt;/li&gt;
&lt;li&gt;Bundled ffprobe-static (no more separate ffmpeg install)&lt;/li&gt;
&lt;li&gt;Better DeepL language code mapping&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;It's packaged as a portable .exe - no install needed, just extract and run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/Blue-B/WhisperSubTranslate" rel="noopener noreferrer"&gt;WhisperSubTranslate&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;License&lt;/strong&gt;: GPL-3.0&lt;/p&gt;

&lt;p&gt;If you're working on something similar or have suggestions, I'd love to hear about it.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>javascript</category>
      <category>electron</category>
      <category>whisper</category>
    </item>
  </channel>
</rss>
