<?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: Bravi</title>
    <description>The latest articles on DEV Community by Bravi (@bravilogy).</description>
    <link>https://dev.to/bravilogy</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%2F3937742%2F0bd65bd4-03d5-4e6b-9b9d-25a7af8c0e0d.jpg</url>
      <title>DEV Community: Bravi</title>
      <link>https://dev.to/bravilogy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bravilogy"/>
    <language>en</language>
    <item>
      <title>The missing UI for the things you run.</title>
      <dc:creator>Bravi</dc:creator>
      <pubDate>Sun, 19 Jul 2026 21:14:43 +0000</pubDate>
      <link>https://dev.to/bravilogy/the-missing-ui-for-the-things-you-run-2ggk</link>
      <guid>https://dev.to/bravilogy/the-missing-ui-for-the-things-you-run-2ggk</guid>
      <description>&lt;h2&gt;
  
  
  Hello world, again.
&lt;/h2&gt;

&lt;p&gt;Last time I wrote about confish, it was a config tool. You'd define a typed schema, your scripts would fetch values from it, and you could nudge a threshold from the sofa without a redeploy. That post ended with "my scripts and bots haven't stopped asking for more" - and what they asked for surprised me. They didn't want more values coming down. They had things to send back up.&lt;/p&gt;

&lt;p&gt;Because here's the other half of the automation problem. Every script eventually produces something worth looking at. The scraper finds things. The backup job finishes (or doesn't). The Pi in the loft measures a temperature. Where does all that output go?&lt;/p&gt;

&lt;p&gt;If you're anything like me: a Telegram DM to yourself. A JSON file on the box. A spreadsheet with an importer that broke in March. Or another email getting lost in space. Yet another pain point where I saw a pattern forming.&lt;/p&gt;

&lt;p&gt;Config was data flowing down to your things. This one's about the data flowing back up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Push it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Feeds&lt;/strong&gt; are typed collections of living state you push into confish - crawl results, job runs, incidents, sensor readings. Same philosophy as config: define the fields once (string, number, boolean, date), and your code pushes items that fit them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;confish&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Confish&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Confish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a1b2c3d4e5f6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confish_sk_...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backup-runs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-07-14&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;homelab&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration_s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;214&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;set&lt;/code&gt; is an upsert on a key you choose, so pushing the same item twice updates it in place - retries are harmless, and a "running" item can become a "done" item without growing a list. The &lt;code&gt;ttl&lt;/code&gt; means the item cleans itself up a week later even if the script that pushed it crashes and never gets another word in. Finished runs and resolved incidents just quietly age out.&lt;/p&gt;

&lt;p&gt;And like config, a feed's definition belongs to the application while each environment holds its own items - the same code pushes to staging and production with only the API key swapped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show it
&lt;/h2&gt;

&lt;p&gt;Here's the part that finally killed that spreadsheet export for me. For each feed you pick a layout - cards, table, compact list, or hero - and assign fields to roles: this one's the title, this one's the badge, sort by that one. The dashboard renders whatever you push. Live, streamed in as items arrive, no refresh.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frsongx9sp9w96q0nxah4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frsongx9sp9w96q0nxah4.png" alt="A feed rendered as a live table of backup runs in the confish dashboard" width="800" height="676"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hero deserves a special mention: it renders items big and centred, which turns out to be exactly right for singleton state, or a collection with just a handful of items. Here is my "Luckster" euromillions bot using it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2bultqgnhy30gfxjhkzx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2bultqgnhy30gfxjhkzx.png" alt="A hero-layout feed showing lottery results pushed by a bot" width="800" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the loop becomes: your script pushes typed items, and there's a live, decent-looking view of them on your phone. No frontend built, no charting library evaluated at midnight, nothing deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Share it
&lt;/h2&gt;

&lt;p&gt;Then it got out of hand, in a good way.&lt;/p&gt;

&lt;p&gt;Any feed can be published at a read-only public link: unguessable URL, &lt;code&gt;noindex&lt;/code&gt;, live updates, and it shows the items and nothing else - no app, no environment, no config details. Anyone with the link can install it on a phone or desktop as its own little app (each share is a proper installable PWA).&lt;/p&gt;

&lt;p&gt;And when one feed isn't enough, a &lt;strong&gt;board&lt;/strong&gt; composes several - feeds from any of your applications, each section pinned to whichever environment you choose, stacked in the order you set, each rendering with its own layout. Boards start as private drafts; you flip the public link on when it's ready.&lt;/p&gt;

&lt;p&gt;Mine is a homelab board: uptime checks in a hero up top, last night's backup runs in a table, open incidents in a list. It's installed on my phone and the tablet, and it keeps itself current while I do nothing whatsoever. A zero-build status page for things that run on a shelf.&lt;/p&gt;

&lt;p&gt;(Public links and boards are on the paid plan - pushing and viewing your own feeds isn't.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Name it
&lt;/h2&gt;

&lt;p&gt;Somewhere in all this, I looked at the product and realised the category had quietly changed underneath it.&lt;/p&gt;

&lt;p&gt;Configuration is state flowing &lt;em&gt;down&lt;/em&gt; to your things. Actions are commands flowing &lt;em&gt;down&lt;/em&gt;. Logs are events flowing &lt;em&gt;up&lt;/em&gt;. And feeds - the corner that was missing - are state flowing &lt;em&gt;up&lt;/em&gt;. Four pillars, two directions, and none of them is "the feature" with the others as extras. The grid filled itself in, one complaint at a time.&lt;/p&gt;

&lt;p&gt;So confish isn't a config tool with add-ons anymore. It's the dashboard your bots, scripts, and cron jobs never had - &lt;strong&gt;the missing UI for the things you run&lt;/strong&gt;. Change a value, dispatch a command, watch the logs land, and see the state your things push back. One place, from anywhere, including the sofa.&lt;/p&gt;

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

&lt;p&gt;If something you run keeps DMing you JSON - or worse, keeps its results entirely to itself - give feeds a spin at &lt;a href="https://confi.sh" rel="noopener noreferrer"&gt;confi.sh&lt;/a&gt;. All five SDKs (Go, JavaScript, Python, PHP, Rust) speak feeds, and it's a couple of REST calls if your language isn't on the list.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;P.S. There's another way to talk to confish that keeps nagging at me. I'll come back to you on that.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>automation</category>
    </item>
    <item>
      <title>I kept rebuilding the same config panel for my bots, so I built one tool for all of them</title>
      <dc:creator>Bravi</dc:creator>
      <pubDate>Thu, 11 Jun 2026 20:28:55 +0000</pubDate>
      <link>https://dev.to/bravilogy/i-kept-rebuilding-the-same-config-panel-for-my-bots-so-i-built-one-tool-for-all-of-them-bnp</link>
      <guid>https://dev.to/bravilogy/i-kept-rebuilding-the-same-config-panel-for-my-bots-so-i-built-one-tool-for-all-of-them-bnp</guid>
      <description>&lt;h2&gt;
  
  
  Hello world.
&lt;/h2&gt;

&lt;p&gt;I love automating things. And if you do too, you probably know how this goes: a Telegram bot here, a price watcher there, a scraper on a Raspberry Pi that sits on a shelf blinking its LED, a handful of cron jobs on a cheap VPS. Dozens of small things quietly doing useful work in the background while you get on with your day.&lt;/p&gt;

&lt;p&gt;Here's the thing though - almost every one of them eventually wants tweaking &lt;strong&gt;while it's running&lt;/strong&gt;. A threshold. A toggle. A retry count. Some tiny number you should be able to change in a second, ideally from your phone, on the sofa, whilst watching that intense episode of [insert your favourite TV show here], without opening a terminal.&lt;/p&gt;

&lt;p&gt;How it actually goes: you edit a hardcoded value or a &lt;code&gt;.env&lt;/code&gt;, push, redeploy, wait for the pipeline to succeed. For a one-character change.&lt;/p&gt;

&lt;p&gt;I did that. A lot. And then I built a little control panel for that price watcher. A few inputs, a save button, an endpoint the script could read from.&lt;/p&gt;

&lt;p&gt;Then I built a second one for another project. Then a third. Somewhere around the third hand-rolled dashboard doing the same job slightly differently, I admitted this was a pattern and built the thing I actually wanted: one place to manage config for all of my running things.&lt;/p&gt;

&lt;p&gt;That's &lt;a href="https://confi.sh" rel="noopener noreferrer"&gt;confish&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shape it
&lt;/h2&gt;

&lt;p&gt;You define a typed schema once per application - whatever fields your thing needs, each with a type (string, number, boolean, date, array). Then you spin up as many environments as you like: staging, prod, one per client, one per Raspberry Pi. Same fields, different values. Your code hits one endpoint and gets typed config back.&lt;/p&gt;

&lt;p&gt;Here's a script reading its config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;confish&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Confish&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Confish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a1b2c3d4e5f6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confish_sk_...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And what comes back is already cast to the types you defined - numbers as numbers, booleans as booleans, and so on:&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;"site_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My Application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_upload_mb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maintenance_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowed_origins"&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="s2"&gt;"https://example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://app.example.com"&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;Change &lt;code&gt;max_upload_mb&lt;/code&gt; in the dashboard and the next fetch sees &lt;code&gt;50&lt;/code&gt;. No redeploy, no rebuild. Suddenly your scraper's politeness delay, your bot's cooldown, your job's batch size - they're all just values you can nudge from anywhere, including that sofa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grow it
&lt;/h2&gt;

&lt;p&gt;The schema-and-fetch core was the plan. Everything else got added because my own scripts kept asking for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logs.&lt;/strong&gt; My scripts were already talking to confish, so I wanted them to tell me how they were doing without me SSHing in to tail a file. Now the 3am cron job can simply report in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scrape finished&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Entries land in the dashboard, per environment, with levels and context - so when something looks off, the first clue is one tab away instead of one SSH session away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Webhooks.&lt;/strong&gt; When config changes, confish can POST to a URL you set per environment, so a service can react instead of polling. The payload includes a &lt;code&gt;changes&lt;/code&gt; array of exactly which fields moved, deliveries retry with exponential backoff on &lt;code&gt;5xx&lt;/code&gt;, and every SDK ships a one-liner signature verifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Command it
&lt;/h2&gt;

&lt;p&gt;I saved my favourite part for last.&lt;/p&gt;

&lt;p&gt;Sometimes I don't want to change a value - I want a running thing to &lt;strong&gt;do something&lt;/strong&gt;. Flush a cache. Rebuild an index. Restart a worker. The usual answer is to expose an endpoint on the app, secure it, open it to the internet, and then quietly worry about it forever. For a personal scraper sitting behind NAT on a home server, that's a lot of ceremony - port forwarding, a reverse proxy, maybe a tunnel, maybe that "temporary" ngrok URL you forget about - all so you can say "please flush your cache."&lt;/p&gt;

&lt;p&gt;Actions flip the direction. Your app never listens for anything. It &lt;strong&gt;polls&lt;/strong&gt;. You dispatch a command from the dashboard, it sits in a queue on the environment, and your app picks it up on its next poll - acknowledges it, runs your handler, reports progress, and marks it done or failed. Nothing inbound. No open port, no public URL, no ingress rules. Works the same on a VPS, a Pi, or your old laptop in a drawer.&lt;/p&gt;

&lt;p&gt;The raw flow is just a handful of REST calls (poll, ack, update, complete/fail), but each SDK wraps it in a &lt;code&gt;consume()&lt;/code&gt; loop with adaptive backoff when idle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flush-cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Clearing cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keys&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1402&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cleared&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1402&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;took_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So from my phone I can hit "flush-cache" on a box that has no inbound access at all, and watch the result come back in the timeline a second later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wire it
&lt;/h2&gt;

&lt;p&gt;There are official SDKs for Go, JavaScript, Python, PHP, and Rust (&lt;a href="https://github.com/confishhq" rel="noopener noreferrer"&gt;github.com/confishhq&lt;/a&gt;) - each wraps the same REST API, so if your language isn't on the list, plain HTTP works fine.&lt;/p&gt;

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

&lt;p&gt;If you've got bots, scrapers, or cron jobs that deserve better than a redeploy per tweak, give it a spin at &lt;a href="https://confi.sh" rel="noopener noreferrer"&gt;confi.sh&lt;/a&gt; - I'd genuinely love to hear what you end up wiring it to. And if you've solved this differently, or you look at the Actions model and think "hmm, not convinced" - tell me in the comments. Half the features above exist because someone (usually me) complained about something, and my scripts and bots haven't stopped asking for more - so watch this space.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;P.S. I just shipped dark mode - so when that 3am cron job has something to say, checking on it won't flashbang you.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>automation</category>
      <category>api</category>
      <category>devto</category>
    </item>
  </channel>
</rss>
