<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Western Wilson</title><link>https://westernwilson.com/tags/devtools/</link><description>Recent content in Devtools on Western Wilson</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sun, 21 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://westernwilson.com/tags/devtools/index.xml" rel="self" type="application/rss+xml"/><item><title>Containerising my coding agent</title><link>https://westernwilson.com/reflections/containerising-my-coding-agent/</link><pubDate>Sun, 21 Jun 2026 00:00:00 +0000</pubDate><guid>https://westernwilson.com/reflections/containerising-my-coding-agent/</guid><description>I containerised Claude Code this week because I wanted to let it run with fewer guardrails and not hand it unlimited access the rest of my machine. There’s a Hacker News thread from 6 months ago about someone running Claude in yolo mode who asked it to clean something up and watched it rm -rf their home directory 😅. So let’s aim to at least avoid that.&#10;Thankfully, I found Dev Containers, which turned into its own rabbit hole but lets us lock down Claude without having to maintain our own docker image.&#10;</description><content:encoded><![CDATA[<p>I containerised Claude Code this week because I wanted to let it run with fewer guardrails and not hand it unlimited access the rest of my machine. There&rsquo;s a <a href="https://news.ycombinator.com/item?id=46268222">Hacker News thread</a> from 6 months ago about someone running Claude in yolo mode who asked it to clean something up and watched it <code>rm -rf</code> their home directory 😅. So let&rsquo;s aim to at least avoid that.</p>
<p>Thankfully, I found <a href="https://containers.dev">Dev Containers</a>, which turned into its own rabbit hole but lets us lock down Claude without having to maintain our own docker image.</p>
<p>This reflection post talks through the containerised setup and some issues I faced while setting it up. If you just want the solution, then the implementation is in the <a href="https://github.com/wjkw1/westernwilson.com/tree/main/.devcontainer">.devcontainer/</a> directory of this website&rsquo;s source code.</p>
<h2 id="feature-or-docker-image" class="group relative scroll-mt-20">
  Feature or docker image?
  <a
    href="https://westernwilson.com/reflections/containerising-my-coding-agent/#feature-or-docker-image"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>I assumed that Anthropic would ship an official Claude base image you build on top of. They don&rsquo;t, they only post a recommended starting point. Claude Code installs into any <a href="https://code.claude.com/docs/en/devcontainer">Dev Container</a> through the <code>ghcr.io/anthropics/devcontainer-features/claude-code:1.0</code> feature, which you reference in <code>devcontainer.json</code> like <a href="https://containers.dev/features">any other devcontainers feature</a>. It works wherever the Dev Containers spec is supported e.g. VS Code, Codespaces, JetBrains, and (with some rough edges) Cursor. In VS Code it can be configured to pull in the Claude Code extension, keeping Claude decoupled from the base image entirely.</p>
<p>That leaves three layers to assemble: a base image, the tooling you use, and Claude Code on top. The real decision is whether you bake those middle two layers into a custom docker image you push to a registry, or assemble them per-repo through <code>devcontainer.json</code> features. A custom image is the better call for a team, where you want a validated, secure, and reusable environment everyone builds on. For a solo setup spun up per-repo, features are easier. The cost is a slow first build and less control over exactly what lands in the image, which is what <code>post-create.sh</code> helps automate somewhat.</p>
<p>My <code>devcontainer.json</code> ended up looking like this, trimmed to the main parts:</p>
<div class="highlight"><pre tabindex="0"><code class="language-jsonc" data-lang="jsonc"><span><span>{
</span></span><span><span>  <span>&#34;image&#34;</span>: <span>&#34;mcr.microsoft.com/devcontainers/base:ubuntu&#34;</span>,
</span></span><span><span>  <span>&#34;features&#34;</span>: {
</span></span><span><span>    <span>&#34;ghcr.io/anthropics/devcontainer-features/claude-code:1.0&#34;</span>: {}
</span></span><span><span>  },
</span></span><span><span>  <span>&#34;remoteUser&#34;</span>: <span>&#34;vscode&#34;</span>,
</span></span><span><span>  <span>&#34;mounts&#34;</span>: [
</span></span><span><span>    <span>&#34;source=claude-code-config-${devcontainerId},target=/home/vscode/.claude,type=volume&#34;</span>
</span></span><span><span>  ],
</span></span><span><span>  <span>&#34;runArgs&#34;</span>: [<span>&#34;--cap-add=NET_ADMIN&#34;</span>, <span>&#34;--cap-add=NET_RAW&#34;</span>],
</span></span><span><span>  <span>&#34;postCreateCommand&#34;</span>: <span>&#34;sudo zsh .devcontainer/post-create.sh&#34;</span>,
</span></span><span><span>  <span>&#34;postStartCommand&#34;</span>: <span>&#34;sudo /usr/local/sbin/init-firewall.sh&#34;</span>,
</span></span><span><span>  <span>&#34;waitFor&#34;</span>: <span>&#34;postStartCommand&#34;</span>
</span></span><span><span>}
</span></span></code></pre></div><p>The non-root <code>vscode</code> user is important if you ever run with <code>--dangerously-skip-permissions</code>, Claude Code requires it. The named volume on <code>~/.claude</code> means login persists across rebuilds instead of re-authenticating every time. The <code>NET_ADMIN</code>/<code>NET_RAW</code> capabilities exist so that we can create the containerised firewall. <code>waitFor: postStartCommand</code> blocks the terminal until that firewall script finishes, so there&rsquo;s no window where Claude is already running before egress is locked down.</p>
<p><code>post-create.sh</code> is the everyday stuff a feature-based setup doesn&rsquo;t hand you for free: setting git aliases, adding a couple of zsh aliases for <code>kubectl</code> and <code>terraform</code>, locking down <code>sudo</code>/<code>su</code>.</p>
<p><code>init-firewall.sh</code> sets default-DROP policies on iptables, then builds an <code>ipset</code> allowlist of everything the container is allowed to reach outbound. Part of that allowlist is GitHub&rsquo;s own published IP ranges, fetched live at container start:</p>
<div class="highlight"><pre tabindex="0"><code class="language-zsh" data-lang="zsh"><span><span>gh_ranges<span>=</span><span>$(</span>curl -s https://api.github.com/meta<span>)</span>
</span></span><span><span><span>while</span> read -r cidr; <span>do</span>
</span></span><span><span>    ipset add allowed-domains <span>&#34;</span>$cidr<span>&#34;</span>
</span></span><span><span><span>done</span> &lt; &lt;<span>(</span>echo <span>&#34;</span>$gh_ranges<span>&#34;</span> | jq -r <span>&#39;(.web + .api + .git)[]&#39;</span> | aggregate -q<span>)</span>
</span></span></code></pre></div><p>Everything else —npm, <code>api.anthropic.com</code>, the VS Code marketplace— gets resolved and added individually. The firewall script finishes by checking its own work: it tries <code>curl https://example.com</code> and expects that to fail, then tries <code>curl https://api.github.com/zen</code> and expects that to succeed.</p>
<h2 id="firewall-and-sudo" class="group relative scroll-mt-20">
  Firewall and sudo
  <a
    href="https://westernwilson.com/reflections/containerising-my-coding-agent/#firewall-and-sudo"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>I spent a decent chunk of time configuring who&rsquo;s allowed to touch <code>iptables</code>. The <code>common-utils</code> feature grants the <code>vscode</code> user blanket passwordless <code>sudo</code> by default, and my first cut of <code>post-create.sh</code> narrowed that down to the <code>iptables</code>/<code>iptables-save</code>/<code>ipset</code> binaries themselves, with no argument restriction. Which meant Claude or anything else running as <code>vscode</code>, including under <code>--dangerously-skip-permissions</code>, could just run <code>sudo iptables -F &amp;&amp; sudo iptables -P OUTPUT ACCEPT</code> and the firewall would be gone! Anthropic&rsquo;s own reference devcontainer avoids this by granting <code>sudo</code> on one fixed script path instead of the raw binaries, baked into their image at build time so the script itself can&rsquo;t be edited either. I didn&rsquo;t want a custom Dockerfile yet, but dev containers can do the same thing with just <code>postCreateCommand</code>: copy <code>init-firewall.sh</code> to a root-owned <code>/usr/local/sbin/init-firewall.sh</code> outside the bind-mounted workspace while the feature&rsquo;s broad sudo grant is still active, then narrow <code>sudo</code> down to NOPASSWD on that one frozen executable file only. The tradeoff is that editing the live <code>init-firewall.sh</code> to allow a new domain now needs a full <strong>Rebuild Container</strong>, not just a restart, since the frozen copy is what actually runs.</p>
<p>Claude has it&rsquo;s own way to limit it&rsquo;s own execution too using <code>.claude/settings.json</code>. We&rsquo;ve configured this on top to deny any <code>Bash(sudo *)</code> and <code>Bash(su *)</code> commands, so in normal mode Claude won&rsquo;t even attempt the command. But <code>--dangerously-skip-permissions</code> does exactly what its name says: it bypasses every permission rule, deny rules included. So that file is a speed bump for the default mode, not something to lean on once the safety&rsquo;s off. The frozen-script sudoers restriction is the one that locks it down regardless, because it&rsquo;s enforced by the OS, not by Claude Code.</p>
<h2 id="gap-in-the-firewall" class="group relative scroll-mt-20">
  Gap in the firewall
  <a
    href="https://westernwilson.com/reflections/containerising-my-coding-agent/#gap-in-the-firewall"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>One thing I noticed was that <code>api.github.com/meta</code>&rsquo;s ip ranges aren&rsquo;t scoped to the API. They cover GitHub&rsquo;s whole edge network, which includes GitHub Pages in some cases. For example, this website, <a href="https://westernwilson.com">westernwilson.com</a>, runs on GitHub Pages. So with the firewall up and supposedly restricting the agent to an approved set of domains, I could still reach my own site. And by the same logic, any other <code>*.github.io</code> page, or anything hosted there by someone with worse intentions. A page on <code>*.github.io</code> could potentially hold a payload hosted somewhere else entirely, and the firewall would wave it through without ever knowing the difference.</p>
<p>I see three ways to close that off:</p>
<ul>
<li>Proxy the traffic and filter on hostname instead of IP. Cleanest in theory, but Encrypted Client Hello can still hide the hostname from anything sitting.</li>
<li>Drop <code>*.github.io</code> from the allowlist entirely. Unfortunately this also takes <code>raw.githubusercontent.com</code> out too since they share the same ip ranges, but if that&rsquo;s not something the container needs, it&rsquo;s a fine trade.</li>
<li>Accept the risk and move on.</li>
</ul>
<p>I haven&rsquo;t picked one yet, for now I&rsquo;m sitting with the risk while I think about which tradeoff is actually worth it.</p>
<h2 id="logging-in-to-claude" class="group relative scroll-mt-20">
  Logging in to Claude
  <a
    href="https://westernwilson.com/reflections/containerising-my-coding-agent/#logging-in-to-claude"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>Logging in had its own gotcha. In VS Code there is a big orange &ldquo;use your subscription&rdquo; button that opens a browser and waits for a callback to <code>localhost</code>, which never makes it back into the container and just hangs. The fix is to skip the button, open a terminal inside the container and run <code>claude</code>, which kicks off the same browser flow but resolves the callback correctly.</p>
<p>We then use <code>~/.claude</code>, which is now owned by the <code>vscode</code> thanks to the <code>chown</code> at the top of <code>post-create.sh</code>, so once logged in, we gain stickier login sessions.</p>
<p>In doing the whole login dance from a bare terminal meant that my first real session with Claude Code inside the dev container was just text based interaction with no IDE extension wrapper around it at all. It&rsquo;s an interesting way to work and removes a lot of the UI distraction, but not something I&rsquo;ll rely on fulltime.</p>
<h2 id="port-forwarding" class="group relative scroll-mt-20">
  Port forwarding
  <a
    href="https://westernwilson.com/reflections/containerising-my-coding-agent/#port-forwarding"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>The other thing that briefly worried me is VS Code forwarded around thirty-two ports the moment the container came up. My first thought was that something in the container was reaching out on its own. It&rsquo;s not, after some searching I found that it&rsquo;s VS Code&rsquo;s automatic port-forwarding scanning the container for anything listening on a port, for example a dev server that you&rsquo;ll want to open in a browser. It&rsquo;s a separate, fairly trigger-happy feature, and apparently a known source of noise; <a href="https://github.com/microsoft/vscode-remote-release/issues/10926">This GitHub issue</a> describes the exact same &ldquo;over 20 ports auto-forwarded&rdquo; behaviour after a setting silently flips from <code>process</code> to <code>hybrid</code> detection mode.</p>
<p>If you want to see the actual config rather than the excerpts above, it&rsquo;s all in this repo under <a href="https://github.com/wjkw1/westernwilson.com/tree/main/.devcontainer">.devcontainer/</a>. Treat it as a personal setup to extend, not a hardened image to copy verbatim.</p>
<p>Further reading: Anthropic&rsquo;s <a href="https://code.claude.com/docs/en/devcontainer">Dev Containers docs</a> and the <a href="https://code.visualstudio.com/docs/devcontainers/tutorial">VS Code Dev Containers tutorial</a>.</p>
]]></content:encoded></item><item><title>Tools &amp; Tech</title><link>https://westernwilson.com/techstack/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate><guid>https://westernwilson.com/techstack/</guid><description>A snapshot of the tools, languages, and tech I’m currently using or exploring. For what I’m up to right now, see my /now page.&#10;Hardware &amp; software of choice Macbook Pro 16 AnyType for personal notes Claude Flow for pomodoro timing Arc browser Kindle ereader (11th Gen Paperwhite) Technical Stack Cloud &amp; Infrastructure: AWS (EKS, IAM, RDS), Terraform, Docker, Kubernetes, Helm, PostgreSQL, OpenStack, GCP (wanting to learn) CI/CD: GitHub Actions, TeamCity, Octopus Deploy Languages: Python (FastAPI, Flask), Bash, Go (learning), some C# &amp; Java Observability: New Relic, Sumo Logic, Prometheus, Grafana, Kibana / ELK Practices: REST API Design • Infrastructure as Code • Observability • Security Controls • Agile/Scrum Also worked with:&#10;</description><content:encoded><![CDATA[<p>A snapshot of the tools, languages, and tech I&rsquo;m currently using or exploring. For what I&rsquo;m up to right now, see my <a href="https://westernwilson.com/now/">/now</a> page.</p>
<h2 id="hardware--software-of-choice" class="group relative scroll-mt-20">
  Hardware &amp; software of choice
  <a
    href="https://westernwilson.com/techstack/#hardware--software-of-choice"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<ul>
<li>Macbook Pro 16</li>
<li><a href="https://anytype.io">AnyType</a> for personal notes</li>
<li><a href="https://claude.ai">Claude</a></li>
<li><a href="https://www.flow.app">Flow</a> for pomodoro timing</li>
<li><a href="https://arc.net">Arc</a> browser</li>
<li>Kindle ereader (11th Gen Paperwhite)</li>
</ul>
<h2 id="technical-stack" class="group relative scroll-mt-20">
  Technical Stack
  <a
    href="https://westernwilson.com/techstack/#technical-stack"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<ul>
<li><strong>Cloud &amp; Infrastructure:</strong> <a href="https://aws.amazon.com">AWS</a> (<a href="https://aws.amazon.com/eks/">EKS</a>, <a href="https://aws.amazon.com/iam/">IAM</a>, <a href="https://aws.amazon.com/rds/">RDS</a>), <a href="https://developer.hashicorp.com/terraform">Terraform</a>, <a href="https://www.docker.com">Docker</a>, <a href="https://kubernetes.io">Kubernetes</a>, <a href="https://helm.sh">Helm</a>, <a href="https://www.postgresql.org">PostgreSQL</a>, <a href="https://www.openstack.org">OpenStack</a>, <a href="https://cloud.google.com">GCP</a> (wanting to learn)</li>
<li><strong>CI/CD</strong>: <a href="https://github.com/features/actions">GitHub Actions</a>, <a href="https://www.jetbrains.com/teamcity/">TeamCity</a>, <a href="https://octopus.com">Octopus Deploy</a></li>
<li><strong>Languages</strong>: <a href="https://www.python.org">Python</a> (<a href="https://fastapi.tiangolo.com">FastAPI</a>, <a href="https://flask.palletsprojects.com">Flask</a>), <a href="https://www.gnu.org/software/bash/">Bash</a>, <a href="https://go.dev">Go</a> (learning), some C# &amp; Java</li>
<li><strong>Observability:</strong> <a href="https://newrelic.com">New Relic</a>, <a href="https://www.sumologic.com/">Sumo Logic</a>, <a href="https://prometheus.io">Prometheus</a>, <a href="https://grafana.com">Grafana</a>, <a href="https://www.elastic.co/kibana">Kibana / ELK</a></li>
<li><strong>Practices</strong>: REST API Design • Infrastructure as Code • Observability • Security Controls • Agile/Scrum</li>
</ul>
<p><strong>Also worked with:</strong></p>
<ul>
<li><strong>Frontend:</strong> <a href="https://vuejs.org">Vue.js</a> (<a href="https://nuxt.com">Nuxt</a>), <a href="https://react.dev">React</a> (<a href="https://nextjs.org">Next.js</a>), <a href="https://tailwindcss.com">Tailwind CSS</a>, <a href="https://gohugo.io">Hugo</a></li>
<li><strong>ML basics</strong>: <a href="https://developers.google.com/machine-learning/crash-course">Google&rsquo;s MLCC</a> with hands-on experience using <a href="https://pandas.pydata.org">Pandas</a>, <a href="https://numpy.org">NumPy</a>, <a href="https://www.tensorflow.org">TensorFlow</a></li>
</ul>
]]></content:encoded></item><item><title>Linting and formatting code</title><link>https://westernwilson.com/reflections/linting-and-formatting/</link><pubDate>Thu, 02 Apr 2026 00:00:00 +0000</pubDate><guid>https://westernwilson.com/reflections/linting-and-formatting/</guid><description>TL;DR: Code style and formatting should not steal your brainpower. Set up linting/formatting at three touchpoints (your editor, your pre-commit hook, and your CI pipeline) and formatting becomes automatic from the moment you write code to the moment it merges. At the end, this reflection walks through exactly how to do that for a Python FastAPI project.&#10;Linting and formatting code Recently, while working on a personal DevOps project, I spent some time looking into linting tools. They amazingly save your brainpower for the actual problem, instead of code styling. Whether it’s two spaces or four per indent, single quotes or double, I don’t mind. Other people have thought deeply about these trade-offs, landed on reasonable answers, and codified them. I’m happy to piggyback on that.&#10;</description><content:encoded><![CDATA[<p><strong>TL;DR:</strong> Code style and formatting should <strong>not</strong> steal your brainpower. Set up linting/formatting at three touchpoints (your editor, your pre-commit hook, and your CI pipeline) and formatting becomes automatic from the moment you write code to the moment it merges. At the end, this reflection walks through exactly how to do that for a Python <a href="https://fastapi.tiangolo.com">FastAPI</a> project.</p>
<h2 id="linting-and-formatting-code" class="group relative scroll-mt-20">
  Linting and formatting code
  <a
    href="https://westernwilson.com/reflections/linting-and-formatting/#linting-and-formatting-code"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>Recently, while working on a <a href="https://github.com/wjkw1/devops-profile-coffee-card-app-demo">personal DevOps project</a>, I spent some time looking into linting tools. They amazingly save your brainpower for the actual problem, instead of code styling. Whether it&rsquo;s two spaces or four per indent, single quotes or double, I don&rsquo;t mind. Other people have thought deeply about these trade-offs, landed on reasonable answers, and codified them. I&rsquo;m happy to piggyback on that.</p>
<p>I aim to write code that works, and let the styling happen automatically.</p>
<p>There&rsquo;s a side benefit too: when a codebase consistently uses a popular linting tool, anyone familiar with it can scan the code and immediately know where to look. It&rsquo;s like how headings in a research paper let you scan before you commit to reading.</p>
<h2 id="three-touchpoints-that-matter" class="group relative scroll-mt-20">
  Three touchpoints that matter
  <a
    href="https://westernwilson.com/reflections/linting-and-formatting/#three-touchpoints-that-matter"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>Good linting hygiene should show up at meaningful points in your development lifecycle:</p>
<ol>
<li><strong>In your IDE (or text editor), as you write</strong></li>
<li><strong>Locally, before you commit</strong></li>
<li><strong>In CI/CD, before code merges</strong></li>
</ol>
<p>Each layer serves a different purpose. Together, they make code quality essentially automatic. (Tools like <a href="https://www.sonarsource.com/products/sonarqube/">SonarQube</a> go deeper —security, coverage, and code smells at a project level— but that&rsquo;s a reflection for another time.)</p>
<figure class="img-light"><img src="https://westernwilson.com/reflections/linting-and-formatting/linting_touchpoints_light.svg"
			alt="Three linting touchpoints">
</figure>

<figure class="img-dark"><img src="https://westernwilson.com/reflections/linting-and-formatting/linting_touchpoints_dark.svg"
			alt="Three linting touchpoints">
</figure>

<p><strong>In the IDE (or text editor)</strong>, I use <a href="https://code.visualstudio.com">VS Code</a> with the relevant extensions installed and Format on Save enabled. It does what it sounds like: when the file saves, the formatter runs. Sometimes this needs dev dependencies installed to work properly; check the official docs and you&rsquo;ll figure it out quickly, I&rsquo;ll go into <a href="https://docs.astral.sh/ruff/">Ruff</a> for Python specifically later in this post.</p>
<p><strong>Locally, before committing</strong>, is where things get interesting. At various companies I&rsquo;ve seen the same pattern repeat: developer forgets to format, pushes code, CI fails, they fix locally, force-push, CI reruns. This adds ten minutes to the dev cycle, minimum. Multiply that across a team and it adds up quickly.</p>
<p>Git&rsquo;s <a href="https://pre-commit.com">pre-commit</a> hooks solve this cleanly. When you attempt a commit, a hook runs your formatting and linting checks first. If they fail, the commit doesn&rsquo;t go through. This means no broken CI and no annoying formatting fix commits. This enables the code to validate itself before it ever leaves your machine.</p>
<p>The one catch: developers can skip the setup step entirely, which is exactly why the third layer exists.</p>
<p><strong>In CI/CD</strong>, the pipeline enforces what the pre-commit hook encourages. Even if someone bypasses local hooks, nothing merges unless the linting passes. This is your actual quality gate and is what you rely on.</p>
<h2 id="why-ruff-instead-of-black-and-flake8" class="group relative scroll-mt-20">
  Why Ruff instead of Black and Flake8
  <a
    href="https://westernwilson.com/reflections/linting-and-formatting/#why-ruff-instead-of-black-and-flake8"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>Three or more years ago, I used <a href="https://black.readthedocs.io/en/stable/">Black</a> for formatting and <a href="https://flake8.pycqa.org/en/latest/">Flake8</a> for static analysis. Two tools, two configs, two things to install and maintain. It was a minor friction, but that friction compounded and was a little naff.</p>
<p>Ruff replaces both with a single tool. It&rsquo;s faster, simpler, and opinionated enough that you don&rsquo;t have to make many decisions. For a project where the point is <em>less thinking about styling and formatting</em>, that&rsquo;s exactly what I wanted.</p>
<h2 id="the-setup-python--fastapi" class="group relative scroll-mt-20">
  The Setup (Python / FastAPI)
  <a
    href="https://westernwilson.com/reflections/linting-and-formatting/#the-setup-python--fastapi"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>Three things to configure, in roughly this order:</p>
<h3 id="step-1-vs-code--ruff-extension" class="group relative scroll-mt-20">
  Step 1: VS Code + Ruff extension
  <a
    href="https://westernwilson.com/reflections/linting-and-formatting/#step-1-vs-code--ruff-extension"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h3>
<p>Install the <a href="https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff">Ruff extension</a> from the VS Code marketplace — the official one is published by Astral (charliermarsh.ruff). Once installed, add this to your .vscode/settings.json:</p>
<div class="highlight"><pre tabindex="0"><code class="language-json" data-lang="json"><span><span>{
</span></span><span><span>  <span>&#34;[python]&#34;</span>: {
</span></span><span><span>    <span>&#34;editor.formatOnSave&#34;</span>: <span>true</span>,
</span></span><span><span>    <span>&#34;editor.defaultFormatter&#34;</span>: <span>&#34;charliermarsh.ruff&#34;</span>,
</span></span><span><span>    <span>&#34;editor.codeActionsOnSave&#34;</span>: {
</span></span><span><span>      <span>&#34;source.fixAll.ruff&#34;</span>: <span>&#34;explicit&#34;</span>,
</span></span><span><span>      <span>&#34;source.organizeImports.ruff&#34;</span>: <span>&#34;explicit&#34;</span>
</span></span><span><span>    }
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code></pre></div><p>Committing <code>.vscode/settings.json</code> to the repo is intentional. Anyone who clones the project and opens it in VS Code gets the same formatting behaviour automatically. No extra knowledge required.</p>
<p>No separate Ruff installation is needed as the VS Code extension ships its own binary, and the pre-commit hook (added in Step 2) bundles its own too. You just need to add your Ruff config to <code>ruff.toml</code>:</p>
<div class="highlight"><pre tabindex="0"><code class="language-toml" data-lang="toml"><span><span>[<span>tool</span>.<span>ruff</span>]
</span></span><span><span><span>line-length</span> = <span>88</span>
</span></span><span><span><span>target-version</span> = <span>&#34;py312&#34;</span>
</span></span><span><span>
</span></span><span><span>[<span>tool</span>.<span>ruff</span>.<span>lint</span>]
</span></span><span><span><span>select</span> = [<span>&#34;E&#34;</span>, <span>&#34;F&#34;</span>, <span>&#34;I&#34;</span>]
</span></span></code></pre></div><p>The select block opts into pycodestyle errors (E), Pyflakes (F), and isort import sorting (I). This covers what you&rsquo;d previously have needed Black, Flake8, and <a href="https://pycqa.github.io/isort/">isort</a> to do separately.</p>
<h3 id="step-2-pre-commit-hooks" class="group relative scroll-mt-20">
  Step 2: Pre-commit hooks
  <a
    href="https://westernwilson.com/reflections/linting-and-formatting/#step-2-pre-commit-hooks"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h3>
<p>First, install the pre-commit package:</p>
<div class="highlight"><pre tabindex="0"><code class="language-bash" data-lang="bash"><span><span>pip install pre-commit
</span></span></code></pre></div><p>Then create a <code>.pre-commit-config.yaml</code> in the root of your repo. Here&rsquo;s exactly what&rsquo;s in the project:</p>
<div class="highlight"><pre tabindex="0"><code class="language-yaml" data-lang="yaml"><span><span><span>repos</span>:
</span></span><span><span>  - <span>repo</span>: <span>https://github.com/astral-sh/ruff-pre-commit</span>
</span></span><span><span>    <span>rev</span>: <span>v0.11.4</span>
</span></span><span><span>    <span>hooks</span>:
</span></span><span><span>      - <span>id</span>: <span>ruff</span>
</span></span><span><span>        <span>args</span>: [--<span>fix]</span>
</span></span><span><span>        <span>files</span>: <span>^api/</span>
</span></span><span><span>      - <span>id</span>: <span>ruff-format</span>
</span></span><span><span>        <span>files</span>: <span>^api/</span>
</span></span></code></pre></div><p>A few things worth noting here. The files: <code>^api/</code> pattern scopes the hooks to the API directory only — the frontend has its own linting story (<a href="https://eslint.org">ESLint</a> + <a href="https://prettier.io">Prettier</a>), so there&rsquo;s no point running Ruff across the whole repo. The ruff hook handles lint checks and the <code>--fix</code> flag means it&rsquo;ll auto-fix what it can rather than just reporting issues. The ruff-format hook handles formatting, equivalent to what Black would have done.
Once the file is in place, install the hooks:</p>
<div class="highlight"><pre tabindex="0"><code class="language-bash" data-lang="bash"><span><span>pre-commit install
</span></span></code></pre></div><p>This wires the hooks into your local <code>.git/hooks/pre-commit</code>. From this point, every time you run git commit, Ruff runs first. If it finds issues it can&rsquo;t auto-fix, the commit is blocked and the problems are printed to the terminal. Fix them, stage the changes, and commit again.
You can also run it manually across all files at any point:</p>
<div class="highlight"><pre tabindex="0"><code class="language-bash" data-lang="bash"><span><span>pre-commit run --all-files
</span></span></code></pre></div><p>Useful for a first run after setting up, or after pulling in changes from someone else who hasn&rsquo;t had the hooks installed.</p>
<p>That&rsquo;s it. Once it&rsquo;s done, your automatic project linting works in the background. Which is perfect.</p>
]]></content:encoded></item><item><title>AnyType setup with PARA</title><link>https://westernwilson.com/reflections/2025-07-12-anytype-with-para/</link><pubDate>Sat, 12 Jul 2025 00:00:00 +0000</pubDate><guid>https://westernwilson.com/reflections/2025-07-12-anytype-with-para/</guid><description>Find out how I use Tiago Forte’s PARA methodology to organise my notes inside AnyType. If it’s your first time hearing about PARA and AnyType, then lucky you!&#10;Setting the scene First a bit of background, I’m a copious note-taker. I’ve been this way ever since I was a wee boy. Writing notes helped me focus on the speaker better, even when my notes tended to be more of a gibberish scribble and mostly illegible for future Western. With physical note taking however I would consistently forget my notebooks and have to write on spare paper. Then I’d lose the spare paper and the state of my notes was a mixed bag. So, I shifted to digital notes to try solve this problem. What have I tried? Google Docs and Drive, OneNote, BookStack, Wiki.js, Confluence, Notion, Evernote, Obsidian, Google Keep, and others.&#10;</description><content:encoded><![CDATA[<p>Find out how I use Tiago Forte&rsquo;s PARA methodology to organise my notes inside AnyType.
If it&rsquo;s your first time hearing about PARA and AnyType, then lucky you!</p>
<h2 id="setting-the-scene" class="group relative scroll-mt-20">
  Setting the scene
  <a
    href="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/#setting-the-scene"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>First a bit of background, I&rsquo;m a copious note-taker. I&rsquo;ve been this way ever since I was a wee boy. Writing notes helped me focus on the speaker better, even when my notes tended to be more of a gibberish scribble and mostly illegible for future Western.
With physical note taking however I would consistently forget my notebooks and have to write on spare paper. Then I&rsquo;d lose the spare paper and the state of my notes was a mixed bag.
So, I shifted to digital notes to try solve this problem. What have I tried? <a href="https://drive.google.com">Google Docs and Drive</a>, <a href="https://www.microsoft.com/en-us/microsoft-365/onenote">OneNote</a>, <a href="https://www.bookstackapp.com">BookStack</a>, <a href="https://js.wiki">Wiki.js</a>, <a href="https://www.atlassian.com/software/confluence">Confluence</a>, <a href="https://www.notion.so">Notion</a>, <a href="https://evernote.com">Evernote</a>, <a href="https://obsidian.md">Obsidian</a>, <a href="https://keep.google.com">Google Keep</a>, and others.</p>
<p>Unfortunately, no tool was perfect; they all missed one aspect of my requirements, or felt clunky, or had features locked behind paywalls, or couldn&rsquo;t be used while travelling without Internet access (Notion, I&rsquo;m looking at you!).
My requirements:</p>
<ol>
<li>Offline notes with online sync</li>
<li>Free with no paywall</li>
<li>Cross-platform – Windows, Mac &amp; Android</li>
<li>The Notion-like block WYSIWYG text editing</li>
<li>Configurable to use PARA to organise notes (recent ~5yr old requirement)</li>
<li>Markdown export so I&rsquo;m not locked into a tool</li>
<li>Great security</li>
<li>(optional) Graph view to show linkage between notes, so you can see grouped ideas</li>
<li>(optional) Import function or API to script the import</li>
<li>(optional) Collaboration with other note takers</li>
</ol>
<h2 id="introducing-anytype" class="group relative scroll-mt-20">
  Introducing AnyType
  <a
    href="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/#introducing-anytype"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>AnyType hits every requirement above and took them further; open-source, offline first, free forever, online peer to peer sync between cross-platform devices, block editing with Markdown text shortcuts, AnyType Gallery to share and use community templates, great security first, imports and exports, graph view, API and basic collaboration features.
The app is positioning itself as a web3 everything app built on freedom and trust. They&rsquo;re building something special here and since it&rsquo;s inception has consistently improved month on month. It&rsquo;s much more polished than it used to be, it&rsquo;s a joy to work with nowadays.
I&rsquo;ll explain some of the features below, but honestly the best way to get a feel for it is to download and try it yourself: <a href="https://download.anytype.io/">https://download.anytype.io/</a>
The main concepts in AnyType to understand are:</p>
<ol>
<li><a href="https://doc.anytype.io/anytype-docs/getting-started/object-editor">‍Objects</a> - all things are objects in AnyType ‍</li>
<li><a href="https://doc.anytype.io/anytype-docs/getting-started/types">Types</a> - categorise the Objects you create to have a Type ‍</li>
<li><a href="https://doc.anytype.io/anytype-docs/getting-started/types/relations">Properties</a> - attach metadata to Types and create relations between them ‍</li>
<li><a href="https://doc.anytype.io/anytype-docs/getting-started/sets">Query</a> - List view showing a single Object/Type</li>
<li><a href="https://doc.anytype.io/anytype-docs/getting-started/sets/collections">‍Collections</a> - Folder-like collection of different Objects/Types</li>
<li><a href="https://doc.anytype.io/anytype-docs/getting-started/types/templates">‍Templates</a> - speed up your workflow with pre-defined templates for object creation</li>
</ol>
<p>For example, to create a Book tracking system, you could define:‍</p>
<ol>
<li><strong>Book</strong> type with the properties: Title, GoodreadsUrl, and links to an <strong>Author</strong></li>
<li><strong>Author</strong> type with the properties: name &amp; bio‍</li>
<li><strong>Quote</strong> type with a property that links to our <strong>Book</strong></li>
</ol>
<p>Now, when you create a new Book, you can link it to a new or existing Author and write whatever notes about the book that you want. Then when inspired by a Quote, you can create a Quote and link it to your Book. The note taking is flexible, linkage can happen anywhere organically, and you can write whatever you want from within the Author, Book or Quote types. AnyType allows and in fact encourages these relationships between Types.</p>
<figure><img src="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/anytype-schema.png"
			alt="Conceptual example of how Book types could relate to each other in AnyType"><figcaption>
			<p>Conceptual example of how Book types could relate to each other in AnyType</p>
		</figcaption>
</figure>

<figure><img src="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/anytype-graph.png"
			alt="AnyType Graph View of example Book objects"><figcaption>
			<p>AnyType Graph View of example Book objects</p>
		</figcaption>
</figure>

<h2 id="quickly-explaining-para" class="group relative scroll-mt-20">
  Quickly explaining PARA
  <a
    href="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/#quickly-explaining-para"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>If you’re new to <a href="https://fortelabs.com/blog/para/">Tiago Forte’s PARA methodology</a>, you’re in for a treat. PARA is a simple yet powerful system to organise your digital life—whether that’s notes, projects, or files—into four clear categories. It stands for:</p>
<ul>
<li>‍<strong>P - Projects:</strong> Active tasks or outcomes you’re currently working on. ‍</li>
<li><strong>A - Areas:</strong> Ongoing responsibilities or spheres of activity without a specific end date. ‍</li>
<li><strong>R - Resources:</strong> Useful reference materials or topics of interest.</li>
<li>‍<strong>A - Archives:</strong> Inactive items you want to keep but don’t need right now.
As you use this system over time, you’ll develop an intuitive sense for where each note belongs. The key mindset shift for me was letting go of the need to choose the “perfect” category and remembering that it’s easy to move things around later.</li>
</ul>
<h2 id="how-i-use-para-within-anytype" class="group relative scroll-mt-20">
  How I use PARA within AnyType
  <a
    href="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/#how-i-use-para-within-anytype"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>Using PARA inside AnyType means you can create custom Types and Properties for each category, linking notes, tasks, and references easily. You can quickly switch between your active Projects, Areas and Resources, while your Archives keep everything tidy but accessible.
I created a Template Space that should be available to copy from the AnyGallery thanks to my approved <a href="https://github.com/anyproto/gallery/issues/171">github submission</a>. I&rsquo;ll explain how it works below.</p>
<h3 id="key-features" class="group relative scroll-mt-20">
  Key features‍
  <a
    href="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/#key-features"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h3>
<ul>
<li>PARA Dashboard is a Query of type Queries with a custom property <strong>PARA Type</strong> set to PARA</li>
<li>Projects, Areas and Resources are a Query of Collection objects with the <strong>PARA Type</strong> set to Project, Area, or Resource respectively.</li>
<li>A collection with an <strong>Is Archive</strong> property checked will show as Archived on the various dashboards.</li>
</ul>
<h3 id="1-para-type-classification" class="group relative scroll-mt-20">
  1. &lsquo;PARA Type&rsquo; Classification
  <a
    href="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/#1-para-type-classification"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h3>
<ul>
<li>Introduces a dedicated &ldquo;PARA Type&rdquo; property that allows you to specify whether a Collection is a Project, Area, Resource, or **PARA.**‍</li>
<li>When a Query&rsquo;s PARA Type is set to PARA it will show on our PARA Dashboard.</li>
<li>When a Collection&rsquo;s PARA Type is set to Project, Area or Resource, they will display on their respective Query of Collections dashboard i.e. Projects, Areas, or Resources</li>
<li>This structure makes it easy to filter, group, and view your content according to the PARA methodology.</li>
</ul>
<h3 id="2-is-archive-property" class="group relative scroll-mt-20">
  2. &lsquo;Is Archive&rsquo; Property
  <a
    href="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/#2-is-archive-property"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h3>
<ul>
<li>Every <strong>Collection</strong> includes an <strong>Is Archived</strong> property.</li>
<li>Easily mark items as archived to declutter your active workspace without permanently deleting anything.</li>
<li>Archived items are automatically filtered out from main views but remain accessible for future reference.</li>
<li>See all archived Projects, Areas and Resources both on the Archived query view, and also within their respective Projects, Areas, or Resources query views.</li>
</ul>
<h3 id="3-manual-link-management-via-templates" class="group relative scroll-mt-20">
  3. Manual Link Management via Templates
  <a
    href="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/#3-manual-link-management-via-templates"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h3>
<ul>
<li>Every Collection also has a &ldquo;<strong>PARA Linked to</strong>&rdquo; property, this overcomes a limitation I noticed where Queries were not showing on the Graph view for Collections created from their Query view.</li>
<li>Templates automatically create this <strong>manual link</strong> property for each of our Projects, Areas , or Resources</li>
<li>This linkage can be removed at any time, giving you control over what appears in the Graph view.</li>
<li>Declutter your visual workspace by removing unnecessary connections, while still maintaining essential relationships.</li>
</ul>
<h3 id="how-it-all-works-together" class="group relative scroll-mt-20">
  How it all works together
  <a
    href="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/#how-it-all-works-together"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h3>
<ul>
<li>‍<strong>Creating New Projects, Areas or Resources:</strong> Templates are used by default to add new Projects, Areas, or Resources. Each template ensures the correct PARA Type and properties are set when creating an object from each dashboard.</li>
<li>‍<strong>Change PARA Type:</strong> Updating the PARA Type property on a given collection will move it between Projects, Areas or Resources as needed. This is especially helpful if, for example, a Resource evolves into a Project later on.</li>
<li><strong>Creating New Objects:</strong> Within any specific Project, Area, or Resource, you can add any object—the default is a Page—and it will be created with the appropriate properties. You can link any type, such as Tasks, Bookmarks, Books, Quotes, literally any Type.</li>
<li>‍<strong>Manually Link to PARA:</strong> Use AnyType&rsquo;s &ldquo;Link to Collection&rdquo; feature to link any object newly created to a specific Project, Area or Resource yourself. This is particularly useful in Graph view if you like to see clusters of related information.</li>
<li>‍<strong>Archive Projects, Areas or Resources:</strong> Archiving is handled with a simple checkbox field on Collections that have relevant PARA Types.</li>
</ul>
<figure><img src="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/anytype-graph-2.png">
</figure>

<h2 id="shared-gallery" class="group relative scroll-mt-20">
  Shared Gallery
  <a
    href="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/#shared-gallery"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>There’s a wide variety of community-created templates available for AnyType, giving you plenty of options to explore and enhance your workflow.</p>
<p>It&rsquo;s easy to import and play with them too, simply go to the gallery and import it into a new space. While you can import templates into existing spaces, be mindful that this may introduce extra Types and Properties, potentially cluttering your setup and making cleanup tedious. So my advice is to first verify the new Types and Properties in a new, isolated space before merging them it into your main spaces.</p>
<h2 id="conclusion--next-steps" class="group relative scroll-mt-20">
  Conclusion &amp; Next steps
  <a
    href="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/#conclusion--next-steps"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>If you’ve made it this far, congratulations! Hopefully, you’ve picked up something new and feel inspired to try out AnyType or the PARA system for your own daily organisation.</p>
<h3 id="bonus-note-on-anytypes-api--mcp-server" class="group relative scroll-mt-20">
  Bonus note on AnyType&rsquo;s API &amp; MCP server
  <a
    href="https://westernwilson.com/reflections/2025-07-12-anytype-with-para/#bonus-note-on-anytypes-api--mcp-server"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h3>
<p>Recently AnyType added an official API and Model Context Protocol (MCP) server. This advancement allows AI agents and assistants to interact with and update your documentation directly. In the <a href="https://developers.anytype.io/docs/examples/intermediate/mcp/">docs here</a> they highlight <a href="https://raycast.com/?via=western-AnyType">RayCast.ai</a> for seamless AI Chatbot integration and workflows.</p>
<p>With these tools, RayCast turns your AnyType knowledge base into an even more powerful resource—ready to answer your questions and automate parts of your note-taking system.</p>
]]></content:encoded></item><item><title>Migration to Webflow &amp; Hugo</title><link>https://westernwilson.com/reflections/2025-07-6-migrating-to-webflow/</link><pubDate>Sun, 06 Jul 2025 00:00:00 +0000</pubDate><guid>https://westernwilson.com/reflections/2025-07-6-migrating-to-webflow/</guid><description>Update 2026: Turns out Webflow and I didn’t get along for long. My workflow is drafting everything in AnyType, but copy-pasting from there into Webflow was a mess. I’d spend more time cleaning up the output than actually writing. The custom domain redirect situation was also irking me. So, true to form, I redesigned and migrated again, this time to Hugo. Make of that what you will.&#10;My website has gone through many iterations and frameworks:&#10;</description><content:encoded><![CDATA[<p><em>Update 2026: Turns out Webflow and I didn&rsquo;t get along for long. My workflow is drafting everything in AnyType, but copy-pasting from there into Webflow was a mess. I&rsquo;d spend more time cleaning up the output than actually writing. The custom domain redirect situation was also irking me. So, true to form, I redesigned and migrated again, this time to <a href="https://gohugo.io">Hugo</a>. Make of that what you will.</em></p>
<p>My website has gone through many iterations and frameworks:</p>
<ol>
<li>Static site using html, css &amp; JavaScript</li>
<li>Gatsby.js React framework</li>
<li>Quick attempt at using Next.js React</li>
<li>Nuxt.js Vue.js framework</li>
<li>Now Webflow</li>
</ol>
<p>My goal was to get something that worked easily, let me update a blog quickly, and didn&rsquo;t require too much maintenance. Finally, I&rsquo;ve achieved that vague and ambitious goal with Webflow.</p>
<h2 id="bit-of-historical-context" class="group relative scroll-mt-20">
  Bit of historical context
  <a
    href="https://westernwilson.com/reflections/2025-07-6-migrating-to-webflow/#bit-of-historical-context"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<h3 id="basic-static-site" class="group relative scroll-mt-20">
  Basic static site
  <a
    href="https://westernwilson.com/reflections/2025-07-6-migrating-to-webflow/#basic-static-site"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h3>
<p>While at University, near 2015 I was first learning software development as a main focus. I felt like I needed an online personal profile, but I didn&rsquo;t really know how to do it. So across 4 years I managed to cobble something together, though it wasn&rsquo;t pretty and I haven&rsquo;t got any screenshots to show it off.
My first profile website used basic html and css. Honestly, not enjoyable&hellip; the css process took so much time and tweaking that eventually it caused longer and longer procrastination breaks (gaming). I finally understood the devs struggling with css memes.
Skip ahead to my first graduate job, I learnt there are other ways to develop and I trusted that there had to be an easier way than crying over css (joking). So, with the internet at my disposal, I searched for a modern framework to build and deploy my site.</p>
<h3 id="gatsbyjs" class="group relative scroll-mt-20">
  GatsbyJS
  <a
    href="https://westernwilson.com/reflections/2025-07-6-migrating-to-webflow/#gatsbyjs"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h3>
<p>The many rabbit-holes I ventured figuring out what the &ldquo;best&rdquo; framework was, watching YouTube, doing tutorials of each framework and creating starter programs. Eventually I realised I was thinking about doing, but not actually getting anywhere, so I just leapt into one of them.
Introducing GatsbyJS, which at the time was a new React framework that helped build static sites quickly from templates, it even had blog plugins to make the whole process easier, so this was exactly what I was looking for&hellip; or so I thought.
My basic knowledge of html, css, and javascript helped, but I had to learn React, JSX syntax, Webpack, and how to deploy my site. They had docs, but it wasn&rsquo;t super polished so there was a lot of time spent figuring it out on my own. On a grad salary I was looking for the cheapest (free) options for hosting; there were two options: GitHub Pages and Netlify.
First, I tried GitHub Pages and succeeded in deploying it, but the workflow was tacky, it required me to run a command from my local machine to deploy it, which didn&rsquo;t feel right.
I learnt about Git Flow and wanted it to deploy when I pushed up my code up to master (I know that&rsquo;s not entirely correct, and am now aware that PRs and merges offer a nicer dev workflow flow, but at the time pushing to master was the way).
So I moved to Netlify, which watched your repository for changes on a specific branch, and deployed whatever was there for you. This worked great for me, and allowed me to iterate with development on Gatsby quickly. However&hellip; as it was early on, the framework and plugins were changing often. Updating the npm packages caused the website to break so much that I gave up making changes to the website altogether. I resigned to allowing my website to exist in it&rsquo;s current state. Until I got inspired by a front-end engineer at another company I worked at.
For added context, my career was taking me down the DevOps Engineer pathway, but I still enjoyed playing around with front-end development. So I&rsquo;d chat to other engineers about what they were doing and what they enjoyed using. This inspired my next framework choice that I thought would surely be easier.</p>
<h3 id="nuxtjs" class="group relative scroll-mt-20">
  NuxtJS
  <a
    href="https://westernwilson.com/reflections/2025-07-6-migrating-to-webflow/#nuxtjs"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h3>
<p>Inspired by my teammate, and with an expert to back me should I fall into any of the traps. I started building a Nuxt website. I gave up on designing a website and though, &ldquo;lets just get something working&rdquo;.
Following through the docs I cobbled together a very basic website that used their content module to generate pages from markdown. This allowed me to at least write blogs using markdown text and the pages would generate on deployment.
The deployment was done through GitHub Pages and I eventually got it deploying using GitHub Actions, which I was using more at work. This was great and Nuxt v2 was working well.
Then the Nuxt v3 update happened, and all my shit broke. So I spend another weekend tweaking and figuring out why the content renderers wouldn&rsquo;t load my blog pages from Markdown. Turns out the interface changed for how we loaded content, so the old method didn&rsquo;t work.
With that fixed, I happily added a blog about using ChatGPT or Perplexity to support my writing style. Woohoo!</p>
<h3 id="time-skip-to-now" class="group relative scroll-mt-20">
  Time skip to now
  <a
    href="https://westernwilson.com/reflections/2025-07-6-migrating-to-webflow/#time-skip-to-now"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h3>
<p>I&rsquo;m taking a break from work, travelling asia (see /blog/now-june-2025), I decided to attempt to write blogs to capture my learnings while creating music.
Looking ahead to writing 10 or more blogs, I realised how much of a hassle my Nuxt stack would be for updating and posting blogs. Changes required markdown updates pushed to a git branch and merged into main, I could immediately see this adding friction that would stop me from doing the actual blog writing. Also because of how I was loading blog images, it made the whole /public/img/ directory structure ugly, and in the future I would have a huge dump of images that were difficult to update.
Honestly, I might not even write so many blogs that this becomes a problem, but this future problem was enough to distract me from making music. I didn&rsquo;t want to create a big mess that I had to clean up later so I looked for another solution.
I looked into WordPress quickly, which a couple older colleagues of mine used for their personal blogs and another colleague who built the company websites using WordPress. It would&rsquo;ve worked, and been cheap, but I didn&rsquo;t want to touch it. It felt old, and I knew there were better tools out there.</p>
<h2 id="so-why-webflow" class="group relative scroll-mt-20">
  So, why Webflow?
  <a
    href="https://westernwilson.com/reflections/2025-07-6-migrating-to-webflow/#so-why-webflow"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>I was on LinkedIn one day and saw a post from Nikolai teaching people how to use Webflow. Nikolai actually introduced me to webflow way back in 2017 and it&rsquo;d been swirling in my brain ever since, it seemed like a great tool. It&rsquo;s fantastic for Web Designer to build websites without worrying about the nitty gritty of maintaining a website with code. It still requires fundamental understandings of website development and html &amp; css, but it&rsquo;s mostly no-code.
After the whole Nuxt Vue major update I thought I cbf with this, lets try Webflow out.
I don&rsquo;t want to pay for Webflow yet, thats why my site is only available on <a href="https://westernwilson.webflow.io">https://westernwilson.webflow.io</a>, in the future I intend to upgrade it so I can use my personal website url under <a href="https://westernwilson.com">https://westernwilson.com</a></p>
<h2 id="what-is-the-development-process-like" class="group relative scroll-mt-20">
  What is the development process like?
  <a
    href="https://westernwilson.com/reflections/2025-07-6-migrating-to-webflow/#what-is-the-development-process-like"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>I kind of cheated a bit, I used their AI tool to design and kick-start my new and improved personal profile website, then I tweaked the required bits and here we have it, a new website! The tweaking of Webflow was much easier for me than the frameworks. Everything you need to understand is in their docs, and to publish is very easy. No updating of packages under the hood, and it all just works.
To be honest, the Webflow structure I currently have could probably be improved, but I&rsquo;ll leave it as is for now since it works.
The major learning was configuring the Content Management System (CMS) that comes in-built with Webflow to store my blogs. Couple searches on the web and Perplexity helped me figure this out over a day or so.
So now, I have a blog website that gets updated from a CMS. I&rsquo;m stoked! Adding links or audio files is a lot easier than before too.
You still need some understanding of how web development html tags are structured and how they pair with css styling, but for me this solution is perfect.</p>
<h2 id="final-words" class="group relative scroll-mt-20">
  Final words
  <a
    href="https://westernwilson.com/reflections/2025-07-6-migrating-to-webflow/#final-words"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>I&rsquo;m happy with Webflow, because now I can focus on playing around making music and writing blogs instead of constantly upgrading and tweaking my website.</p>
]]></content:encoded></item><item><title>Supercharge Your SSH with VS Code</title><link>https://westernwilson.com/reflections/2023-10-29-ssh-using-vscode/</link><pubDate>Sun, 29 Oct 2023 00:00:00 +0000</pubDate><guid>https://westernwilson.com/reflections/2023-10-29-ssh-using-vscode/</guid><description>Have you ever wished you could use the full feature set of Visual Studio Code (VS Code) while connecting to remote servers via SSH? If you’re a developer or sysadmin, you’ll appreciate the productivity boost this can bring. In this blog post, we’ll walk you through the process of setting up VS Code as your primary SSH tool for both work and home use. Let’s dive in!&#10;Overview VS Code is renowned for its versatility and has extensions for all types of development activities. This guide will show you how to harness its power as an SSH client, giving you a seamless development experience on remote servers.&#10;</description><content:encoded><![CDATA[<p><em>Have you ever wished you could use the full feature set of Visual Studio Code (VS Code) while connecting to remote servers via SSH? If you&rsquo;re a developer or sysadmin, you&rsquo;ll appreciate the productivity boost this can bring. In this blog post, we&rsquo;ll walk you through the process of setting up VS Code as your primary SSH tool for both work and home use. Let&rsquo;s dive in!</em></p>
<h2 id="overview" class="group relative scroll-mt-20">
  Overview
  <a
    href="https://westernwilson.com/reflections/2023-10-29-ssh-using-vscode/#overview"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>VS Code is renowned for its versatility and has extensions for all types of development activities. This guide will show you how to harness its power as an SSH client, giving you a seamless development experience on remote servers.</p>
<h2 id="what-is-vs-code" class="group relative scroll-mt-20">
  What is VS Code?
  <a
    href="https://westernwilson.com/reflections/2023-10-29-ssh-using-vscode/#what-is-vs-code"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>Before we get started, let&rsquo;s quickly clarify what VS Code is. <a href="https://code.visualstudio.com/">Visual Studio Code</a>, often referred to as VS Code, is a free, open-source code editor developed by Microsoft. It&rsquo;s widely used in the software development industry and is known for its robust features and extensibility.</p>
<h2 id="what-is-ssh" class="group relative scroll-mt-20">
  What is SSH?
  <a
    href="https://westernwilson.com/reflections/2023-10-29-ssh-using-vscode/#what-is-ssh"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p><a href="https://en.wikipedia.org/wiki/Secure_Shell">SSH</a> stands for Secure Shell, and it&rsquo;s a network protocol used for secure data communication, remote command-line access, and other network services. It&rsquo;s a fundamental tool for connecting to remote servers securely.</p>
<h2 id="official-vs-code-tutorial" class="group relative scroll-mt-20">
  Official VS Code Tutorial
  <a
    href="https://westernwilson.com/reflections/2023-10-29-ssh-using-vscode/#official-vs-code-tutorial"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h2>
<p>For a detailed, step-by-step guide on setting up VS Code as your primary SSH tool, please visit the official <a href="https://code.visualstudio.com/docs/remote/ssh">VS Code tutorial</a>.</p>
<h1 id="in-a-nutshell" class="group relative scroll-mt-20">
  In a Nutshell
  <a
    href="https://westernwilson.com/reflections/2023-10-29-ssh-using-vscode/#in-a-nutshell"
    class="heading-anchor ml-2 text-sm no-underline! opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 [@media(hover:none)]:opacity-100 [color:var(--c-muted)] hover:[color:var(--c-accent)] transition-opacity"
    aria-label="Link to this heading"
  ><i class="fa-solid fa-link" aria-hidden="true"></i></a>
</h1>
<p>In a nutshell, here&rsquo;s a brief overview of what you&rsquo;ll find in the official tutorial:</p>
<ul>
<li>Installing the required VS Code packages on the SSH box.</li>
<li>Installing the Remote Developer extension on your local machine&rsquo;s VS Code.</li>
<li>Setting up your SSH config file for VS Code for easy reconnection.</li>
<li>(<em>Optional</em>) Creating an SSH key for effortless logins.</li>
</ul>
<p>By enabling VS Code&rsquo;s ssh capabilities you can centralise the tool you use for development. Happy coding!</p>
]]></content:encoded></item></channel></rss>