<?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: Niels Madan</title>
    <description>The latest articles on DEV Community by Niels Madan (@nlsmdn).</description>
    <link>https://dev.to/nlsmdn</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%2F3933288%2F4749443f-3128-472f-831f-582ffc3682a3.png</url>
      <title>DEV Community: Niels Madan</title>
      <link>https://dev.to/nlsmdn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nlsmdn"/>
    <language>en</language>
    <item>
      <title>Skill Showcase: /guide</title>
      <dc:creator>Niels Madan</dc:creator>
      <pubDate>Wed, 09 Sep 2026 15:05:31 +0000</pubDate>
      <link>https://dev.to/nlsmdn/skill-showcase-guide-da6</link>
      <guid>https://dev.to/nlsmdn/skill-showcase-guide-da6</guid>
      <description>&lt;p&gt;Has this ever happened to you?&lt;/p&gt;

&lt;p&gt;You need to do something in some UI somewhere. Set up some permissions, some deploy, some settings in your OS or app, something like that. You ask your agent to tell you the steps to do the thing, of course, because what else are you going to do? Google? Pshaw! In 2026? Not on my watch. You follow the instructions, get stuck on something, some button not where it's supposed to be, something changed since the LLM training cutoff, so you ask your agent about it. Your agent looks up the latest docs, tells you what you actually need to do. You do it. So that step is done, but what was the next one again? You scroll up to where the initial instructions were, figure out where in the steps you got stuck, continue, hit another bump, ask, do, scroll up all the way again, what step are you on?&lt;/p&gt;

&lt;p&gt;It's not dignified.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdfudel59dk35cqd5jl3q.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdfudel59dk35cqd5jl3q.gif" alt="Gandalf sliding across the floor during his fight with Saruman" width="760" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Do Better
&lt;/h2&gt;

&lt;p&gt;Let's make a skill for it! But you don't have to. Because I already did. Here's what it does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Looks up the latest docs to begin with&lt;/li&gt;
&lt;li&gt;Gives you detailed step by step instructions&lt;/li&gt;
&lt;li&gt;When you get stuck or have a question:

&lt;ul&gt;
&lt;li&gt;It gives you instructions on how to get past that point&lt;/li&gt;
&lt;li&gt;Then repeats the guide, highlighting where you are&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see it in action!&lt;/p&gt;

&lt;p&gt;Here I'm deploying an app to Railway for the first time. I get myself a /guide, which looks up Railway's current docs, then hands me fifteen steps to click through.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
▶ 1. New Project → Deploy from GitHub repo → select *redacted* ← you are here
  2. Deploy → land on Project Canvas
  3. Rename first service → api
  4. Create → Database → Add PostgreSQL
  5. Create → Database → Add Redis
  6. api Settings: Config File Path /apps/api/railway.toml; Root Directory empty
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I click through step 1, but my screen doesn't match step 2. Railway wants to deploy four services, not three. So I ask.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; So it proposes to deploy four services, workers,  web, api and evals. I guess I should remove evals?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I get back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Yes — remove **evals**. It's a CLI/eval harness, not a service: every script is a one-shot `tsx` run (`bench`, `seed`, `calibrate`) or a dev-only watcher (`calibrate:serve`). No `start` server, no long-running process, and no `railway.toml`.

[more blablabla]

That puts you right at step 2 — three services selected, landing on the canvas.

---
✓ Step 1 done
▶ 2. Deploy with only web/api/workers selected (deselect/remove evals) → land on Project Canvas  ← you are here
  3. Rename first service → api
  4. Create → Database → Add PostgreSQL
  5. Create → Database → Add Redis
  6. api Settings: Config File Path /apps/api/railway.toml; Root Directory empty
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2 used to say "Deploy → land on Project Canvas". Now it tells me what to do about evals.&lt;/p&gt;

&lt;p&gt;No scrolling, I know what the next step is, dignity intact.&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%2Fzvj3gts4jyquhnb89445.jpg" 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%2Fzvj3gts4jyquhnb89445.jpg" alt="Gandalf the White holding his staff" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Steal the skill &lt;a href="https://github.com/nielsmadan/skills/tree/main/skills/guide" rel="noopener noreferrer"&gt;here&lt;/a&gt;. I'm all for just telling an agent that you want a copy of the skill and adding it to your own roster, so you can evolve it to fit your needs, but I also have the skill on &lt;a href="https://skills.sh/nielsmadan/skills" rel="noopener noreferrer"&gt;skills.sh&lt;/a&gt; and &lt;a href="https://github.com/nielsmadan/skills" rel="noopener noreferrer"&gt;a plugin of all my skills&lt;/a&gt; if that's how you roll.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>skills</category>
      <category>agents</category>
    </item>
    <item>
      <title>A single source of truth for AI agent permissions (Claude, Codex, Gemini, OpenCode)</title>
      <dc:creator>Niels Madan</dc:creator>
      <pubDate>Mon, 18 May 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/nlsmdn/a-single-source-of-truth-for-ai-agent-permissions-claude-codex-gemini-opencode-1khl</link>
      <guid>https://dev.to/nlsmdn/a-single-source-of-truth-for-ai-agent-permissions-claude-codex-gemini-opencode-1khl</guid>
      <description>&lt;p&gt;I bounce around between agents / harnesses, mostly just to see what's out there, but keeping their different configurations in sync can be a pain. Particularly, permissions drift quickly, so I built myself a little system to keep them in sync.&lt;/p&gt;

&lt;p&gt;The idea is simple: create a single source of truth for all permissions, then generate the permission files for the agents (Claude, Codex, Gemini, opencode in my case) from it.&lt;/p&gt;

&lt;p&gt;Here's an excerpt of the permission file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[shell]&lt;/span&gt;

&lt;span class="c"&gt;# allow — run without prompting on every agent.&lt;/span&gt;
&lt;span class="py"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c"&gt;# git — read-only&lt;/span&gt;
  &lt;span class="s"&gt;"git status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"git log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"git diff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c"&gt;# ... gh, glab, file inspection, version checks, docker, xcrun ...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# deny — hard-blocked on every agent.&lt;/span&gt;
&lt;span class="py"&gt;deny&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"git push"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"git reset --hard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"git clean -fd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# ask — always prompt for confirmation, even in auto modes.&lt;/span&gt;
&lt;span class="py"&gt;ask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"heroku"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/nielsmadan/agentic-coding/blob/main/permissions/permissions.toml" rel="noopener noreferrer"&gt;Full file.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then we have a simple Python script that does the generation:&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;CODEX_DECISION&lt;/span&gt; &lt;span class="o"&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;allow&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;allow&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;deny&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;forbidden&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;ask&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;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;codex_rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prefix_rule(pattern = [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;], decision = &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_codex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allow&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;deny&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;ask&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CODEX_DECISION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;codex_rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;# ... write lines to codex/rules/permissions.rules ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's one &lt;code&gt;render_&amp;lt;agent&amp;gt;&lt;/code&gt; per target format, so &lt;code&gt;"git status"&lt;/code&gt; becomes &lt;code&gt;prefix_rule(pattern = ["git", "status"], decision = "allow")&lt;/code&gt; for Codex, &lt;code&gt;"Bash(git status:*)"&lt;/code&gt; for Claude, and so on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nielsmadan/agentic-coding/blob/main/permissions/sync.py" rel="noopener noreferrer"&gt;Full file.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we just have to add a guard to make sure we, and when I say we I mean some agent of course, don't accidentally ignore this whole setup. I use &lt;a href="https://github.com/evilmartians/lefthook" rel="noopener noreferrer"&gt;lefthook&lt;/a&gt; for all my git hook guarding needs:&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="na"&gt;pre-commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sync-permissions&lt;/span&gt;&lt;span class="pi"&gt;:&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;python3 permissions/sync.py --check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sync.py --check&lt;/code&gt; regenerates everything in memory and diffs it against what's on disk. If anything is stale, it exits nonzero and the commit is blocked.&lt;/p&gt;

&lt;p&gt;Check out my &lt;a href="https://github.com/nielsmadan/agentic-coding" rel="noopener noreferrer"&gt;agentic coding config repo&lt;/a&gt; for more agentic coding config goodness.&lt;/p&gt;

&lt;p&gt;Originally posted on nlsmdn.com. If this was useful, that's where I put new posts first.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>devtools</category>
      <category>python</category>
    </item>
  </channel>
</rss>
