I keep two folders of “tools I actually use.” One is a terminal aliases file. The other is a browser bookmarks bar labeled local. Both claim to be local-first. They solve different jobs, and mixing them up costs me more time than picking the slower option on purpose.
This isn’t “CLI good, browser bad.” It’s a decision rule for frontend DX: when should this live in the tab, and when should it stay in the shell?
The false dichotomy
People talk like local tooling is one pile. It isn’t.
- A CLI is scriptable, pipeable, and great at files that already live on disk. Agents love it. CI loves it. Diffs love it.
- A browser-local tool is visual, drop-zone friendly, and great at blobs you don’t want to save as
tmp-whatever-3.pngforever. No install. Nochmod +x. Close the tab and it’s gone.
Both can keep data off a third-party server. That’s the local-first part. The UX surface is what actually changes how you work.
Where I default to the CLI
I reach for the shell when most of these are true:
-
The input is already a path — repo files,
dist/, fixtures, screenshots ine2e/artifacts/ - I need to repeat it — same transform tomorrow, or in CI, or for every PR
-
I want a pipe —
rg→jq→ script → commit message - An agent will do it — coding agents are fluent with argv and stdout; they’re clumsy in a GUI
- Exit codes matter — green/red in CI beats “looks fine in the preview pane”
Examples that belong here for me:
- Lint, typecheck, format, bundle-size budgets
- Image optimization of a whole
public/folder - Converting a design-token JSON into CSS variables as part of build
- Replaying a failing Playwright run with the same flags
If I have to teach a teammate (or an agent) a click path, I’ve already lost the CLI case.
Where I default to the browser
I open a local page / side panel when most of these are true:
- The input is a blob I don’t want on disk yet — paste, drag-drop, clipboard screenshot
- I need eyes — color, layout, chart shape, crop, “does this GIF feel right”
- One-shot — I’m not putting this in CI; I’m answering a Slack “can you check this file”
-
Install friction would kill the job — non-dev collaborator, locked-down laptop, hotel Wi‑Fi where
npm iis a joke - The output is also a blob — download once, attach, done; no need for a long-lived path in the monorepo
Examples:
- Quick HEIC / WebP peek before I decide whether to commit it
- Charting a client CSV I’m not allowed to upload anywhere
- Diffing two JSON pastes from support tickets
- Checking contrast on a hex someone dropped in chat
If the “tool” requires me to invent a filename first, the browser usually wins.
The agent angle (this changed my defaults)
Coding agents flipped a few of my old habits.
Agents are excellent at CLI. They can run pnpm test, read the log, and try again. They are mediocre at “open this URL, drop a file, click Export.” So anything I want agents to own — refactors, migrations, CI gates — stays CLI-shaped.
But agents also create a lot of visual leftovers: screenshots, generated SVG, “try this CSS,” chart previews. Those leftovers are terrible as permanent repo files and fine as ephemeral browser transforms. I stopped asking the agent to curl a SaaS converter for those. I either keep a local browser tool bookmarked, or I ask the agent to write a small Node script if the job will recur.
Rule of thumb I use now:
If the agent should do it twice, make a CLI (or a script in the repo).
If a human needs to see it once, keep it in the browser.
A boring decision checklist
When I’m adding a new “helper” for the team, I ask:
| Question | Lean CLI | Lean browser |
|---|---|---|
| Will CI run it? | yes | no |
| Is the primary input a disk path? | yes | no (paste/drop) |
| Is judgment visual? | no | yes |
| Should an agent own the loop? | yes | no |
| Do non-devs need it without Node? | no | yes |
| Is the artifact temporary? | maybe | usually |
If the table splits 3–3, I build the CLI first and add a thin browser UI later only if humans keep bouncing off the flags. Flags are documentation; a drop zone is empathy. Both can exist — just don’t start with two surfaces.
Anti-patterns I’ve paid for
Electron for a one-button transform. Congrats, you shipped an OS update surface for a FileReader job.
“Just paste into this SaaS.” Local-first died the moment the paste left the machine. Browser-local and cloud-paste are not the same category.
CLI with no --stdin / no file args, only interactive prompts. Agents and CI choke. If it’s CLI, make it non-interactive by default.
Browser tool that quietly POSTs “for processing.” Read the network tab once. Local means the heavy work stays in-tab unless I opt in.
How I keep the split sane on a team
-
Repo scripts for anything that gates merges (
package.jsonscripts,just,make— pick one). - A short internal doc titled “browser-local helpers” with 5–10 bookmarks, not 40. If it isn’t in the doc, it isn’t a team tool.
- No duplicate truth — don’t maintain a CLI and a web UI that drift. Prefer one engine (shared WASM / shared Node package) and two thin fronts only when both audiences are real.
- Name the constraint in the PR — “this is agent-facing → CLI” or “this is designer-facing → browser” stops bikesheds.
Takeaway
Local-first isn’t a single product category. It’s a privacy/ownership promise that still needs a surface.
Put repeatable, path-based, agent-friendly work in the CLI. Put visual, ephemeral, drop-zone work in the browser. Optimize for who (or what) will run the loop next — you once, CI forever, or an agent at 2 a.m.
The wrong surface doesn’t just feel awkward. It quietly pushes people back to uploading files to whoever’s landing page loaded first. Pick deliberately, and the local habit sticks.
Top comments (0)