<?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: ke jia</title>
    <description>The latest articles on DEV Community by ke jia (@ke_jia_24bb2f9f84f14f728a).</description>
    <link>https://dev.to/ke_jia_24bb2f9f84f14f728a</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%2F3935917%2F2200f40f-1cab-4c01-b5b1-39e52ae90676.png</url>
      <title>DEV Community: ke jia</title>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ke_jia_24bb2f9f84f14f728a"/>
    <language>en</language>
    <item>
      <title>Secrets Don't Leak in One Moment: The 6 Ways They Slip Into Your Repo (and How to Catch Each One)</title>
      <dc:creator>ke jia</dc:creator>
      <pubDate>Fri, 30 Oct 2026 00:39:50 +0000</pubDate>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a/secrets-dont-leak-in-one-moment-the-6-ways-they-slip-into-your-repo-and-how-to-catch-each-one-3691</link>
      <guid>https://dev.to/ke_jia_24bb2f9f84f14f728a/secrets-dont-leak-in-one-moment-the-6-ways-they-slip-into-your-repo-and-how-to-catch-each-one-3691</guid>
      <description>&lt;p&gt;Last year I found a production database password in one of my own public repos. Not a test password. Not a placeholder. The real one, in a config file, visible to anyone who knew how to read git history. It had been there for 8 months.&lt;/p&gt;

&lt;p&gt;I didn't find it during an audit. I found it because a friend asked a question that made me look.&lt;/p&gt;

&lt;p&gt;That's the uncomfortable truth about secret leaks: they almost never happen in one dramatic moment. There's no single "oops, I committed the .env" incident you can point to and learn from. There's a slow accumulation of small, reasonable decisions that add up to a credential sitting in public view.&lt;/p&gt;

&lt;p&gt;This post is a field guide. I'll walk through the 6 ways secrets actually slip into a repo, and for each one I'll show you how to catch it. I'll also show you a small zero-dependency scanner I built after that 8-month incident — dotguard — and how to wire it into your workflow so the catching happens automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The .env file that was never meant to be public
&lt;/h2&gt;

&lt;p&gt;The classic. You create &lt;code&gt;.env&lt;/code&gt;, put real values in it, and one of three things happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You commit it before adding it to &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You add it to &lt;code&gt;.gitignore&lt;/code&gt; after the first commit&lt;/li&gt;
&lt;li&gt;You commit it to a private repo, then make the repo public&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second one is the sneaky one. &lt;code&gt;.gitignore&lt;/code&gt; only prevents &lt;em&gt;new&lt;/em&gt; files from being tracked. If &lt;code&gt;.env&lt;/code&gt; was already committed, adding it to &lt;code&gt;.gitignore&lt;/code&gt; does nothing — git keeps tracking it, and the file stays in every clone. I've watched teams "fix" a leak by adding &lt;code&gt;.env&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt;, then wonder why the file is still there.&lt;/p&gt;

&lt;p&gt;How to catch it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# What is git actually tracking?&lt;/span&gt;
git ls-files | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nb"&gt;env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that prints &lt;code&gt;.env&lt;/code&gt;, it's tracked, and &lt;code&gt;.gitignore&lt;/code&gt; is not helping you. You need &lt;code&gt;git rm --cached .env&lt;/code&gt; plus a history cleanup.&lt;/p&gt;

&lt;p&gt;For the ongoing case, a scanner that walks your working tree and flags files with secret-looking content is the right tool. That's what dotguard does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; @wuchunjie/dotguard
dotguard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One command. It scans the current directory — &lt;code&gt;.env&lt;/code&gt; files, config files, source code — and reports anything that looks like a hardcoded credential. Zero dependencies, so it runs anywhere Node runs, including on a CI runner with nothing preinstalled.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The .env.example with real values
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.env.example&lt;/code&gt; is the file you're &lt;em&gt;supposed&lt;/em&gt; to commit. It's the template teammates copy when they start. The problem is how it gets made: someone copies their real &lt;code&gt;.env&lt;/code&gt;, deletes a few lines, and calls it an example. The "example" values are production values.&lt;/p&gt;

&lt;p&gt;I've seen &lt;code&gt;DATABASE_URL=postgres://admin:RealPassword123@prod-db:5432/app&lt;/code&gt; sitting in a &lt;code&gt;.env.example&lt;/code&gt; for over a year. Every new clone of the repo carried it. Every new developer had it in their shell history.&lt;/p&gt;

&lt;p&gt;How to catch it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Treat &lt;code&gt;.env.example&lt;/code&gt; as code. Review it in PRs like you would any other file.&lt;/li&gt;
&lt;li&gt;Scan it. It has the same shape as &lt;code&gt;.env&lt;/code&gt;, so a content scanner should treat it the same way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;dotguard scans config files, not just files named &lt;code&gt;.env&lt;/code&gt;, so a &lt;code&gt;.env.example&lt;/code&gt; with real values gets flagged the same way as the real thing. That's the whole point of scanning content instead of filenames.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Hardcoded in source
&lt;/h2&gt;

&lt;p&gt;Not every secret lives in an env file. Some live in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;config.js&lt;/code&gt; or &lt;code&gt;settings.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Client-side code — the worst kind, because it ships to every user's browser&lt;/li&gt;
&lt;li&gt;Test fixtures&lt;/li&gt;
&lt;li&gt;Dockerfiles (&lt;code&gt;ENV API_KEY=...&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;CI config files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The client-side case deserves special attention. A &lt;em&gt;public&lt;/em&gt; API key in frontend code is a design decision, not a leak — the key is meant to be public. But a &lt;em&gt;private&lt;/em&gt; key, a signing secret, or an admin token in a bundle is a leak that ships to millions of devices, and no amount of &lt;code&gt;.gitignore&lt;/code&gt; fixes it.&lt;/p&gt;

&lt;p&gt;How to catch it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Crude, but catches a lot&lt;/span&gt;
git &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"API_KEY&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;SECRET&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;TOKEN&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;PASSWORD"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem with &lt;code&gt;git grep&lt;/code&gt; is that you have to guess the variable names. A content scanner doesn't guess — it looks at the shape of the value: long high-entropy strings, known key prefixes, &lt;code&gt;password = "..."&lt;/code&gt; assignments. That's a different failure mode, and it catches the secrets that don't have obvious names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotguard &lt;span class="nt"&gt;--path&lt;/span&gt; ./src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. The log line
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logs are where secrets go to die — and to be copied. A token printed to stdout ends up in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local log files&lt;/li&gt;
&lt;li&gt;CI logs (public on public repos)&lt;/li&gt;
&lt;li&gt;Error reporting services&lt;/li&gt;
&lt;li&gt;Screenshots pasted into Slack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is two-part. First, don't log secrets: audit your logging and redact known fields. Second, scan the places where logs accumulate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotguard &lt;span class="nt"&gt;--path&lt;/span&gt; ./logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a low-frequency, high-impact check. Run it when you're cleaning up a project, or when you're about to open a repo up.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Git history
&lt;/h2&gt;

&lt;p&gt;This is the one that got me. You delete the file, you feel better, you move on. But git doesn't delete — it remembers. Every commit that ever contained the secret is still reachable from history.&lt;/p&gt;

&lt;p&gt;The 8-month password was in a file I'd deleted months earlier. A single command found it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="s2"&gt;"password"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thirty seconds. Eight months of exposure.&lt;/p&gt;

&lt;p&gt;How to catch it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Search all history for a string&lt;/span&gt;
git log &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="s2"&gt;"SECRET_PATTERN"&lt;/span&gt;

&lt;span class="c"&gt;# Grep every commit (slow on big repos)&lt;/span&gt;
git &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"PATTERN"&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;git rev-list &lt;span class="nt"&gt;--all&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The uncomfortable part: if a secret was in public history, it's already leaked. Rewriting history with BFG or git filter-repo doesn't un-leak it — it just stops the bleeding for future clones. The only real fix is rotation.&lt;/p&gt;

&lt;p&gt;How to prevent it from happening in the first place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A pre-push hook that runs a scanner and rejects the push if it finds something&lt;/li&gt;
&lt;li&gt;A CI gate that fails the build on a finding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;dotguard's CI integration is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret Scan&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx @wuchunjie/dotguard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want a pre-push hook, the pattern is the same: run the scanner, and if it reports findings, exit non-zero. Check dotguard's output behavior and wire it in — the point is to make the scan happen &lt;em&gt;before&lt;/em&gt; the push, not after the leak.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The screenshot and the error report
&lt;/h2&gt;

&lt;p&gt;The weirdest vector, and the one no tool can catch. A stack trace with a connection string pasted into a GitHub issue. A screenshot of a terminal with the env dump visible. An error report that includes request headers.&lt;/p&gt;

&lt;p&gt;This is a process problem, not a tooling problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redact before you paste. Make it a habit, not a rule.&lt;/li&gt;
&lt;li&gt;Review public issues and PRs for sensitive content before they go out.&lt;/li&gt;
&lt;li&gt;Configure your error reporting to strip headers and query params.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can't scan for a screenshot. You can only build the habit of looking before you share.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do when you find one
&lt;/h2&gt;

&lt;p&gt;Order matters here, and people get it wrong:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rotate the credential first.&lt;/strong&gt; Not after cleanup. The moment you find a leaked secret, assume it's compromised. Rotation is the fix; everything else is cleanup.&lt;/li&gt;
&lt;li&gt;Remove it from the working tree.&lt;/li&gt;
&lt;li&gt;Clean history if it was public (BFG, git filter-repo).&lt;/li&gt;
&lt;li&gt;Add the pattern to your scanner and your &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Note down which credential was affected and for how long, so the team can check for misuse.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A note on false positives
&lt;/h2&gt;

&lt;p&gt;Any content scanner will flag things that aren't real secrets: test fixtures with fake keys, documentation examples, placeholder values. That's expected, and it's why the output is a list to review, not a list to panic about.&lt;/p&gt;

&lt;p&gt;The workflow is: run the scanner, look at each hit, and decide. If it's a real secret, rotate it. If it's a fixture, either change the value to something obviously fake or add it to your allowlist. A scanner that's fast and mostly right is still better than no scanner, because it turns "do we have a leak?" from a vague worry into a concrete list you can work through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a security tool with zero dependencies
&lt;/h2&gt;

&lt;p&gt;There's a reason dotguard has no dependencies, and it's not just minimalism. A secret scanner is a tool you want to trust with your most sensitive files. Every dependency is another supply chain you have to audit, another version to pin, another package that could get compromised. A scanner with zero dependencies is a scanner you can read end to end in an afternoon.&lt;/p&gt;

&lt;p&gt;It also means it works in the places you actually need it: a fresh CI runner, a locked-down corporate machine, a container with nothing but Node. No &lt;code&gt;npm install&lt;/code&gt; step that could fail, no lockfile to manage, no transitive dependency that quietly pulls in something you didn't ask for.&lt;/p&gt;

&lt;p&gt;For a tool whose job is to protect you from things you didn't notice, being small and boring is a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5-minute setup
&lt;/h2&gt;

&lt;p&gt;If you take nothing else from this post, take this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; @wuchunjie/dotguard
dotguard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One command. Zero dependencies. Scans your whole project for hardcoded secrets. Add the CI step if you want it to be a gate instead of a suggestion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The habit, not the tool
&lt;/h2&gt;

&lt;p&gt;Secrets leak slowly, and the fix isn't a single tool — it's a habit: scan before you push, scan in CI, and when you find something, rotate first.&lt;/p&gt;

&lt;p&gt;dotguard is the smallest version of that habit: one command, no config, no dependencies. It's at &lt;code&gt;@wuchunjie/dotguard&lt;/code&gt; on npm, MIT licensed, source at &lt;a href="https://github.com/wuchunjie00/dotguard" rel="noopener noreferrer"&gt;github.com/wuchunjie00/dotguard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you've found a secret in your own repo, I'd love to hear how you found it. The slow leaks are the ones worth talking about.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>14 DevTools, Zero npm Packages: Why I Ship Pure HTML Instead of JavaScript</title>
      <dc:creator>ke jia</dc:creator>
      <pubDate>Fri, 23 Oct 2026 13:59:26 +0000</pubDate>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a/14-devtools-zero-npm-packages-why-i-ship-pure-html-instead-of-javascript-o7l</link>
      <guid>https://dev.to/ke_jia_24bb2f9f84f14f728a/14-devtools-zero-npm-packages-why-i-ship-pure-html-instead-of-javascript-o7l</guid>
      <description>&lt;p&gt;I recently shipped a collection of 14 developer tools. A Cron parser, a JWT decoder, a Base64 encoder, a Diff tool, a QR code generator, and a dozen more. They're all useful, all client-side, and all instantly available in a browser tab.&lt;/p&gt;

&lt;p&gt;Here's the part that surprises people: &lt;strong&gt;none of them have a &lt;code&gt;package.json&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;npm install&lt;/code&gt;. No build step. No &lt;code&gt;node_modules&lt;/code&gt; folder. No bundler. No framework. Each tool is a single HTML file you can open directly, host on any static server, or even email to a coworker.&lt;/p&gt;

&lt;p&gt;This post is a comparison. I'll lay out the two approaches side by side — the "default" npm/JavaScript way and the pure-HTML way — and show you exactly where each one wins. Because the honest answer is that pure HTML is &lt;em&gt;not&lt;/em&gt; always the right choice. I'll tell you when it is, and when you should absolutely reach for npm instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The default way, and why it feels heavy
&lt;/h2&gt;

&lt;p&gt;If you asked most developers to build a "generate a UUID" tool, the path of least resistance looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create vite@latest uuid-tool
&lt;span class="nb"&gt;cd &lt;/span&gt;uuid-tool
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;uuid
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have a project. A &lt;code&gt;package.json&lt;/code&gt;. A &lt;code&gt;vite.config.js&lt;/code&gt;. A &lt;code&gt;node_modules&lt;/code&gt; folder with a few hundred transitive dependencies. A build pipeline. And a tool that does one thing: print a random UUID.&lt;/p&gt;

&lt;p&gt;None of that is &lt;em&gt;wrong&lt;/em&gt;. For a real product, a real app, a real codebase that a team maintains, the npm ecosystem is a superpower. The problem is that the default has become so automatic that we reach for it even when the task is a 40-line utility that will never change.&lt;/p&gt;

&lt;p&gt;For a one-off tool, that's a lot of machinery to spin up just to print a string.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the collection actually is
&lt;/h2&gt;

&lt;p&gt;Before the comparison, here's what we're comparing. The &lt;a href="https://wuchunjie00.github.io/devtools/" rel="noopener noreferrer"&gt;developer toolbox&lt;/a&gt; is 14 tools, each a self-contained HTML page:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cron Expression Parser&lt;/td&gt;
&lt;td&gt;Parse and visualize cron expressions (second/minute/hour/day/month/week)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YAML to JSON&lt;/td&gt;
&lt;td&gt;Convert between YAML and JSON with live preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL Slug Generator&lt;/td&gt;
&lt;td&gt;Turn titles into URL-friendly slugs, multilingual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML to Text&lt;/td&gt;
&lt;td&gt;Strip HTML down to clean text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QR Code Generator&lt;/td&gt;
&lt;td&gt;Generate QR codes for URLs, text, contacts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML Entities Encoder/Decoder&lt;/td&gt;
&lt;td&gt;Encode/decode special characters to entities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UUID Generator&lt;/td&gt;
&lt;td&gt;RFC 4122 v4 UUIDs, batch generation, format conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password Generator&lt;/td&gt;
&lt;td&gt;Strong passwords with custom length and character sets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lorem Ipsum Generator&lt;/td&gt;
&lt;td&gt;Placeholder text by paragraph/sentence/word&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash Generator&lt;/td&gt;
&lt;td&gt;SHA-1/256/384/512 for text and files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base64 Encoder/Decoder&lt;/td&gt;
&lt;td&gt;Text and file Base64, URL-safe mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JWT Decoder&lt;/td&gt;
&lt;td&gt;Decode and inspect JSON Web Tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diff Tool&lt;/td&gt;
&lt;td&gt;Side-by-side text diff with added/removed/changed highlighting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSV to JSON&lt;/td&gt;
&lt;td&gt;Convert between CSV and JSON with live preview&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every single one of these runs entirely in the browser. Your data never leaves the page. There's no backend, no API key, no "upload to our server" step. That last point matters more than it sounds, and I'll come back to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comparison: pure HTML vs. npm
&lt;/h2&gt;

&lt;p&gt;Let's go category by category. I'll be honest about where npm wins, because a comparison that only shows one side isn't a comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Setup and distribution
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;npm:&lt;/strong&gt; You need Node installed, you run &lt;code&gt;npm install&lt;/code&gt;, you run a build, and you deploy the output. To &lt;em&gt;use&lt;/em&gt; the tool, someone has to run your build or you have to host the built assets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pure HTML:&lt;/strong&gt; The file &lt;em&gt;is&lt;/em&gt; the distribution. You open it. You're done. You can host it on GitHub Pages, Netlify, S3, a company intranet, or literally send the &lt;code&gt;.html&lt;/code&gt; file over Slack. There is no "how do I run this?" question.&lt;/p&gt;

&lt;p&gt;Winner: &lt;strong&gt;Pure HTML&lt;/strong&gt;, by a wide margin, for tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Build step
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;npm:&lt;/strong&gt; A build step means a build config, a bundler, and the inevitable "it works on my machine but not in CI" debugging. It also means your source and your artifact are two different things that can drift apart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pure HTML:&lt;/strong&gt; No build. What you write is what runs. There's nothing to misconfigure.&lt;/p&gt;

&lt;p&gt;Winner: &lt;strong&gt;Pure HTML.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Dependencies and supply chain
&lt;/h3&gt;

&lt;p&gt;This is the big one, and it's the reason I care about this so much.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;npm:&lt;/strong&gt; Every &lt;code&gt;npm install&lt;/code&gt; pulls in a tree of transitive dependencies. Every one of those is a potential supply-chain risk. A compromised package, a malicious &lt;code&gt;postinstall&lt;/code&gt; script, a typosquatted name — the npm ecosystem has seen all of it. When you depend on 300 packages to generate a UUID, you've accepted 300 chances for something to go wrong, most of which you've never read the source of.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pure HTML:&lt;/strong&gt; Zero dependencies. The only code running is the code you wrote and can read in its entirety. There is no &lt;code&gt;node_modules&lt;/code&gt; to audit, no lockfile to trust, no transitive package you've never seen. The attack surface is the file itself, and the file is small enough to actually review.&lt;/p&gt;

&lt;p&gt;Winner: &lt;strong&gt;Pure HTML&lt;/strong&gt;, and this is the one I'd weight most heavily.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Offline and air-gapped use
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;npm:&lt;/strong&gt; A built app can run offline, sure. But &lt;em&gt;building&lt;/em&gt; it requires the network and the registry. And if you're in an air-gapped environment, getting the dependencies in is a real chore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pure HTML:&lt;/strong&gt; The file works with no network at all. Open it on a machine with no internet and it just works. For teams in restricted environments, that's not a nice-to-have, it's the whole point.&lt;/p&gt;

&lt;p&gt;Winner: &lt;strong&gt;Pure HTML.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Startup and performance
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;npm:&lt;/strong&gt; A bundled SPA has to download and parse JavaScript before it can do anything. For a tool that does one thing, that's overhead you pay on every load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pure HTML:&lt;/strong&gt; The page is small. It loads fast. There's no framework to hydrate, no bundle to parse. For a utility, the "app" is ready the moment the file finishes loading.&lt;/p&gt;

&lt;p&gt;Winner: &lt;strong&gt;Pure HTML&lt;/strong&gt; for small tools. (For a large, complex app, a well-optimized bundle can beat a hand-rolled page — but that's not what we're building here.)&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Where npm genuinely wins
&lt;/h3&gt;

&lt;p&gt;Now the honest part. Pure HTML is not a universal answer. npm is the right choice when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You need a real library.&lt;/strong&gt; If your tool needs a mature parser, a charting engine, or a crypto library with a track record, you want the battle-tested npm package, not a 200-line reimplementation you have to maintain forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're building a product, not a tool.&lt;/strong&gt; If this is going to be a maintained product with a team, tests, CI, and a release process, the npm ecosystem's tooling is a feature, not a burden.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need to share code across projects.&lt;/strong&gt; If the same logic lives in five places, a package with a version and a changelog beats copy-pasting HTML.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need the ecosystem's edge cases handled.&lt;/strong&gt; Date handling, internationalization, accessibility — the npm packages have had years of edge cases fixed by other people.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule I use: &lt;strong&gt;if the tool is small, stable, and single-purpose, pure HTML. If it's growing, shared, or complex, npm.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I actually build these
&lt;/h2&gt;

&lt;p&gt;For the curious: each tool is one HTML file with inline CSS and inline JavaScript. No framework, no transpiler. A few notes on the interesting bits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hashing and UUIDs&lt;/strong&gt; use the browser's built-in &lt;code&gt;Web Crypto API&lt;/code&gt; (&lt;code&gt;crypto.subtle.digest&lt;/code&gt;, &lt;code&gt;crypto.randomUUID&lt;/code&gt;). No external crypto library needed — the browser already ships a vetted implementation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File handling&lt;/strong&gt; (Hash, Base64) uses the &lt;code&gt;FileReader&lt;/code&gt; API. Files are read locally and processed in the page. Nothing is uploaded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Diff tool&lt;/strong&gt; implements a simple line-based diff in vanilla JS. It's not a full Myers diff, but for the "compare two config files" use case it's plenty, and it's 100% readable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;YAML/CSV parsing&lt;/strong&gt; are small, focused parsers written for the specific shapes these tools need. They're not general-purpose YAML engines, and that's intentional — they do one job and are easy to verify.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The constraint of "no dependencies" is actually a &lt;em&gt;design&lt;/em&gt; constraint, not just a technical one. It forces you to keep each tool small and focused. If a tool starts needing a real library, that's a signal it's outgrown the pure-HTML model — and that's fine, that's when you graduate it to npm.&lt;/p&gt;

&lt;h2&gt;
  
  
  The security angle (why "client-side" is a feature)
&lt;/h2&gt;

&lt;p&gt;I keep coming back to this, so let me be explicit. A lot of "free online tools" work by sending your data to a server. You paste your JWT, your CSV, your config, and it goes to &lt;em&gt;their&lt;/em&gt; machine. For a JWT decoder or a CSV converter, that means your secrets and your data are now in someone else's logs.&lt;/p&gt;

&lt;p&gt;The pure-HTML approach sidesteps this entirely. Because there's no backend, &lt;strong&gt;your data never leaves your browser.&lt;/strong&gt; The JWT you decode, the CSV you convert, the text you hash — it's processed locally and gone. For tools that touch sensitive data, "it runs in your browser" isn't a marketing line, it's the actual security model.&lt;/p&gt;

&lt;p&gt;Combine that with zero dependencies (no supply-chain risk) and you get a tool that's safe in two different ways: it doesn't leak your data, and it doesn't pull in untrusted code.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I'd reach for npm instead
&lt;/h2&gt;

&lt;p&gt;To be concrete, here are the tools in this collection that I &lt;em&gt;wouldn't&lt;/em&gt; have built in pure HTML if they were bigger:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;full-featured YAML editor&lt;/strong&gt; with schema validation and autocomplete would need a real YAML library and a code editor component. That's npm territory.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;charting tool&lt;/strong&gt; would want a maintained charting library, not a hand-rolled canvas renderer.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;large-file diff&lt;/strong&gt; with merge capabilities would want a serious diff engine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 14 tools here are all deliberately in the "small and stable" zone. That's the zone where pure HTML wins. The moment a tool leaves that zone, I'd graduate it to a proper npm project without hesitation.&lt;/p&gt;

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

&lt;p&gt;The collection is live at &lt;a href="https://wuchunjie00.github.io/devtools/" rel="noopener noreferrer"&gt;wuchunjie00.github.io/devtools&lt;/a&gt;, and the source is on &lt;a href="https://github.com/wuchunjie00/devtools" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Every tool is a single HTML file you can read in full. If you're curious how a JWT decoder or a Diff tool works without a single dependency, the source is the best documentation — it's short enough to actually read.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;The npm ecosystem is one of the best things that ever happened to JavaScript, and for real products it's irreplaceable. But the default has become so automatic that we spin up a build pipeline to print a UUID.&lt;/p&gt;

&lt;p&gt;For small, stable, single-purpose tools, there's a lighter path: &lt;strong&gt;pure HTML, zero dependencies, runs in the browser, data never leaves the page.&lt;/strong&gt; It's faster to ship, safer to run, and easier to trust — because the entire codebase is one file you can read in ten minutes.&lt;/p&gt;

&lt;p&gt;Use npm when you're building a product. Use pure HTML when you're building a tool. And the next time you're about to &lt;code&gt;npm create&lt;/code&gt; a project that's really just a converter, ask yourself: does this actually need a &lt;code&gt;node_modules&lt;/code&gt; folder?&lt;/p&gt;

&lt;p&gt;Sometimes the most modern thing you can do is write a single HTML file.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Grep-able Notes: Why Every Snippet Has to Survive a Search</title>
      <dc:creator>ke jia</dc:creator>
      <pubDate>Sat, 26 Sep 2026 11:46:09 +0000</pubDate>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a/grep-able-notes-why-every-snippet-has-to-survive-a-search-4949</link>
      <guid>https://dev.to/ke_jia_24bb2f9f84f14f728a/grep-able-notes-why-every-snippet-has-to-survive-a-search-4949</guid>
      <description>&lt;p&gt;A snippet library is only as good as its worst search. You can store 200 perfect snippets, and if the one you need at 11pm doesn't come back from &lt;code&gt;snippetx search&lt;/code&gt;, the library is a vault you can't open.&lt;/p&gt;

&lt;p&gt;This post is about the naming and structuring discipline that makes a snippet library &lt;em&gt;grep-able&lt;/em&gt; — the property that every entry can be found by a short, natural query. It's the part of the system that's easy to skip and impossible to retrofit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode: the unfindable snippet
&lt;/h2&gt;

&lt;p&gt;The telltale pattern of a library that's failing at search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;snippetx search deploy
&lt;span class="c"&gt;# (12 results, none of them the one I need)&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;snippetx search &lt;span class="s2"&gt;"deploy to staging with the new pipeline"&lt;/span&gt;
&lt;span class="c"&gt;# (0 results — I searched by *description*, not by name)&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;snippetx search staging
&lt;span class="c"&gt;# (4 results — one of them is it, but I had to guess)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The snippet I need exists. I named it &lt;code&gt;deploy-new-pipeline-v2&lt;/code&gt; and saved it six months ago. But the query I'd actually type — "staging," "the new one," "the pipeline one" — doesn't match the name, and the content search doesn't help because the name is what I remember, not the content.&lt;/p&gt;

&lt;p&gt;The library has the snippet. The library can't give it to me. That's the grep-ability failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naming discipline
&lt;/h2&gt;

&lt;p&gt;The fix is naming, and it's a discipline, not a one-time decision:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Name by what you'll search for, not what it is.&lt;/strong&gt; The question isn't "what is this snippet?" — it's "what words will I type when I need it, six months from now, in a hurry?" For the staging deploy, the search words are "staging" and "deploy," so the name is &lt;code&gt;deploy-staging&lt;/code&gt;. Not &lt;code&gt;deploy-new-pipeline-v2&lt;/code&gt; (what it is), not &lt;code&gt;deployment-config&lt;/code&gt; (what a category would be). The name is the &lt;em&gt;query&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Use the words you actually use.&lt;/strong&gt; If I call the service "the API" in conversation, the snippet name should contain "api," not "backend-service." If I call the environment "staging" and not "pre-prod," the name contains "staging." The library's vocabulary has to match my vocabulary, because my vocabulary is the query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Be specific enough to disambiguate, general enough to recall.&lt;/strong&gt; &lt;code&gt;deploy-staging&lt;/code&gt; is findable. &lt;code&gt;deploy-staging-prod-fallback-canary-v2&lt;/code&gt; is not — it's over-specified, and the query "deploy staging" won't match it because the name has moved on. The right name is the shortest string that (a) matches my natural query and (b) distinguishes this snippet from the other deploy snippets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The language tag is a filter, not a name.&lt;/strong&gt; snippetx's optional language tag (&lt;code&gt;nginx&lt;/code&gt;, &lt;code&gt;js&lt;/code&gt;, &lt;code&gt;sh&lt;/code&gt;) is for &lt;code&gt;snippetx list &amp;lt;filter&amp;gt;&lt;/code&gt;, not for search. Don't put the language in the name if it's already in the tag — &lt;code&gt;nginx-301-regex&lt;/code&gt; with the tag &lt;code&gt;nginx&lt;/code&gt; is redundant; &lt;code&gt;301-regex&lt;/code&gt; with the tag &lt;code&gt;nginx&lt;/code&gt; is cleaner. (In practice I keep the prefix in the name too, because substring search on the name is more reliable than I'd like to admit.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The content has to be greppable too
&lt;/h2&gt;

&lt;p&gt;The name is half the story. The content is the other half, because the search matches content as well as name, and a snippet whose content is a wall of unbroken text is hard to confirm at a glance:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. One logical thing per snippet.&lt;/strong&gt; A snippet that's "the deploy command" is greppable. A snippet that's "the deploy command and the rollback command and the status command" is three snippets in one, and the search for "rollback" finds it but the search for "deploy" also finds it, and I can't tell which is which from the one-line preview. Split it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The first line should be the most recognizable fragment.&lt;/strong&gt; The search result shows the first line of the content as the preview. If the first line is &lt;code&gt;#!/bin/bash&lt;/code&gt; or &lt;code&gt;# TODO: clean this up&lt;/code&gt;, the preview is useless for confirmation. If the first line is the actual command or the most distinctive part of the config, I can confirm "yes, that's the one" from the preview alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Comments are search content.&lt;/strong&gt; A &lt;code&gt;# deploy to staging, uses the canary pipeline&lt;/code&gt; comment at the top of the snippet is matched by the search for "canary" and "staging" and "deploy." The comment is part of the grep surface — write it for the future-me who's searching, not for the present-me who just saved it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test: the 11pm query
&lt;/h2&gt;

&lt;p&gt;The discipline works if it passes one test: &lt;strong&gt;at 11pm, in a hurry, with no context, can I type the words I'd naturally use and get the right snippet in the top result?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not "can I find it if I think carefully about the naming convention." That's the 2pm test, and it passes for most libraries. The 11pm test is the real one, because the 11pm me isn't thinking about conventions — the 11pm me is typing the first words that come to mind, and the library has to meet those words where they are.&lt;/p&gt;

&lt;p&gt;My library passes the 11pm test for the top 40 entries (the ones I actually use), and that's the bar. The other 160 in the graveyard fail it sometimes, and that's fine — they're the graveyard, and the graveyard's job is to be there, not to be findable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The meta-point
&lt;/h2&gt;

&lt;p&gt;A snippet library is a search system, not a storage system. The storage is trivial (a directory of text files); the &lt;em&gt;search&lt;/em&gt; is the product. And search quality is determined at save time — by the name, the structure, the first line, the comments — not at query time. You can't make an un-greppable snippet greppable after the fact without re-saving it, and re-saving 200 snippets is the retrofit I described above.&lt;/p&gt;

&lt;p&gt;So the discipline is front-loaded: name it for the query, structure it for the preview, comment it for the search. Do it at save time, for ten seconds, and the 11pm me gets the snippet. Skip it, and the 11pm me gets a vault they can't open.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;snippetx add &amp;lt;name-for-the-query&amp;gt; &amp;lt;lang&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  More Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;scaffoldx-cli&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Production-ready project templates in seconds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx scaffoldx-cli&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/dotguard" rel="noopener noreferrer"&gt;dotguard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scan .env files for exposed secrets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/dotguard&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/gitpulse" rel="noopener noreferrer"&gt;gitpulse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Git repo analytics in your terminal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/gitpulse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terminal code snippet manager&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/snippetx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If these save you time, consider &lt;a href="https://ko-fi.com/wuchunjie" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;. All tools are MIT-licensed, zero-dependency, and run fully offline.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>productivity</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The 200-Snippet Graveyard: Why Cheap Storage Beats Curation</title>
      <dc:creator>ke jia</dc:creator>
      <pubDate>Sat, 26 Sep 2026 11:45:06 +0000</pubDate>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a/the-200-snippet-graveyard-why-cheap-storage-beats-curation-49hi</link>
      <guid>https://dev.to/ke_jia_24bb2f9f84f14f728a/the-200-snippet-graveyard-why-cheap-storage-beats-curation-49hi</guid>
      <description>&lt;p&gt;My snippet library has 200 entries. I've used maybe 40 of them in the last month. The other 160 are, by any curation standard, dead weight — a graveyard of snippets I saved two years ago for projects that no longer exist.&lt;/p&gt;

&lt;p&gt;I'm not going to clean them up. And I want to argue that the graveyard is a feature, not a bug, because the alternative — a curated, pruned, "quality" library — has a cost that's worse than the clutter.&lt;/p&gt;

&lt;p&gt;This post is about that trade, and about why "cheap storage" is the right design principle for a personal snippet library.&lt;/p&gt;

&lt;h2&gt;
  
  
  The curation argument, steelmanned
&lt;/h2&gt;

&lt;p&gt;The case for pruning is real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search noise.&lt;/strong&gt; 200 entries means &lt;code&gt;snippetx search deploy&lt;/code&gt; returns 12 results instead of 3. The right one is buried under stale ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;False confidence.&lt;/strong&gt; A stale snippet in the library looks as trustworthy as a current one. I might copy a deploy command from a project I left two years ago, and it'll be subtly wrong for the current setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance illusion.&lt;/strong&gt; A "curated" library implies I'm maintaining it, and the maintenance is a recurring tax I'll eventually stop paying — at which point the library rots from the top down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All true. I grant every point. Here's why I still don't prune.&lt;/p&gt;

&lt;h2&gt;
  
  
  The counter: curation is a decision, and decisions are expensive
&lt;/h2&gt;

&lt;p&gt;Pruning a library means, for each entry, a judgment call: "is this worth keeping?" That's a decision with a real cost — not just the time, but the &lt;em&gt;commitment&lt;/em&gt;. Every "keep" is a promise to maintain it; every "delete" is a risk of deleting the thing I'll need in three months for a project I can't currently see.&lt;/p&gt;

&lt;p&gt;The graveyard avoids the decision entirely. The rule is: &lt;strong&gt;save freely, never prune, let search do the filtering.&lt;/strong&gt; The cost of a stale entry is near-zero (it's a few lines of text in a local directory); the cost of a wrong deletion is real (the thing I can't reconstruct, in the moment I need it). Asymmetric costs favor the side with no decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The graveyard is a record, not a clutter
&lt;/h2&gt;

&lt;p&gt;Here's the part that changed my mind about pruning: &lt;strong&gt;the dead snippets are a record of how I worked.&lt;/strong&gt; The 160 unused entries aren't waste — they're a log of the patterns I encountered, the problems I solved, the configs I needed, over two years. I don't use them, but they tell me things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cluster of &lt;code&gt;docker-compose&lt;/code&gt; entries from 2024 tells me that was when I was doing the containerization work.&lt;/li&gt;
&lt;li&gt;The three &lt;code&gt;stripe-webhook&lt;/code&gt; entries tell me I wrestled with webhook signatures harder than I remember.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;k8s-ingress&lt;/code&gt; entries tell me that project's shape, in a form I'd have to reconstruct from memory otherwise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A curated library is a record of what I &lt;em&gt;currently&lt;/em&gt; value. A graveyard library is a record of what I &lt;em&gt;actually did&lt;/em&gt;. The second is more honest, and more useful when I need to remember "how did I solve this before?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Search is the curation that actually works
&lt;/h2&gt;

&lt;p&gt;The graveyard only works if search is good enough to cut through the noise, and this is where the tool design matters. snippetx's search is a simple substring match across names and content, and it's good enough for 200 entries because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The names are distinctive.&lt;/strong&gt; I name snippets with the &lt;code&gt;area-purpose-detail&lt;/code&gt; convention, so &lt;code&gt;snippetx search 301-regex&lt;/code&gt; returns one result, not twelve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The result is scannable.&lt;/strong&gt; The search output is a one-line-per-entry format (id, name, language, first line of content), and I can pick the right one in a glance. The stale entries are &lt;em&gt;visible&lt;/em&gt; as stale (old project names in the snippet name), which is itself a filter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;200 is small enough.&lt;/strong&gt; The graveyard works at 200. At 2,000, substring search would drown, and I'd need tags or fuzzy matching. The scale limit is real — I'll hit it eventually — and when I do, the answer is better search, not pruning. The storage stays cheap; the retrieval gets smarter.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The design principle
&lt;/h2&gt;

&lt;p&gt;The general principle: &lt;strong&gt;for personal reference tools, storage should be cheap enough that you never have to decide what to keep.&lt;/strong&gt; The moment keeping something requires a decision, you stop saving the marginal thing, and the library becomes a curation project instead of a record.&lt;/p&gt;

&lt;p&gt;snippetx's storage is a local directory of text files. The marginal cost of one more entry is zero — no quota, no subscription, no "your library is 80% full." That's the feature. The curation SaaS with its 500-entry limit and its "organize your library" prompts is solving a problem I don't have, by creating a problem I did.&lt;/p&gt;

&lt;p&gt;The graveyard is 160 snippets I'll never use. It's also the only complete record I have of two years of actual work. I'm keeping both properties, and the price is a search result with a few extra lines. That's a trade I'll make again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @wuchunjie/snippetx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  More Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;scaffoldx-cli&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Production-ready project templates in seconds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx scaffoldx-cli&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/dotguard" rel="noopener noreferrer"&gt;dotguard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scan .env files for exposed secrets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/dotguard&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/gitpulse" rel="noopener noreferrer"&gt;gitpulse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Git repo analytics in your terminal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/gitpulse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terminal code snippet manager&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/snippetx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If these save you time, consider &lt;a href="https://ko-fi.com/wuchunjie" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;. All tools are MIT-licensed, zero-dependency, and run fully offline.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>productivity</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>My Nginx Configs Live in a Terminal, Not a Wiki</title>
      <dc:creator>ke jia</dc:creator>
      <pubDate>Sat, 26 Sep 2026 11:45:00 +0000</pubDate>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a/my-nginx-configs-live-in-a-terminal-not-a-wiki-2ka1</link>
      <guid>https://dev.to/ke_jia_24bb2f9f84f14f728a/my-nginx-configs-live-in-a-terminal-not-a-wiki-2ka1</guid>
      <description>&lt;p&gt;I used to keep my nginx configs in a wiki. A shared page, "Useful Nginx Snippets," with maybe 30 config blocks, each with a heading and a paragraph of context. It was a fine wiki page, and it was a terrible reference.&lt;/p&gt;

&lt;p&gt;The problem wasn't the content — it was the &lt;em&gt;retrieval&lt;/em&gt;. When I'm configuring a server at 11pm and I need the exact &lt;code&gt;location&lt;/code&gt; block that does a 301 with a regex capture, I'm not reading a wiki page. I'm in a terminal, I'm in the middle of an edit, and the wiki is a context switch: browser, search, scroll, find the block, copy, switch back. Four context switches for one config block.&lt;/p&gt;

&lt;p&gt;So the configs moved to &lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt;. Now the same block is two commands in the terminal I'm already in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;snippetx search redirect
b7e2d1  nginx-301-regex  nginx  location ~ ^/old/&lt;span class="o"&gt;(&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;301 /new/&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;snippetx copy b7e2d1 | pbcopy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No browser, no scroll, no switch. The config is in the clipboard by the time my hand is back on the terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why configs are the worst wiki content
&lt;/h2&gt;

&lt;p&gt;Config blocks are a specific failure mode for wiki-based reference, and it's worth naming why:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. They're exact, and wikis are fuzzy.&lt;/strong&gt; A config block has to be &lt;em&gt;precisely&lt;/em&gt; right — a missing semicolon, a wrong capture group, a directive in the wrong scope, and it doesn't work. Wiki pages are written for reading, and the act of copying from a rendered wiki page introduces errors: the markdown eats a character, the copy grabs a trailing space, the heading gets included. The terminal-to-terminal path (snippetx to clipboard) has none of that — the bytes are the bytes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. They're modified on use, not read on use.&lt;/strong&gt; I rarely want the config &lt;em&gt;as stored&lt;/em&gt;. I want it as a &lt;em&gt;starting point&lt;/em&gt; — the 301 block with a different path, the rate-limit block with a different limit. A wiki gives me the stored version and I edit it after copying. snippetx gives me the stored version &lt;em&gt;in the pipeline&lt;/em&gt;, where I can transform it before it hits the clipboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Retrieve the rate-limit block, change the limit, copy&lt;/span&gt;
snippetx copy rate-limit-10rps | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/10r/50r/g'&lt;/span&gt; | pbcopy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last command is the whole argument. The snippet is a &lt;em&gt;template in a pipeline&lt;/em&gt;, and the pipeline is where the modification happens. A wiki can't be a pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. They're looked up by fragment, not by title.&lt;/strong&gt; I rarely know the &lt;em&gt;name&lt;/em&gt; of the config I need. I know a fragment: "the one with the &lt;code&gt;proxy_set_header&lt;/code&gt; for the auth token." A wiki's search is title-and-full-text, and "proxy_set_header auth" matches five blocks. snippetx's search is the same, but the result is a one-liner I can scan in the terminal — the id, the name, and the first line of the block — and I pick the right one in a glance. The wiki's result is a list of page sections I have to scroll to.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the library looks like now
&lt;/h2&gt;

&lt;p&gt;About 40 nginx entries, plus a dozen of other config blocks (envoy, docker compose fragments, kubectl one-liners). The naming convention is &lt;code&gt;area-purpose-detail&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nginx-301-regex&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nginx-rate-limit-10rps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nginx-auth-header-proxy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nginx-ssl-redirect&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;compose-postgres-16&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The names are searchable by any fragment, and the language tag (&lt;code&gt;nginx&lt;/code&gt;, &lt;code&gt;compose&lt;/code&gt;, &lt;code&gt;kubectl&lt;/code&gt;) lets me filter the list when I'm in a specific config context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The migration cost, honestly
&lt;/h2&gt;

&lt;p&gt;Moving the configs from the wiki to snippetx took an afternoon, and it was the most boring part: re-typing each block as a &lt;code&gt;snippetx add&lt;/code&gt; (piping the block in from a file). The wiki's 30 blocks became 40 snippets (I added the ones I'd been meaning to save while I was in there).&lt;/p&gt;

&lt;p&gt;The payoff showed up the first night I needed a config at an unusual hour. I was in the middle of a deploy, the terminal was open, and the config I needed was two commands away instead of four context switches away. The difference was maybe ten seconds, but the &lt;em&gt;flow&lt;/em&gt; difference was real — I didn't leave the terminal, and I didn't lose the thread of the deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The meta-point
&lt;/h2&gt;

&lt;p&gt;Reference material should live in the medium you use it in. My configs are used in a terminal, so they live in a terminal. The wiki is the right home for the &lt;em&gt;explanation&lt;/em&gt; of the configs (why the rate limit is 10rps, what the auth header does) — and the explanation can link to the snippet id. But the &lt;em&gt;config itself&lt;/em&gt; belongs in the pipe.&lt;/p&gt;

&lt;p&gt;The wiki tells you what the block does. snippetx gives you the block, in the terminal, ready to be modified and pasted. Both are useful. They're just not the same artifact, and keeping them in the same place is how the wiki became a terrible reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;snippetx search nginx &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; snippetx copy &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; | pbcopy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  More Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;scaffoldx-cli&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Production-ready project templates in seconds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx scaffoldx-cli&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/dotguard" rel="noopener noreferrer"&gt;dotguard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scan .env files for exposed secrets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/dotguard&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/gitpulse" rel="noopener noreferrer"&gt;gitpulse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Git repo analytics in your terminal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/gitpulse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terminal code snippet manager&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/snippetx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If these save you time, consider &lt;a href="https://ko-fi.com/wuchunjie" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;. All tools are MIT-licensed, zero-dependency, and run fully offline.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>productivity</category>
      <category>devops</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Snippet List I Hand to Contractors Before Their First Commit</title>
      <dc:creator>ke jia</dc:creator>
      <pubDate>Sat, 26 Sep 2026 11:44:10 +0000</pubDate>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a/the-snippet-list-i-hand-to-contractors-before-their-first-commit-24p7</link>
      <guid>https://dev.to/ke_jia_24bb2f9f84f14f728a/the-snippet-list-i-hand-to-contractors-before-their-first-commit-24p7</guid>
      <description>&lt;p&gt;When a contractor joins a project for a week or a sprint, the onboarding problem isn't the code — it's the &lt;em&gt;context&lt;/em&gt;. The code is in the repo. The context is in the heads of the people who've been here a year, and it doesn't transfer in a wiki page.&lt;/p&gt;

&lt;p&gt;I've stopped writing wiki onboarding pages. Instead, I hand the contractor a &lt;strong&gt;snippet list&lt;/strong&gt; — a curated set of the commands, configs, and one-liners they'll actually need in the first 48 hours, stored in &lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt; and shared as a plain-text export.&lt;/p&gt;

&lt;p&gt;This post is about that practice, and why a list of commands is a better onboarding artifact than a document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the wiki page fails
&lt;/h2&gt;

&lt;p&gt;The standard onboarding doc has a structural problem: &lt;strong&gt;it's written for the reader who doesn't have the questions yet.&lt;/strong&gt; It explains the architecture, the conventions, the deploy process — all true, all useful, all in the wrong order. The contractor's first-day questions are concrete and immediate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How do I run the tests?"&lt;/li&gt;
&lt;li&gt;"What's the command to deploy to staging?"&lt;/li&gt;
&lt;li&gt;"How do I get a local database with the right schema?"&lt;/li&gt;
&lt;li&gt;"What's the curl to hit the health endpoint?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The wiki page answers none of these in a form that's usable at the moment of asking. It has the information, but it's buried in prose, and the contractor is in a terminal, not a browser.&lt;/p&gt;

&lt;p&gt;The snippet list answers the questions &lt;em&gt;as commands&lt;/em&gt;, in the form they'll actually be typed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the list contains
&lt;/h2&gt;

&lt;p&gt;The list is small on purpose — 10-20 entries, each a command or config with a two-word name and a one-line description. The actual entries, from a recent project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run-tests&lt;/span&gt;
npm &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--watch&lt;/span&gt;

&lt;span class="c"&gt;# seed-local-db&lt;/span&gt;
npm run db:reset &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run db:seed

&lt;span class="c"&gt;# deploy-staging&lt;/span&gt;
npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./scripts/deploy.sh staging

&lt;span class="c"&gt;# health-check&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://staging.example.com/health | jq &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# tail-api-logs&lt;/span&gt;
kubectl logs &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; prod &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;api

&lt;span class="c"&gt;# reset-migrations&lt;/span&gt;
npx prisma migrate reset &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# create-user&lt;/span&gt;
npm run script:create-user &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--email&lt;/span&gt; dev@example.com

&lt;span class="c"&gt;# clear-cache&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; node_modules/.cache &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each entry is a &lt;em&gt;thing they'll do&lt;/em&gt;, not a &lt;em&gt;concept they'll learn&lt;/em&gt;. The list is a map of the first 48 hours, in the contractor's own working medium (the terminal), in the form they'll use it (copy-paste).&lt;/p&gt;

&lt;h2&gt;
  
  
  How it's shared
&lt;/h2&gt;

&lt;p&gt;The list lives in a snippetx library on my machine, and the sharing is a plain-text export — no account, no app, no permission dance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Export the onboarding list as a file&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;id &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;snippetx search onboarding | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"### &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;snippetx show &lt;span class="nv"&gt;$id&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  snippetx copy &lt;span class="nv"&gt;$id&lt;/span&gt;
  &lt;span class="nb"&gt;echo
&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; onboarding-snippets.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contractor gets &lt;code&gt;onboarding-snippets.txt&lt;/code&gt; — a file they can read, grep, and paste from. It's version-controlled in the repo (or sent over chat), it's plain text, and it works on any machine with a terminal. No onboarding SaaS, no shared library account, no "let me get you access."&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed after I started doing this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. The first-day questions dropped by half.&lt;/strong&gt; The questions that remained were the ones a command list can't answer ("why does the deploy script do X?") — the genuinely conceptual ones. The "how do I do Y" questions, which used to be 80% of the first-day interruptions, mostly didn't come, because the answer was in the file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The contractors were productive faster.&lt;/strong&gt; A contractor who can run the tests and hit the health endpoint in the first hour is a contractor who's &lt;em&gt;in&lt;/em&gt; the project by lunch. The time previously spent hunting for commands was time spent reading code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The list improved itself.&lt;/strong&gt; Every first-day question that &lt;em&gt;wasn't&lt;/em&gt; in the list became a new entry. After three contractors, the list had converged on the actual 48-hour workflow, which is more accurate than any wiki page I'd have written from memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The meta-point
&lt;/h2&gt;

&lt;p&gt;Onboarding isn't a documentation problem; it's a &lt;em&gt;medium&lt;/em&gt; problem. The contractor works in a terminal, so the onboarding should be in a terminal. The wiki page is the right artifact for the conceptual context (the architecture, the why) and the wrong artifact for the operational context (the commands, the how).&lt;/p&gt;

&lt;p&gt;The snippet list is the operational half. It's small, it's concrete, it's in the right medium, and it improves itself with every contractor who uses it. The wiki page can keep the concepts. The terminal gets the commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @wuchunjie/snippetx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  More Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;scaffoldx-cli&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Production-ready project templates in seconds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx scaffoldx-cli&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/dotguard" rel="noopener noreferrer"&gt;dotguard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scan .env files for exposed secrets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/dotguard&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/gitpulse" rel="noopener noreferrer"&gt;gitpulse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Git repo analytics in your terminal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/gitpulse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terminal code snippet manager&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/snippetx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If these save you time, consider &lt;a href="https://ko-fi.com/wuchunjie" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;. All tools are MIT-licensed, zero-dependency, and run fully offline.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>productivity</category>
      <category>devops</category>
      <category>javascript</category>
    </item>
    <item>
      <title>snippetx copy | pbcopy: The Pipe That Made Snippets Actually Useful</title>
      <dc:creator>ke jia</dc:creator>
      <pubDate>Sat, 26 Sep 2026 11:44:05 +0000</pubDate>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a/snippetx-copy-pbcopy-the-pipe-that-made-snippets-actually-useful-3jho</link>
      <guid>https://dev.to/ke_jia_24bb2f9f84f14f728a/snippetx-copy-pbcopy-the-pipe-that-made-snippets-actually-useful-3jho</guid>
      <description>&lt;p&gt;A snippet manager that makes you click "Copy to Clipboard" in a GUI has a hidden cost: it takes you out of the terminal. You're in a shell, mid-pipeline, and the copy action is a context switch — mouse to the app, click, back to the terminal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt; sidesteps the whole problem with one design decision: &lt;strong&gt;&lt;code&gt;snippetx copy &amp;lt;id&amp;gt;&lt;/code&gt; prints the snippet to stdout.&lt;/strong&gt; No clipboard API, no GUI, no context switch. The snippet is just another thing that can be piped, and "copy to clipboard" becomes &lt;code&gt;| pbcopy&lt;/code&gt; (macOS), &lt;code&gt;| clip.exe&lt;/code&gt; (Windows), or &lt;code&gt;| xclip -selection clipboard&lt;/code&gt; (Linux).&lt;/p&gt;

&lt;p&gt;This post is about why that one decision — stdout as the copy mechanism — is the difference between a snippet manager you &lt;em&gt;use&lt;/em&gt; and one you &lt;em&gt;own&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipe, concretely
&lt;/h2&gt;

&lt;p&gt;The full workflow, entirely in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find the snippet&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;snippetx search nginx
a3f9c2  nginx-redirect  nginx  server &lt;span class="o"&gt;{&lt;/span&gt; ... 301 ... &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Copy it to the clipboard&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;snippetx copy a3f9c2 | pbcopy

&lt;span class="c"&gt;# ...paste it wherever you need it, terminal or GUI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No mouse. No app switch. The search, the retrieval, and the copy are three commands in the same shell, in the same breath. The snippet flows through the same pipes as everything else you do in a terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why stdout is the right "copy"
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. It composes with everything.&lt;/strong&gt; A snippet that prints to stdout can go to a clipboard, to a file, to another command, to a here-doc. The same &lt;code&gt;copy&lt;/code&gt; command serves all of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# To a file&lt;/span&gt;
snippetx copy a3f9c2 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/redirect.conf

&lt;span class="c"&gt;# Into a command&lt;/span&gt;
docker run &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; alpine sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'cat &amp;gt; /app/entrypoint.sh'&lt;/span&gt; &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;snippetx copy entrypoint&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Piped through another tool&lt;/span&gt;
snippetx copy a3f9c2 | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/old.example.com/new.example.com/g'&lt;/span&gt; | pbcopy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last one is the killer use case: retrieve the snippet, transform it, &lt;em&gt;then&lt;/em&gt; copy. A GUI copy button can't do that. A stdout pipe can, because the snippet is just text in a pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. It's scriptable.&lt;/strong&gt; A snippet that's on stdout can be used in a script, a Makefile, a CI step. "Copy the deploy snippet and run it" is a shell line, not a manual sequence. The library becomes part of your automation, not just a reference you consult.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It works everywhere a terminal works.&lt;/strong&gt; No clipboard daemon, no X11 dependency, no GUI toolkit. Over SSH, in a container, on a headless box, in a tmux pane — &lt;code&gt;snippetx copy &amp;lt;id&amp;gt;&lt;/code&gt; produces the text, and what you do with the text is up to the environment. The tool doesn't assume a desktop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cross-platform clipboard, honestly
&lt;/h2&gt;

&lt;p&gt;The one wrinkle: the clipboard command differs by OS. That's not a snippetx limitation — it's a property of the environment, and the fix is a shell alias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.zshrc (macOS)&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;clip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'pbcopy'&lt;/span&gt;
&lt;span class="c"&gt;# ~/.bashrc (Linux)&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;clip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'xclip -selection clipboard'&lt;/span&gt;
&lt;span class="c"&gt;# Windows: use 'clip.exe' directly&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the alias, the workflow is &lt;code&gt;snippetx copy &amp;lt;id&amp;gt; | clip&lt;/code&gt; on any machine, and the alias is a one-time setup. The point stands: the copy is a pipe, and the pipe is the same everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the pipe did to my usage
&lt;/h2&gt;

&lt;p&gt;The GUI-copy version of this tool (I used one) had a telltale pattern: I'd look up a snippet, copy it, and &lt;em&gt;sometimes&lt;/em&gt; not paste it, because the context switch was enough friction that I'd re-type the thing from memory instead. The stdout version removed that failure mode — the snippet is already in the clipboard by the time I'm back at the paste point, and re-typing is now the slower path.&lt;/p&gt;

&lt;p&gt;The usage data is the proof: the top snippets in my library are the ones that are &lt;em&gt;piped into&lt;/em&gt; other commands, not just copied. The &lt;code&gt;copy | transform | clip&lt;/code&gt; pattern is the most-used workflow in the library, and it only exists because the copy is a pipe.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design lesson
&lt;/h2&gt;

&lt;p&gt;The general principle: &lt;strong&gt;if your tool's output is text, make it stdout.&lt;/strong&gt; A tool that writes to stdout is a tool that composes. A tool that writes to a GUI, a file, or a proprietary format is a tool that's a dead end in the pipeline.&lt;/p&gt;

&lt;p&gt;snippetx could have had a "copy to clipboard" button, a GUI viewer, a mobile sync — and it would have been a &lt;em&gt;better app&lt;/em&gt;. But it would have been a worse &lt;em&gt;tool&lt;/em&gt;, because the app-ness would have put walls between the snippet and the rest of the terminal. The stdout decision is the difference, and it's the reason the snippets actually flow into the work instead of sitting in a library I visit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;snippetx search &amp;lt;term&amp;gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; snippetx copy &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; | pbcopy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  More Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;scaffoldx-cli&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Production-ready project templates in seconds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx scaffoldx-cli&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/dotguard" rel="noopener noreferrer"&gt;dotguard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scan .env files for exposed secrets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/dotguard&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/gitpulse" rel="noopener noreferrer"&gt;gitpulse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Git repo analytics in your terminal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/gitpulse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terminal code snippet manager&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/snippetx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If these save you time, consider &lt;a href="https://ko-fi.com/wuchunjie" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;. All tools are MIT-licensed, zero-dependency, and run fully offline.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>productivity</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>If You Paste It Twice, It Earns a Name: The Rule That Built My Snippet Library</title>
      <dc:creator>ke jia</dc:creator>
      <pubDate>Sat, 26 Sep 2026 11:43:21 +0000</pubDate>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a/if-you-paste-it-twice-it-earns-a-name-the-rule-that-built-my-snippet-library-4l2h</link>
      <guid>https://dev.to/ke_jia_24bb2f9f84f14f728a/if-you-paste-it-twice-it-earns-a-name-the-rule-that-built-my-snippet-library-4l2h</guid>
      <description>&lt;p&gt;My snippet library has about 200 entries. It wasn't built by curation — I didn't sit down and design a taxonomy. It was built by one rule, applied over months:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If I'm about to paste something for the second time, it earns a name.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The tool doing the holding is &lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt;, a zero-dependency terminal snippet manager. But the library's shape is the rule's shape, and the rule is worth writing down because it's the difference between a snippet library and a junk drawer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule, precisely
&lt;/h2&gt;

&lt;p&gt;The trigger is the &lt;em&gt;second&lt;/em&gt; paste, not the first. This matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First paste:&lt;/strong&gt; just paste it. It's a one-off. Naming it would be overhead with no payoff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Second paste:&lt;/strong&gt; stop. This is now a pattern. Give it a name and save it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;the snippet&amp;gt;'&lt;/span&gt; | snippetx add &amp;lt;name&amp;gt; &amp;lt;lang&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Third and later pastes:&lt;/strong&gt; &lt;code&gt;snippetx search &amp;lt;term&amp;gt;&lt;/code&gt; and &lt;code&gt;snippetx copy &amp;lt;id&amp;gt;&lt;/code&gt;. The paste is now a lookup, and the lookup is faster than re-deriving or hunting through browser history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule has a built-in filter: &lt;strong&gt;one-offs don't get saved.&lt;/strong&gt; That's the feature. A junk drawer is full of one-offs that got saved on the first paste "just in case." My library is full of two-pasters and above, which means every entry has at least two data points proving it's worth keeping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the second paste is the right threshold
&lt;/h2&gt;

&lt;p&gt;Because it's the first point where the snippet has &lt;em&gt;evidence&lt;/em&gt; of recurrence. The first paste is a hypothesis ("I might need this again"). The second paste is a confirmed prediction. Saving at the second paste means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Every entry is earned.&lt;/strong&gt; No speculative entries, no "this looks useful" graveyard. The library's content is a record of what I &lt;em&gt;actually&lt;/em&gt; reuse, which is the only curation that's honest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The naming cost is amortized.&lt;/strong&gt; Naming a snippet takes ten seconds. If the snippet is a one-off, those ten seconds are pure loss. If it's a two-paster, the ten seconds are paid back on the second use and in profit on every use after. The second paste is exactly where the break-even happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The library stays small enough to trust.&lt;/strong&gt; 200 entries is searchable. 2,000 entries is a database I don't want to maintain. The rule's filter keeps me in the first regime.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What the rule did to my habits
&lt;/h2&gt;

&lt;p&gt;After a few months, the rule changed how I work in ways beyond the snippets themselves:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. I name things as I encounter them.&lt;/strong&gt; The act of naming a snippet forces a two-word description, and the two-word description is often the name the thing &lt;em&gt;should&lt;/em&gt; have had. A snippet I named &lt;code&gt;retry-backoff&lt;/code&gt; turned out to be the pattern I'd been re-deriving in five different projects — naming it surfaced the pattern, and the pattern became a function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. My "useful commands" stopped living in my head.&lt;/strong&gt; The rule externalized the stuff I used to memorize. The kubectl one-liners, the jq filters, the nginx redirects — they're in the library now, and "I used to know that command" became "I can find that command." The library is a second brain with a search box, and the search box is the part that makes it work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The library is a map of my actual work.&lt;/strong&gt; The 200 entries, sorted by use frequency, are a record of what I actually spend time on. The top 20 are 90% of the lookups. That's not a curation decision — it's an emergence. The rule produced the library's shape without me deciding what to keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool's role
&lt;/h2&gt;

&lt;p&gt;snippetx is deliberately minimal — it's the storage and the search, nothing more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;snippetx add &amp;lt;name&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;lang]    &lt;span class="c"&gt;# save (pipe content in)&lt;/span&gt;
snippetx list &lt;span class="o"&gt;[&lt;/span&gt;filter]        &lt;span class="c"&gt;# list&lt;/span&gt;
snippetx search &amp;lt;term&amp;gt;        &lt;span class="c"&gt;# search&lt;/span&gt;
snippetx copy &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;            &lt;span class="c"&gt;# output to stdout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No cloud, no sync, no mobile app, no account. The snippets live in a local directory, and the manager is a single zero-dependency Node file. The tool's job is to make the rule &lt;em&gt;cheap to apply&lt;/em&gt; — the &lt;code&gt;add&lt;/code&gt; is one piped command, the &lt;code&gt;search&lt;/code&gt; is one word, the &lt;code&gt;copy&lt;/code&gt; is one id. If the tool were heavier than the rule, the rule would die from friction. It isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The meta-point
&lt;/h2&gt;

&lt;p&gt;A snippet library isn't a storage problem; it's a curation problem, and curation is the part that usually fails. People build junk drawers because saving is cheap and deciding-what-to-keep is hard. The rule inverts that: it makes &lt;em&gt;keeping&lt;/em&gt; the expensive act (you have to name it) and &lt;em&gt;saving&lt;/em&gt; the cheap act (one piped command), and it gates keeping on evidence (the second paste).&lt;/p&gt;

&lt;p&gt;The library that survives is the one where every entry earned its place. The second-paste rule is how mine got there, and it's the whole system — the tool is just the part that makes it frictionless.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @wuchunjie/snippetx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  More Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;scaffoldx-cli&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Production-ready project templates in seconds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx scaffoldx-cli&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/dotguard" rel="noopener noreferrer"&gt;dotguard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scan .env files for exposed secrets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/dotguard&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/gitpulse" rel="noopener noreferrer"&gt;gitpulse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Git repo analytics in your terminal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/gitpulse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terminal code snippet manager&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/snippetx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If these save you time, consider &lt;a href="https://ko-fi.com/wuchunjie" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;. All tools are MIT-licensed, zero-dependency, and run fully offline.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>productivity</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The FastAPI Template Is the One My Non-Frontend Friends Actually Use</title>
      <dc:creator>ke jia</dc:creator>
      <pubDate>Sat, 26 Sep 2026 11:43:16 +0000</pubDate>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a/the-fastapi-template-is-the-one-my-non-frontend-friends-actually-use-2imc</link>
      <guid>https://dev.to/ke_jia_24bb2f9f84f14f728a/the-fastapi-template-is-the-one-my-non-frontend-friends-actually-use-2imc</guid>
      <description>&lt;p&gt;Most scaffolders are Node-only, and that's fine — Node is the default runtime for web tooling. But my non-frontend friends (the data people, the ML people, the infra people) don't want a Node project when they want a backend. They want Python.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;FastAPI template in scaffoldx-cli&lt;/a&gt; is the one I hand them. It's a small, complete FastAPI service — async, Pydantic-validated, with OpenAPI docs and a pytest setup — generated by the same &lt;code&gt;npx&lt;/code&gt; command as the Node templates. One tool, two ecosystems, and the Python side is where the unexpected adoption happened.&lt;/p&gt;

&lt;p&gt;This post is about that template, and about why a "Node CLI that also does Python" is a better pattern than a separate Python scaffolder.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the template ships
&lt;/h2&gt;

&lt;p&gt;The generated service is a minimal-but-complete FastAPI app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An async app&lt;/strong&gt; — &lt;code&gt;async def&lt;/code&gt; handlers from the start, because "make it async later" is a refactor you don't want to do after the handlers are written. The template's position: if you're building a modern service, it's async from the first file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Pydantic model&lt;/strong&gt; — a request/response schema that's the single source of truth for validation. The handler takes a typed model, not a raw &lt;code&gt;Request&lt;/code&gt; you have to parse by hand. Type validation is the framework's job, not yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A health endpoint&lt;/strong&gt; — &lt;code&gt;/health&lt;/code&gt; that returns &lt;code&gt;{"status": "ok"}&lt;/code&gt;, so the service has a liveness check from the first deploy. Boring, and exactly the right kind of boring for a starter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAPI docs for free&lt;/strong&gt; — FastAPI generates &lt;code&gt;/docs&lt;/code&gt; and &lt;code&gt;/openapi.json&lt;/code&gt; automatically. The template doesn't add anything here; it just doesn't get in the way of the framework's best feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A pytest setup&lt;/strong&gt; — a &lt;code&gt;test_health.py&lt;/code&gt; that hits the health endpoint, so the first test run is green and the "how do I add a test" question has an answer in the repo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole service is a handful of files: &lt;code&gt;main.py&lt;/code&gt;, a &lt;code&gt;models.py&lt;/code&gt;, a test, a &lt;code&gt;requirements.txt&lt;/code&gt;. It runs with &lt;code&gt;uvicorn main:app&lt;/code&gt; and the docs are at &lt;code&gt;/docs&lt;/code&gt; before you've written a line of your own logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it lives in a Node CLI
&lt;/h2&gt;

&lt;p&gt;The obvious question: why is a Python template in an &lt;code&gt;npx&lt;/code&gt; command? Isn't that weird?&lt;/p&gt;

&lt;p&gt;It's not, for three reasons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The user's entry point is the CLI, not the language.&lt;/strong&gt; My non-frontend friends are already in a terminal running &lt;code&gt;npx&lt;/code&gt; for their Node projects. Adding a Python backend to the same command means one tool to remember, one muscle memory, one place to look when they want to start a project of &lt;em&gt;any&lt;/em&gt; kind. The &lt;code&gt;npx&lt;/code&gt; entry point is a convenience, not a statement about what the generated code is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The scaffolder is language-agnostic by design.&lt;/strong&gt; scaffoldx-cli is a file writer — it writes template files to disk. The templates are content, not code the scaffolder executes. A Python template is just a set of Python files; the scaffolder doesn't care what language they're in. That's the whole reason a single CLI can serve multiple ecosystems: the scaffolder's job (write files, git init, first commit) is orthogonal to the template's language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A separate Python scaffolder is a separate maintenance burden.&lt;/strong&gt; A second tool means a second package to publish, a second version to track, a second "where did I put that" for the user. Folding the Python templates into the existing CLI keeps the surface area at one tool. The user doesn't need to know or care that the scaffolder is a Node script; they need &lt;code&gt;npx scaffoldx-cli&lt;/code&gt; to give them a FastAPI app, and it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the non-frontend friends actually do with it
&lt;/h2&gt;

&lt;p&gt;The adoption pattern is consistent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The data person&lt;/strong&gt; uses it as the API layer for a data pipeline — the Pydantic models become the contract between the pipeline and the consumer, and the OpenAPI docs are the integration spec.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ML person&lt;/strong&gt; uses it as the serving wrapper for a model — the async handler loads the model, the Pydantic model validates the inference request, and &lt;code&gt;/docs&lt;/code&gt; is the endpoint reference the front-end team uses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The infra person&lt;/strong&gt; uses it as a reference implementation — "here's what a minimal, correct, tested FastAPI service looks like" — and copies the structure rather than the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In all three cases, the value isn't "it saved me 30 minutes of typing." It's that the template encodes the &lt;em&gt;shape&lt;/em&gt; of a correct FastAPI service (async, validated, documented, tested) in a way that's easier to copy than to derive, and the &lt;code&gt;npx&lt;/code&gt; entry point means they can get that shape without leaving the tool they already use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest limit
&lt;/h2&gt;

&lt;p&gt;The template is a starter, not a framework. It doesn't add ORM integration, auth, or deployment config — those are decisions your specific service will make. Its job is to get you from "I want a FastAPI service" to "I have a running, validated, documented, tested service I can extend" in the minimum number of steps. Everything after that is your architecture, on a base that's already correct in the places that are easy to get wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx scaffoldx-cli
&lt;span class="c"&gt;# choose: FastAPI Backend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One tool, two ecosystems, and the Python side is the one that surprised me. Sometimes the most useful feature is the one in the language you didn't expect to need it in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx scaffoldx-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  More Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;scaffoldx-cli&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Production-ready project templates in seconds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx scaffoldx-cli&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/dotguard" rel="noopener noreferrer"&gt;dotguard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scan .env files for exposed secrets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/dotguard&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/gitpulse" rel="noopener noreferrer"&gt;gitpulse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Git repo analytics in your terminal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/gitpulse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terminal code snippet manager&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/snippetx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If these save you time, consider &lt;a href="https://ko-fi.com/wuchunjie" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;. All tools are MIT-licensed, zero-dependency, and run fully offline.&lt;/p&gt;

</description>
      <category>python</category>
      <category>javascript</category>
      <category>cli</category>
      <category>devtools</category>
    </item>
    <item>
      <title>The Landing Page Template Is the One I Give to Clients Who Hate Setup</title>
      <dc:creator>ke jia</dc:creator>
      <pubDate>Sat, 26 Sep 2026 11:42:27 +0000</pubDate>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a/the-landing-page-template-is-the-one-i-give-to-clients-who-hate-setup-g5e</link>
      <guid>https://dev.to/ke_jia_24bb2f9f84f14f728a/the-landing-page-template-is-the-one-i-give-to-clients-who-hate-setup-g5e</guid>
      <description>&lt;p&gt;Some clients don't want a framework. They don't want a build step. They don't want a &lt;code&gt;node_modules&lt;/code&gt; folder, a dev server, or a terminal they have to understand. They want a page.&lt;/p&gt;

&lt;p&gt;For those clients, the &lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;Modern Landing Page template in scaffoldx-cli&lt;/a&gt; is the one I reach for. It's the template that says: "here's a responsive, modern landing page, no build step, no framework, no setup. Open the file, edit the text, ship it."&lt;/p&gt;

&lt;p&gt;This post is about that template, and about the underrated art of matching the tool to the client's tolerance for complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the template ships
&lt;/h2&gt;

&lt;p&gt;The generated project is deliberately small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One HTML file&lt;/strong&gt; — semantic structure, a header, a hero, a features section, a CTA, a footer. Modern reset CSS, no framework, no CDN dependencies (the CSS is inline, so the page works offline and has no third-party requests).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive by construction&lt;/strong&gt; — the layout is mobile-first with a few breakpoints; it looks right on a phone before it looks right on a desktop, because that's the order most visitors will actually see it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A CTA section that's actually wired&lt;/strong&gt; — the call-to-action button links to a mailto or a form placeholder, so the page has a conversion path from the first render, not after the client asks "where does the button go?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A gradient hero&lt;/strong&gt; — the visual hook is there by default, so the page doesn't look like a wireframe. The client sees a &lt;em&gt;page&lt;/em&gt;, not a skeleton.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing is a handful of files you can open in any text editor. No &lt;code&gt;npm install&lt;/code&gt;, no dev server, no build. Double-click &lt;code&gt;index.html&lt;/code&gt; and it's there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "no build step" is a feature for this client
&lt;/h2&gt;

&lt;p&gt;The client who hates setup isn't being irrational — they're telling you their constraint. For them, every build step is a failure mode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A build step is a dependency on my machine.&lt;/strong&gt; If I have to run &lt;code&gt;npm run build&lt;/code&gt; and my Node version is wrong, the page doesn't ship. No build step means the deliverable is files, and files don't have a Node version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A build step is a maintenance burden I'll inherit.&lt;/strong&gt; Every framework I add is something I'll have to understand when I want to change the button color in six months. No framework means the "change the button color" edit is a CSS value, and I can find it in ten seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A build step is a trust barrier.&lt;/strong&gt; Many of these clients will hand the site to someone else (a junior, an agency, a VA) for maintenance. The simpler the stack, the more maintainable it is by a stranger.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The landing page template is designed for exactly this maintenance profile: the next person to touch it needs to understand HTML and CSS, and that's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to use it
&lt;/h2&gt;

&lt;p&gt;Honest limits, because "no build step" isn't always right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The page will grow into an app.&lt;/strong&gt; If the client's "landing page" is actually the front door to a product with auth, state, and API calls, a framework is the right start. The landing template's simplicity is an asset for a page and a liability for an app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The design is complex.&lt;/strong&gt; Heavy animation, a complex component system, or a design system the client will extend — that's framework territory. The template's inline CSS is great for a page and a pain for a design system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The team is a web team.&lt;/strong&gt; If the maintainers are professional frontenders who live in a framework, the no-build template is a step backward for them. Match the tool to the maintainers, not just the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the client who wants a page and will maintain it (or hand it to someone who will), the no-build template is the professional choice — not the lazy one. It's the choice that matches the deliverable to the actual maintenance reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical flow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx scaffoldx-cli
&lt;span class="c"&gt;# choose: Modern Landing Page&lt;/span&gt;
&lt;span class="c"&gt;# name it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is a directory with &lt;code&gt;index.html&lt;/code&gt; and a small &lt;code&gt;styles.css&lt;/code&gt;. Open &lt;code&gt;index.html&lt;/code&gt; in a browser — the page is there, responsive, with the CTA wired. Edit the text in the HTML, the colors in the CSS, and you're done. No terminal required after the one &lt;code&gt;npx&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;That's the whole pitch: &lt;strong&gt;one command, zero setup, a page that works and is maintainable by the person who'll actually maintain it.&lt;/strong&gt; For the right client, that's not a compromise — it's the deliverable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx scaffoldx-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  More Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;scaffoldx-cli&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Production-ready project templates in seconds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx scaffoldx-cli&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/dotguard" rel="noopener noreferrer"&gt;dotguard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scan .env files for exposed secrets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/dotguard&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/gitpulse" rel="noopener noreferrer"&gt;gitpulse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Git repo analytics in your terminal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/gitpulse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terminal code snippet manager&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/snippetx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If these save you time, consider &lt;a href="https://ko-fi.com/wuchunjie" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;. All tools are MIT-licensed, zero-dependency, and run fully offline.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>cli</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Strict TypeScript by Default: The Starter That Refuses to Let You Be Lazy</title>
      <dc:creator>ke jia</dc:creator>
      <pubDate>Sat, 26 Sep 2026 11:42:13 +0000</pubDate>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a/strict-typescript-by-default-the-starter-that-refuses-to-let-you-be-lazy-40bg</link>
      <guid>https://dev.to/ke_jia_24bb2f9f84f14f728a/strict-typescript-by-default-the-starter-that-refuses-to-let-you-be-lazy-40bg</guid>
      <description>&lt;p&gt;Every TypeScript starter ships a &lt;code&gt;tsconfig.json&lt;/code&gt;. Almost every one ships it with &lt;code&gt;strict: false&lt;/code&gt;, or with a partial strict config that leaves the most useful checks off. The reasoning is usually kindness: "don't scare the new dev with a wall of type errors on day one."&lt;/p&gt;

&lt;p&gt;I think that's backwards. The first week of a project is when type discipline gets set, and a starter that defaults to lax checking is teaching the team the wrong lesson. It's easier to start strict and loosen a specific check that's genuinely in your way than to start lax and retrofit strictness onto a codebase that's already full of &lt;code&gt;any&lt;/code&gt;-shaped holes.&lt;/p&gt;

&lt;p&gt;This is the position I built into the &lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;TypeScript templates in scaffoldx-cli&lt;/a&gt;: &lt;strong&gt;&lt;code&gt;strict: true&lt;/code&gt; from the first file, no opt-out prompt, no "relaxed mode" option.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What strict mode actually buys you in week one
&lt;/h2&gt;

&lt;p&gt;The checks that matter most in a new project are the ones that catch the &lt;em&gt;first&lt;/em&gt; errors, when they're cheap to fix:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;noImplicitAny&lt;/code&gt;&lt;/strong&gt; — the function parameter you forgot to type. In week one, that's a five-second fix. In month three, after 200 untyped parameters have propagated, it's a refactor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;strictNullChecks&lt;/code&gt;&lt;/strong&gt; — the &lt;code&gt;user.name&lt;/code&gt; that throws when &lt;code&gt;user&lt;/code&gt; is undefined. This is the single most valuable check in the list, and it's the one lax configs disable most often "because it's annoying." The annoyance is the &lt;em&gt;point&lt;/em&gt;: it's the check that turns "undefined at 2am in production" into "type error at 2pm in development."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;noImplicitReturns&lt;/code&gt;&lt;/strong&gt; — the function that returns a value on some paths and &lt;code&gt;undefined&lt;/code&gt; on others. A new project is when your function shapes are being decided; catching a missing return while the function is three lines long is free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;noUnusedLocals&lt;/code&gt; / &lt;code&gt;noUnusedParameters&lt;/code&gt;&lt;/strong&gt; — the dead code that tells you the design changed and the old path wasn't cleaned up. In a new project, "unused" almost always means "I should delete this," and the compiler is doing your cleanup pass for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "it's too annoying" objection, handled
&lt;/h2&gt;

&lt;p&gt;The standard objection: "strict mode generates a wall of errors and slows me down." True, in the first hour. The counter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The errors are front-loaded, not ongoing.&lt;/strong&gt; A strict config produces its maximum error count in the first few files, when the codebase is small. Once the base types are in place, the steady-state error rate drops to near zero — you're not fighting the compiler all day, you're fighting it for an hour and then it's a safety net.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "annoying" errors are the cheap ones.&lt;/strong&gt; The errors strict mode generates in week one are almost all five-second fixes (add a type, handle the null, return on the missing path). The errors it &lt;em&gt;prevents&lt;/em&gt; are the expensive ones (runtime undefined, silent type drift, the refactor that strict mode would have made unnecessary). The trade is a hour of cheap fixes for a year of avoided expensive ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Loosening one check is easy; tightening is hard.&lt;/strong&gt; If &lt;code&gt;noUnusedParameters&lt;/code&gt; is genuinely in your way (say, you have a callback interface that requires an unused argument), you can suppress it per-line or disable that one flag. Starting lax means you've disabled all the checks, and re-enabling them one by one on a grown codebase is the expensive direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The starter's actual config
&lt;/h2&gt;

&lt;p&gt;The TypeScript templates ship with a tsconfig that's strict and boring:&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;"compilerOptions"&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;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ESNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noUnusedLocals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noUnusedParameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noImplicitReturns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;&lt;code&gt;skipLibCheck: true&lt;/code&gt; is the one pragmatic concession — it stops the compiler from type-checking &lt;code&gt;node_modules&lt;/code&gt;'s declaration files, which is a tax nobody wants. Everything else is on. There's no &lt;code&gt;--relaxed&lt;/code&gt; flag, no prompt asking if you want strict mode, no "easy mode" template. The position is deliberate: a starter that makes you think about your type discipline is a starter that's doing its job.&lt;/p&gt;

&lt;h2&gt;
  
  
  The meta-point
&lt;/h2&gt;

&lt;p&gt;A starter encodes a position on how the team will work. "We type-check strictly from day one" is a position, and it's the right one for a new project, where the cost of the position is an hour and the benefit is a year. The lax starter encodes the opposite position — "we'll get to types when we need to" — and "when we need to" is always later and more expensive than "day one."&lt;/p&gt;

&lt;p&gt;Pick the starter that argues for the discipline you want, because the discipline you don't pick on day one is the discipline you'll have to fight for in month three.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx scaffoldx-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  More Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;scaffoldx-cli&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Production-ready project templates in seconds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx scaffoldx-cli&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/dotguard" rel="noopener noreferrer"&gt;dotguard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scan .env files for exposed secrets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/dotguard&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/gitpulse" rel="noopener noreferrer"&gt;gitpulse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Git repo analytics in your terminal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/gitpulse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terminal code snippet manager&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/snippetx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If these save you time, consider &lt;a href="https://ko-fi.com/wuchunjie" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;. All tools are MIT-licensed, zero-dependency, and run fully offline.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>cli</category>
    </item>
    <item>
      <title>The Discord Bot Template That Doesn't Drag in a Framework</title>
      <dc:creator>ke jia</dc:creator>
      <pubDate>Sat, 26 Sep 2026 11:41:18 +0000</pubDate>
      <link>https://dev.to/ke_jia_24bb2f9f84f14f728a/the-discord-bot-template-that-doesnt-drag-in-a-framework-5fhe</link>
      <guid>https://dev.to/ke_jia_24bb2f9f84f14f728a/the-discord-bot-template-that-doesnt-drag-in-a-framework-5fhe</guid>
      <description>&lt;p&gt;The first thing most Discord bot tutorials do is add a framework. A "bot framework" with a plugin system, a command registry, an event bus, and a configuration layer. For a bot that says "ping" when you say "pong," that's like buying a crane to hang one picture.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;Discord Bot template in scaffoldx-cli&lt;/a&gt; takes the opposite position: &lt;strong&gt;discord.js, slash commands, and an event handler — and nothing else.&lt;/strong&gt; No framework, no plugin system, no abstractions you haven't asked for.&lt;/p&gt;

&lt;p&gt;This post is the case for that position, and the honest accounting of when you &lt;em&gt;should&lt;/em&gt; reach for the framework anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the template ships
&lt;/h2&gt;

&lt;p&gt;The generated bot is three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A client&lt;/strong&gt; that connects with your token (read from the environment, never committed — the template's &lt;code&gt;.gitignore&lt;/code&gt; excludes &lt;code&gt;.env&lt;/code&gt; and ships &lt;code&gt;.env.example&lt;/code&gt; with &lt;code&gt;DISCORD_TOKEN=your-token-here&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A slash-command registration step&lt;/strong&gt; that registers a starter &lt;code&gt;/ping&lt;/code&gt; command with the API, so the first run has a working command to test against.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An event handler skeleton&lt;/strong&gt; — &lt;code&gt;messageCreate&lt;/code&gt; and the command interaction handler — with the routing logic explicit and short enough to read in one sitting.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole generated &lt;code&gt;index.js&lt;/code&gt; is under 100 lines. You can read the entire bot, including the parts you didn't write, in five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "no framework" is a feature, not a limitation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. The abstraction tax is real, and it's paid in debugging.&lt;/strong&gt; A bot framework's command registry is convenient until the command doesn't fire, and you spend an hour discovering that the registry's matching logic normalizes command names in a way your input didn't. With discord.js's native interaction handler, the routing is two lines of code you can trace in your head. The framework's abstraction is a wall between you and the actual event, and for a small bot, that wall is pure cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. discord.js is already the framework.&lt;/strong&gt; This is the key point people miss: discord.js v14 &lt;em&gt;is&lt;/em&gt; a complete, well-maintained library. It has the client, the event system, the interaction router, and the REST API. The "bot framework" layer on top is solving problems that discord.js already solved, for the convenience of a structure that only pays off at a certain scale. Below that scale, it's a middleman.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The template scales by adding, not by migrating.&lt;/strong&gt; When the bot outgrows 100 lines, you don't migrate to a framework — you add files. A &lt;code&gt;commands/&lt;/code&gt; directory, a small router, a config module. Each addition is a normal Node.js refactor, visible in the diff, with no framework migration in the middle. The "no framework" start doesn't trap you; it just delays the abstraction until you actually need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you should use the framework anyway
&lt;/h2&gt;

&lt;p&gt;Honest counterargument, because "no framework" is not universally right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Large teams, many contributors.&lt;/strong&gt; A framework's command registry and plugin conventions are a coordination tool — they make 10 people add commands without colliding. If your bot is a team product with 5+ contributors, the framework's structure is worth its tax.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin architecture as a product.&lt;/strong&gt; If the bot's &lt;em&gt;feature&lt;/em&gt; is that users can install plugins, you need the plugin system. That's a product requirement, not a convenience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You've already chosen an ecosystem.&lt;/strong&gt; If your team standardizes on a framework for consistency across projects, consistency has its own value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the common case — a small bot, one or two developers, a feature list you can hold in your head — the no-framework start is the faster path to a working bot, and the path that keeps the code readable as it grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx scaffoldx-cli
&lt;span class="c"&gt;# choose: Discord Bot&lt;/span&gt;
&lt;span class="c"&gt;# name it, npm install, put your token in .env&lt;/span&gt;
node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First run registers &lt;code&gt;/ping&lt;/code&gt;, and the bot answers in the channel where you invoke it. That's the whole demo loop — no framework setup, no plugin config, no reading a framework's documentation before you've written a single line of your own logic.&lt;/p&gt;

&lt;p&gt;The template's job is to get you from "I want to build a Discord bot" to "my bot is responding to a slash command" in the minimum number of steps that don't involve reading a framework's architecture doc. Everything you add after that is your design, on a base you fully understand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx scaffoldx-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  More Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/scaffoldx-cli" rel="noopener noreferrer"&gt;scaffoldx-cli&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Production-ready project templates in seconds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx scaffoldx-cli&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/dotguard" rel="noopener noreferrer"&gt;dotguard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Scan .env files for exposed secrets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/dotguard&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/gitpulse" rel="noopener noreferrer"&gt;gitpulse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Git repo analytics in your terminal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/gitpulse&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wuchunjie00/snippetx" rel="noopener noreferrer"&gt;snippetx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terminal code snippet manager&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx @wuchunjie/snippetx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If these save you time, consider &lt;a href="https://ko-fi.com/wuchunjie" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;. All tools are MIT-licensed, zero-dependency, and run fully offline.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>cli</category>
      <category>devtools</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
