<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://freshbrewed.science/feed.xml" rel="self" type="application/atom+xml" /><link href="https://freshbrewed.science/" rel="alternate" type="text/html" /><updated>2026-06-27T00:26:13+00:00</updated><id>https://freshbrewed.science/feed.xml</id><title type="html">Fresh/Brewed</title><subtitle>Tales from Cloudy McCloudface.
</subtitle><entry><title type="html">Hugo and AWS part 2</title><link href="https://freshbrewed.science/2026/06/26/hugoaws2.html" rel="alternate" type="text/html" title="Hugo and AWS part 2" /><published>2026-06-26T01:00:01+00:00</published><updated>2026-06-26T01:00:01+00:00</updated><id>https://freshbrewed.science/2026/06/26/hugoaws2</id><content type="html" xml:base="https://freshbrewed.science/2026/06/26/hugoaws2.html"><![CDATA[<p>A week ago <a href="/2026/06/19/hugoaws.html">I posted “Hugo and AWS (S3+CloudFront)”</a> that covered DNS setup, Route 53, bucket storage and Hugo setup.  We had a basic “Hello World” file started.</p>

<p>However, at the time, I called out we really needed to address content, pipelines and more to make it functional.</p>

<p>Today we’ll tackle all that and more…</p>

<h1 id="fixing-the-access-denied-on--paths">Fixing the “Access Denied” on “/” paths</h1>

<p>I want to create the function and use the newer 2.0 js framework.  I give it a name and description</p>

<p><a href="/content/images/2026/06/hugoaws-05.png"><img src="/content/images/2026/06/hugoaws-05.png" alt="/content/images/2026/06/hugoaws-05.png" /></a></p>

<p>I’ll create the JS function to rewrite “/” and “.” as “./index.html”:</p>
<pre><code class="language-javscript">async function handler(event) {
    var request = event.request;
    var uri = request.uri;

    // Check whether the URI is missing a file name (ends in a slash)
    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    } 
    // Check whether the URI is missing a file extension
    else if (!uri.includes('.')) {
        request.uri += '/index.html';
    }

    return request;
}
</code></pre>

<p>The function looks good, so I save it then publish under the publish tab</p>

<p><a href="/content/images/2026/06/hugoaws-04.png"><img src="/content/images/2026/06/hugoaws-04.png" alt="/content/images/2026/06/hugoaws-04.png" /></a></p>

<p>Now that it’s published, we can associate this function to a CloudFront distribution</p>

<p><a href="/content/images/2026/06/hugoaws-03.png"><img src="/content/images/2026/06/hugoaws-03.png" alt="/content/images/2026/06/hugoaws-03.png" /></a></p>

<p>With the distribution selected, I just need to select the cache behavior and associate</p>

<p><a href="/content/images/2026/06/hugoaws-02.png"><img src="/content/images/2026/06/hugoaws-02.png" alt="/content/images/2026/06/hugoaws-02.png" /></a></p>

<p>Now the paths resolve without “index.html”:</p>

<p><a href="/content/images/2026/06/hugoaws-01.png"><img src="/content/images/2026/06/hugoaws-01.png" alt="/content/images/2026/06/hugoaws-01.png" /></a></p>

<h2 id="images-in-hugo">Images in Hugo</h2>

<p>My next goal was to tackle how to do image refs in Hugo.  I did some searching and found we could just use “/static/images”</p>

<p><a href="/content/images/2026/06/hugoaws-06.png"><img src="/content/images/2026/06/hugoaws-06.png" alt="/content/images/2026/06/hugoaws-06.png" /></a></p>

<p>Running <code class="language-plaintext highlighter-rouge">hugo server</code> then</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(base) builder@LuiGi:~/Workspaces/fbtech$ hugo server
Watching for changes in /home/builder/Workspaces/fbtech/archetypes, /home/builder/Workspaces/fbtech/content/posts, /home/builder/Workspaces/fbtech/static/images
Watching for config changes in /home/builder/Workspaces/fbtech/config/_default, /home/builder/Workspaces/fbtech/go.mod
Start building sites …
hugo v0.163.3-4d22555aebf458d5d150500c9ac4bee5b24cf0d3+extended linux/amd64 BuildDate=2026-06-18T16:18:24Z VendorInfo=snap:0.163.3


                  │ EN
──────────────────┼────
 Pages            │ 13
 Paginator pages  │  0
 Non-page files   │  0
 Static files     │  8
 Processed images │  0
 Aliases          │  2
 Cleaned          │  0

Built in 18 ms
Environment: "development"
Serving pages from disk
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at //localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop
</code></pre></div></div>

<p>let me confirm it was working</p>

<p><a href="/content/images/2026/06/hugoaws-07.png"><img src="/content/images/2026/06/hugoaws-07.png" alt="/content/images/2026/06/hugoaws-07.png" /></a></p>

<h2 id="adding-cicd">Adding CICD</h2>

<p>Next, I updated the repo to have Repo actions</p>

<p><a href="/content/images/2026/06/hugoaws-08.png"><img src="/content/images/2026/06/hugoaws-08.png" alt="/content/images/2026/06/hugoaws-08.png" /></a></p>

<p>I added AWS Secrets and worked out a proper CICD flow.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>name: CICD
run-name: $ triggered CICD
on: [push]

jobs:
  Explore-Gitea-Actions:
    runs-on: my_custom_label
    container: node:22
    steps:
      - uses: actions/checkout@v3 # Checks out your repository
      - name: Install Hugo
        run: |
          whoami
          which hugo || true
          apt update
          cat /etc/os-release
          apt install -y ca-certificates curl gnupg 
          wget https://github.com/gohugoio/hugo/releases/download/v0.163.3/hugo_0.163.3_linux-amd64.deb
          dpkg -i hugo_0.163.3_linux-amd64.deb
          apt update
          apt install -y golang-go

      - name: Build Hugo
        run: |
          hugo --help
          hugo version
          hugo build

      - name: Install AWS CLI
        run: |
          DEBIAN_FRONTEND=noninteractive apt update -y \
            &amp;&amp; umask 0002 \
            &amp;&amp; DEBIAN_FRONTEND=noninteractive apt install -y awscli

      - name: Sync to AWS
        run: |
          cd ./public
          aws s3 cp --recursive ./ s3://freshbrewed.tech/
        if: gitea.ref == 'refs/heads/main'   
        env: # Or as an environment variable
          AWS_SECRET_ACCESS_KEY: $
          AWS_ACCESS_KEY_ID: $

      - name: Sync to AWS
        run: |
          paws cloudfront create-invalidation --distribution-id EWYXMJ6L92Q6I --paths "/index.html" 
        if: gitea.ref == 'refs/heads/main'
        env: # Or as an environment variable
          AWS_SECRET_ACCESS_KEY: $
          AWS_ACCESS_KEY_ID: $
</code></pre></div></div>

<p>I realized i might need a more programmatic invalidation block and so I used Cline remotely back to my home lab.  It wasn’t fast but it worked</p>

<p><a href="/content/images/2026/06/hugoaws-09.png"><img src="/content/images/2026/06/hugoaws-09.png" alt="/content/images/2026/06/hugoaws-09.png" /></a></p>

<p>a test showed it was functioning</p>

<p><a href="/content/images/2026/06/hugoaws-10.png"><img src="/content/images/2026/06/hugoaws-10.png" alt="/content/images/2026/06/hugoaws-10.png" /></a></p>

<h2 id="staging-site-for-prs">Staging site for PRs</h2>

<p>I want a “candidate” site i can use to validate posts before going live.</p>

<p>I’ll create a bucket</p>

<p><a href="/content/images/2026/06/hugoaws-11.png"><img src="/content/images/2026/06/hugoaws-11.png" alt="/content/images/2026/06/hugoaws-11.png" /></a></p>

<p>Once created</p>

<p><a href="/content/images/2026/06/hugoaws-12.png"><img src="/content/images/2026/06/hugoaws-12.png" alt="/content/images/2026/06/hugoaws-12.png" /></a></p>

<p>I’ll update the pipeline for non-mainline builds</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">CICD</span>
<span class="na">run-name</span><span class="pi">:</span> <span class="s">$ triggered CICD</span>
<span class="na">on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">push</span><span class="pi">]</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">Explore-Gitea-Actions</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">my_custom_label</span>
    <span class="na">container</span><span class="pi">:</span> <span class="s">node:22</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span> <span class="c1"># Checks out your repository</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install Hugo</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">whoami</span>
          <span class="s">which hugo || true</span>
          <span class="s">apt update</span>
          <span class="s">cat /etc/os-release</span>
          <span class="s">apt install -y ca-certificates curl gnupg </span>
          <span class="s">wget https://github.com/gohugoio/hugo/releases/download/v0.163.3/hugo_0.163.3_linux-amd64.deb</span>
          <span class="s">dpkg -i hugo_0.163.3_linux-amd64.deb</span>
          <span class="s">apt update</span>
          <span class="s">apt install -y golang-go</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build Hugo</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">hugo --help</span>
          <span class="s">hugo version</span>
          <span class="s">hugo build</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install AWS CLI</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">DEBIAN_FRONTEND=noninteractive apt update -y \</span>
            <span class="s">&amp;&amp; umask 0002 \</span>
            <span class="s">&amp;&amp; DEBIAN_FRONTEND=noninteractive apt install -y awscli</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Sync to AWS</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">cd ./public</span>
          <span class="s">aws s3 cp --recursive ./ s3://fb-tech-test/</span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">gitea.ref != 'refs/heads/main'</span>   
        <span class="na">env</span><span class="pi">:</span> <span class="c1"># Or as an environment variable</span>
          <span class="na">AWS_SECRET_ACCESS_KEY</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">AWS_ACCESS_KEY_ID</span><span class="pi">:</span> <span class="s">$</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Sync to AWS</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">cd ./public</span>
          <span class="s">aws s3 cp --recursive ./ s3://freshbrewed.tech/</span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">gitea.ref == 'refs/heads/main'</span>   
        <span class="na">env</span><span class="pi">:</span> <span class="c1"># Or as an environment variable</span>
          <span class="na">AWS_SECRET_ACCESS_KEY</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">AWS_ACCESS_KEY_ID</span><span class="pi">:</span> <span class="s">$</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Sync to AWS</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">paths=$(find ./public -type f -name "*.html" -print | sed 's|^./public/|\/|g' | sed 's/\n/ /g')</span>
          <span class="s">aws cloudfront create-invalidation --distribution-id EWYXMJ6L92Q6I --paths "$paths" </span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">gitea.ref == 'refs/heads/main'</span>
        <span class="na">env</span><span class="pi">:</span> <span class="c1"># Or as an environment variable</span>
          <span class="na">AWS_SECRET_ACCESS_KEY</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">AWS_ACCESS_KEY_ID</span><span class="pi">:</span> <span class="s">$</span>
</code></pre></div></div>

<p>I tested on a new <code class="language-plaintext highlighter-rouge">quicktest</code> branch</p>

<p><a href="/content/images/2026/06/hugoaws-13.png"><img src="/content/images/2026/06/hugoaws-13.png" alt="/content/images/2026/06/hugoaws-13.png" /></a></p>

<p>and saw it pushed properly</p>

<p><a href="/content/images/2026/06/hugoaws-14.png"><img src="/content/images/2026/06/hugoaws-14.png" alt="/content/images/2026/06/hugoaws-14.png" /></a></p>

<p>I enabled static site hosting on the bucket then checked the URL</p>

<p><a href="/content/images/2026/06/hugoaws-15.png"><img src="/content/images/2026/06/hugoaws-15.png" alt="/content/images/2026/06/hugoaws-15.png" /></a></p>

<p>But then it gave an error in testing</p>

<p><a href="/content/images/2026/06/hugoaws-16.png"><img src="/content/images/2026/06/hugoaws-16.png" alt="/content/images/2026/06/hugoaws-16.png" /></a></p>

<p>This means I cannot access the files from a web browser.  We need to figure this out.</p>

<p><a href="/content/images/2026/06/hugoaws-17.png"><img src="/content/images/2026/06/hugoaws-17.png" alt="/content/images/2026/06/hugoaws-17.png" /></a></p>

<p>The solution lied in creating a bucket policy.  Here you can see it is empty</p>

<p><a href="/content/images/2026/06/hugoaws-18.png"><img src="/content/images/2026/06/hugoaws-18.png" alt="/content/images/2026/06/hugoaws-18.png" /></a></p>

<p>But when I use</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"Version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-17"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Statement"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"Sid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PublicReadGetObject"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Principal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="s2">"s3:GetObject"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:s3:::fb-tech-test/*"</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>as you see below</p>

<p><a href="/content/images/2026/06/hugoaws-19.png"><img src="/content/images/2026/06/hugoaws-19.png" alt="/content/images/2026/06/hugoaws-19.png" /></a></p>

<p>and save it</p>

<p><a href="/content/images/2026/06/hugoaws-20.png"><img src="/content/images/2026/06/hugoaws-20.png" alt="/content/images/2026/06/hugoaws-20.png" /></a></p>

<p>We can now view the contents</p>

<p><a href="/content/images/2026/06/hugoaws-21.png"><img src="/content/images/2026/06/hugoaws-21.png" alt="/content/images/2026/06/hugoaws-21.png" /></a></p>

<h2 id="testing-the-full-flow">Testing the full flow</h2>

<p>How might this look in a regular blogging scenario.</p>

<p>Here I have the repo on main:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ git remote show origin
* remote origin
  Fetch URL: https://forgejo.freshbrewed.science/builderadmin/fbtech.git
  Push  URL: https://forgejo.freshbrewed.science/builderadmin/fbtech.git
  HEAD branch: main
  Remote branches:
    main      tracked
    quicktest tracked
  Local branch configured for 'git pull':
    main merges with remote main
  Local ref configured for 'git push':
    main pushes to main (up to date)
builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ ls
archetypes  assets  config  content  data  go.mod  go.sum  i18n  index.html  layouts  static  themes
</code></pre></div></div>

<p>Let’s say I want to create a new blog entry.</p>

<p>I would start by creating an Issue on the topic (I might in reality have a brain dump of a few ideas to pontificate upon)</p>

<p><a href="/content/images/2026/06/hugoaws-25.png"><img src="/content/images/2026/06/hugoaws-25.png" alt="/content/images/2026/06/hugoaws-25.png" /></a></p>

<p>With the issue created (<a href="https://forgejo.freshbrewed.science/builderadmin/fbtech/issues/1">issue 1</a>)</p>

<p><a href="/content/images/2026/06/hugoaws-26.png"><img src="/content/images/2026/06/hugoaws-26.png" alt="/content/images/2026/06/hugoaws-26.png" /></a></p>

<p>I create a branch to work out this idea</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git checkout -b 1-closed-harnesses
Switched to a new branch '1-closed-harnesses'
</code></pre></div></div>

<p><em>Note: I always find it easiest to just copy a former for the header (until I get a muscle memory on the syntax)</em></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cp content/posts/hello-world.md content/posts/1-closing-harnesses.md
$ vi content/posts/1-closing-harnesses.md
</code></pre></div></div>

<p>I worked the post for a bit and wrapped with a spellcheck and push</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ git add content/posts/
builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ git commit -m "fix spell"
[1-closed-harnesses 6cc39f0] fix spell
 1 file changed, 6 insertions(+), 6 deletions(-)
builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ git push
fatal: The current branch 1-closed-harnesses has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin 1-closed-harnesses

builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ git push --set-upstream origin 1-closed-harnesses
Enumerating objects: 51, done.
Counting objects: 100% (51/51), done.
Delta compression using up to 16 threads
Compressing objects: 100% (29/29), done.
Writing objects: 100% (37/37), 52.35 KiB | 7.48 MiB/s, done.
Total 37 (delta 9), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a new pull request for '1-closed-harnesses':
remote:   https://forgejo.freshbrewed.science/builderadmin/fbtech/compare/main...1-closed-harnesses
remote:
remote: . Processing 1 references
remote: Processed 1 references in total
To https://forgejo.freshbrewed.science/builderadmin/fbtech.git
 * [new branch]      1-closed-harnesses -&gt; 1-closed-harnesses
Branch '1-closed-harnesses' set up to track remote branch '1-closed-harnesses' from 'origin'.
</code></pre></div></div>

<p>I can now do a <a href="https://forgejo.freshbrewed.science/builderadmin/fbtech/pulls/2">new PR on Forgejo</a></p>

<p><a href="/content/images/2026/06/hugoaws-27.png"><img src="/content/images/2026/06/hugoaws-27.png" alt="/content/images/2026/06/hugoaws-27.png" /></a></p>

<p>which also adds a new theme by way of submodule (could be a risk)</p>

<p><a href="/content/images/2026/06/hugoaws-28.png"><img src="/content/images/2026/06/hugoaws-28.png" alt="/content/images/2026/06/hugoaws-28.png" /></a></p>

<p>I see the PR is created, but didn’t fire a build <em>for the pr</em></p>

<p><em>(coming back later: actually it did, you can see the green check next to “fix spell” commit)</em></p>

<p><a href="/content/images/2026/06/hugoaws-29.png"><img src="/content/images/2026/06/hugoaws-29.png" alt="/content/images/2026/06/hugoaws-29.png" /></a></p>

<p>but the push did do it for the branch</p>

<p><a href="/content/images/2026/06/hugoaws-30.png"><img src="/content/images/2026/06/hugoaws-30.png" alt="/content/images/2026/06/hugoaws-30.png" /></a></p>

<p>which is good enough because we see the build results per commit in the Forgejo PR history</p>

<p><a href="/content/images/2026/06/hugoaws-31.png"><img src="/content/images/2026/06/hugoaws-31.png" alt="/content/images/2026/06/hugoaws-31.png" /></a></p>

<p>Staging looks good</p>

<p><a href="/content/images/2026/06/hugoaws-32.png"><img src="/content/images/2026/06/hugoaws-32.png" alt="/content/images/2026/06/hugoaws-32.png" /></a></p>

<p>So lets merge the PR and see if it fixes the issue (closes it) and publishes to the main website:</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/hugoaws-33.mp4" type="video/mp4" />
</video>

<p>the final error we see in the recording above is due to invalidation paths.</p>

<p>I added some debug to the last step</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Sync to AWS</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">paths=$(find ./public -type f -name "*.html" -print | sed 's|^./public/|\/|g' | sed 's/\n/ /g')</span>
          <span class="s">echo "paths: $paths"</span>
          <span class="s">echo "----"</span>
          <span class="s">set -x</span>
          <span class="s">aws cloudfront create-invalidation --distribution-id EWYXMJ6L92Q6I --paths "$paths" </span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">gitea.ref == 'refs/heads/main'</span>
        <span class="na">env</span><span class="pi">:</span> <span class="c1"># Or as an environment variable</span>
          <span class="na">AWS_SECRET_ACCESS_KEY</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">AWS_ACCESS_KEY_ID</span><span class="pi">:</span> <span class="s">$</span>
</code></pre></div></div>

<p>I’m still figuring out the issue on the paths (pasting the same command locally works so there must be a hidden character).</p>

<p>I now see the live site (running the create invalidation by hand)</p>

<p><a href="/content/images/2026/06/hugoaws-34.png"><img src="/content/images/2026/06/hugoaws-34.png" alt="/content/images/2026/06/hugoaws-34.png" /></a></p>

<p><strong>fast forward, after watching a great US vs Turkey FIFA match, i was inspired…</strong></p>

<p>It would seem the double quotes were getting interpolated.  I switched to moving the command to an inline script</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Sync to AWS</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">paths=$(find ./public -type f -name "*.html" | sed -e 's|^./public/|/|' -e 's/.*/"&amp;"/' | tr '\n' ' ')</span>
          <span class="s">cat &lt;&lt; EOF &gt; myaction.sh</span>
          <span class="s">aws cloudfront create-invalidation --distribution-id EWYXMJ6L92Q6I --paths $paths</span>
          <span class="s">EOF</span>
          <span class="s">cat myaction.sh | base64 -w 0</span>
          <span class="s">chmod 755 ./myaction.sh</span>
          <span class="s">./myaction.sh</span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">gitea.ref == 'refs/heads/main'</span>
        <span class="na">env</span><span class="pi">:</span> <span class="c1"># Or as an environment variable</span>
          <span class="na">AWS_SECRET_ACCESS_KEY</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">AWS_ACCESS_KEY_ID</span><span class="pi">:</span> <span class="s">$</span>
</code></pre></div></div>

<p>and now it works</p>

<p><a href="/content/images/2026/06/hugoaws-36.png"><img src="/content/images/2026/06/hugoaws-36.png" alt="/content/images/2026/06/hugoaws-36.png" /></a></p>

<h1 id="summary">Summary</h1>

<p>Today we sorted out bucket access policies to take care of public read issues, JS for auto-completing “/” URLs (so we didn’t need to add index.html to all URLs), and images and themes.  We tackled CICD with Forgejo/Gitea and how to use an S3 bucket as a basic ‘staging’ endpoint.</p>

<p>I had to sort out a minor issue with paths and <code class="language-plaintext highlighter-rouge">aws cloudfront create-invalidation</code> but I would say we are now sorted.  The <a href="https://forgejo.freshbrewed.science/builderadmin/fbtech">repo is public</a> so you are welcome to use the code yourself.  Here is the final CICD workflow YAML</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">CICD</span>
<span class="na">run-name</span><span class="pi">:</span> <span class="s">$ triggered CICD</span>
<span class="na">on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">push</span><span class="pi">]</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">Explore-Gitea-Actions</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">my_custom_label</span>
    <span class="na">container</span><span class="pi">:</span> <span class="s">node:22</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span> <span class="c1"># Checks out your repository</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install Hugo</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">whoami</span>
          <span class="s">which hugo || true</span>
          <span class="s">apt update</span>
          <span class="s">cat /etc/os-release</span>
          <span class="s">apt install -y ca-certificates curl gnupg unzip sudo groff</span>
          <span class="s">wget https://github.com/gohugoio/hugo/releases/download/v0.163.3/hugo_0.163.3_linux-amd64.deb</span>
          <span class="s">dpkg -i hugo_0.163.3_linux-amd64.deb</span>
          <span class="s">apt update</span>
          <span class="s">apt install -y golang-go          </span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build Hugo</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">hugo --help</span>
          <span class="s">hugo version</span>
          <span class="s">hugo build          </span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install AWS CLI</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s"># DEBIAN_FRONTEND=noninteractive apt update -y \</span>
          <span class="s"># &amp;&amp; umask 0002 \</span>
          <span class="s"># &amp;&amp; DEBIAN_FRONTEND=noninteractive apt install -y awscli</span>

          <span class="s">curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"</span>
          <span class="s">unzip awscliv2.zip</span>
          <span class="s">./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli --update          </span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Sync to AWS</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">cd ./public</span>
          <span class="s">aws s3 cp --recursive ./ s3://fb-tech-test/          </span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">gitea.ref != 'refs/heads/main'</span>   
        <span class="na">env</span><span class="pi">:</span> <span class="c1"># Or as an environment variable</span>
          <span class="na">AWS_SECRET_ACCESS_KEY</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">AWS_ACCESS_KEY_ID</span><span class="pi">:</span> <span class="s">$</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Sync to AWS</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">cd ./public</span>
          <span class="s">aws s3 cp --recursive ./ s3://freshbrewed.tech/          </span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">gitea.ref == 'refs/heads/main'</span>   
        <span class="na">env</span><span class="pi">:</span> <span class="c1"># Or as an environment variable</span>
          <span class="na">AWS_SECRET_ACCESS_KEY</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">AWS_ACCESS_KEY_ID</span><span class="pi">:</span> <span class="s">$</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Sync to AWS</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">paths=$(find ./public -type f -name "*.html" | sed -e 's|^./public/|/|' -e 's/.*/"&amp;"/' | tr '\n' ' ')</span>
          <span class="s">cat &lt;&lt; EOF &gt; myaction.sh</span>
          <span class="s">aws cloudfront create-invalidation --distribution-id EWYXMJ6L92Q6I --paths $paths</span>
          <span class="s">EOF</span>
          <span class="s">cat myaction.sh | base64 -w 0</span>
          <span class="s">chmod 755 ./myaction.sh</span>
          <span class="s">./myaction.sh          </span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">gitea.ref == 'refs/heads/main'</span>
        <span class="na">env</span><span class="pi">:</span> <span class="c1"># Or as an environment variable</span>
          <span class="na">AWS_SECRET_ACCESS_KEY</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">AWS_ACCESS_KEY_ID</span><span class="pi">:</span> <span class="s">$</span>
</code></pre></div></div>

<p>The question I’m left with is whether I change this blog over just yet.  This Jekyll blog has something like 587 posts and the repo alone is 25Gb expanded.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ ls -l _posts/ | wc -l
589
builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ du -chs ./
25G     ./
25G     total
</code></pre></div></div>

<p>This means that even with some optimizations and shallow cloning, the builds for blog posts are about 16 minutes each</p>

<p><a href="/content/images/2026/06/hugoaws-37.png"><img src="/content/images/2026/06/hugoaws-37.png" alt="/content/images/2026/06/hugoaws-37.png" /></a></p>

<p>compared to a fresh Hugo one</p>

<p><a href="/content/images/2026/06/hugoaws-38.png"><img src="/content/images/2026/06/hugoaws-38.png" alt="/content/images/2026/06/hugoaws-38.png" /></a></p>

<p>I also have some custom code in this Jekyll blog for figuring out “who is latest” that wouldn’t work without packing the date in the name</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># the grep -v ' 20[0-9][0-9]-[0-9][0-9]-xx' | 
# is so that when we have "future" posts staged here, they aren't picked up
export LATESTFILE=`ls -l _posts/ | grep ".* 20[0-9][0-9]-[0-9][0-9].*markdown" | grep -v ' 20[0-9][0-9]-[0-9][0-9]-xx' | tail -n1 | sed 's/.* \(20[0-9][0-9]-[0-9][0-9].*markdown\)/\1/'`
export LATESTURL=`ls -l _posts/ | grep ".* 20[0-9][0-9]-[0-9][0-9].*markdown" | grep -v ' 20[0-9][0-9]-[0-9][0-9]-xx' |  tail -n1 | sed 's/.* \(20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-\)\(.*\)\.markdown/\2.html/'`
cat _posts/$LATESTFILE | grep "^title: " | head -n 1 | sed 's/^title: "\(.*\)"/\1/' | tr -d '\n'&gt; title.txt
cat _posts/$LATESTFILE | grep "^social: " | head -n 1 | sed 's/^social: "\(.*\)"/\1/' | tr -d '\n'&gt; social.txt
</code></pre></div></div>

<p>Things to stew on…</p>

<p>That said, hopefully you found a complete solution for Hugo blog posting on the cheap to AWS.  I estimate this whole setup will cost me less than $1/month.</p>]]></content><author><name>Isaac Johnson</name></author><category term="Hugo" /><category term="AWS" /><category term="Forgejo" /><category term="CICD" /><summary type="html"><![CDATA[A week ago I posted “Hugo and AWS (S3+CloudFront)” that covered DNS setup, Route 53, bucket storage and Hugo setup. We had a basic “Hello World” file started.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://freshbrewed.science/content/images/2026/06/hugoaws-35.png" /><media:content medium="image" url="https://freshbrewed.science/content/images/2026/06/hugoaws-35.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">IBM Bob</title><link href="https://freshbrewed.science/2026/06/23/ibmbob.html" rel="alternate" type="text/html" title="IBM Bob" /><published>2026-06-23T01:00:01+00:00</published><updated>2026-06-23T01:00:01+00:00</updated><id>https://freshbrewed.science/2026/06/23/ibmbob</id><content type="html" xml:base="https://freshbrewed.science/2026/06/23/ibmbob.html"><![CDATA[<p>IBM now has <a href="https://bob.ibm.com/">IBM Bob</a> which is a their own AI Coding set of tools including an IDE and CLI.  Bob uses “Bob Coins” which you can see detailed in <a href="https://bob.ibm.com/pricing">the pricing page</a>.  We start with 40 that we have 30 days to use.</p>

<p>As I write, I’ll price at the “Pro” plan level, which I think is the most common people might try as its US$20/mo for 40 Bobcoins.  That makes them US$0.50 each.  Its worth pointing out that “Pro+” at US$60/mo gives 160 (so 0.375 per coin) and oddly “Ultra” is a worse deal at 500 Bobcoins for $200 (or 0.40 per coin).  Now, if you have billing enabled, overages cost US$0.50/coin.</p>

<p>I’ll signup and see what we can do with Bob.</p>

<h1 id="signup">Signup</h1>

<p>I have had a pretty disastrous record thus far trying to sign up for IBM suites.</p>

<p>With a bit of apprehension, I went to <a href="https://bob.ibm.com/trial">https://bob.ibm.com/trial</a> to start a free trial.</p>

<p>I tried my fb email and it said “you have an account”</p>

<p><a href="/content/images/2026/06/ibmbob-01.png"><img src="/content/images/2026/06/ibmbob-01.png" alt="/content/images/2026/06/ibmbob-01.png" /></a></p>

<p>Surprisingly, unlike IBM Cloud, this did not block me.</p>

<p><a href="/content/images/2026/06/ibmbob-02.png"><img src="/content/images/2026/06/ibmbob-02.png" alt="/content/images/2026/06/ibmbob-02.png" /></a></p>

<p>I then saw an “Access your trial now” link (and a welcome email come through)</p>

<p><a href="/content/images/2026/06/ibmbob-03.png"><img src="/content/images/2026/06/ibmbob-03.png" alt="/content/images/2026/06/ibmbob-03.png" /></a></p>

<p>There is a “Bob IDE” and a CLI.</p>

<h2 id="ide">IDE</h2>

<p>I downloaded the Bob Windows IDE</p>

<p><a href="/content/images/2026/06/ibmbob-04.png"><img src="/content/images/2026/06/ibmbob-04.png" alt="/content/images/2026/06/ibmbob-04.png" /></a></p>

<p>Even the EULA feels IBM-ish with “Program Name” and “Program Number” listed</p>

<p><a href="/content/images/2026/06/ibmbob-05.png"><img src="/content/images/2026/06/ibmbob-05.png" alt="/content/images/2026/06/ibmbob-05.png" /></a></p>

<p>After reading through it, i saw it would use 1.08Gb of disk space (pretty large IDE IMHO).</p>

<p>We can import settings from VS Code, Windsurf or Cursor</p>

<p><a href="/content/images/2026/06/ibmbob-06.png"><img src="/content/images/2026/06/ibmbob-06.png" alt="/content/images/2026/06/ibmbob-06.png" /></a></p>

<p>As suspected, it’s a variant of code.</p>

<p><a href="/content/images/2026/06/ibmbob-07.png"><img src="/content/images/2026/06/ibmbob-07.png" alt="/content/images/2026/06/ibmbob-07.png" /></a></p>

<p>I was about to complain about how long it was taking, but then I realized it was firing up <em>all</em> my VS Code plugins</p>

<p><a href="/content/images/2026/06/ibmbob-08.png"><img src="/content/images/2026/06/ibmbob-08.png" alt="/content/images/2026/06/ibmbob-08.png" /></a></p>

<p>I went ahead and logged into Bob</p>

<p><a href="/content/images/2026/06/ibmbob-09.png"><img src="/content/images/2026/06/ibmbob-09.png" alt="/content/images/2026/06/ibmbob-09.png" /></a></p>

<p>A lot of tools put the usage in the lower right, but Bob uses the upper area with a clearly visible usage widget</p>

<p><a href="/content/images/2026/06/ibmbob-10.png"><img src="/content/images/2026/06/ibmbob-10.png" alt="/content/images/2026/06/ibmbob-10.png" /></a></p>

<p>I looked through the settings for a model selector but found none. So this must be using IBM Granite at a larger level than the open-weights version</p>

<p><a href="/content/images/2026/06/ibmbob-11.png"><img src="/content/images/2026/06/ibmbob-11.png" alt="/content/images/2026/06/ibmbob-11.png" /></a></p>

<p>Let’s set it to create a Python app</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/ibmbob-12.mp4" type="video/mp4" />
</video>

<p>I set it to auto-approve but it still prompted me to review and save every file so it was taking a while</p>

<p><a href="/content/images/2026/06/ibmbob-13.png"><img src="/content/images/2026/06/ibmbob-13.png" alt="/content/images/2026/06/ibmbob-13.png" /></a></p>

<p>At one point it just went out to lunch for over 20 minutes - no error or status… I went and watched the rest of a FIFA World Cup game and came back it had moved to the next step</p>

<p><a href="/content/images/2026/06/ibmbob-14.png"><img src="/content/images/2026/06/ibmbob-14.png" alt="/content/images/2026/06/ibmbob-14.png" /></a></p>

<p>since we know Bob coins today are $20/40 coins, the final tally (before I test it) is 4.19 coins or about US$2.10</p>

<p><a href="/content/images/2026/06/ibmbob-15.png"><img src="/content/images/2026/06/ibmbob-15.png" alt="/content/images/2026/06/ibmbob-15.png" /></a></p>

<p>My first error came installing requirements</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Collecting zipp&gt;=3.20
  Downloading zipp-3.23.1-py3-none-any.whl (10 kB)
Collecting editorconfig&gt;=0.12.2
  Downloading editorconfig-0.17.1-py3-none-any.whl (16 kB)
Building wheels for collected packages: greenlet
  Building wheel for greenlet (PEP 517) ... error
  ERROR: Command errored out with exit status 1:
   command: 'c:\users\isaac\desktop\deacon-care-list\venv\scripts\python.exe' 'c:\users\isaac\desktop\deacon-care-list\venv\lib\site-packages\pip\_vendor\pep517\in_process\_in_process.py' build_wheel 'C:\Users\isaac\AppData\Local\Temp\tmpsh82wlg3'
       cwd: C:\Users\isaac\AppData\Local\Temp\pip-install-ao8i_xtx\greenlet_218ed73e89a745a39457f10769c784bd
  Complete output (112 lines):
  running bdist_wheel
  running build
  running build_py
  creating build\lib.win-amd64-cpython-39\greenlet
  copying src\greenlet\__init__.py -&gt; build\lib.win-amd64-cpython-39\greenlet
  creating build\lib.win-amd64-cpython-39\greenlet\platform
  copying src\greenlet\platform\__init__.py -&gt; build\lib.win-amd64-cpython-39\greenlet\platform
</code></pre></div></div>

<p>I let Bob try and fix it</p>

<p><a href="/content/images/2026/06/ibmbob-16.png"><img src="/content/images/2026/06/ibmbob-16.png" alt="/content/images/2026/06/ibmbob-16.png" /></a></p>

<p>It seems this time the auto-approve worked (so perhaps that setting only takes affect and the start of a task).</p>

<p>That worked (and up to 5.65 Bob coins)</p>

<p><a href="/content/images/2026/06/ibmbob-17.png"><img src="/content/images/2026/06/ibmbob-17.png" alt="/content/images/2026/06/ibmbob-17.png" /></a></p>

<p>I got another crash and another shot to let Bob fix it</p>

<p><a href="/content/images/2026/06/ibmbob-18.png"><img src="/content/images/2026/06/ibmbob-18.png" alt="/content/images/2026/06/ibmbob-18.png" /></a></p>

<p>We are up to 6.82 coins and I had yet another error</p>

<p><a href="/content/images/2026/06/ibmbob-19.png"><img src="/content/images/2026/06/ibmbob-19.png" alt="/content/images/2026/06/ibmbob-19.png" /></a></p>

<p>8.66 and another error.. i started to fix the requirements related ones myself just from console output</p>

<p>I got a secret error I sent at it</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(venv) PS C:\Users\isaac\Desktop\deacon-care-list&gt; python -m app.main
Traceback (most recent call last):
  File "C:\Program Files\Python39\lib\runpy.py", line 197, in _run_module_as_main      
    return _run_code(code, main_globals, None,
  File "C:\Program Files\Python39\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "C:\Users\isaac\Desktop\deacon-care-list\app\main.py", line 248, in &lt;module&gt;    
    CookieBackendConfig(
  File "&lt;string&gt;", line 14, in __init__
  File "C:\Users\isaac\Desktop\deacon-care-list\venv\lib\site-packages\litestar\middleware\session\client_side.py", line 268, in __post_init__
    raise ImproperlyConfiguredException("secret length must be 16 (128 bit), 24 (192 bit) or 32 (256 bit)")
litestar.exceptions.http_exceptions.ImproperlyConfiguredException: 500: secret length must be 16 (128 bit), 24 (192 bit) or 32 (256 bit)
(venv) PS C:\Users\isaac\Desktop\deacon-care-list&gt;
</code></pre></div></div>

<p>But the fix didn’t seem to work (setting a larger secret to 32 characters).</p>

<p>I fixed that myself ultimately and then had to fix one more error before the app would finally run.  I ended up spending $6.00 on this (and we haven’t even setup containers or database migrations or kubernetes manifests yet!)</p>

<p><a href="/content/images/2026/06/ibmbob-20.png"><img src="/content/images/2026/06/ibmbob-20.png" alt="/content/images/2026/06/ibmbob-20.png" /></a></p>

<p>I have an app running</p>

<p><a href="/content/images/2026/06/ibmbob-21.png"><img src="/content/images/2026/06/ibmbob-21.png" alt="/content/images/2026/06/ibmbob-21.png" /></a></p>

<p>The app is very vertical which means it probably works well on phones</p>

<p><a href="/content/images/2026/06/ibmbob-22.png"><img src="/content/images/2026/06/ibmbob-22.png" alt="/content/images/2026/06/ibmbob-22.png" /></a></p>

<p>I went to families and saw a failed to load error</p>

<p><a href="/content/images/2026/06/ibmbob-23.png"><img src="/content/images/2026/06/ibmbob-23.png" alt="/content/images/2026/06/ibmbob-23.png" /></a></p>

<p>But sadly there are lots of errors.  The dashboard throws console errors</p>

<p><a href="/content/images/2026/06/ibmbob-24.png"><img src="/content/images/2026/06/ibmbob-24.png" alt="/content/images/2026/06/ibmbob-24.png" /></a></p>

<p>and</p>

<p><a href="/content/images/2026/06/ibmbob-25.png"><img src="/content/images/2026/06/ibmbob-25.png" alt="/content/images/2026/06/ibmbob-25.png" /></a></p>

<p>And it doesn’t save “Add Families” so no way to add new ones..</p>

<h2 id="cli">CLI</h2>

<p>Let’s move on to the <a href="https://bob.ibm.com/download?bob=shell">CLI</a> to see if we can resolve some of these issues</p>

<p><a href="/content/images/2026/06/ibmbob-26.png"><img src="/content/images/2026/06/ibmbob-26.png" alt="/content/images/2026/06/ibmbob-26.png" /></a></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~$ nvm use lts/jod
Now using node v22.22.0 (npm v10.9.4)
builder@DESKTOP-QADGF36:~$ curl -fsSL https://bob.ibm.com/download/bobshell.sh | bash

Checking Node.js Installation

✓ Node.js version 22.22.0 detected (minimum 22.15 required)

Package Manager Selection


→ Tip: You can skip this selection by using: --package-manager or --pm (e.g., --pm npm)
→ Only npm is available. Using it automatically.


Downloading bobshell

→ Fetching latest version...
✓ Latest version: 1.0.4


Installing bobshell

→ Installing bobshell 1.0.4 with npm...



Installation Complete! ✓

✓ bobshell 1.0.4 has been successfully installed

→ You can now run: bob

Usage data is collected by default.

→ To opt out, type /settings, navigate to Enable Usage Metrics and set the flag to false

</code></pre></div></div>

<p>I then moved the app into WSL</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces$ mv /mnt/c/Users/isaac/Desktop/deacon-care-list ./
builder@DESKTOP-QADGF36:~/Workspaces$ cd deacon-care-list/
builder@DESKTOP-QADGF36:~/Workspaces/deacon-care-list$ ls
QUICKSTART.md  README.md  WINDOWS_INSTALL.md  app  create_admin.py  deacon_care.db  requirements.txt  static  venv
</code></pre></div></div>

<p>I’ll git init and make a gitignore file</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/deacon-care-list$ gi linux,python | tee .gitignore
# Created by https://www.toptal.com/developers/gitignore/api/linux,python
# Edit at https://www.toptal.com/developers/gitignore?templates=linux,python

### Linux ###
*~

# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*

# KDE directory preferences
.directory

# Linux trash folder which might appear on any partition or disk
.Trash-*

# .nfs files are created when an open file is removed but is still being accessed
.nfs*

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml

# ruff
.ruff_cache/

# LSP config files
pyrightconfig.json

# End of https://www.toptal.com/developers/gitignore/api/linux,python
</code></pre></div></div>

<p>Then add what i have - this creates a good checkpoint to which we can roll back if needed</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/deacon-care-list$ git add .gitignore
builder@DESKTOP-QADGF36:~/Workspaces/deacon-care-list$ git add -A
builder@DESKTOP-QADGF36:~/Workspaces/deacon-care-list$ git commit -m "phase1"
[main (root-commit) fabe738] phase1
 35 files changed, 4271 insertions(+)
 create mode 100755 .env.example
 create mode 100644 .gitignore
 create mode 100755 QUICKSTART.md
 create mode 100755 README.md
 create mode 100755 WINDOWS_INSTALL.md
 create mode 100755 app/__init__.py
 create mode 100755 app/config.py
 create mode 100755 app/controllers/__init__.py
 create mode 100755 app/controllers/auth.py
 create mode 100755 app/controllers/family.py
 create mode 100755 app/database.py
 create mode 100755 app/main.py
 create mode 100755 app/models/__init__.py
 create mode 100755 app/models/contact.py
 create mode 100755 app/models/family.py
 create mode 100755 app/models/note.py
 create mode 100755 app/models/parishioner.py
 create mode 100755 app/models/user.py
 create mode 100755 app/schemas/__init__.py
 create mode 100755 app/schemas/dto.py
 create mode 100755 app/services/__init__.py
 create mode 100755 app/services/auth_service.py
 create mode 100755 app/services/family_service.py
 create mode 100755 app/templates/404.html
 create mode 100755 app/templates/base.html
 create mode 100755 app/templates/dashboard.html
 create mode 100755 app/templates/family_detail.html
 create mode 100755 app/templates/family_form.html
 create mode 100755 app/templates/family_list.html
 create mode 100755 app/templates/login.html
 create mode 100755 create_admin.py
 create mode 100755 deacon_care.db
 create mode 100755 requirements.txt
 create mode 100755 static/css/style.css
 create mode 100755 static/js/app.js
</code></pre></div></div>

<p>I can now invoke <code class="language-plaintext highlighter-rouge">bob</code> to run Bob CLI</p>

<p><a href="/content/images/2026/06/ibmbob-27.png"><img src="/content/images/2026/06/ibmbob-27.png" alt="/content/images/2026/06/ibmbob-27.png" /></a></p>

<p>I had to launch twice to get it to give me a URL as it was hung up trying to fire up a Firefox in WSL</p>

<p><a href="/content/images/2026/06/ibmbob-28.png"><img src="/content/images/2026/06/ibmbob-28.png" alt="/content/images/2026/06/ibmbob-28.png" /></a></p>

<p>Next, I started it working on the save family issue</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/ibmbob-29.mp4" type="video/mp4" />
</video>

<p>I didn’t capture the whole thing as I mostly wanted you to see how performant it was and how it worked.</p>

<p>That said, it did fix the issue.  I appreciated how it kept testing <em>(I was testing on the side as well)</em></p>

<p><a href="/content/images/2026/06/ibmbob-30.png"><img src="/content/images/2026/06/ibmbob-30.png" alt="/content/images/2026/06/ibmbob-30.png" /></a></p>

<p>This took 3.9 bob coins so fixing that error was almost $2</p>

<p>Let’s add a feature next:</p>

<p><a href="/content/images/2026/06/ibmbob-31.png"><img src="/content/images/2026/06/ibmbob-31.png" alt="/content/images/2026/06/ibmbob-31.png" /></a></p>

<p>It seemed to add the feature in pretty rapid order</p>

<p><a href="/content/images/2026/06/ibmbob-32.png"><img src="/content/images/2026/06/ibmbob-32.png" alt="/content/images/2026/06/ibmbob-32.png" /></a></p>

<p>It worked, but the photo is a bit large.</p>

<p><a href="/content/images/2026/06/ibmbob-33.png"><img src="/content/images/2026/06/ibmbob-33.png" alt="/content/images/2026/06/ibmbob-33.png" /></a></p>

<p>I kept working it till the dashboard errors were fixed and the image size looked proper.  At this point I’ve used 25.17 coins of my initial 40 grant (ie. spent about $12.55 on this app so far)</p>

<p><a href="/content/images/2026/06/ibmbob-34.png"><img src="/content/images/2026/06/ibmbob-34.png" alt="/content/images/2026/06/ibmbob-34.png" /></a></p>

<p>I added more features and have to say I adore the traditional IBM blue used.  Harkens to a simpler time on PS/1 terminals.</p>

<p><a href="/content/images/2026/06/ibmbob-35.png"><img src="/content/images/2026/06/ibmbob-35.png" alt="/content/images/2026/06/ibmbob-35.png" /></a></p>

<p>As Bob built admin scripts, I appreciated the top comment blocks and user guide markdown files</p>

<p><a href="/content/images/2026/06/ibmbob-36.png"><img src="/content/images/2026/06/ibmbob-36.png" alt="/content/images/2026/06/ibmbob-36.png" /></a></p>

<p>I noticed a <code class="language-plaintext highlighter-rouge">.bob</code> folder I likely need to add to my gitignore file.  It seems to keep a running history of work in sessions</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(.venv) builder@DESKTOP-QADGF36:~/Workspaces/deacon-care-list$ cat .bob/notes/pending-notes.txt
{"id":"7ab072b2-7854-46a5-b630-e5078b7cb8c4","ts":"2026-06-23T12:39:57.036Z","path":"/home/builder/Workspaces/deacon-care-list/app/models/user.py","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"63142331-a097-4a11-a362-23169d350506","ts":"2026-06-23T12:40:05.785Z","path":"/home/builder/Workspaces/deacon-care-list/migrations/add_is_active_to_users.py","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"46315469-dd54-4597-8548-a851c86aca07","ts":"2026-06-23T12:40:29.306Z","path":"/home/builder/Workspaces/deacon-care-list/app/services/auth_service.py","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"097be3a7-4942-4bd5-b7e5-2ffa38ce072b","ts":"2026-06-23T12:40:36.400Z","path":"/home/builder/Workspaces/deacon-care-list/app/schemas/auth_dto.py","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"a2962d29-242b-489a-8323-ca4d87eb582c","ts":"2026-06-23T12:40:43.672Z","path":"/home/builder/Workspaces/deacon-care-list/app/controllers/auth.py","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"8dc1def9-59c1-42b4-968d-328be7dc35c0","ts":"2026-06-23T12:40:54.890Z","path":"/home/builder/Workspaces/deacon-care-list/app/controllers/auth.py","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"8fcbfea0-c593-4500-9edc-168dbe3fa442","ts":"2026-06-23T12:41:14.914Z","path":"/home/builder/Workspaces/deacon-care-list/admin_users.py","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"9377e801-bdb6-4369-98f3-8c248dd48f7c","ts":"2026-06-23T12:42:19.361Z","path":"/home/builder/Workspaces/deacon-care-list/admin_users.py","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"dadae84a-50f5-4d71-a651-8318751d3f3a","ts":"2026-06-23T12:42:24.790Z","path":"/home/builder/Workspaces/deacon-care-list/admin_users.py","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"54736401-1317-4020-b433-37683f4ce3de","ts":"2026-06-23T12:42:32.192Z","path":"/home/builder/Workspaces/deacon-care-list/admin_users.py","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"d688c6ef-d23b-4ee6-8d33-076d2a196e7e","ts":"2026-06-23T12:42:51.430Z","path":"/home/builder/Workspaces/deacon-care-list/app/models/__init__.py","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"39638360-0d7d-4965-97e0-4a6032bdd0be","ts":"2026-06-23T12:43:10.539Z","path":"/home/builder/Workspaces/deacon-care-list/app/templates/change_password.html","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"4643873c-301a-480a-a249-677b6495044c","ts":"2026-06-23T12:43:28.063Z","path":"/home/builder/Workspaces/deacon-care-list/app/main.py","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"d513697c-b9e1-46b1-8a2c-4612a3a7baa0","ts":"2026-06-23T12:43:57.457Z","path":"/home/builder/Workspaces/deacon-care-list/app/templates/base.html","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
{"id":"c2a771df-b9e6-4ec0-9a6f-fb281fbad329","ts":"2026-06-23T12:45:09.424Z","path":"/home/builder/Workspaces/deacon-care-list/ADMIN_USERS_GUIDE.md","version":"1.0.0","taskID":"a333fec6-86d2-4c27-b1a7-d84c6837f796"}
</code></pre></div></div>

<p>In testing, it changed the admin password which surprised me (as it has a testuser).. I had to go widescreen to figure out what it was changed to:</p>

<p><a href="/content/images/2026/06/ibmbob-37.png"><img src="/content/images/2026/06/ibmbob-37.png" alt="/content/images/2026/06/ibmbob-37.png" /></a></p>

<p>I wrapped my session by parameterizing the Upload dir, DB dir and app ports.. I can see that I’m not all used up on Bob Coins</p>

<p><a href="/content/images/2026/06/ibmbob-38.png"><img src="/content/images/2026/06/ibmbob-38.png" alt="/content/images/2026/06/ibmbob-38.png" /></a></p>

<p>We can see it gives a basic summary when we complete a CLI session:</p>

<p><a href="/content/images/2026/06/ibmbob-41.png"><img src="/content/images/2026/06/ibmbob-41.png" alt="/content/images/2026/06/ibmbob-41.png" /></a></p>

<h1 id="summary">Summary</h1>

<p>I have an app - it needs to be containerized, built with CICD, pushed to Forgejo and likely hosted in K8s still.  I’ll have to use an alternate tool for that as we completely used all the free trail credits (40 Bobcoins)</p>

<p>Here is a tour of the app we built:</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/ibmbob-40.mp4" type="video/mp4" />
</video>

<p>When I compare it with other tools, with the exception of that one blip that seemed to take forever last night, it did a great job.  The CLI seemed to do a better job testing things than the IDE.</p>

<p>The comments are solid, the layout - which I gave it no guidance on - was clean and well done.  Overall I liked the performance for a basic Python app.</p>

<p>However, the cost is something to consider.  I didn’t even complete this app and we spent the full 40 Bobcoins.  If I was spending $20 a month and could just <em>start</em> one app a month, I would question the value.</p>

<p>Other than the cost, I was pleased and likely will revisit to see if 40 was all I got or it refreshes at some point.  I will assume it was one and done.</p>]]></content><author><name>Isaac Johnson</name></author><category term="IBM" /><category term="Bob" /><category term="GenAI" /><category term="Python" /><summary type="html"><![CDATA[IBM now has IBM Bob which is a their own AI Coding set of tools including an IDE and CLI. Bob uses “Bob Coins” which you can see detailed in the pricing page. We start with 40 that we have 30 days to use.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://freshbrewed.science/content/images/2026/06/ibmbob-39.png" /><media:content medium="image" url="https://freshbrewed.science/content/images/2026/06/ibmbob-39.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Hugo and AWS (S3+CloudFront)</title><link href="https://freshbrewed.science/2026/06/19/hugoaws.html" rel="alternate" type="text/html" title="Hugo and AWS (S3+CloudFront)" /><published>2026-06-19T01:00:01+00:00</published><updated>2026-06-19T01:00:01+00:00</updated><id>https://freshbrewed.science/2026/06/19/hugoaws</id><content type="html" xml:base="https://freshbrewed.science/2026/06/19/hugoaws.html"><![CDATA[<p>Back in April, I did a three part series on Hugo blogs: <a href="https://freshbrewed.science/2026/04/16/hugo.html">Hugo and Azure Static Sites with Front Door</a>, <a href="https://freshbrewed.science/2026/04/21/hugo2.html">Hugo and Static Sites in GCP</a> and lastly <a href="https://freshbrewed.science/2026/04/23/hugo3.html">Hugo and Serverless in GCP</a>.</p>

<p>Today I want to tackle Hugo and AWS.  Honestly, this is full circle back to 2021 when I posted about <a href="https://www.freshbrewed.science/github-blog-jekyll/index.html">using Github and Jekyll</a> for this blog here which was in replacement of Ghost (which I wrote about here <a href="https://www.freshbrewed.science/automating-updates-with-azure-devops/index.html">in 2019</a>)</p>

<p>We’ll tackle registering a DNS name, setting up AWS Route53 for DNS hosting then get into creating a local blog with Hugo.  We’ll lastly circle back to setting up a CloudFront distribution tied to an S3 bucket and getting proper certs and routing.</p>

<h1 id="dns">DNS</h1>

<p>We’ll create the HZ in AWS Cloud53</p>

<p><a href="/content/images/2026/06/awshugo-01.png"><img src="/content/images/2026/06/awshugo-01.png" alt="/content/images/2026/06/awshugo-01.png" /></a></p>

<p>Which will list the NS servers I’ll need to use with Gandi</p>

<p><a href="/content/images/2026/06/awshugo-03.png"><img src="/content/images/2026/06/awshugo-03.png" alt="/content/images/2026/06/awshugo-03.png" /></a></p>

<p>Then back in Gandi, I then need to set it to the AWS external nameservers</p>

<p><a href="/content/images/2026/06/awshugo-02.png"><img src="/content/images/2026/06/awshugo-02.png" alt="/content/images/2026/06/awshugo-02.png" /></a></p>

<p>Back in AWS Console, I can create a new bucket based on the existing one used for <a href="https://freshbrewed.science/">freshbrewed.science</a></p>

<p>I can now see my new S3 bucket</p>

<p><a href="/content/images/2026/06/awshugo-04.png"><img src="/content/images/2026/06/awshugo-04.png" alt="/content/images/2026/06/awshugo-04.png" /></a></p>

<p>I’ll make a basic <code class="language-plaintext highlighter-rouge">index.html</code> just to verify it works:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(base) builder@LuiGi:~/Workspaces/fbtech$ cat index.html
&lt;HTML&gt;
        &lt;HEAD&gt;
                &lt;TITLE&gt;test site&lt;/TITLE&gt;
        &lt;/HEAD&gt;
        &lt;BODY&gt;
                &lt;H1&gt;Test Site 2026-06&lt;/H1&gt;
        &lt;/BODY&gt;
&lt;/HTML&gt;
</code></pre></div></div>

<p>At this point I have a HZ, but no A or CNAME records so it goes nowhere</p>

<p><a href="/content/images/2026/06/awshugo-05.png"><img src="/content/images/2026/06/awshugo-05.png" alt="/content/images/2026/06/awshugo-05.png" /></a></p>

<p>So I can copy that HTML file over to the new bucket</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ cat index.html
&lt;HTML&gt;
        &lt;HEAD&gt;
                &lt;TITLE&gt;test site&lt;/TITLE&gt;
        &lt;/HEAD&gt;
        &lt;BODY&gt;
                &lt;H1&gt;Test Site 2026-06&lt;/H1&gt;
        &lt;/BODY&gt;
&lt;/HTML&gt;
builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ aws s3 cp ./index.html s3://freshbrewed.tech/index.html
upload: ./index.html to s3://freshbrewed.tech/index.html
</code></pre></div></div>

<p>But the whole file-based ACLs won’t work anymore with how I set the bucket up</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ aws s3 cp ./index.html s3://freshbrewed.tech/index.html --acl public-read
upload failed: ./index.html to s3://freshbrewed.tech/index.html An error occurred (AccessControlListNotSupported) when calling the PutObject operation: The bucket does not allow ACLs
</code></pre></div></div>

<p>To create a CloudFront, I basically have two choices to consider, free tier or pay as you go.  I’m thinking of the free tier for this test, but I know from history that pay-as-you-go is really cheap</p>

<p><a href="/content/images/2026/06/awshugo-06.png"><img src="/content/images/2026/06/awshugo-06.png" alt="/content/images/2026/06/awshugo-06.png" /></a></p>

<p>I can now set a name and optionally a subdomain</p>

<p><a href="/content/images/2026/06/awshugo-07.png"><img src="/content/images/2026/06/awshugo-07.png" alt="/content/images/2026/06/awshugo-07.png" /></a></p>

<p>Next we specify our origin</p>

<p><a href="/content/images/2026/06/awshugo-08.png"><img src="/content/images/2026/06/awshugo-08.png" alt="/content/images/2026/06/awshugo-08.png" /></a></p>

<p>Next we can enable monitoring in security (but not DDOS protections with the free plan)</p>

<p><a href="/content/images/2026/06/awshugo-09.png"><img src="/content/images/2026/06/awshugo-09.png" alt="/content/images/2026/06/awshugo-09.png" /></a></p>

<p>Next we have certs.. It will create a cert, but we likely will need to nudge it from time to time</p>

<p><a href="/content/images/2026/06/awshugo-10.png"><img src="/content/images/2026/06/awshugo-10.png" alt="/content/images/2026/06/awshugo-10.png" /></a></p>

<p>In this screen you actually need to click “Create certificate” to create and deploy it (not just press next).</p>

<p>You’ll then have a cert ready to use</p>

<p><a href="/content/images/2026/06/awshugo-11.png"><img src="/content/images/2026/06/awshugo-11.png" alt="/content/images/2026/06/awshugo-11.png" /></a></p>

<p>I need to bare in mind that in 6 months, i might need to come back and worth through a renewal process since ACM isn’t necessarily going to do that for me:</p>

<p><a href="/content/images/2026/06/awshugo-12.png"><img src="/content/images/2026/06/awshugo-12.png" alt="/content/images/2026/06/awshugo-12.png" /></a></p>

<p>Lastly, I’ll confirm the settings and create the distribution</p>

<p><a href="/content/images/2026/06/awshugo-13.png"><img src="/content/images/2026/06/awshugo-13.png" alt="/content/images/2026/06/awshugo-13.png" /></a></p>

<p>I then get confirmation the distribution was created</p>

<p><a href="/content/images/2026/06/awshugo-14.png"><img src="/content/images/2026/06/awshugo-14.png" alt="/content/images/2026/06/awshugo-14.png" /></a></p>

<p>We are almost done.  The last mile is to get DNS routing properly.  At this point, an nslookup will still show nothing:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nslookup freshbrewed.tech 8.8.8.8
Server:         8.8.8.8
Address:        8.8.8.8#53

Non-authoritative answer:
*** Can't find freshbrewed.tech: No answer
</code></pre></div></div>

<p>And a web page would show</p>

<p><a href="/content/images/2026/06/awshugo-18.png"><img src="/content/images/2026/06/awshugo-18.png" alt="/content/images/2026/06/awshugo-18.png" /></a></p>

<p>So we click “Route domains to cloudfront”</p>

<p><a href="/content/images/2026/06/awshugo-15.png"><img src="/content/images/2026/06/awshugo-15.png" alt="/content/images/2026/06/awshugo-15.png" /></a></p>

<p>Then click “Set up automatically”</p>

<p><a href="/content/images/2026/06/awshugo-16.png"><img src="/content/images/2026/06/awshugo-16.png" alt="/content/images/2026/06/awshugo-16.png" /></a></p>

<p>We can then see the DNS records were created by status</p>

<p><a href="/content/images/2026/06/awshugo-17.png"><img src="/content/images/2026/06/awshugo-17.png" alt="/content/images/2026/06/awshugo-17.png" /></a></p>

<p>moreover, the nslookup now works</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nslookup freshbrewed.tech 8.8.8.8
Server:         8.8.8.8
Address:        8.8.8.8#53

Non-authoritative answer:
Name:   freshbrewed.tech
Address: 99.84.203.96
Name:   freshbrewed.tech
Address: 99.84.203.98
Name:   freshbrewed.tech
Address: 99.84.203.20
Name:   freshbrewed.tech
Address: 99.84.203.61
Name:   freshbrewed.tech
Address: 2600:9000:20c5:0:1e:bda5:24c0:93a1
Name:   freshbrewed.tech
Address: 2600:9000:20c5:ba00:1e:bda5:24c0:93a1
Name:   freshbrewed.tech
Address: 2600:9000:20c5:3c00:1e:bda5:24c0:93a1
Name:   freshbrewed.tech
Address: 2600:9000:20c5:9e00:1e:bda5:24c0:93a1
Name:   freshbrewed.tech
Address: 2600:9000:20c5:4600:1e:bda5:24c0:93a1
Name:   freshbrewed.tech
Address: 2600:9000:20c5:400:1e:bda5:24c0:93a1
Name:   freshbrewed.tech
Address: 2600:9000:20c5:2c00:1e:bda5:24c0:93a1
Name:   freshbrewed.tech
Address: 2600:9000:20c5:8400:1e:bda5:24c0:93a1
</code></pre></div></div>

<p>I can now see the Test page</p>

<p><a href="/content/images/2026/06/awshugo-19.png"><img src="/content/images/2026/06/awshugo-19.png" alt="/content/images/2026/06/awshugo-19.png" /></a></p>

<p>but we lack a default page</p>

<p><a href="/content/images/2026/06/awshugo-20.png"><img src="/content/images/2026/06/awshugo-20.png" alt="/content/images/2026/06/awshugo-20.png" /></a></p>

<p>To fix that, I’ll “edit” the settings in General and set a Default root object</p>

<p><a href="/content/images/2026/06/awshugo-21.png"><img src="/content/images/2026/06/awshugo-21.png" alt="/content/images/2026/06/awshugo-21.png" /></a></p>

<p>And now that works:</p>

<p><a href="/content/images/2026/06/awshugo-22.png"><img src="/content/images/2026/06/awshugo-22.png" alt="/content/images/2026/06/awshugo-22.png" /></a></p>

<h1 id="hugo">Hugo</h1>

<p>It is June 18th and the last day of using Gemini CLI</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/awshugo-23.mp4" type="video/mp4" />
</video>

<p>But literally as I was working around 1:12pm CT it died - the official ban on AI Pro hit me and it would not start back up:</p>

<p><a href="/content/images/2026/06/awshugo-24.png"><img src="/content/images/2026/06/awshugo-24.png" alt="/content/images/2026/06/awshugo-24.png" /></a></p>

<p>let’s try with Gemma:e4b and Cline</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/awshugo-26.mp4" type="video/mp4" />
</video>

<p>But then it didn’t seem want to execute the steps</p>

<p><a href="/content/images/2026/06/awshugo-27.png"><img src="/content/images/2026/06/awshugo-27.png" alt="/content/images/2026/06/awshugo-27.png" /></a></p>

<p>So let’s switch it to use Vertex AI</p>

<p><a href="/content/images/2026/06/awshugo-28.png"><img src="/content/images/2026/06/awshugo-28.png" alt="/content/images/2026/06/awshugo-28.png" /></a></p>

<p>I can then use my Gemini API Key</p>

<p><a href="/content/images/2026/06/awshugo-29.png"><img src="/content/images/2026/06/awshugo-29.png" alt="/content/images/2026/06/awshugo-29.png" /></a></p>

<p>And Gemini Pro (but this will cost me)</p>

<p><a href="/content/images/2026/06/awshugo-30.png"><img src="/content/images/2026/06/awshugo-30.png" alt="/content/images/2026/06/awshugo-30.png" /></a></p>

<p>I had to switch to Gemini 2.5 Pro (not 3) but it worked to create a Hugo app which I could build</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ hugo build
Start building sites …
hugo v0.160.0+extended+withdeploy linux/amd64 BuildDate=2026-04-04T13:32:34Z VendorInfo=Homebrew


                  │ EN
──────────────────┼────
 Pages            │ 10
 Paginator pages  │  0
 Non-page files   │  0
 Static files     │  7
 Processed images │  0
 Aliases          │  0
 Cleaned          │  0

Total in 214 ms
</code></pre></div></div>

<p>I can now copy over</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ cd public/
builder@DESKTOP-QADGF36:~/Workspaces/fbtech/public$ aws s3 cp --recursive ./ s3://freshbrewed.tech/
upload: ./android-chrome-192x192.png to s3://freshbrewed.tech/android-chrome-192x192.png
upload: ./apple-touch-icon.png to s3://freshbrewed.tech/apple-touch-icon.png
upload: ./favicon-16x16.png to s3://freshbrewed.tech/favicon-16x16.png
upload: ./android-chrome-512x512.png to s3://freshbrewed.tech/android-chrome-512x512.png
upload: ./404.html to s3://freshbrewed.tech/404.html
upload: ./favicon-32x32.png to s3://freshbrewed.tech/favicon-32x32.png
upload: categories/index.xml to s3://freshbrewed.tech/categories/index.xml
upload: categories/index.html to s3://freshbrewed.tech/categories/index.html
upload: ./favicon.ico to s3://freshbrewed.tech/favicon.ico
upload: ./index.xml to s3://freshbrewed.tech/index.xml
upload: ./index.json to s3://freshbrewed.tech/index.json
upload: ./index.html to s3://freshbrewed.tech/index.html
upload: css/main.bundle.min.77a79972dfd1fba3e37eaab972e5cf479aea24376fc39d827f5760735ebc834e.css to s3://freshbrewed.tech/css/main.bundle.min.77a79972dfd1fba3e37eaab972e5cf479aea24376fc39d827f5760735ebc834e.css
upload: ./robots.txt to s3://freshbrewed.tech/robots.txt
upload: ./site.webmanifest to s3://freshbrewed.tech/site.webmanifest
upload: js/appearance.min.8a082f81b27f3cb2ee528df0b0bdc39787034cf2cc34d4669fbc9977c929023c.js to s3://freshbrewed.tech/js/appearance.min.8a082f81b27f3cb2ee528df0b0bdc39787034cf2cc34d4669fbc9977c929023c.js
upload: ./sitemap.xml to s3://freshbrewed.tech/sitemap.xml
upload: tags/index.html to s3://freshbrewed.tech/tags/index.html
upload: tags/index.xml to s3://freshbrewed.tech/tags/index.xml
</code></pre></div></div>

<p>Now I can wait for the Cloud CDN to expire the cache of index.html or just expire it now.  Most of the time I have a script command do this in CICD deploys</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ aws cloudfront create-invalidation --distribution-id EWYXMJ6L92Q6I --paths "/index.html"
{
    "Location": "https://cloudfront.amazonaws.com/2020-05-31/distribution/EWYXMJ6L92Q6I/invalidation/I563GC9MVWALPB9I5480B117SK",
    "Invalidation": {
        "Id": "I563GC9MVWALPB9I5480B117SK",
        "Status": "InProgress",
        "CreateTime": "2026-06-19T15:56:48.403000+00:00",
        "InvalidationBatch": {
            "Paths": {
                "Quantity": 1,
                "Items": [
                    "/index.html"
                ]
            },
            "CallerReference": "cli-1781884607-825053"
        }
    }
}
</code></pre></div></div>

<p>It’s there but blank</p>

<p><a href="/content/images/2026/06/awshugo-31.png"><img src="/content/images/2026/06/awshugo-31.png" alt="/content/images/2026/06/awshugo-31.png" /></a></p>

<p>I ask Gemini 2.5 via Cline again</p>

<p><a href="/content/images/2026/06/awshugo-32.png"><img src="/content/images/2026/06/awshugo-32.png" alt="/content/images/2026/06/awshugo-32.png" /></a></p>

<p>Looks like I just missed the fact that the Hello World post created earlier was set to draft</p>

<p><a href="/content/images/2026/06/awshugo-33.png"><img src="/content/images/2026/06/awshugo-33.png" alt="/content/images/2026/06/awshugo-33.png" /></a></p>

<p>However, when done, firing up <code class="language-plaintext highlighter-rouge">hugo server</code> fails to show a page</p>

<p><a href="/content/images/2026/06/awshugo-34.png"><img src="/content/images/2026/06/awshugo-34.png" alt="/content/images/2026/06/awshugo-34.png" /></a></p>

<h2 id="antigravity">Antigravity</h2>

<p>Let’s now try the closed-source replacement for Gemini CLI to see if it can help, <a href="https://antigravity.google/">Antigravity</a></p>

<p>I can install with <code class="language-plaintext highlighter-rouge">curl</code> (no Debian package)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl -fsSL https://antigravity.google/cli/install.sh | bash
⠋ Detecting system environment...
✓ Platform detected: linux_amd64
⠋ Querying release repository...
✓ Latest available version: 1.0.10
⠋ Downloading release package...
✓ Download complete and checksum verified.
⠋ Extracting binary from archive...
⠋ Configuring shell environment...
I0619 12:34:31.649427 1161078 installer.go:27] Running Antigravity CLI setup...
I0619 12:34:31.649974 1161078 installer.go:147] Appending PATH export to profile /home/builder/.bashrc: export PATH="/home/builder/.local/bin:$PATH"
I0619 12:34:31.652259 1161078 installer.go:184] Successfully updated shell profile: /home/builder/.bashrc
I0619 12:34:31.652595 1161078 installer.go:147] Appending PATH export to profile /home/builder/.profile: export PATH="/home/builder/.local/bin:$PATH"
I0619 12:34:31.653719 1161078 installer.go:184] Successfully updated shell profile: /home/builder/.profile
I0619 12:34:31.654059 1161078 installer_unix.go:40] PATH verification: ~/.local/bin is correctly configured in active PATH environment.

✅ Antigravity CLI installed successfully at /home/builder/.local/bin/agy
Run 'agy' to start the CLI
</code></pre></div></div>

<p>After I sort out auth, I can set a theme and import Gemini CLI extensions</p>

<p><a href="/content/images/2026/06/awshugo-36.png"><img src="/content/images/2026/06/awshugo-36.png" alt="/content/images/2026/06/awshugo-36.png" /></a></p>

<p>Let’s see if it can fix it</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/awshugo-37.mp4" type="video/mp4" />
</video>

<p>Running the server, it looks good now</p>

<p><a href="/content/images/2026/06/awshugo-38.png"><img src="/content/images/2026/06/awshugo-38.png" alt="/content/images/2026/06/awshugo-38.png" /></a></p>

<p>Now I can build and push the updated site:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ hugo build
Start building sites …
hugo v0.160.0+extended+withdeploy linux/amd64 BuildDate=2026-04-04T13:32:34Z VendorInfo=Homebrew


                  │ EN
──────────────────┼────
 Pages            │ 13
 Paginator pages  │  0
 Non-page files   │  0
 Static files     │  7
 Processed images │  0
 Aliases          │  2
 Cleaned          │  0

Total in 195 ms
builder@DESKTOP-QADGF36:~/Workspaces/fbtech$ cd public/
builder@DESKTOP-QADGF36:~/Workspaces/fbtech/public$ aws s3 cp --recursive ./ s3://freshbrewed.tech/
upload: ./apple-touch-icon.png to s3://freshbrewed.tech/apple-touch-icon.png
upload: ./404.html to s3://freshbrewed.tech/404.html
upload: ./favicon-32x32.png to s3://freshbrewed.tech/favicon-32x32.png
upload: categories/index.xml to s3://freshbrewed.tech/categories/index.xml
upload: ./android-chrome-192x192.png to s3://freshbrewed.tech/android-chrome-192x192.png
upload: ./android-chrome-512x512.png to s3://freshbrewed.tech/android-chrome-512x512.png
upload: ./favicon-16x16.png to s3://freshbrewed.tech/favicon-16x16.png
upload: ./favicon.ico to s3://freshbrewed.tech/favicon.ico
upload: ./index.html to s3://freshbrewed.tech/index.html
upload: categories/index.html to s3://freshbrewed.tech/categories/index.html
upload: ./index.json to s3://freshbrewed.tech/index.json
upload: js/appearance.min.8a082f81b27f3cb2ee528df0b0bdc39787034cf2cc34d4669fbc9977c929023c.js to s3://freshbrewed.tech/js/appearance.min.8a082f81b27f3cb2ee528df0b0bdc39787034cf2cc34d4669fbc9977c929023c.js
upload: ./index.xml to s3://freshbrewed.tech/index.xml
upload: css/main.bundle.min.77a79972dfd1fba3e37eaab972e5cf479aea24376fc39d827f5760735ebc834e.css to s3://freshbrewed.tech/css/main.bundle.min.77a79972dfd1fba3e37eaab972e5cf479aea24376fc39d827f5760735ebc834e.css
upload: page/1/index.html to s3://freshbrewed.tech/page/1/index.html
upload: posts/index.html to s3://freshbrewed.tech/posts/index.html
upload: posts/hello-world/index.html to s3://freshbrewed.tech/posts/hello-world/index.html
upload: posts/index.xml to s3://freshbrewed.tech/posts/index.xml
upload: posts/page/1/index.html to s3://freshbrewed.tech/posts/page/1/index.html
upload: ./robots.txt to s3://freshbrewed.tech/robots.txt
upload: ./site.webmanifest to s3://freshbrewed.tech/site.webmanifest
upload: ./sitemap.xml to s3://freshbrewed.tech/sitemap.xml
upload: tags/index.xml to s3://freshbrewed.tech/tags/index.xml
upload: tags/index.html to s3://freshbrewed.tech/tags/index.html
builder@DESKTOP-QADGF36:~/Workspaces/fbtech/public$ aws cloudfront create-invalidation --distribution-id EWYXMJ6L92Q6I --paths "/index.html"
{
    "Location": "https://cloudfront.amazonaws.com/2020-05-31/distribution/EWYXMJ6L92Q6I/invalidation/I48B3N8I4NT97RV9133BQL8SRB",
    "Invalidation": {
        "Id": "I48B3N8I4NT97RV9133BQL8SRB",
        "Status": "InProgress",
        "CreateTime": "2026-06-19T17:42:37.369000+00:00",
        "InvalidationBatch": {
            "Paths": {
                "Quantity": 1,
                "Items": [
                    "/index.html"
                ]
            },
            "CallerReference": "cli-1781890956-362860"
        }
    }
}
</code></pre></div></div>

<p>Which looks updated:</p>

<p><a href="/content/images/2026/06/awshugo-39.png"><img src="/content/images/2026/06/awshugo-39.png" alt="/content/images/2026/06/awshugo-39.png" /></a></p>

<h1 id="summary">Summary</h1>

<p>Truth time - this post is delayed a day because I took a night to go camping and fishing with my youngest daughter and it was awesome (though we got skunked on the water).</p>

<p>With regards to Hugo and AWS, this will be by far the cheapest option as it will use the free tier of CloudFront (and my other production site which uses paid barely tips the scale).  Unless I store large videos, the s3 bucket costs will be pennies a month.</p>

<p>I found it very odd to be there when Gemini CLI died.. it just quit in the middle.  You then saw me use a few other tools before finishing up with <a href="https://antigravity.google/">Antigravity</a>.</p>

<p>I still don’t like it though. I don’t like closed source, i don’t like having to install things with curl (or for the IDE having to literally download and extract a Zip likes its 2012 and we are still using tucows.. seriously).  I don’t like that ctrl-c doesnt exit the CLI. And i know others that find it had some odd behaviours under heavy use <em>(HOWEVER, i have seen all these tools have odd behaviours with heavy use so I won’t hold that against agy)</em>.</p>

<p>That said, agy is cheap - i still have a (mostly) unlimited plan with the AI pro account I got with my Pixel Fold.  It does work - Agy does the thing I asked it to do and it does it relatively fast.  It has session resume.  And lastly, some of my other gripes <em>(like forcing me to opt into data collection)</em> have been fixed.. So it could be just growing pains.</p>

<p>But this isn’t about LLM tools – this post is to be a guide on how to setup Hugo with AWS S3 and CloudFront and I think to that end we did the job.</p>

<p>The next step will be to get CICD working and really build out some content.  I am also debating this path to replace the Jekyll Blog (ie. what you are reading here).</p>]]></content><author><name>Isaac Johnson</name></author><category term="hugo" /><category term="blog" /><category term="aws" /><category term="cloudfront" /><category term="s3" /><summary type="html"><![CDATA[Back in April, I did a three part series on Hugo blogs: Hugo and Azure Static Sites with Front Door, Hugo and Static Sites in GCP and lastly Hugo and Serverless in GCP.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://freshbrewed.science/content/images/2026/06/awshugo-40.png" /><media:content medium="image" url="https://freshbrewed.science/content/images/2026/06/awshugo-40.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">OS Apps: lifeGLANCE and dayGLANCE</title><link href="https://freshbrewed.science/2026/06/16/smallos.html" rel="alternate" type="text/html" title="OS Apps: lifeGLANCE and dayGLANCE" /><published>2026-06-16T01:00:01+00:00</published><updated>2026-06-16T01:00:01+00:00</updated><id>https://freshbrewed.science/2026/06/16/smallos</id><content type="html" xml:base="https://freshbrewed.science/2026/06/16/smallos.html"><![CDATA[<p>A while back there was a <a href="https://mariushosting.com/how-to-install-lifeglance-on-your-synology-nas/">Marius post</a> about <a href="https://github.com/krelltunez/lifeGLANCE">LifeGLANCE</a>.  As I checked into it, I found the author, <a href="https://github.com/krelltunez/">krelltunez</a> has another interesting app, <a href="https://github.com/krelltunez/dayGLANCE">dayGLANCE</a> which is a standalone planner with integrations and minimal lockins.</p>

<p>Let’s give both a fair shake, running in both Docker and Kubernetes.  Let’s start with lifeGLANCE</p>

<h1 id="lifeglance">lifeGLANCE</h1>

<p>We can begin by using the docker invokation</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker run -d \
  -p 8080:80 \
  --restart unless-stopped \
  ghcr.io/krelltunez/lifeglance:latest
</code></pre></div></div>

<p>Since port 8080 is already in use for me, I’ll use 8088</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker run -d -p 8088:80 --restart unless-stopped ghcr.io/krelltunez/lifeglance:latest
0561550355c092ff91109a2ab94624537f7763f9817f949371d5180783f1dcc3
</code></pre></div></div>

<p>I can see it up and running on 8088</p>

<p><a href="/content/images/2026/06/lifeglance-01.png"><img src="/content/images/2026/06/lifeglance-01.png" alt="/content/images/2026/06/lifeglance-01.png" /></a></p>

<p>It now starts to ask me some questions</p>

<p><a href="/content/images/2026/06/lifeglance-02.png"><img src="/content/images/2026/06/lifeglance-02.png" alt="/content/images/2026/06/lifeglance-02.png" /></a></p>

<p>It’s easy to add in or edit entries</p>

<p><a href="/content/images/2026/06/lifeglance-03.png"><img src="/content/images/2026/06/lifeglance-03.png" alt="/content/images/2026/06/lifeglance-03.png" /></a></p>

<p>You can put in events like birth of kids</p>

<p><a href="/content/images/2026/06/lifeglance-04.png"><img src="/content/images/2026/06/lifeglance-04.png" alt="/content/images/2026/06/lifeglance-04.png" /></a></p>

<p>But when events are close as you see above, there is a “2” which we can click in on and see more details</p>

<p><a href="/content/images/2026/06/lifeglance-05.png"><img src="/content/images/2026/06/lifeglance-05.png" alt="/content/images/2026/06/lifeglance-05.png" /></a></p>

<p>Now, I could imagine adding lots of things but for now, that can suffice for personal things</p>

<p>We can pull stats</p>

<p><a href="/content/images/2026/06/lifeglance-06.png"><img src="/content/images/2026/06/lifeglance-06.png" alt="/content/images/2026/06/lifeglance-06.png" /></a></p>

<p>While there does not appear to be a REST API I do see we can export and import JSON that looks like</p>

<p><a href="/content/images/2026/06/lifeglance-07.png"><img src="/content/images/2026/06/lifeglance-07.png" alt="/content/images/2026/06/lifeglance-07.png" /></a></p>

<p>I thought, what if I asked Gemini CLI?</p>

<p><a href="/content/images/2026/06/lifeglance-08.png"><img src="/content/images/2026/06/lifeglance-08.png" alt="/content/images/2026/06/lifeglance-08.png" /></a></p>

<p>It looked like this</p>

<p><a href="/content/images/2026/06/lifeglance-09.png"><img src="/content/images/2026/06/lifeglance-09.png" alt="/content/images/2026/06/lifeglance-09.png" /></a></p>

<p>It suggested it worked</p>

<p><a href="/content/images/2026/06/lifeglance-10.png"><img src="/content/images/2026/06/lifeglance-10.png" alt="/content/images/2026/06/lifeglance-10.png" /></a></p>

<p>I’ll import it now</p>

<p><a href="/content/images/2026/06/lifeglance-11.png"><img src="/content/images/2026/06/lifeglance-11.png" alt="/content/images/2026/06/lifeglance-11.png" /></a></p>

<p>I now have a nice interactive timeline for Kubernetes</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/lifeglance-12.mp4" type="video/mp4" />
</video>

<p>I then asked for one with Google Pixel phones</p>

<p><a href="/content/images/2026/06/lifeglance-13.png"><img src="/content/images/2026/06/lifeglance-13.png" alt="/content/images/2026/06/lifeglance-13.png" /></a></p>

<p>It did some lookups on UUIDs</p>

<p><a href="/content/images/2026/06/lifeglance-14.png"><img src="/content/images/2026/06/lifeglance-14.png" alt="/content/images/2026/06/lifeglance-14.png" /></a></p>

<p>Which worked great</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/lifeglance-12.mp4" type="video/mp4" />
</video>

<p>Let’s get a Kubernetes YAML manifest going</p>

<p><a href="/content/images/2026/06/lifeglance-16.png"><img src="/content/images/2026/06/lifeglance-16.png" alt="/content/images/2026/06/lifeglance-16.png" /></a></p>

<p>It finished pretty quickly</p>

<p><a href="/content/images/2026/06/lifeglance-17.png"><img src="/content/images/2026/06/lifeglance-17.png" alt="/content/images/2026/06/lifeglance-17.png" /></a></p>

<p>It created</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Namespace</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">lifeglance</span>

<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">lifeglance</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">lifeglance</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">lifeglance</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">replicas</span><span class="pi">:</span> <span class="m">1</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">matchLabels</span><span class="pi">:</span>
      <span class="na">app</span><span class="pi">:</span> <span class="s">lifeglance</span>
  <span class="na">template</span><span class="pi">:</span>
    <span class="na">metadata</span><span class="pi">:</span>
      <span class="na">labels</span><span class="pi">:</span>
        <span class="na">app</span><span class="pi">:</span> <span class="s">lifeglance</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">containers</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">lifeglance</span>
        <span class="na">image</span><span class="pi">:</span> <span class="s">lifeglance:latest</span> <span class="c1"># Replace with your actual image</span>
        <span class="na">imagePullPolicy</span><span class="pi">:</span> <span class="s">IfNotPresent</span>
        <span class="na">ports</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">80</span>
        <span class="c1"># volumeMounts:</span>
        <span class="c1"># - name: storage</span>
        <span class="c1">#   mountPath: /app/data # Optional path if the app ever needs server-side storage</span>
      <span class="c1"># volumes:</span>
      <span class="c1"># - name: storage</span>
      <span class="c1">#   persistentVolumeClaim:</span>
      <span class="c1">#     claimName: lifeglance-pvc</span>

<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">lifeglance</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">lifeglance</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">lifeglance</span>
  <span class="na">ports</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
      <span class="na">port</span><span class="pi">:</span> <span class="m">80</span>
      <span class="na">targetPort</span><span class="pi">:</span> <span class="m">80</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">ClusterIP</span>

<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Ingress</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">lifeglance</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">lifeglance</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">kubernetes.io/ingress.class</span><span class="pi">:</span> <span class="s">nginx</span>
    <span class="c1"># Uncomment and adjust for SSL if needed</span>
    <span class="c1"># cert-manager.io/cluster-issuer: "letsencrypt-prod"</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">rules</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">host</span><span class="pi">:</span> <span class="s">lifeglance.local</span> <span class="c1"># Replace with your domain</span>
    <span class="na">http</span><span class="pi">:</span>
      <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
        <span class="na">pathType</span><span class="pi">:</span> <span class="s">Prefix</span>
        <span class="na">backend</span><span class="pi">:</span>
          <span class="na">service</span><span class="pi">:</span>
            <span class="na">name</span><span class="pi">:</span> <span class="s">lifeglance</span>
            <span class="na">port</span><span class="pi">:</span>
              <span class="na">number</span><span class="pi">:</span> <span class="m">80</span>
  <span class="c1"># tls:</span>
  <span class="c1"># - hosts:</span>
  <span class="c1">#   - lifeglance.local</span>
  <span class="c1">#   secretName: lifeglance-tls</span>

<span class="nn">---</span>
<span class="c1"># Optional: PVC if server-side persistence is needed in the future</span>
<span class="c1"># apiVersion: v1</span>
<span class="c1"># kind: PersistentVolumeClaim</span>
<span class="c1"># metadata:</span>
<span class="c1">#   name: lifeglance-pvc</span>
<span class="c1">#   namespace: lifeglance</span>
<span class="c1"># spec:</span>
<span class="c1">#   accessModes:</span>
<span class="c1">#     - ReadWriteOnce</span>
<span class="c1">#   resources:</span>
<span class="c1">#     requests:</span>
<span class="c1">#       storage: 1Gi</span>
</code></pre></div></div>

<p>Let’s create a DNS entry</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gcloud dns --project=myanthosproject2 record-sets create lifeglance.steeped.icu --zone="steepedicu" --type="A" --ttl="300" --rrdatas="76.156.69.232"
NAME                     TYPE  TTL  DATA
lifeglance.steeped.icu.  A     300  76.156.69.232
</code></pre></div></div>

<p>I tweaked the manifest, removing the namespace and fixing the ingress</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">$ cat ./k8s-manifest.yaml</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">lifeglance</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">lifeglance</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">replicas</span><span class="pi">:</span> <span class="m">1</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">matchLabels</span><span class="pi">:</span>
      <span class="na">app</span><span class="pi">:</span> <span class="s">lifeglance</span>
  <span class="na">template</span><span class="pi">:</span>
    <span class="na">metadata</span><span class="pi">:</span>
      <span class="na">labels</span><span class="pi">:</span>
        <span class="na">app</span><span class="pi">:</span> <span class="s">lifeglance</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">containers</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">lifeglance</span>
        <span class="na">image</span><span class="pi">:</span>  <span class="s">ghcr.io/krelltunez/lifeglance</span>
        <span class="na">imagePullPolicy</span><span class="pi">:</span> <span class="s">IfNotPresent</span>
        <span class="na">ports</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">80</span>
        <span class="na">volumeMounts</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">storage</span>
          <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/app/data</span> <span class="c1"># Optional path if the app ever needs server-side storage</span>
      <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">storage</span>
        <span class="na">persistentVolumeClaim</span><span class="pi">:</span>
          <span class="na">claimName</span><span class="pi">:</span> <span class="s">lifeglance-pvc</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">lifeglance</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">lifeglance</span>
  <span class="na">ports</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
      <span class="na">port</span><span class="pi">:</span> <span class="m">80</span>
      <span class="na">targetPort</span><span class="pi">:</span> <span class="m">80</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">ClusterIP</span>

<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Ingress</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">lifeglance-ingress</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">kubernetes.io/ingress.class</span><span class="pi">:</span> <span class="s">nginx</span>
    <span class="na">cert-manager.io/cluster-issuer</span><span class="pi">:</span> <span class="s">gcpleprod2</span>
    <span class="na">ingress.kubernetes.io/proxy-body-size</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0"</span>
    <span class="na">ingress.kubernetes.io/ssl-redirect</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
    <span class="na">kubernetes.io/tls-acme</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
    <span class="na">nginx.ingress.kubernetes.io/proxy-body-size</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0"</span>
    <span class="na">nginx.ingress.kubernetes.io/proxy-read-timeout</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3600"</span>
    <span class="na">nginx.ingress.kubernetes.io/proxy-send-timeout</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3600"</span>
    <span class="na">nginx.ingress.kubernetes.io/ssl-redirect</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
    <span class="na">nginx.org/client-max-body-size</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0"</span>
    <span class="na">nginx.org/proxy-connect-timeout</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3600"</span>
    <span class="na">nginx.org/proxy-read-timeout</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3600"</span>
    <span class="na">nginx.org/websocket-services</span><span class="pi">:</span> <span class="s">lifeglance</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">rules</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">host</span><span class="pi">:</span> <span class="s">lifeglance.steeped.icu</span>
    <span class="na">http</span><span class="pi">:</span>
      <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
        <span class="na">pathType</span><span class="pi">:</span> <span class="s">Prefix</span>
        <span class="na">backend</span><span class="pi">:</span>
          <span class="na">service</span><span class="pi">:</span>
            <span class="na">name</span><span class="pi">:</span> <span class="s">lifeglance</span>
            <span class="na">port</span><span class="pi">:</span>
              <span class="na">number</span><span class="pi">:</span> <span class="m">80</span>
  <span class="na">tls</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">lifeglance.steeped.icu</span>
    <span class="na">secretName</span><span class="pi">:</span> <span class="s">lifeglancegcp-tls</span>
<span class="nn">---</span>
<span class="c1"># Optional: PVC if server-side persistence is needed in the future</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">PersistentVolumeClaim</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">lifeglance-pvc</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">accessModes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">ReadWriteOnce</span>
  <span class="na">resources</span><span class="pi">:</span>
    <span class="na">requests</span><span class="pi">:</span>
      <span class="na">storage</span><span class="pi">:</span> <span class="s">1Gi</span>
</code></pre></div></div>

<p>Once I see the cert is satisfied</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get cert | grep lifeglance
lifeglancegcp-tls                   True    lifeglancegcp-tls                   2m20s
</code></pre></div></div>

<p>which worked</p>

<p><a href="/content/images/2026/06/lifeglance-18.png"><img src="/content/images/2026/06/lifeglance-18.png" alt="/content/images/2026/06/lifeglance-18.png" /></a></p>

<p>However, without auth or limits, anyone could come in and change things</p>

<p><a href="/content/images/2026/06/lifeglance-19.png"><img src="/content/images/2026/06/lifeglance-19.png" alt="/content/images/2026/06/lifeglance-19.png" /></a></p>

<h1 id="dayglance">DayGLANCE</h1>

<p>Another one <a href="https://mariushosting.com/how-to-install-dayglance-on-your-synology-nas/">from Marius</a> and <a href="https://github.com/krelltunez/">Krelltunez</a> comes <a href="https://github.com/krelltunez/dayglance">Dayglance</a>.</p>

<p>I can pull the repo and do a docker compose up</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(base) builder@LuiGi:~/Workspaces/dayGLANCE$ docker compose up
[+] Running 15/15
 ✔ dayglance Pulled                                                                                                                                                                                                                                       9.7s
   ✔ 6a0ac1617861 Already exists                                                                                                                                                                                                                          0.0s
   ✔ abaae85d1626 Pull complete                                                                                                                                                                                                                           0.9s
   ✔ 43f834d60d8a Pull complete                                                                                                                                                                                                                           1.0s
   ✔ de1b677d8c00 Pull complete                                                                                                                                                                                                                           1.0s
   ✔ 94d083cf706a Pull complete                                                                                                                                                                                                                           1.1s
   ✔ e654dbbbb9e1 Pull complete                                                                                                                                                                                                                           1.2s
   ✔ a5008f4a4b25 Pull complete                                                                                                                                                                                                                           1.2s
   ✔ fbaed3f7fcbe Pull complete                                                                                                                                                                                                                           4.9s
   ✔ 00b2445c86b6 Pull complete                                                                                                                                                                                                                           8.2s
   ✔ 3f5ad9a9facb Pull complete                                                                                                                                                                                                                           8.3s
   ✔ a2a9beac1ed4 Pull complete                                                                                                                                                                                                                           8.3s
   ✔ db0cd702728e Pull complete                                                                                                                                                                                                                           8.4s
   ✔ c2ef2a718465 Pull complete                                                                                                                                                                                                                           8.5s
   ✔ f62856b017a8 Pull complete                                                                                                                                                                                                                           8.5s
[+] Running 2/2
 ✔ Network dayglance_default  Created                                                                                                                                                                                                                     0.1s
 ✔ Container dayglance        Created                                                                                                                                                                                                                     0.1s
Attaching to dayglance
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: using the "epoll" event method
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: nginx/1.31.1
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: built by gcc 15.2.0 (Alpine 15.2.0)
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: OS: Linux 6.17.0-23-generic
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1073741816:1073741816
dayglance  | Proxy server listening on 127.0.0.1:3001
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker processes
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 13
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 14
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 15
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 16
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 17
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 18
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 19
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 20
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 21
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 22
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 23
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 24
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 25
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 26
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 27
dayglance  | 2026/06/11 21:49:41 [notice] 1#1: start worker process 28
</code></pre></div></div>

<p>Now localhost:6767 brings up the welcome page</p>

<p><a href="/content/images/2026/06/lifeglance-20.png"><img src="/content/images/2026/06/lifeglance-20.png" alt="/content/images/2026/06/lifeglance-20.png" /></a></p>

<p>We can now use the “+” to add a new scheduled task</p>

<p><a href="/content/images/2026/06/dayglance-02.png"><img src="/content/images/2026/06/dayglance-02.png" alt="/content/images/2026/06/dayglance-02.png" /></a></p>

<p>Once a scheduled task is created, we can setup subtasks, notes, move to tomorrow, etc</p>

<p><a href="/content/images/2026/06/dayglance-03.png"><img src="/content/images/2026/06/dayglance-03.png" alt="/content/images/2026/06/dayglance-03.png" /></a></p>

<p>Notes take in markdown</p>

<p><a href="/content/images/2026/06/dayglance-04.png"><img src="/content/images/2026/06/dayglance-04.png" alt="/content/images/2026/06/dayglance-04.png" /></a></p>

<p>So when viewed later, you can see things like bold or bulleted lists</p>

<p><a href="/content/images/2026/06/dayglance-05.png"><img src="/content/images/2026/06/dayglance-05.png" alt="/content/images/2026/06/dayglance-05.png" /></a></p>

<p>We then have inbox tasks which can have colour, priority and tags</p>

<p><a href="/content/images/2026/06/dayglance-06.png"><img src="/content/images/2026/06/dayglance-06.png" alt="/content/images/2026/06/dayglance-06.png" /></a></p>

<p>I can then easily drag an inbox task into the timeline</p>

<p><a href="/content/images/2026/06/dayglance-07.png"><img src="/content/images/2026/06/dayglance-07.png" alt="/content/images/2026/06/dayglance-07.png" /></a></p>

<p><strong>However</strong> you can move it back out of the timeline using the inbox icon</p>

<p><a href="/content/images/2026/06/dayglance-13.png"><img src="/content/images/2026/06/dayglance-13.png" alt="/content/images/2026/06/dayglance-13.png" /></a></p>

<p>We can also create reminders</p>

<p><a href="/content/images/2026/06/dayglance-08.png"><img src="/content/images/2026/06/dayglance-08.png" alt="/content/images/2026/06/dayglance-08.png" /></a></p>

<p>There is also multi-user mode, such as for a household</p>

<p><a href="/content/images/2026/06/dayglance-09.png"><img src="/content/images/2026/06/dayglance-09.png" alt="/content/images/2026/06/dayglance-09.png" /></a></p>

<p>I can now pick a person for a task (useful for things like dishes or laundry)</p>

<p><a href="/content/images/2026/06/dayglance-10.png"><img src="/content/images/2026/06/dayglance-10.png" alt="/content/images/2026/06/dayglance-10.png" /></a></p>

<p>As far as CloudSync goes, it can handle any WebDAV destination like Koofr or Nextcloud</p>

<p><a href="/content/images/2026/06/dayglance-11.png"><img src="/content/images/2026/06/dayglance-11.png" alt="/content/images/2026/06/dayglance-11.png" /></a></p>

<p>For those metric driven, the bottom left will give us stats for the week</p>

<p><a href="/content/images/2026/06/dayglance-12.png"><img src="/content/images/2026/06/dayglance-12.png" alt="/content/images/2026/06/dayglance-12.png" /></a></p>

<p>We can configure local and remote backups. Remote backups can only go to a WebDAV URL</p>

<p><a href="/content/images/2026/06/dayglance-14.png"><img src="/content/images/2026/06/dayglance-14.png" alt="/content/images/2026/06/dayglance-14.png" /></a></p>

<p>However, clicking “export” will download a JSON file you can save for safekeeping locally</p>

<p><a href="/content/images/2026/06/dayglance-15.png"><img src="/content/images/2026/06/dayglance-15.png" alt="/content/images/2026/06/dayglance-15.png" /></a></p>

<p>which we can see in the history</p>

<p><a href="/content/images/2026/06/dayglance-16.png"><img src="/content/images/2026/06/dayglance-16.png" alt="/content/images/2026/06/dayglance-16.png" /></a></p>

<p>I asked Gemini CLI where the data is stored because i searched all over the container but couldn’t find it.</p>

<p><a href="/content/images/2026/06/dayglance-17.png"><img src="/content/images/2026/06/dayglance-17.png" alt="/content/images/2026/06/dayglance-17.png" /></a></p>

<p>Gemini CLI, which will be dead soon, told me it was stateless and in a browser.</p>

<p>That seemed a bit crazy, so i fired up a different browser and indeed it was refreshed!</p>

<p><a href="/content/images/2026/06/dayglance-18.png"><img src="/content/images/2026/06/dayglance-18.png" alt="/content/images/2026/06/dayglance-18.png" /></a></p>

<p>This is an interesting offering in that the data is <em>only in the users browser</em> so theoretically we could host this on a URL and each person gets their own view when accessing it.</p>

<p>Like before, let’s create a DNS entry</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gcloud dns --project=myanthosproject2 record-sets create dayglance.steeped.icu --zone="steepedicu" --type="A" --ttl="300" --rrdatas="76.156.69.232"
NAME                    TYPE  TTL  DATA
dayglance.steeped.icu.  A     300  76.156.69.232
</code></pre></div></div>

<p>We can the create a kubernetes.yaml manifest (thanks again Gemini CLI)</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">$ cat kubernetes.yaml</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">dayglance</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">dayglance</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">replicas</span><span class="pi">:</span> <span class="m">2</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">matchLabels</span><span class="pi">:</span>
      <span class="na">app</span><span class="pi">:</span> <span class="s">dayglance</span>
  <span class="na">template</span><span class="pi">:</span>
    <span class="na">metadata</span><span class="pi">:</span>
      <span class="na">labels</span><span class="pi">:</span>
        <span class="na">app</span><span class="pi">:</span> <span class="s">dayglance</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">containers</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">dayglance</span>
        <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/krelltunez/dayglance:latest</span>
        <span class="na">imagePullPolicy</span><span class="pi">:</span> <span class="s">IfNotPresent</span>
        <span class="na">ports</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">80</span>
        <span class="na">resources</span><span class="pi">:</span>
          <span class="na">requests</span><span class="pi">:</span>
            <span class="na">cpu</span><span class="pi">:</span> <span class="s">100m</span>
            <span class="na">memory</span><span class="pi">:</span> <span class="s">128Mi</span>
          <span class="na">limits</span><span class="pi">:</span>
            <span class="na">cpu</span><span class="pi">:</span> <span class="s">500m</span>
            <span class="na">memory</span><span class="pi">:</span> <span class="s">512Mi</span>
        <span class="c1"># Liveness and readiness probes ensure the app is healthy before serving traffic</span>
        <span class="na">livenessProbe</span><span class="pi">:</span>
          <span class="na">httpGet</span><span class="pi">:</span>
            <span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
            <span class="na">port</span><span class="pi">:</span> <span class="m">80</span>
          <span class="na">initialDelaySeconds</span><span class="pi">:</span> <span class="m">5</span>
          <span class="na">periodSeconds</span><span class="pi">:</span> <span class="m">10</span>
        <span class="na">readinessProbe</span><span class="pi">:</span>
          <span class="na">httpGet</span><span class="pi">:</span>
            <span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
            <span class="na">port</span><span class="pi">:</span> <span class="m">80</span>
          <span class="na">initialDelaySeconds</span><span class="pi">:</span> <span class="m">2</span>
          <span class="na">periodSeconds</span><span class="pi">:</span> <span class="m">5</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">dayglance</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">dayglance</span>
  <span class="na">ports</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
      <span class="na">port</span><span class="pi">:</span> <span class="m">80</span>
      <span class="na">targetPort</span><span class="pi">:</span> <span class="m">80</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">ClusterIP</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Ingress</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">dayglance-ingress</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">kubernetes.io/ingress.class</span><span class="pi">:</span> <span class="s">nginx</span>
    <span class="na">cert-manager.io/cluster-issuer</span><span class="pi">:</span> <span class="s">gcpleprod2</span>
    <span class="na">ingress.kubernetes.io/proxy-body-size</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0"</span>
    <span class="na">ingress.kubernetes.io/ssl-redirect</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
    <span class="na">kubernetes.io/tls-acme</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
    <span class="na">nginx.ingress.kubernetes.io/proxy-body-size</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0"</span>
    <span class="na">nginx.ingress.kubernetes.io/proxy-read-timeout</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3600"</span>
    <span class="na">nginx.ingress.kubernetes.io/proxy-send-timeout</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3600"</span>
    <span class="na">nginx.ingress.kubernetes.io/ssl-redirect</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
    <span class="na">nginx.org/client-max-body-size</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0"</span>
    <span class="na">nginx.org/proxy-connect-timeout</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3600"</span>
    <span class="na">nginx.org/proxy-read-timeout</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3600"</span>
    <span class="na">nginx.org/websocket-services</span><span class="pi">:</span> <span class="s">dayglance</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">rules</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">host</span><span class="pi">:</span> <span class="s">dayglance.steeped.icu</span>
    <span class="na">http</span><span class="pi">:</span>
      <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
        <span class="na">pathType</span><span class="pi">:</span> <span class="s">Prefix</span>
        <span class="na">backend</span><span class="pi">:</span>
          <span class="na">service</span><span class="pi">:</span>
            <span class="na">name</span><span class="pi">:</span> <span class="s">dayglance</span>
            <span class="na">port</span><span class="pi">:</span>
              <span class="na">number</span><span class="pi">:</span> <span class="m">80</span>
  <span class="na">tls</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">dayglance.steeped.icu</span>
    <span class="na">secretName</span><span class="pi">:</span> <span class="s">dayglancegcp-tls</span>
</code></pre></div></div>

<p>Now I can fire it up</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl apply -f ./kubernetes.yaml
deployment.apps/dayglance created
service/dayglance created
Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
ingress.networking.k8s.io/dayglance-ingress created
</code></pre></div></div>

<p>The pods fired up right away</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get po | grep day
dayglance-5f8db5d55b-8d746                           1/1     Running            0                   36s
dayglance-5f8db5d55b-bgwbx                           1/1     Running            0                   36s
</code></pre></div></div>

<p>Once I saw the cert was satisfied</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(base) builder@LuiGi:~/Workspaces/dayGLANCE$ kubectl get cert dayglancegcp-tls
NAME               READY   SECRET             AGE
dayglancegcp-tls   False   dayglancegcp-tls   68s
(base) builder@LuiGi:~/Workspaces/dayGLANCE$ kubectl get cert dayglancegcp-tls
NAME               READY   SECRET             AGE
dayglancegcp-tls   True    dayglancegcp-tls   75s
</code></pre></div></div>

<p>I could fire up using the proper DNS name and https</p>

<p><a href="/content/images/2026/06/dayglance-19.png"><img src="/content/images/2026/06/dayglance-19.png" alt="/content/images/2026/06/dayglance-19.png" /></a></p>

<p>What is interesting is that this is entirely in the browser.  So if you jump between machines a lot, this won’t work, but otherwise it gives you a webapp with all the data stored only locally</p>

<p><a href="/content/images/2026/06/dayglance-20.png"><img src="/content/images/2026/06/dayglance-20.png" alt="/content/images/2026/06/dayglance-20.png" /></a></p>

<p>If one went this way, I would do a regular backup locally and store in a cloud storage tool like Dropbox/Onedrive or similar.</p>

<p><a href="/content/images/2026/06/dayglance-21.png"><img src="/content/images/2026/06/dayglance-21.png" alt="/content/images/2026/06/dayglance-21.png" /></a></p>

<h1 id="summary">Summary</h1>

<p>Both dayGLANCE and lifeGLANCE are very interesting tools.  I love the look and feel in both.  As local tools, I can see the value but they need an auth layer to make them really functional in a shared environment.  For instance, I cannot see how dayGLANCE would work with chores unless it’s used on a shared computer like a family room computer or smart fridge.</p>

<p>With lifeGLANCE I would love to add a read-only view and basic auth for editing.  I think this would be a fun app to share with CV’s for a job timeline view that was interactive.  Using it to plot other data (like Kubernetes versions) was easy enough with some LLM tooling.</p>

<p>I’ll leave both live and likely revisit <a href="https://github.com/krelltunez/">krelltunez</a> Github page to see what is new from time to time.</p>]]></content><author><name>Isaac Johnson</name></author><category term="containers" /><category term="kubernetes" /><category term="LifeGLANCE" /><category term="DayGLANCE" /><category term="opensource" /><category term="krelltunez" /><summary type="html"><![CDATA[A while back there was a Marius post about LifeGLANCE. As I checked into it, I found the author, krelltunez has another interesting app, dayGLANCE which is a standalone planner with integrations and minimal lockins.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://freshbrewed.science/content/images/2026/06/glanceappsg.png" /><media:content medium="image" url="https://freshbrewed.science/content/images/2026/06/glanceappsg.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Local LLM CLI: omp, goose and codex</title><link href="https://freshbrewed.science/2026/06/12/moreagents.html" rel="alternate" type="text/html" title="Local LLM CLI: omp, goose and codex" /><published>2026-06-12T01:00:01+00:00</published><updated>2026-06-12T01:00:01+00:00</updated><id>https://freshbrewed.science/2026/06/12/moreagents</id><content type="html" xml:base="https://freshbrewed.science/2026/06/12/moreagents.html"><![CDATA[<p>Today we’ll look at few different local LLM runners (a colleague I know calls them harnesses and that is another completely valid term).  Regardless of if you call them CLI Coding Tools or AI Coding Agents, let’s look at few newer interesting ones: <a href="https://omp.sh/">Oh My Pi</a> and <a href="https://goose-docs.ai/">Goose</a></p>

<p>I’ll also circle back on one I haven’t tried in a minute, <a href="https://chatgpt.com/codex">Codex from OpenAI</a> which can use any OpenAI compatible endpoint (for which Ollama is).</p>

<h1 id="omp---oh-my-pi">OMP - Oh My Pi</h1>

<p>They took Mario Zechner’s Pi agent which I love and use, but is very simple, and added some pretty key features.</p>

<p>If we review <a href="https://omp.sh/">omp.sh</a>, we see these include:</p>
<ul>
  <li>Tool calling</li>
  <li>Subagents</li>
  <li>web_search built in</li>
</ul>

<p>And lots more (they have 14 on the site, I just picked 3 that caught my eye)</p>

<p>Let’s <a href="https://github.com/can1357/oh-my-pi">got to the Github</a> for install steps.</p>

<p>I’ll start with curl</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl -fsSL https://omp.sh/install | sh
Fetching latest release...
Using version: v15.5.6
Downloading omp-linux-x64...

✓ Installed omp to /home/builder/.local/bin/omp
Run 'omp' to get started!
</code></pre></div></div>

<p>I can now run <code class="language-plaintext highlighter-rouge">omp</code> to launch oh-my-pi</p>

<p><a href="/content/images/2026/05/omp-01.png"><img src="/content/images/2026/05/omp-01.png" alt="/content/images/2026/05/omp-01.png" /></a></p>

<p>I had a bit of a challenge finding decent docs for adding local models, but eventually I figured out that we can use <code class="language-plaintext highlighter-rouge">ollama</code> and still use <code class="language-plaintext highlighter-rouge">discovery</code></p>

<p>We can see it auto-found the models</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat ~/.omp/agent/models.yml
providers:
  ollama:
    baseUrl: http://192.168.1.220:11434/v1
    api: openai-responses
    auth: none
    discovery:
      type: ollama
</code></pre></div></div>

<p><a href="/content/images/2026/05/omp-02.png"><img src="/content/images/2026/05/omp-02.png" alt="/content/images/2026/05/omp-02.png" /></a></p>

<p>Try as i might, i couldn’t get it to just show me files.  I hopped into a proper Ubuntu 26 host and tried there</p>

<p><a href="/content/images/2026/05/omp-04.png"><img src="/content/images/2026/05/omp-04.png" alt="/content/images/2026/05/omp-04.png" /></a></p>

<p>I even tried to tell it, explicitly, to use the read tool and it refused</p>

<p><a href="/content/images/2026/05/omp-05.png"><img src="/content/images/2026/05/omp-05.png" alt="/content/images/2026/05/omp-05.png" /></a></p>

<p>I circled back a bit later and tried it directly on Linux to just eliminate WSL issues.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@builder-Lenny16:~/Workspaces/viktui$  curl -fsSL https://omp.sh/install | sh
Installing via bun...
bun add v1.3.14 (0d9b296a)

installed @oh-my-pi/pi-coding-agent@15.11.0 with binaries:
 - omp

110 packages installed [3.99s]

Blocked 3 postinstalls. Run `bun pm -g untrusted` for details.

✓ Installed omp via bun
Run 'omp' to get started!
</code></pre></div></div>

<p>The boot loader has a sweat animation now</p>

<p><a href="/content/images/2026/06/omp-10.png"><img src="/content/images/2026/06/omp-10.png" alt="/content/images/2026/06/omp-10.png" /></a></p>

<p>But it didn’t seem to kick in with gemma4:12b on default settings</p>

<p><a href="/content/images/2026/06/omp-11.png"><img src="/content/images/2026/06/omp-11.png" alt="/content/images/2026/06/omp-11.png" /></a></p>

<p>I got the same thing on e4b with medium thinking. I tried rebooting the host just in case it was misbehaving.  I tried alternate models too like ministral-3:8b but it would just give one word answers and stop.</p>

<h1 id="codex">Codex</h1>

<p>I haven’t experimented recently with <code class="language-plaintext highlighter-rouge">Codex</code>, the CLI from OpenAI.</p>

<p>Let’s first install it</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install -g @openai/codex
</code></pre></div></div>

<p>I <em>should</em> be able to fire it up with <code class="language-plaintext highlighter-rouge">ollama launch codex</code>, but after picking my local model, it vomits</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ollama launch codex
Error loading config.toml: --profile `ollama-launch` cannot be used while /home/builder/.codex/config.toml contains legacy `profile = "ollama-launch"` or `[profiles.ollama-launch]` config; move those settings into /home/builder/.codex/ollama-launch.config.toml and remove the legacy profile selector/table. See https://developers.openai.com/codex/config-advanced#profiles for more information.
Error: exit status 1
</code></pre></div></div>

<p>Ollama wrote the profile in there</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat ~/.codex/config.toml
[profiles.ollama-launch]
openai_base_url = "http://127.0.0.1:11434/v1/"
model_provider = "ollama-launch"
forced_login_method = "api"

[model_providers.ollama-launch]
name = "Ollama"
base_url = "http://127.0.0.1:11434/v1/"
wire_api = "responses"
</code></pre></div></div>

<p>But it seems to die when launched like that</p>

<p>I fired up codex with the <code class="language-plaintext highlighter-rouge">--oss</code> flag and it pulled down a 14Gb file, but it too seems to fail at showing me files</p>

<p><a href="/content/images/2026/05/codex-01.png"><img src="/content/images/2026/05/codex-01.png" alt="/content/images/2026/05/codex-01.png" /></a></p>

<p>Though I can confirm it is hosted in Ollama</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@builder-Lenny16:~/Workspaces/mytest2$ ollama list
NAME                                       ID              SIZE      MODIFIED
gpt-oss:20b                                17052f91a42e    13 GB     10 minutes ago
</code></pre></div></div>

<p>But it just keeps failing to show me files</p>

<p><a href="/content/images/2026/05/codex-02.png"><img src="/content/images/2026/05/codex-02.png" alt="/content/images/2026/05/codex-02.png" /></a></p>

<p>I tried later directly on the host and it refused to reply</p>

<p><a href="/content/images/2026/06/codex-01.png"><img src="/content/images/2026/06/codex-01.png" alt="/content/images/2026/06/codex-01.png" /></a></p>

<p>I thought, maybe if I used <code class="language-plaintext highlighter-rouge">ollama launch codex</code> on that same host it might work. <strong>FINALLY SUCCESS</strong></p>

<p><a href="/content/images/2026/06/codex-02.png"><img src="/content/images/2026/06/codex-02.png" alt="/content/images/2026/06/codex-02.png" /></a></p>

<p>This really worked slick</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/codex-03.mp4" type="video/mp4" />
</video>

<p>This is the diagram in SYSTEM.md it created with gemma4:e4b</p>

<p><a href="/content/images/2026/06/codex-04.png"><img src="/content/images/2026/06/codex-04.png" alt="/content/images/2026/06/codex-04.png" /></a></p>

<h1 id="goose">Goose</h1>

<p>Let’s install <a href="https://goose-docs.ai/docs/getting-started/installation/">from the docs</a></p>

<p><a href="/content/images/2026/05/goose-01.png"><img src="/content/images/2026/05/goose-01.png" alt="/content/images/2026/05/goose-01.png" /></a></p>

<p>Let’s do the test that all these CLIs seem to be failing… list some files</p>

<p><a href="/content/images/2026/05/goose-02.png"><img src="/content/images/2026/05/goose-02.png" alt="/content/images/2026/05/goose-02.png" /></a></p>

<p>let’s hold on the liver patte… this Goose is cooking.</p>

<video muted="" controls="">
    <source src="/content/images/2026/05/goose-02.mp4" type="video/mp4" />
</video>

<p>As we can see, it created a helpers file, but neglected to do the rest.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@builder-Lenny16:~/Workspaces/mytest2$ tree .
.
├── CONTRIBUTING.md
├── Dockerfile
├── k8s
│   ├── deployment.yaml
│   ├── ingress.yaml
│   └── service.yaml
├── README.md
├── requirements.txt
├── server.py
└── templates
    └── _helpers.tpl

3 directories, 9 files
</code></pre></div></div>

<p>My second try worked better</p>

<p><a href="/content/images/2026/05/goose-03.png"><img src="/content/images/2026/05/goose-03.png" alt="/content/images/2026/05/goose-03.png" /></a></p>

<p>But still wasn’t quite there</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@builder-Lenny16:~/Workspaces/mytest2$ tree .
.
├── CONTRIBUTING.md
├── Dockerfile
├── helm
│   └── mychart
│       ├── Chart.yaml
│       ├── templates
│       └── values.yaml
├── k8s
│   ├── deployment.yaml
│   ├── ingress.yaml
│   └── service.yaml
├── README.md
├── requirements.txt
├── server.py
└── templates
    └── _helpers.tpl

6 directories, 11 files
</code></pre></div></div>

<p>I fought gemma:e4b for a while before coming back to gpt-oss:20b.</p>

<p><a href="/content/images/2026/05/goose-04.png"><img src="/content/images/2026/05/goose-04.png" alt="/content/images/2026/05/goose-04.png" /></a></p>

<h1 id="bye-bye-gca">BYE BYE GCA</h1>

<p>While GCA still works today, and can describe files</p>

<p><a href="/content/images/2026/05/cline2-04.png"><img src="/content/images/2026/05/cline2-04.png" alt="/content/images/2026/05/cline2-04.png" /></a></p>

<p>The fact is that <a href="https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/">come June 18 2026 it will stop working</a> for those of us with Paid AI Pro accounts to move to the <a href="https://github.com/google-antigravity/antigravity-cli">closed-source Antigravity</a> CLI/Desktop app.</p>

<p>which sucks.  However, we have options for local LLMs</p>

<h1 id="cline-in-vs-code">Cline in VS Code</h1>

<p>I installed Cline via the extensions marketplace</p>

<p><a href="/content/images/2026/05/cline2-05.png"><img src="/content/images/2026/05/cline2-05.png" alt="/content/images/2026/05/cline2-05.png" /></a></p>

<p>I then configured it to use the gaming laptop</p>

<p><a href="/content/images/2026/05/cline2-01.png"><img src="/content/images/2026/05/cline2-01.png" alt="/content/images/2026/05/cline2-01.png" /></a></p>

<p>I then asked it to describe the project in the directory</p>

<p><a href="/content/images/2026/05/cline2-02.png"><img src="/content/images/2026/05/cline2-02.png" alt="/content/images/2026/05/cline2-02.png" /></a></p>

<p>This was entirely accurate! finally a tool that can read files</p>

<p><a href="/content/images/2026/05/cline2-03.png"><img src="/content/images/2026/05/cline2-03.png" alt="/content/images/2026/05/cline2-03.png" /></a></p>

<h1 id="continuedev">Continue.dev</h1>

<p>I can install, as I did with Cline, from the extensions marketplace</p>

<p><a href="/content/images/2026/05/continuedev-01.png"><img src="/content/images/2026/05/continuedev-01.png" alt="/content/images/2026/05/continuedev-01.png" /></a></p>

<p>Continue.dev does amazing work and it’s worth (to me) to always switch to the latest pre-release version</p>

<p><a href="/content/images/2026/05/continuedev-02.png"><img src="/content/images/2026/05/continuedev-02.png" alt="/content/images/2026/05/continuedev-02.png" /></a></p>

<p>I’ll open up the config, and in my case it is YAML so I’ll use</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Local Config</span>
<span class="na">version</span><span class="pi">:</span> <span class="s">1.0.0</span>
<span class="na">schema</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">models</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">gemma4-e4b-220</span>
    <span class="na">provider</span><span class="pi">:</span> <span class="s">ollama</span>
    <span class="na">model</span><span class="pi">:</span> <span class="s2">"</span><span class="s">gemma4:e4b"</span>
    <span class="na">apiBase</span><span class="pi">:</span> <span class="s">http://192.168.1.220:11434</span>
</code></pre></div></div>

<p>I’m now going to fire off a request to look at the same files, this time using gemma4:e4b on the gaming laptop</p>

<p><a href="/content/images/2026/05/continuedev-03.png"><img src="/content/images/2026/05/continuedev-03.png" alt="/content/images/2026/05/continuedev-03.png" /></a></p>

<p>I can also just pass it a file</p>

<p><a href="/content/images/2026/05/continuedev-04.png"><img src="/content/images/2026/05/continuedev-04.png" alt="/content/images/2026/05/continuedev-04.png" /></a></p>

<h1 id="codex-vs-goose">Codex vs Goose</h1>

<p>I think I like both of these tools right now.  So let’s compare them</p>

<p>Last time I used Goose, i was using a small model, so I’ll use <code class="language-plaintext highlighter-rouge">goose configure</code> to change that.</p>

<p>I’ll use gemma4:12b for this</p>

<p><a href="/content/images/2026/06/goose-10.png"><img src="/content/images/2026/06/goose-10.png" alt="/content/images/2026/06/goose-10.png" /></a></p>

<p>As you can see, both e4b and 12b just failed to do anything with goose</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/goose-11.mp4" type="video/mp4" />
</video>

<p>We can see I have similar issues with Codex with both 12b and e4b. It starts but just sort of times out eventually</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/codex-06.mp4" type="video/mp4" />
</video>

<h1 id="summary">Summary</h1>

<p>These tools initially suffered from some issues reading files.  In some cases I got past that only to have them start to fall down on anything complicated.</p>

<p>In some cases, I could get an older model like qwen3:14b to give some code, but not write it</p>

<p><a href="/content/images/2026/06/codex-07.png"><img src="/content/images/2026/06/codex-07.png" alt="/content/images/2026/06/codex-07.png" /></a></p>

<p>Or the older qwen2.5 coder spit back code, but wouldn’t update the file</p>

<p><a href="/content/images/2026/06/codex-08.png"><img src="/content/images/2026/06/codex-08.png" alt="/content/images/2026/06/codex-08.png" /></a></p>

<p>(though Goose didn’t do the same)</p>

<p><a href="/content/images/2026/06/goose-12.png"><img src="/content/images/2026/06/goose-12.png" alt="/content/images/2026/06/goose-12.png" /></a></p>

<p>So i guess the answer is they are fair, but I’ll likely stick with Cline for my local Harness for now.</p>

<p>More interesting to me was how well Cline works in VS Code.  Having that as an option against Continue.dev will really help in the absense of GCA.</p>]]></content><author><name>Isaac Johnson</name></author><category term="GenAI" /><category term="omp" /><category term="goose" /><category term="codex" /><category term="GCA" /><category term="Ollama" /><summary type="html"><![CDATA[Today we’ll look at few different local LLM runners (a colleague I know calls them harnesses and that is another completely valid term). Regardless of if you call them CLI Coding Tools or AI Coding Agents, let’s look at few newer interesting ones: Oh My Pi and Goose]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://freshbrewed.science/content/images/2026/06/gooseompbg.png" /><media:content medium="image" url="https://freshbrewed.science/content/images/2026/06/gooseompbg.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Gemma4 12b with Cline and Continue</title><link href="https://freshbrewed.science/2026/06/09/clinegemma4.html" rel="alternate" type="text/html" title="Gemma4 12b with Cline and Continue" /><published>2026-06-09T01:00:01+00:00</published><updated>2026-06-09T01:00:01+00:00</updated><id>https://freshbrewed.science/2026/06/09/clinegemma4</id><content type="html" xml:base="https://freshbrewed.science/2026/06/09/clinegemma4.html"><![CDATA[<p>Just this last week <a href="https://developers.googleblog.com/gemma-4-12b-the-developer-guide/">Google rolled out Gemma4 12b</a> which is really interesting to me because the 20+ billion models are a bit much for my hardware, but the 8b and less have some limitations.</p>

<p>12 billion models are a real butter zone for the hardware I have.  It is not direct, but I try to find a model where billion matches my Gbs of VRAM.</p>

<p>I didn’t want to jump in right away because initially it didn’t work in released <a href="https://ollama.com/search">Ollama</a> until the end of the week.  And moreover, I don’t like to rush articles just to catch news cycles.</p>

<p>This weekend I put it through the paces.  The first half was written using spotty WiFi during a swim meet at <a href="https://gophersports.com/sports/2018/5/21/facilities-aquatic-center-html">the Freeman Aquatic Center at the U of M</a> over a hidden port routed back to my gaming laptop.  The second half (adding sort order) was locally in network.</p>

<h1 id="usage">usage</h1>

<p>First, I had to wait till around Friday when I could finally get a released Ollama that worked (the 0.30.3 was fixed on the releases and the download of 0.4 failed as a pre-release).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@builder-Lenny16:~$ !52
curl -fsSL https://ollama.com/install.sh | sh
&gt;&gt;&gt; Cleaning up old version at /usr/local/lib/ollama
&gt;&gt;&gt; Installing ollama to /usr/local
&gt;&gt;&gt; Downloading ollama-linux-amd64.tar.zst
######################################################################## 100.0%
&gt;&gt;&gt; Adding ollama user to render group...
&gt;&gt;&gt; Adding ollama user to video group...
&gt;&gt;&gt; Adding current user to ollama group...
&gt;&gt;&gt; Creating ollama systemd service...
&gt;&gt;&gt; Enabling and starting ollama service...
&gt;&gt;&gt; NVIDIA GPU installed.
builder@builder-Lenny16:~$ ollama --version
ollama version is 0.30.5
Warning: client version is 0.30.4-rc0
builder@builder-Lenny16:~$ ollama pull gemma4:12b
pulling manifest
pulling 1278394b6936: 100% ▕████████████████████████████████████████████████████████████████████████████▏ 7.4 GB
pulling 675ad6e68101: 100% ▕████████████████████████████████████████████████████████████████████████████▏ 175 MB
pulling 0d542e0c8804: 100% ▕████████████████████████████████████████████████████████████████████████████▏  10 KB
pulling 56380ca2ab89: 100% ▕████████████████████████████████████████████████████████████████████████████▏   42 B
pulling c805f5b265d8: 100% ▕████████████████████████████████████████████████████████████████████████████▏  548 B
verifying sha256 digest
writing manifest
success
</code></pre></div></div>

<p>Next, I started down the path of using it to update a basic TUI I started (without AI) using a TUI framework.</p>

<p>As you can see it worked through a plan and came back successful:</p>

<p><a href="/content/images/2026/06/clineg4-01.png"><img src="/content/images/2026/06/clineg4-01.png" alt="/content/images/2026/06/clineg4-01.png" /></a></p>

<p>When done, I can “compact” the current task</p>

<p><a href="/content/images/2026/06/clineg4-02.png"><img src="/content/images/2026/06/clineg4-02.png" alt="/content/images/2026/06/clineg4-02.png" /></a></p>

<p>I got some bugs and started with cline CLI</p>

<p><a href="/content/images/2026/06/clineg4-03.png"><img src="/content/images/2026/06/clineg4-03.png" alt="/content/images/2026/06/clineg4-03.png" /></a></p>

<p>But it just wouldn’t move forward with a fix.  I tried the same model on the same host, but this time using the Cline extension in vs code.</p>

<p><a href="/content/images/2026/06/clineg4-04.png"><img src="/content/images/2026/06/clineg4-04.png" alt="/content/images/2026/06/clineg4-04.png" /></a></p>

<p>This time I saw it actively reviewing and fixing the file. Till the thought process started writing into the file…</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/clineg4-05.mp4" type="video/mp4" />
</video>

<p>I canceled then realized the real issue was just a bad regexp at some point left one errant line in the file (replace_in_file). i removed it myself and got past that error.</p>

<p>I tried a new prompt in the UI and found it would get a failure at times, but retry which worked <em>(as it stands, I’m trying this in a sports venue with poor signal so it could be an affect of the network)</em></p>

<p><a href="/content/images/2026/06/clineg4-06.png"><img src="/content/images/2026/06/clineg4-06.png" alt="/content/images/2026/06/clineg4-06.png" /></a></p>

<p>and completed:</p>

<p><a href="/content/images/2026/06/clineg4-07.png"><img src="/content/images/2026/06/clineg4-07.png" alt="/content/images/2026/06/clineg4-07.png" /></a></p>

<p>When a change is done, I can click “Explain changes”</p>

<p><a href="/content/images/2026/06/clineg4-08.png"><img src="/content/images/2026/06/clineg4-08.png" alt="/content/images/2026/06/clineg4-08.png" /></a></p>

<p>I did have a complicated problem and was able to use GCA (since they haven’t shut it off yet) to sort out</p>

<p><a href="/content/images/2026/06/clineg4-09.png"><img src="/content/images/2026/06/clineg4-09.png" alt="/content/images/2026/06/clineg4-09.png" /></a></p>

<h1 id="continue-vs-cline">Continue vs Cline</h1>

<p>Here we can see adding a sort option via Continue.dev.</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/clineg4-10.mp4" type="video/mp4" />
</video>

<p>It seemed to update the backend index.ts as I pointed the file to it, but neglected to add to the UI layer even when i set it to use the index.ts file.</p>

<p>let’s now try in Continue.dev</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/clineg4-11.mp4" type="video/mp4" />
</video>

<p>Here we see it worked but did time out on responses a few times.</p>

<p>Now I have working reverse sort order toggle:</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/clineg4-12.mp4" type="video/mp4" />
</video>

<h2 id="cline-cli">Cline CLI</h2>

<p>I tried the CLI again but couldn’t get it to get past the thinking block</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cline --thinking none --auto-approve ture --compaction agentic --retries 6 "the TUI in ./vikt
ui shows tasks. If i click a task, i would like to see details. Consult swagger in ./viktui/swagger.json"
[thinking] The user wants to implement a feature where clicking on a task in the TUI (Terminal User Interface) displays its details. They also mentioned consulting `swagger.json` for details about how tasks are structured or what fields are available.

First, I need to understand the current TUI implementation and how tasks are displayed.
Then, I'll examine `viktui/swagger.json` to find information about the "task" entity and its detail fields.
Finally, I will implement the logic to fetch/display details

$ cline "the TUI in ./viktui shows tasks. If i click a task, i would like to see details. Consult swagger in ./viktui/swagger.json"
[thinking] The user wants to implement a feature where clicking a task in the TUI displays its details. I need to understand how the TUI handles task selection and what "details" are available from the backend (as defined in the `swagger.json`).

Plan:
1. Explore the project structure to find relevant files for the TUI logic and data models.
2. Examine `viktui/swagger.json` to understand the API schema for tasks and their details.
3. Identify where task list items are rendered and where
</code></pre></div></div>

<p>or in interactive mode:</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/clineg4-21.mp4" type="video/mp4" />
</video>

<h1 id="hosted-gemma">Hosted Gemma</h1>

<p>I saw there are some Fireworks.AI hosted models via Azure AI Foundry but just for chat completion and they are locked out for me:</p>

<p><a href="/content/images/2026/06/clineg4-13.png"><img src="/content/images/2026/06/clineg4-13.png" alt="/content/images/2026/06/clineg4-13.png" /></a></p>

<p>You can get a hosted endpoint in Google Vertex AI (Agent Platform) for $5.18/hour which seems really high to me:</p>

<p><a href="/content/images/2026/06/clineg4-14.png"><img src="/content/images/2026/06/clineg4-14.png" alt="/content/images/2026/06/clineg4-14.png" /></a></p>

<p>There is no Gemma hosted model in AliCloud (though they have their own Qwen3.7)</p>

<p><a href="/content/images/2026/06/clineg4-15.png"><img src="/content/images/2026/06/clineg4-15.png" alt="/content/images/2026/06/clineg4-15.png" /></a></p>

<p>And AWS Bedrock just has Gemma3 at the time of this writing</p>

<p><a href="/content/images/2026/06/clineg4-16.png"><img src="/content/images/2026/06/clineg4-16.png" alt="/content/images/2026/06/clineg4-16.png" /></a></p>

<p>But the pricing is token based, not hourly so it could be a valid option if you lake GPU</p>

<p><a href="/content/images/2026/06/clineg4-17.png"><img src="/content/images/2026/06/clineg4-17.png" alt="/content/images/2026/06/clineg4-17.png" /></a></p>

<p>I do see some up-and-commers with Gemma4.  For instance, <a href="https://featherless.ai">featherless.ai</a> has all the Gemma4 models</p>

<p><a href="/content/images/2026/06/clineg4-18.png"><img src="/content/images/2026/06/clineg4-18.png" alt="/content/images/2026/06/clineg4-18.png" /></a></p>

<p>They have an unlimited buffet style <a href="https://featherless.ai/?_gl=1*iyxpvc*_up*MQ..*_gs*MQ..&amp;gclid=Cj0KCQjwrZTRBhDSARIsAHidYfddT6kMvhkRbw_UlyQPig0EkxRCx036u18pmhUCi33IfsS-Sj_NhcYaAmgEEALw_wcB&amp;gbraid=0AAAABArlnAERrGdiKorMzAGsbw7wl5y1I#pricing">pricing</a>:</p>

<p><a href="/content/images/2026/06/clineg4-19.png"><img src="/content/images/2026/06/clineg4-19.png" alt="/content/images/2026/06/clineg4-19.png" /></a></p>

<p>The $10 model looks like one could do small work - its not the model limitation that would be hard, its the 16k context limit that would make it pretty limiting for coding work.  But the 32k context $25 plan would be interesting.</p>

<p>Looking at <a href="https://featherless.ai/status">their status page</a> does show they have some uptime issues:</p>

<p><a href="/content/images/2026/06/clineg4-20.png"><img src="/content/images/2026/06/clineg4-20.png" alt="/content/images/2026/06/clineg4-20.png" /></a></p>

<h2 id="gemma-for-not-coding">Gemma for not coding</h2>

<p>I’ll try OpenWebUI which runs on that host to access Ollama</p>

<p><a href="/content/images/2026/06/clineg4-23.png"><img src="/content/images/2026/06/clineg4-23.png" alt="/content/images/2026/06/clineg4-23.png" /></a></p>

<p>I’m eager to see any reason I would want multi-agent setups for real world scenarios.</p>

<p>I’ll ask:</p>

<blockquote>
  <p>I would like to know why one would choose a multi-agent architecture (A2A, for instance) for a production setup when agents are inherently non-deterministic, hallucinate, require GPUs (so more cost), are often changing and are very hard to test.  What values do they provide that would override the more controlled nature of microservice design, kubernetes with it’s controls and monitoring, and the deterministic nature of proper language based applications?</p>
</blockquote>

<p><a href="/content/images/2026/06/clineg4-24.png"><img src="/content/images/2026/06/clineg4-24.png" alt="/content/images/2026/06/clineg4-24.png" /></a></p>

<p>I kept the argument going, following it up with</p>

<blockquote>
  <p>If agents can write the code, why not just use agents to write the complicated regexp and if-then-else logic - we would have more testable code that does not require GPUs nor persistently running agents. If the answer is to add more guardrails like judges, that seems just to inflate costs further with the hosts that adding more agents on agents will improve the quality. Why not just avoid the issue an only use agents for the human language interpretation (which I agree agents are uniquely adept to process)?</p>
</blockquote>

<p><a href="/content/images/2026/06/clineg4-25.png"><img src="/content/images/2026/06/clineg4-25.png" alt="/content/images/2026/06/clineg4-25.png" /></a></p>

<p>Twice it timed out in roughly the same spot so we’ll leave it there.</p>

<p>For comparison, I fired this at Gemini Pro (both queries together):</p>

<p>I actually want to share what Gemini 3.1 Pro replied with as it both confirmed my hypothesis but also challenged it as well:</p>

<h3 id="gemini-31-pro-on-the-question">Gemini 3.1 Pro on the question:</h3>
<hr />

<p>You are hitting on the exact friction point that seasoned architects are wrestling with right now. The short answer is: you are mostly right. For the vast majority of traditional enterprise workloads, replacing a deterministic, highly observable Kubernetes microservice with an LLM-driven agent is an architectural downgrade. It introduces latency, massive cost overhead, and non-determinism where predictability is required.</p>

<p>If a workflow can be expressed as a Directed Acyclic Graph (DAG), a state machine, or a complex tree of <code class="language-plaintext highlighter-rouge">if-then-else</code> logic, it should be.</p>

<p>However, there is a specific threshold where multi-agent (A2A) architectures become necessary. Here is the engineering rationale for when and why that trade-off is actually worth the GPU tax and testing headaches.</p>

<h4 id="the-limits-of-deterministic-code-in-a-fuzzy-world">The Limits of Deterministic Code in a Fuzzy World</h4>
<p>Your point about using agents to write the complicated Regex and conditional logic is valid—up to a point. That works beautifully when the input data has a bounded number of variations.</p>

<p>Multi-agent architectures are chosen when the <strong>state space of the problem is effectively infinite or highly unstructured</strong>, making it impossible to pre-compute the routing or write enough Regex to catch the edge cases.</p>

<ul>
  <li><strong>Example</strong>: Triaging unstructured, cross-system incident alerts. If a system relies on PagerDuty, Datadog/OTel metrics, and custom application logs, the variety of how failures manifest is boundless. Static logic breaks the moment a log format changes or a novel failure mode occurs. Agents can adapt to schema drift and contextual ambiguity at runtime in a way that compiled code simply cannot.</li>
</ul>

<h4 id="why-multi-agent-a2a-specifically">Why Multi-Agent (A2A) Specifically?</h4>
<p>If you accept that you have to use an LLM to handle “fuzzy” logic, the next question is why use multiple agents instead of one large prompt. The answer parallels why we moved from monoliths to microservices: Separation of concerns.</p>

<p>Single agents with massive, generalized prompts degrade quickly. Their context windows get polluted, leading to the exact hallucinations you want to avoid. A2A architectures apply microservice principles to non-deterministic systems:</p>

<ul>
  <li><strong>Scoped Context</strong>: A “Planner” agent only has prompts and context relevant to breaking down a task. A “Coder” agent only sees the specific function it needs to write.</li>
  <li><strong>Constrained Output</strong>: By forcing agents to communicate through strict JSON contracts (Agent A outputs a JSON schema that Agent B consumes), you force temporary state checkpoints. This makes the system slightly easier to monitor and test than a single black-box LLM.</li>
</ul>

<h4 id="the-judge-cost-inflation">The “Judge” Cost Inflation</h4>
<p>You are spot-on that adding “Judge” agents inflates costs and latency. The architectural justification for this pattern comes down to comparing the cost of the compute versus the cost of human labor, not CPU cycles.</p>

<p>If an A2A loop costs $0.80 in API calls and takes 45 seconds to run, it is a catastrophic failure if it’s replacing a $0.0001 deterministic Lambda function. But if that same loop is autonomously researching a zero-day vulnerability, writing a patch, and opening a PR—a task that takes a Senior Engineer four hours—that $0.80 and non-deterministic overhead is a massive win.</p>

<h4 id="the-hybrid-sweet-spot">The Hybrid Sweet Spot</h4>
<p>Your instinct to restrict agents solely to human language interpretation is widely considered the most mature, production-ready pattern right now.</p>

<p>This is exactly why bridging protocols are gaining so much traction. By using an LLM strictly as the semantic routing layer, you can parse human intent or unstructured data, and then immediately hand the execution off to deterministic, rigorously tested code via tools like Model Context Protocol (MCP) servers, n8n workflows, or Terraform/OpenTofu pipelines. The agent decides what tool to call, but the tool itself executes predictably.</p>

<p>Agents shouldn’t replace the control plane; they should just be a highly adaptive translation layer sitting on top of it.</p>

<p>Given your focus on controlled, observable infrastructure, have you experimented with exposing any of your existing deterministic tools or IaC pipelines to an LLM via MCP or similar tool-calling frameworks, rather than letting the agent write the execution logic itself?</p>

<hr />

<p>Above was Gemini 3.1 Pro, as I mentioned.  The points I think it raises are good:</p>

<ul>
  <li>If you can control/know your paths, using LLM just as the language front door makes sense (see <a href="https://n8n.tpk.pw/webhook/c47927a0-546c-4817-a1e0-a55be0cc0144/chat">this unhelpful agent I have running in n8n</a>)</li>
  <li>If your agent is about pulling signal from noise - <strong>especially</strong> if its about detecting problems (think security, fraud, production downtimes), it only makes sense to allow something that <em>could</em> help into the mix <em>(that is, a false suggestion or alert on a prod monitor once in a while would be totally fine… but denying cancer coverage over hallucinations would not – how is the agent being used is very very very important)</em>.</li>
</ul>

<p>There is something just a tiny bit creepy in the Gemini 3.1 Pro’s reply in that it clearly knows I write a lot about MCP, n8n, and OpenTofu.. those are strange things to toss into a normal reply - i felt it was rather tailoring it to me (fine, just feels a bit weird).</p>

<p>And I guess this rings well with me. The only “AI” systems that are catching my eyes lately are the autodetect and remediation ones I’ve seen by way of Dash in <a href="https://datadoghq.com">Datadog</a> and Klaudia in <a href="https://komodor.com/">Komodor</a>.  I can recall back in 2018 or so seeing <a href="https://www.bigpanda.io/">BigPanda</a> which was pretty much promising the same thing prior to full AI <em>(I only pushed back because pricing and usage was behind a sales call experience - thanks but no thanks)</em>.</p>

<p>Okay, wow, I got derailed there.  <strong>#SQUIRREL</strong>… the point of this section was to test Gemma4:12b (compared to say Gemini 3.1 Pro) on a topical question for which I’m <em>strongly</em> opinionated and see if it’s answer held true.  I feel it gave a pretty solid answer, but we can see how Gemini 3.1 Pro answered a bit better.</p>

<h1 id="summary">Summary</h1>

<p>Using a <a href="https://www.lenovo.com/us/en/p/laptops/legion-laptops/legion-pro-series/legion-pro-7i-gen-10-16-inch-intel/83f5cto1wwus1">Lenovo Legion</a> with a 12Gb nVidia RTX 5070 I managed to pull in Gemma4:12b and use it successfully to update a TUI to something actually working.</p>

<p>I did find network issues pushed me, at times, to use GCA and later Antigravity, but that was entirely due to remote network issues, not the model.</p>

<p>I’m finding it really quite solid.  I’ve thrown mostly typescript at it thus far, but it has yet to fail me.</p>

<p>I’m leveraging two plugins for VS Code, <a href="https://www.continue.dev/">Continue.dev</a> and <a href="https://cline.bot/">Cline</a>.  On the command line, I was sticking with just Cline.  However, when having network issues, I switched to <code class="language-plaintext highlighter-rouge">agy</code> which is the <a href="https://antigravity.google/product/antigravity-cli">Antigravity CLI</a> from Google which is meant to replace the well-loved <a href="https://geminicli.com/">Gemini CLI</a>.</p>

<p>Lastly, in asking non-coding (but about software architecture) questions, I felt the 12b model gave a very reasoned and sane reply.  Compared to the Gemini 3.1 Pro, it was pretty good (though we see Pro is better, as I would expect).  However, consider the scenerio of being on a plane, or in a corperate office that blocks Gemini endpoints, using Gemma4:12b would be a nice ‘trusted advisor’ (it could be my “Paw”… just saw Hoppers this weekend).</p>]]></content><author><name>Isaac Johnson</name></author><category term="GenAI" /><category term="Cline" /><category term="Continuedev" /><category term="Gemma4" /><category term="Antigravity" /><summary type="html"><![CDATA[Just this last week Google rolled out Gemma4 12b which is really interesting to me because the 20+ billion models are a bit much for my hardware, but the 8b and less have some limitations.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://freshbrewed.science/content/images/2026/06/Gemini_Generated_Image_6cwo3b6cwo3b6cwo.png" /><media:content medium="image" url="https://freshbrewed.science/content/images/2026/06/Gemini_Generated_Image_6cwo3b6cwo3b6cwo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Will the real Antigravity please stand up?</title><link href="https://freshbrewed.science/2026/06/04/aggy.html" rel="alternate" type="text/html" title="Will the real Antigravity please stand up?" /><published>2026-06-04T01:00:01+00:00</published><updated>2026-06-04T01:00:01+00:00</updated><id>https://freshbrewed.science/2026/06/04/aggy</id><content type="html" xml:base="https://freshbrewed.science/2026/06/04/aggy.html"><![CDATA[<p>Where <a href="https://geminicli.com/">Gemini CLI</a> was well-loved and open-source, with heavy adoption and plenty of clones (Qwen coder is a pretty direct fork), <a href="https://antigravity.google/">Antigravity</a> is a horse of a different colour.</p>

<p>As I saw it, Antigravity started as essentially VS Code clone that I had (errantly) assumed would just replace GCA.  However, after Next26 and IO we see “Antigravity” is now the term for a CLI, an IDE and version of AI Studio.</p>

<p>It likely is intended to replace <a href="https://firebase.studio/">Firebase Studio</a> (which itself replaced <a href="https://idx.dev/">Project IDX</a>).  moreover, there are times we call and invoke it as <code class="language-plaintext highlighter-rouge">agy</code> <em>(I had suggested “Aunty G”, but “agy” is better)</em></p>

<p>I generally lean into Open-Source tooling but going forward after June 18th, this will be the only valid way I will be able to use my Gemini AI Pro account to do AI coding.</p>

<p>Does taking my tooling away and making me use a closed-source tool make me a bit salty? yes. yes it does.  But after June 18th my only alternative is really to use Gemini (model) via Vertex AI and pay-as-you go which seems costly.</p>

<p>So let’s give it a go in WSL, Ubuntu and Windows and see how well <em>(or not)</em> it works.</p>

<p><em>The TL/DR is that indeed I was able to use it to build a solid TUI for an Open-Source tool i like - so it did get me there.  Just the journey.. well, let’s see what happened…</em></p>

<h1 id="installing-on-linux">Installing on Linux</h1>

<p>Since this isn’t just an open-source project now, we’ll have to go down the path of <a href="https://antigravity.google/download/linux">the install docs</a> to setup the Debian.</p>

<p>Setup apt</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://us-central1-apt.pkg.dev/doc/repo-signing-key.gpg | \
  sudo gpg --dearmor --yes -o /etc/apt/keyrings/antigravity-repo-key.gpg
echo "deb [signed-by=/etc/apt/keyrings/antigravity-repo-key.gpg] https://us-central1-apt.pkg.dev/projects/antigravity-auto-updater-dev/ antigravity-debian main" | \
  sudo tee /etc/apt/sources.list.d/antigravity.list &gt; /dev/null
</code></pre></div></div>

<p>Then we can apt update and install</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo apt update
$ sudo apt install antigravity
antigravity is already the newest version (1.23.2-1776332190).
Summary:
  Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 5
</code></pre></div></div>

<p><strong>TIP:</strong> <em>this may install a very old version.  In fact, Antigravity (IDE) is now 2.x.  So be aware that for all intents and purposes <code class="language-plaintext highlighter-rouge">agy</code> is the CLI and in most cases now <code class="language-plaintext highlighter-rouge">antigravity</code> is the IDE sometimes. other times <code class="language-plaintext highlighter-rouge">antigravity</code> is a wrapped web app and <code class="language-plaintext highlighter-rouge">antigravity ide</code> (with a space) is the IDE</em></p>

<p>I highly recommend going to <a href="https://antigravity.google/">https://antigravity.google/</a> and reviewing what the latest install steps are today</p>

<p><a href="/content/images/2026/06/agy-56.png"><img src="/content/images/2026/06/agy-56.png" alt="/content/images/2026/06/agy-56.png" /></a></p>

<h2 id="installing-the-cli">Installing the CLI</h2>

<p>Installing the CLI</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl -fsSL https://antigravity.google/cli/install.sh | bash
⠋ Detecting system environment...
✓ Platform detected: linux_amd64
⠋ Querying release repository...
✓ Latest available version: 1.0.3
⠋ Downloading release package...
✓ Download complete and checksum verified.
⠋ Extracting binary from archive...
⠋ Configuring shell environment...
I0530 19:34:53.289211 21171 installer.go:27] Running Antigravity CLI setup...
I0530 19:34:53.289403 21171 installer.go:147] Appending PATH export to profile /home/builder/.bashrc: export PATH="/home/builder/.local/bin:$PATH"
I0530 19:34:53.289768 21171 installer.go:184] Successfully updated shell profile: /home/builder/.bashrc
I0530 19:34:53.289865 21171 installer.go:147] Appending PATH export to profile /home/builder/.profile: export PATH="/home/builder/.local/bin:$PATH"
I0530 19:34:53.290038 21171 installer.go:184] Successfully updated shell profile: /home/builder/.profile
I0530 19:34:53.290052 21171 installer_unix.go:40] PATH verification: ~/.local/bin is correctly configured in active PATH environment.

✅ Antigravity CLI installed successfully at /home/builder/.local/bin/agy
Run 'agy' to start the CLI
</code></pre></div></div>

<p>Now we can fire it up with <code class="language-plaintext highlighter-rouge">agy</code> which is a much better name than Antigravity</p>

<p><a href="/content/images/2026/06/agy-01.png"><img src="/content/images/2026/06/agy-01.png" alt="/content/images/2026/06/agy-01.png" /></a></p>

<p>I can now set my colour scheme and import extensions from Gemini CLI</p>

<p><a href="/content/images/2026/06/agy-02.png"><img src="/content/images/2026/06/agy-02.png" alt="/content/images/2026/06/agy-02.png" /></a></p>

<p>The next screen is about collecting data and I opted out</p>

<p><a href="/content/images/2026/06/agy-03.png"><img src="/content/images/2026/06/agy-03.png" alt="/content/images/2026/06/agy-03.png" /></a></p>

<p>I’m now setup.  I’ll use <code class="language-plaintext highlighter-rouge">/planning</code> to set planning mode</p>

<p><a href="/content/images/2026/06/agy-04.png"><img src="/content/images/2026/06/agy-04.png" alt="/content/images/2026/06/agy-04.png" /></a></p>

<p>I’ll then give it a small project of a TUI to build</p>

<p><a href="/content/images/2026/06/agy-05.png"><img src="/content/images/2026/06/agy-05.png" alt="/content/images/2026/06/agy-05.png" /></a></p>

<p>The plan looks good</p>

<p><a href="/content/images/2026/06/agy-06.png"><img src="/content/images/2026/06/agy-06.png" alt="/content/images/2026/06/agy-06.png" /></a></p>

<p>I’ll approve the plan</p>

<p><a href="/content/images/2026/06/agy-07.png"><img src="/content/images/2026/06/agy-07.png" alt="/content/images/2026/06/agy-07.png" /></a></p>

<p>It says it completed the first draft</p>

<p><a href="/content/images/2026/06/agy-08.png"><img src="/content/images/2026/06/agy-08.png" alt="/content/images/2026/06/agy-08.png" /></a></p>

<h1 id="problems-with-wsl">Problems with WSL</h1>

<p>So i used to do a lot of my development in Windows Subsystem for Linux (WSL).  But here is where we have an issue.</p>

<p>I can “install” it into my Linux environment</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo apt install antigravity
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
antigravity is already the newest version (1.23.2-1776332190).
The following packages were automatically installed and are no longer required:
  aspnetcore-runtime-3.1 aspnetcore-targeting-pack-3.1 aspnetcore-targeting-pack-6.0 crda cryptsetup-run dotnet-apphost-pack-3.1
  dotnet-apphost-pack-6.0 dotnet-runtime-deps-6.0 dotnet-runtime-deps-8.0 dotnet-targeting-pack-3.1 dotnet-targeting-pack-6.0 fluent-bit
  g++-9 gir1.2-gnomebluetooth-1.0 gjs golang-1.13 golang-1.13-doc golang-1.13-go golang-1.13-race-detector-runtime golang-1.13-src
  golang-race-detector-runtime i965-va-driver:i386 intel-media-va-driver:i386 ippusbxd libaom0 libaom0:i386 libaom3:i386 libappstream-glib8
  libasn1-8-heimdal libasn1-8-heimdal:i386 libasync-mergepoint-perl libavcodec58:i386 libavutil56:i386 libboost-context1.71.0
  libboost-iostreams1.71.0 libboost-program-options1.71.0 libboost-thread1.71.0 libcamel-1.2-62 libcbor0.6 libcdio18 libcodec2-0.9
  libcodec2-0.9:i386 libcodec2-1.0:i386 libcroco3 libcurl3-gnutls:i386 libdav1d5:i386 libdbus-glib-1-2 libdc1394-22
  libdigest-bubblebabble-perl libdns-export1109 libedataserver-1.2-24 libedataserverui-1.2-2 libemail-valid-perl libevent-2.1-7
  libffi7:i386 libfl2 libfuture-perl libfwupdplugin1 libgdbm-compat4:i386 libgdbm6:i386 libgdk-pixbuf-xlib-2.0-0:i386
  libgdk-pixbuf2.0-0:i386 libglu1-mesa libglu1-mesa:i386 libgomp1:i386 libgssapi3-heimdal libgssapi3-heimdal:i386 libgupnp-1.2-0
  libhcrypto4-heimdal libhcrypto4-heimdal:i386 libheimbase1-heimdal libheimbase1-heimdal:i386 libheimntlm0-heimdal
  libheimntlm0-heimdal:i386 libhiredis0.14 libhogweed5 libhogweed5:i386 libhx509-5-heimdal libhx509-5-heimdal:i386 libicu66:i386 libidn11
  libieee1284-3:i386 libigdgmm11 libigdgmm11:i386 libigdgmm12:i386 libilmbase24 libio-async-loop-epoll-perl libio-async-perl libjson-c4
  libjsoncpp1 libkrb5-26-heimdal libkrb5-26-heimdal:i386 libldap-2.4-2 libldap-2.4-2:i386 libleveldb1d liblinux-epoll-perl libllvm12
  libllvm12:i386 liblttng-ust-ctl4 liblttng-ust0 libmetrics-any-perl libmozjs-68-0 libmpdec2 libmysqlclient21:i386 libnet-dns-perl
  libnet-dns-sec-perl libnet-ip-perl libnettle7 libnettle7:i386 libnspr4:i386 libnss3:i386 libntfs-3g883 libnuma1:i386 libnumber-range-perl
  libodbc1 libopenexr24 libopengl0:i386 libopenjp2-7:i386 libpci3:i386 libperl4-corelibs-perl libperl5.30 libperl5.30:i386 libperl5.34:i386
  libpgm-5.2-0 libphonenumber7 libpoppler-glib8:i386 libpoppler118:i386 libprotobuf17 libpython2-stdlib libpython2.7-minimal
  libpython2.7-stdlib libpython3.8 libpython3.8-dev libpython3.8-minimal libpython3.8-stdlib libre2-5 libreadline5 libroken18-heimdal
  libroken18-heimdal:i386 librsvg2-2:i386 librsvg2-common:i386 libsane libsane:i386 libsane1:i386 libsereal-perl libshine3:i386
  libsnappy1v5:i386 libsnmp35 libsnmp35:i386 libsnmp40:i386 libsort-key-perl libsoxr0:i386 libssl1.1:i386 libstdc++-9-dev
  libstruct-dumb-perl libswresample3:i386 libtest-fatal-perl libtest-metrics-any-perl libtest-refcount-perl libtext-levenshtein-perl
  liburcu6 libva-drm2:i386 libva-x11-2:i386 libva2:i386 libvdpau1:i386 libvpx6 libvpx6:i386 libwebp6 libwebp6:i386 libwebpmux3:i386
  libwind0-heimdal libwind0-heimdal:i386 libwmf-0.2-7 libwmf0.2-7 libwrap0:i386 libx264-155 libx264-155:i386 libx264-163:i386 libx265-179
  libx265-179:i386 libx265-199:i386 libxml-writer-perl libxmlb1 libxvidcore4:i386 libzvbi0:i386 linux-headers-5.15.0-152
  linux-headers-5.15.0-152-generic linux-headers-5.4.0-208 linux-headers-5.4.0-208-generic linux-headers-5.4.0-216
  linux-headers-5.4.0-216-generic ltrace lz4 mariadb-common mesa-va-drivers:i386 mesa-vdpau-drivers:i386 mousetweaks perl-modules-5.30
  popularity-contest python-pkg-resources python2 python2-minimal python2.7 python2.7-minimal python3-crcmod python3-entrypoints
  python3-requests-unixsocket python3-simplejson python3.8 python3.8-dev python3.8-minimal ruby-afm ruby-ascii85 ruby-blankslate
  ruby-connection-pool ruby-hashery ruby-launchy-shim ruby-molinillo ruby-multi-json ruby-net-http-persistent ruby-oj ruby-parslet
  ruby-pdf-core ruby-pdf-reader ruby-posix-spawn ruby-prawn ruby-prawn-table ruby-rc4 ruby-stringex ruby-thor ruby-toml ruby-ttfunk ruby2.7
  ruby2.7-dev ruby2.7-doc td-agent-bit va-driver-all:i386 vdpau-driver-all:i386 x11proto-input-dev x11proto-randr-dev x11proto-xinerama-dev
  xul-ext-ubufox
Use 'sudo apt autoremove' to remove them.
0 upgraded, 0 newly installed, 0 to remove and 356 not upgraded.
</code></pre></div></div>

<p>But then <code class="language-plaintext highlighter-rouge">agy</code> doesn’t show up</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ agy
Command 'agy' not found, did you mean:
  command 'ag' from deb silversearcher-ag (2.2.0+git20200805-1)
  command 'age' from deb age (1.0.0-1ubuntu0.1)
  command 'asy' from deb asymptote (2.78+ds-2)
Try: sudo apt install &lt;deb name&gt;
</code></pre></div></div>

<p>And if I try <code class="language-plaintext highlighter-rouge">antigravity</code> in WSL it just tells me to set it up in Windows</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ antigravity
To use Antigravity with the Windows Subsystem for Linux, please install Antigravity in Windows and uninstall the Linux version in WSL. You can then use the `antigravity` command in a WSL terminal just as you would in a normal command prompt.
Do you want to continue anyway? [y/N] N
</code></pre></div></div>

<p>Once I do that, however, if i run <code class="language-plaintext highlighter-rouge">antigravity</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~$ antigravity
builder@DESKTOP-QADGF36:~$
</code></pre></div></div>

<p>it just launches the IDE - i don’t want the IDE.  I want a CLI, dammit.</p>

<p><a href="/content/images/2026/06/agy-09.png"><img src="/content/images/2026/06/agy-09.png" alt="/content/images/2026/06/agy-09.png" /></a></p>

<p>But the IDE doesn’t launch and reinstall doesn’t work either</p>

<p><a href="/content/images/2026/06/agy-10.png"><img src="/content/images/2026/06/agy-10.png" alt="/content/images/2026/06/agy-10.png" /></a></p>

<p>I did get it work in such a shoehorned way.</p>

<p>I ran the powershell installer for the IDE</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS C:\Users\isaac&gt; irm https://antigravity.google/cli/install.ps1 | iex
I0602 14:25:03.877606 30028 installer.go:27] Running Antigravity CLI setup...
I0602 14:25:03.878114 30028 installer_windows.go:45] Configuring Windows user PATH registry environment...
I0602 14:25:03.878114 30028 installer_windows.go:117] Successfully added %LOCALAPPDATA%\agy\bin to User PATH registry variable.
I0602 14:25:03.878114 30028 installer_windows.go:148] Broadcasting environment update system-wide...


PS C:\Users\isaac&gt; irm https://antigravity.google/cli/install.ps1 | iex
Notice: 'agy.exe' is already installed at C:\Users\isaac\AppData\Local\agy\bin\agy.exe.
The Antigravity CLI automatically self-updates in the background.
If you want to perform a fresh installation, delete the binary first:
  Remove-Item "C:\Users\isaac\AppData\Local\agy\bin\agy.exe" -Force
</code></pre></div></div>

<p>It hung, i ctrl-c’ed and tried again and said it was there.</p>

<p>I then went to WSL to launch by direct pathing:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/fjtui$ /mnt/c/Users/isaac/AppData/Local/agy/bin/agy.exe
</code></pre></div></div>

<p>And now I have it working</p>

<p><a href="/content/images/2026/06/agy-11.png"><img src="/content/images/2026/06/agy-11.png" alt="/content/images/2026/06/agy-11.png" /></a></p>

<p>ctrl-d doesn’t actually stop the process tho</p>

<video controls="">
    <source src="/content/images/2026/06/agy-12.mp4" type="video/mp4" />
</video>

<p>But I could kill it by finding the process and terminating that</p>

<p><a href="/content/images/2026/06/agy-13.png"><img src="/content/images/2026/06/agy-13.png" alt="/content/images/2026/06/agy-13.png" /></a></p>

<p><strong>update:</strong> speaking with the team, use “/exit” and you’ll be fine, which I tested:</p>

<p><a href="/content/images/2026/06/agy-17.png"><img src="/content/images/2026/06/agy-17.png" alt="/content/images/2026/06/agy-17.png" /></a></p>

<h2 id="usage">Usage</h2>

<p>While I cannot kill the window easily, it did crank up and respond to my ask for a System diagram</p>

<p><a href="/content/images/2026/06/agy-14.png"><img src="/content/images/2026/06/agy-14.png" alt="/content/images/2026/06/agy-14.png" /></a></p>

<p>I corrected the one minor typo and it rendered the MermaidJS just fine in VS Code</p>

<p><a href="/content/images/2026/06/agy-15.png"><img src="/content/images/2026/06/agy-15.png" alt="/content/images/2026/06/agy-15.png" /></a></p>

<p>As you can see, it’s clearly running in windows, not Linux, as the file paths are a bit odd, e.g.</p>

<blockquote>
  <p>file:///wsl.localhost/Ubuntu/home/builder/Workspaces/fjtui/src/api.ts</p>
</blockquote>

<p>However, my asks worked just fine</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/agy-16.mp4" type="video/mp4" />
</video>

<p>I was hoping to setup OTel like I did for Gemini CLI, however with agy, “Telemetry” just means sending data back to Google</p>

<p><a href="/content/images/2026/06/agy-18.png"><img src="/content/images/2026/06/agy-18.png" alt="/content/images/2026/06/agy-18.png" /></a></p>

<h2 id="ai-credits">AI Credits</h2>

<p>There is a bit of an interesting option with Agy in that you can enable “Use AI Credits”</p>

<p><a href="/content/images/2026/06/agy-19.png"><img src="/content/images/2026/06/agy-19.png" alt="/content/images/2026/06/agy-19.png" /></a></p>

<p>What that means is you can go to some “credits” page for AI Pro/Ultra to see how many monthly credits you have.</p>

<p><a href="/content/images/2026/06/agy-20.png"><img src="/content/images/2026/06/agy-20.png" alt="/content/images/2026/06/agy-20.png" /></a></p>

<p>Then if I run out - say I’m just going nuts with AI work and want to add more, I can buy these credits for money:</p>

<p><a href="/content/images/2026/06/agy-21.png"><img src="/content/images/2026/06/agy-21.png" alt="/content/images/2026/06/agy-21.png" /></a></p>

<p>Since you really don’t know what a credit means - that could be a bunch of input tokens or a mix of input / output. it’s whatever Google says they should mean right now.  That is, <a href="https://support.google.com/googleone/answer/16287445?visit_id=639160351622698792-4058908893&amp;p=g1_credit_management&amp;rd=1">nowhere on the docs</a> does it hint at what they mean so it’s just try and figure it out.</p>

<p>I’m not trying to be a stinky pete here - but it’s the same issue I always had with Dynatrace - they had “Davis Units” which amounted to some mystery metric.  When I would push for totals they would just say “use the trial and see what your spend is and we can figure it out from there”.  So, i mean, nothing bad about the people at Dyna - but mystery meat is mystery meat.</p>

<h1 id="antigravity-app">Antigravity “App”</h1>

<p>I don’t know what to say here.  The “App” just shows me effectively the “AI Studio” web page.</p>

<p><a href="/content/images/2026/06/agy-22.png"><img src="/content/images/2026/06/agy-22.png" alt="/content/images/2026/06/agy-22.png" /></a></p>

<p>The Open IDE doesn’t work. i get a blank result in the App Store. I’ve tried reinstalling a few times.</p>

<p><a href="/content/images/2026/06/agy-23.png"><img src="/content/images/2026/06/agy-23.png" alt="/content/images/2026/06/agy-23.png" /></a></p>

<p>then</p>

<p><a href="/content/images/2026/06/agy-24.png"><img src="/content/images/2026/06/agy-24.png" alt="/content/images/2026/06/agy-24.png" /></a></p>

<p>That said, it did help make a pretty cool animation</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/agy-25.mp4" type="video/mp4" />
</video>

<h2 id="ide-in-linux">IDE in Linux</h2>

<p>I had no issues in Linux</p>

<p><a href="/content/images/2026/06/agy-30.png"><img src="/content/images/2026/06/agy-30.png" alt="/content/images/2026/06/agy-30.png" /></a></p>

<p>I used it to update some files then circled back to start to dig into settings.</p>

<p>I found it was nice I could automatically allow some common tools</p>

<p><a href="/content/images/2026/06/agy-31.png"><img src="/content/images/2026/06/agy-31.png" alt="/content/images/2026/06/agy-31.png" /></a></p>

<p>I noticed under settings/models there was a what appeared to be a quota usage page</p>

<p><a href="/content/images/2026/06/agy-32.png"><img src="/content/images/2026/06/agy-32.png" alt="/content/images/2026/06/agy-32.png" /></a></p>

<p>But coming back from settings indicated a crash</p>

<p><a href="/content/images/2026/06/agy-33.png"><img src="/content/images/2026/06/agy-33.png" alt="/content/images/2026/06/agy-33.png" /></a></p>

<p>However, everything was fine and my settings persisted (went back to check).</p>

<p>I was notified of an update</p>

<p><a href="/content/images/2026/06/agy-34.png"><img src="/content/images/2026/06/agy-34.png" alt="/content/images/2026/06/agy-34.png" /></a></p>

<p>But after an apt update, I see no new versions</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo apt install antigravity
antigravity is already the newest version (1.23.2-1776332190).
Summary:
  Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 87
</code></pre></div></div>

<p>I tried downloading and running. I got a welcome screen and i opted to yes install the IDE, but when done, I just got this lousy project page</p>

<p><a href="/content/images/2026/06/agy-35.png"><img src="/content/images/2026/06/agy-35.png" alt="/content/images/2026/06/agy-35.png" /></a></p>

<p>That doesn’t run on the command line.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(base) builder@LuiGi:~/Workspaces/fpcdeacon$ ~/Downloads/Antigravity/Antigravity-x64/antigravity --version
[317160:0603/065227.096138:FATAL:sandbox/linux/suid/client/setuid_sandbox_host.cc:166] The SUID sandbox helper binary was found, but is not configured correctly. Rather than run without sandboxing I'm aborting now. You need to make sure that /home/builder/Downloads/Antigravity/Antigravity-x64/chrome-sandbox is owned by root and has mode 4755.
Trace/breakpoint trap (core dumped)
(base) builder@LuiGi:~/Workspaces/fpcdeacon$ antigravity
(base) builder@LuiGi:~/Workspaces/fpcdeacon$ which antigravity
/usr/bin/antigravity
</code></pre></div></div>

<p>and firing it back up again use the command line <code class="language-plaintext highlighter-rouge">antigravity</code> just shows an ‘update available’ which again takes me to <a href="https://antigravity.google/docs/getting-started">https://antigravity.google/docs/getting-started</a></p>

<p>I can download “Antigravity IDE” which if I double click in Desktop, starts an onboarding wizard</p>

<p><a href="/content/images/2026/06/agy-36.png"><img src="/content/images/2026/06/agy-36.png" alt="/content/images/2026/06/agy-36.png" /></a></p>

<p>I was <strong>Very</strong> annoyed I had to opt in to data collection to complete the installer (then its a false choice)</p>

<p><a href="/content/images/2026/06/agy-37.png"><img src="/content/images/2026/06/agy-37.png" alt="/content/images/2026/06/agy-37.png" /></a></p>

<p>So the very first thing you’ll want to do is go to settings</p>

<p><a href="/content/images/2026/06/agy-38.png"><img src="/content/images/2026/06/agy-38.png" alt="/content/images/2026/06/agy-38.png" /></a></p>

<p>and under account I made user “enable telemetry” was turned off</p>

<p><a href="/content/images/2026/06/agy-39.png"><img src="/content/images/2026/06/agy-39.png" alt="/content/images/2026/06/agy-39.png" /></a></p>

<p>The old IDE could be invoked from the command line, but it seems the new one cannot</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(base) builder@LuiGi:~/Workspaces/fpcdeacon$ antigravity --version
1.107.0
15487b3041e65228cae24980a3f796c905ef582c
x64


(base) builder@LuiGi:~/Workspaces/fpcdeacon$ /home/builder/Downloads/Antigravity\ IDE/antigravity-ide --version
[336860:0603/071022.834824:FATAL:sandbox/linux/suid/client/setuid_sandbox_host.cc:166] The SUID sandbox helper binary was found, but is not configured correctly. Rather than run without sandboxing I'm aborting now. You need to make sure that /home/builder/Downloads/Antigravity IDE/chrome-sandbox is owned by root and has mode 4755.
Trace/breakpoint trap (core dumped)
</code></pre></div></div>

<p>The old editor still is there and its pretty similar in appearance</p>

<p><a href="/content/images/2026/06/agy-40.png"><img src="/content/images/2026/06/agy-40.png" alt="/content/images/2026/06/agy-40.png" /></a></p>

<p>Though, it keeps saying update available even though I just downloaded it from the website</p>

<p><a href="/content/images/2026/06/agy-41.png"><img src="/content/images/2026/06/agy-41.png" alt="/content/images/2026/06/agy-41.png" /></a></p>

<p>I cannot see a releases page (as its not opensource nor has detailed documentation on the downloads page).  But from the URL at the bottom it would seem I downloaded 2.0.3 which lines up.. so no idea why it claims there is an “update available”</p>

<p><a href="/content/images/2026/06/agy-42.png"><img src="/content/images/2026/06/agy-42.png" alt="/content/images/2026/06/agy-42.png" /></a></p>

<p>so i searched it up and found <em>a different releases page than what the IDE links to</em>: <a href="https://antigravity.google/releases">https://antigravity.google/releases</a></p>

<p>So I used <em>that</em> releases page to download 2.0.4</p>

<p><a href="/content/images/2026/06/agy-43.png"><img src="/content/images/2026/06/agy-43.png" alt="/content/images/2026/06/agy-43.png" /></a></p>

<p>It works, but still the icon is just a gear in the Gnome task bar - minor nit pick really</p>

<p><a href="/content/images/2026/06/agy-44.png"><img src="/content/images/2026/06/agy-44.png" alt="/content/images/2026/06/agy-44.png" /></a></p>

<p>The newest agents have a working plan mode… kind of.  The “Proceed” persists provided you have the “Implementation Plan” page up.  Never goes away while that page is up.</p>

<video muted="" controls="">
    <source src="/content/images/2026/06/agy-45.mp4" type="video/mp4" />
</video>

<p>Closing the plan page tho does just make it disappear which suffices.</p>

<p>Another fun bug i found is that the notifications icons in gnome don’t go away.  So it now sits with a “5” likely till i close the IDE</p>

<p><a href="/content/images/2026/06/agy-46.png"><img src="/content/images/2026/06/agy-46.png" alt="/content/images/2026/06/agy-46.png" /></a></p>

<p>I noticed in a run it was asking for “npm run” permissions</p>

<p><a href="/content/images/2026/06/agy-47.png"><img src="/content/images/2026/06/agy-47.png" alt="/content/images/2026/06/agy-47.png" /></a></p>

<p>and checked my advanced settings and indeed saw all those “allows” i made earlier were gone.  So perhaps we have to do that with each update</p>

<h3 id="vim-mode">VIM Mode</h3>

<p>Usually I like VIM, but not in the editor right now.  I searched all over for how to disable/enable.  searching help failed.  but finally i found out how…</p>

<p>Ctrl-shift-P then start to time “VIM” till you see the toggle mode show up.</p>

<p>Then you’ll see the toggle in the lower left</p>

<p><a href="/content/images/2026/06/agy-48.png"><img src="/content/images/2026/06/agy-48.png" alt="/content/images/2026/06/agy-48.png" /></a></p>

<h3 id="resources">Resources</h3>

<p>At first I feared the IDE was chewing CPU until I realized my laptop was just about out of battery and slowing things down intentionally.  But it did prompt me to check memory and CPU usage and it seems the IDE really doesn’t take much:</p>

<p><a href="/content/images/2026/06/agy-49.png"><img src="/content/images/2026/06/agy-49.png" alt="/content/images/2026/06/agy-49.png" /></a></p>

<h1 id="antigravity-app-1">Antigravity App</h1>

<p>The trials at the start left a lot of zombie processes</p>

<p><a href="/content/images/2026/06/agy-50.png"><img src="/content/images/2026/06/agy-50.png" alt="/content/images/2026/06/agy-50.png" /></a></p>

<p>I had to manually kill them to launch an Antigravity window.</p>

<p>For “Antigravity”, which to avoid confusion I’ll forthwith call “The App”.  In The App, we see local folders as ‘Projects’</p>

<p><a href="/content/images/2026/06/agy-51.png"><img src="/content/images/2026/06/agy-51.png" alt="/content/images/2026/06/agy-51.png" /></a></p>

<p>I can now start a conversation and a new ask in an existing project</p>

<p><a href="/content/images/2026/06/agy-52.png"><img src="/content/images/2026/06/agy-52.png" alt="/content/images/2026/06/agy-52.png" /></a></p>

<p>But it died midway through</p>

<p><a href="/content/images/2026/06/agy-53.png"><img src="/content/images/2026/06/agy-53.png" alt="/content/images/2026/06/agy-53.png" /></a></p>

<p>I was on a guest WiFi that often blocks AI tooling so to rule that out, I disabled WiFi on my phone and switched to just cell service and everything worked.  So it could have been my location at the time</p>

<p><a href="/content/images/2026/06/agy-54.png"><img src="/content/images/2026/06/agy-54.png" alt="/content/images/2026/06/agy-54.png" /></a></p>

<h2 id="quick-notes-on-agy-cli-in-cloud-shell">Quick notes on agy (CLI) in Cloud Shell</h2>

<p>You can easily invoke Antigravity in GCP Cloud shell as it comes preinstalled. However the logo is a bit goofed up (tested over several days)</p>

<p><a href="/content/images/2026/06/agy-55.png"><img src="/content/images/2026/06/agy-55.png" alt="/content/images/2026/06/agy-55.png" /></a></p>

<p>And you need to use <code class="language-plaintext highlighter-rouge">/exit</code> to exit, not ctrl-d.</p>

<p>I also noted ctrl-shift-c and ctrl-shift-v work well, but that could be because I’m in Linux.</p>

<h1 id="summary">Summary</h1>

<p>Today we looked at Antigravity and agy the “App”, the “IDE” and and the “CLI”.  The App just seems like a wrapper around aitsudio so I see little reason to use that when you can just go to <a href="https://aistudio.google.com/apps">https://aistudio.google.com/apps</a>.</p>

<p>The IDE is okay, but I really dislike the install process.  It used to be easy to fire up in a terminal, which is where i tend to live.  This works for VS Code and other tools, and it used to work with antigravity in the 1.x level.  But now I need to double click an app in the gnome desktop.  I suppose this isn’t a big deal, but really I hate having to keep downloading an app from a website to double click.  Installers should be table stakes and invokation in a terminal is the best way.</p>

<p>The CLI has some really nice features.  I did like that it could push overages to lumped credit packages I could purchase.  It gives me the best of both ways (free for my tier, but also a way to keep going if i go hog wild some month).</p>

<p>I do not like</p>
<ul>
  <li>that ctrl-D twice was the way to exit and sometimes works but is broken in Linux.</li>
  <li>I had to initially opt in for data collection</li>
  <li>that its messes up its own logo routinely in Linux and cloud shell terminal (looks bad)</li>
  <li>that its closed source</li>
  <li>that it times out in the IDE sometimes</li>
</ul>

<p>I also feel killing Google Code Assist (GCA), while I understand a bit, cuts off the open source code-server/coder approach.</p>

<p>I do know there are hacky ways to try and use your Google Ai subscription <em>outside</em> of Antrigravity but all run the risk of getting banned and I won’t share/link to them.  But there are ways to do this (for the record, i do NOT recommend this and I don’t use them).</p>]]></content><author><name>Isaac Johnson</name></author><category term="GenAI" /><category term="Antigravity" /><summary type="html"><![CDATA[Where Gemini CLI was well-loved and open-source, with heavy adoption and plenty of clones (Qwen coder is a pretty direct fork), Antigravity is a horse of a different colour.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://freshbrewed.science/content/images/2026/06/aggyslimbg2.png" /><media:content medium="image" url="https://freshbrewed.science/content/images/2026/06/aggyslimbg2.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Local LLM CLI: Crush</title><link href="https://freshbrewed.science/2026/06/02/crush.html" rel="alternate" type="text/html" title="Local LLM CLI: Crush" /><published>2026-06-02T01:00:01+00:00</published><updated>2026-06-02T01:00:01+00:00</updated><id>https://freshbrewed.science/2026/06/02/crush</id><content type="html" xml:base="https://freshbrewed.science/2026/06/02/crush.html"><![CDATA[<p>Today we’ll take a look at <a href="https://github.com/charmbracelet/crush">Charm Crush</a>.  I love OS AI CLI’s and this one even <a href="https://github.com/charmbracelet/catwalk/blob/main/internal/providers/providers.go">Open Sources it’s providers</a>.  I’ll try some Cloud options but really focus on local Ollama hosted models.</p>

<p>I’ll also take a moment to bid farewell to Google Code Assist (GCA) and Gemini CLI.  I loved these tools and they’ll go offline June 18th this year in just a couple of weeks.</p>

<h1 id="crush">Crush</h1>

<p>Installing</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nvm use lts/jod
Now using node v22.22.0 (npm v10.9.4)
$ npm install -g @charmland/crush

added 47 packages in 5s
</code></pre></div></div>

<p>I can now launch with <code class="language-plaintext highlighter-rouge">crush</code></p>

<p><a href="/content/images/2026/06/crush-01.png"><img src="/content/images/2026/06/crush-01.png" alt="/content/images/2026/06/crush-01.png" /></a></p>

<p>But I noticed after launch it did some setup and attempted to use Anthropic by way of AWS Bedrock</p>

<p><a href="/content/images/2026/06/crush-02.png"><img src="/content/images/2026/06/crush-02.png" alt="/content/images/2026/06/crush-02.png" /></a></p>

<p>I’ll try Gemini 3.5 Flash</p>

<p><a href="/content/images/2026/06/crush-03.png"><img src="/content/images/2026/06/crush-03.png" alt="/content/images/2026/06/crush-03.png" /></a></p>

<p>Because Google has changed the rules and the “AI Pro” account I have can no longer be used with non-Google owned tools - and they are killing Gemini CLI, which is Open-Source, I must (legally) use my Gemini API key</p>

<p><a href="/content/images/2026/06/crush-04.png"><img src="/content/images/2026/06/crush-04.png" alt="/content/images/2026/06/crush-04.png" /></a></p>

<p>Which, once entered, changes to let me know our model has been swapped</p>

<p><a href="/content/images/2026/06/crush-05.png"><img src="/content/images/2026/06/crush-05.png" alt="/content/images/2026/06/crush-05.png" /></a></p>

<p>While the UI doesn’t let us easily add MCP servers, we can follow the <a href="https://github.com/charmbracelet/crush/blob/main/internal/skills/builtin/crush-config/SKILL.md">docs</a> and add one in a crush.json file</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/mytest2$ cat .crush.json
{
  "$schema": "https://charm.land/crush.json",
  "mcp": {
    "offenquelle": {
      "type": "http",
      "url": "https://mcp.offenquelle.tech/mcp"
    }
  }
}
</code></pre></div></div>

<p>I can now see my private MCP server when i fire it up along with all the discovered tools</p>

<p><a href="/content/images/2026/06/crush-06.png"><img src="/content/images/2026/06/crush-06.png" alt="/content/images/2026/06/crush-06.png" /></a></p>

<p>I can now add some Ollama local models as well</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat .crush.json
{
  "$schema": "https://charm.land/crush.json",
  "mcp": {
    "offenquelle": {
      "type": "http",
      "url": "https://mcp.offenquelle.tech/mcp"
    }
  },
  "providers": {
    "ollama": {
      "name": "Ollama",
      "base_url": "http://192.168.1.220:11434/v1/",
      "type": "openai",
      "models": [
        {
          "name": "Gemma4 e4b",
          "id": "gemma4:e4b",
          "context_window": 256000,
          "default_max_tokens": 20000
        },
        {
          "name": "Qwen3.6 27b",
          "id": "qwen3.6:27b",
          "context_window": 256000,
          "default_max_tokens": 20000
        }
      ]
    }
  }
}
</code></pre></div></div>

<p><a href="/content/images/2026/06/crush-07.png"><img src="/content/images/2026/06/crush-07.png" alt="/content/images/2026/06/crush-07.png" /></a></p>

<p>I tried but couldn’t get Gemma4 to check out the files in the local workspace</p>

<p><a href="/content/images/2026/06/crush-08.png"><img src="/content/images/2026/06/crush-08.png" alt="/content/images/2026/06/crush-08.png" /></a></p>

<p>It took a couple tries with granite4.1, but I did manage to get that model to list files</p>

<p><a href="/content/images/2026/06/crush-09.png"><img src="/content/images/2026/06/crush-09.png" alt="/content/images/2026/06/crush-09.png" /></a></p>

<p>I kept thinking that maybe it was my Crush setup preventing file creation.  I tried Gemini 3.5 flash again to build a full todo app, which it did</p>

<p><a href="/content/images/2026/06/crush-10.png"><img src="/content/images/2026/06/crush-10.png" alt="/content/images/2026/06/crush-10.png" /></a></p>

<p>It claimed that cost US$1.07 but when I checked my bill the next day, it was less than half of that, $0.443</p>

<p><a href="/content/images/2026/06/crush-11.png"><img src="/content/images/2026/06/crush-11.png" alt="/content/images/2026/06/crush-11.png" /></a></p>

<h2 id="filesystem-mcp">Filesystem MCP</h2>

<p>Let’s add the Filesystem MCP server</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  "mcp": {
    "offenquelle": {
      "type": "http",
      "url": "https://mcp.offenquelle.tech/mcp"
    },
    "filesystem": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/home/builder/Workspaces",
        "/tmp"
      ],
      "timeout": 120,
      "disabled": false
    }
  },
</code></pre></div></div>

<p>I thought perhaps using with a larger model like Qwen3.6 27B might work, but it seems to just stop after the find command each time.</p>

<p><a href="/content/images/2026/06/crush-12.png"><img src="/content/images/2026/06/crush-12.png" alt="/content/images/2026/06/crush-12.png" /></a></p>

<p>I then tried Qwen3:14b.  It asked to use the tool which was a good sign</p>

<p><a href="/content/images/2026/06/crush-13.png"><img src="/content/images/2026/06/crush-13.png" alt="/content/images/2026/06/crush-13.png" /></a></p>

<p>It seems to create To-Do lists then not do them.  Though “Run steps” got me closest</p>

<p><a href="/content/images/2026/06/crush-14.png"><img src="/content/images/2026/06/crush-14.png" alt="/content/images/2026/06/crush-14.png" /></a></p>

<p>I saw <a href="https://thenewstack.io/terminal-user-interfaces-review-of-crush-ex-opencode-al/">this newstack post</a> that hinted that perhaps I need to be more explicit with Crush permissions in the JSON file:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"permissions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"allowed_tools"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"view"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"ls"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"grep"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"edit"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"find"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"sed"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"cat"</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>But again, these damn To-Dos, even in Yolo mode:</p>

<video muted="" controls="">
    <source src="/content/images/2026/05/crush-15.mp4" type="video/mp4" />
</video>

<p>I find this so frustrating that it creates a plan but there is no command or obvious way to execute it.  I even hit up Gemini Pro to ask it and tried it’s suggestions</p>

<p><a href="/content/images/2026/06/crush-16.png"><img src="/content/images/2026/06/crush-16.png" alt="/content/images/2026/06/crush-16.png" /></a></p>

<p>I tried devstral-small-2 with no context or window limits to see if that might work (didn’t)</p>

<p><a href="/content/images/2026/06/crush-17.png"><img src="/content/images/2026/06/crush-17.png" alt="/content/images/2026/06/crush-17.png" /></a></p>

<p>Perhaps the fact this is WSL on Copilot OS is causing issues</p>

<p><a href="/content/images/2026/06/crush-18.png"><img src="/content/images/2026/06/crush-18.png" alt="/content/images/2026/06/crush-18.png" /></a></p>

<p>But still it fails even if I’m explicit on pathing</p>

<p><a href="/content/images/2026/06/crush-19.png"><img src="/content/images/2026/06/crush-19.png" alt="/content/images/2026/06/crush-19.png" /></a></p>

<p>Using a local model in Ollama in Windows didn’t create any better results</p>

<p><a href="/content/images/2026/06/crush-20.png"><img src="/content/images/2026/06/crush-20.png" alt="/content/images/2026/06/crush-20.png" /></a></p>

<h2 id="ubuntu--linux">Ubuntu / Linux</h2>

<p>Enough with WSL.  Let’s do this on Linux.  I’ll use the latest LTS, lts/krypton (24.*)</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@bosgamerz9:~/Workspaces/fjtui$ nvm use lts/krypton
Now using node v24.16.0 (npm v11.13.0)
builder@bosgamerz9:~/Workspaces/fjtui$ npm install -g @charmland/crush

added 47 packages in 3s

</code></pre></div></div>

<p>I tried a local model running in Ollama but it seemed to forget what codebase meant.</p>

<p><a href="/content/images/2026/06/crush-25.png"><img src="/content/images/2026/06/crush-25.png" alt="/content/images/2026/06/crush-25.png" /></a></p>

<p>I kept trying different ways to phrase it.</p>

<p><a href="/content/images/2026/06/crush-26.png"><img src="/content/images/2026/06/crush-26.png" alt="/content/images/2026/06/crush-26.png" /></a></p>

<p>I finally got it to list files but then it forgot what it was asked to do!</p>

<p><a href="/content/images/2026/06/crush-27.png"><img src="/content/images/2026/06/crush-27.png" alt="/content/images/2026/06/crush-27.png" /></a></p>

<p>Using IBM Granite on a different laptop, it still just refuses to write files, though it finally reads them</p>

<p><a href="/content/images/2026/06/crush-28.png"><img src="/content/images/2026/06/crush-28.png" alt="/content/images/2026/06/crush-28.png" /></a></p>

<p>I tried running just using the command line and no TUI</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@bosgamerz9:~/Workspaces/fjtui$ crush run "Create a README.md" --cwd /home/builder/Workspaces/fjtui/ | tee README.md
Let me first explore the project to understand what it does.

&lt;tool_calls&gt;&lt;tool_use&gt;
&lt;parameter&gt;name&gt;bash&lt;/parameter&gt;
&lt;parameter&gt;arguments&gt;
&lt;parameter&gt;command&lt;/parameter&gt;
&lt;parameter&gt;cd /home/builder/Workspaces/fjtui/ &amp;&amp; find . -type f | head -50
&lt;/parameter&gt;
&lt;parameter&gt;description&lt;/parameter&gt;
&lt;parameter&gt;List all files in the project
&lt;/parameter&gt;
&lt;/tool_use&gt;
&lt;parameter&gt;name&gt;bash&lt;/parameter&gt;
&lt;parameter&gt;command&lt;/parameter&gt;
&lt;parameter&gt;cd /home/builder/Workspaces/fjtui/ &amp;&amp; cat go.mod 2&gt;/dev/null || cat package.json 2&gt;/dev/null || cat Cargo.toml 2&gt;/dev/null || cat pyproject.toml 2&gt;/dev/null || echo "No known config found"
&lt;/parameter&gt;
&lt;parameter&gt;description&lt;/parameter&gt;
&lt;parameter&gt;Check the project config to determine language/framework
&lt;/parameter&gt;
&lt;/tool_use&gt;
&lt;parameter&gt;name&gt;bash&lt;/parameter&gt;
&lt;parameter&gt;command
&lt;/parameter&gt;
&lt;parameter&gt;cd /home/builder/Workspaces/fjtui/ &amp;&amp; head -60 main.go 2&gt;/dev/null || head -60 main.py 2&gt;/dev/null || head -60 main.rs 2&gt;/dev/null || echo "No main file found"
&lt;/parameter&gt;
&lt;parameter&gt;description&lt;/parameter&gt;
&lt;parameter&gt;Check main entry point
&lt;/parameter&gt;
&lt;/tool_use&gt;
&lt;/tool_calls&gt;
builder@bosgamerz9:~/Workspaces/fjtui$ 
builder@bosgamerz9:~/Workspaces/fjtui$ cat README.md 
Let me first explore the project to understand what it does.

&lt;tool_calls&gt;&lt;tool_use&gt;
&lt;parameter&gt;name&gt;bash&lt;/parameter&gt;
&lt;parameter&gt;arguments&gt;
&lt;parameter&gt;command&lt;/parameter&gt;
&lt;parameter&gt;cd /home/builder/Workspaces/fjtui/ &amp;&amp; find . -type f | head -50
&lt;/parameter&gt;
&lt;parameter&gt;description&lt;/parameter&gt;
&lt;parameter&gt;List all files in the project
&lt;/parameter&gt;
&lt;/tool_use&gt;
&lt;parameter&gt;name&gt;bash&lt;/parameter&gt;
&lt;parameter&gt;command&lt;/parameter&gt;
&lt;parameter&gt;cd /home/builder/Workspaces/fjtui/ &amp;&amp; cat go.mod 2&gt;/dev/null || cat package.json 2&gt;/dev/null || cat Cargo.toml 2&gt;/dev/null || cat pyproject.toml 2&gt;/dev/null || echo "No known config found"
&lt;/parameter&gt;
&lt;parameter&gt;description&lt;/parameter&gt;
&lt;parameter&gt;Check the project config to determine language/framework
&lt;/parameter&gt;
&lt;/tool_use&gt;
&lt;parameter&gt;name&gt;bash&lt;/parameter&gt;
&lt;parameter&gt;command&lt;/parameter&gt;
&lt;parameter&gt;cd /home/builder/Workspaces/fjtui/ &amp;&amp; head -60 main.go 2&gt;/dev/null || head -60 main.py 2&gt;/dev/null || head -60 main.rs 2&gt;/dev/null || echo "No main file found"
&lt;/parameter&gt;
&lt;parameter&gt;description&lt;/parameter&gt;
&lt;parameter&gt;Check main entry point
&lt;/parameter&gt;
&lt;/tool_use&gt;
&lt;/tool_calls&gt;
builder@bosgamerz9:~/Workspaces/fjtui$ 
</code></pre></div></div>

<p>I switched to a faster granite4.1:8b model</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@bosgamerz9:~/Workspaces/fjtui$ crush run "Create a README.md" --cwd /home/builder/Workspaces/fjtui/ | tee READMEOUT.md
I’m unable to perform that action because the requested file or directory is located outside of the permitted workspace paths (/home/builder/Workspaces and /tmp). Please provide a path within those allowed directories, and I’ll be happy to assist you.
builder@bosgamerz9:~/Workspaces/fjtui$ crush run "Create /home/builder/Workspaces/fjtui/README.md" --cwd /home/builder/Workspaces/fjtui/ | tee READMEOUT.md
It looks like there wasn't a matching response for your query. Could you please provide more details or clarify what information you're looking for? I'm here to help with any text, file operations, searches, or other tasks within the capabilities of the tools available. Let me know how I can assist!
builder@bosgamerz9:~/Workspaces/fjtui$ crush run "Create /home/builder/Workspaces/fjtui/README.md with README contents" --cwd /home/builder/Workspaces/fjtui/ | tee READMEOUT.md

</code></pre></div></div>

<p>However, once I removed the errant tool output README.md and tried again, it worked just fine (i replaced backticks with dashes as this blog is in markdown)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@bosgamerz9:~/Workspaces/fjtui$ crush run "Create /home/builder/Workspaces/fjtui/README.md with README contents" --cwd /home/builder/Workspaces/fjtui/ | tee READMEOUT.md
The file at **/home/builder/Workspaces/fjtui/README.md** has been created (or overwritten) with the requested content. Let me know if there's anything else you need!
builder@bosgamerz9:~/Workspaces/fjtui$ cat README.md 
# fjtui
A lightweight UI toolkit for building cross-platform desktop applications.

## Features
- **Cross‑platform**: Works on Linux, macOS, and Windows with a single codebase.
- **Modern API**: Fluent builder pattern for fast UI construction.
- **Lightweight**: Minimal dependencies, small binary size.
- **Easily Extensible**: Custom widgets can be added without modifying core library.

## Getting Started
---bash
# Install via npm or yarn
npm install fjtui
# Or using Yarn
yarn add fjtui
---

### Hello World Example
---javascript
import { Window, Button } from 'fjtui';

const app = new Window({ title: 'Hello, fjtui!' });
app.body.append(new Button({ text: 'Click Me' }));
app.run();
---

## Documentation
Full documentation is available at https://github.com/yourorg/fjtui/blob/main/docs/index.md.

## Contributing
See the [CONTRIBUTING.md](https://github.com/yourorg/fjtui/blob/main/CONTRIBUTING.md) file for guidelines on contributing to this project.

---
*This README is generated by a tool and may require manual updates.*
</code></pre></div></div>

<p>However a simple ask like a system diagram had it fall down again</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@bosgamerz9:~/Workspaces/fjtui$ crush run "Update /home/builder/Workspaces/fjtui/README.md with a system diagram" --cwd /home/builder/Workspaces/fjtui/
The requested operation **`mcp 'systemdiagram'`** is not recognized or supported by the current set of tools and functions provided. There is no tool named `systemdiagram`, nor does the list include a function that matches that identifier.

If you meant to request something else—such as generating a diagram from another source, reading files related to a system diagram, or using a different MCP resource—please clarify your request, and I’ll be happy to assist with an appropriate tool.

builder@bosgamerz9:~/Workspaces/fjtui$ crush run "Update /home/builder/Workspaces/fjtui/README.md with a system diagram" --cwd /home/builder/Workspaces/fjtui/
I’m sorry—I cannot access that directory (`/home/builder/Workspaces`) because it isn’t part of the allowed file system locations for this environment. If you can provide a path within the permitted workspace (e.g., `~/my‑project/config.yaml` or another location shown as available), I’d be happy to read or modify the file for you. Let me know how you’d like to proceed!
builder@bosgamerz9:~/Workspaces/fjtui$ crush run "Update /home/builder/Workspaces/fjtui/README.md with a system diagram" --cwd /home/builder/Workspaces/fjtui/
I’m sorry, but the MCP server named **`fjtui`** isn’t listed among the available servers in this environment, so I’m unable to retrieve or interact with it as requested. If you have another server name or a different request, please let me know!

</code></pre></div></div>

<p>Though, I tried with Cline a few times just to have a comparison and it too just hates to write files</p>

<p><a href="/content/images/2026/06/crush-29.png"><img src="/content/images/2026/06/crush-29.png" alt="/content/images/2026/06/crush-29.png" /></a></p>

<h1 id="gca-one-more-time">GCA one more time</h1>

<p>Gemini Code Assist, GCA, the first offering by Google after Github Copilot will die an untimely death in the next couple weeks on June 18th.</p>

<p>I wanted to revisit why I liked it and will be saddened by it’s loss.</p>

<p>First, launching vs code from WSL on Windows let’s use fire up GCA with ease:</p>

<p><a href="/content/images/2026/06/gca-01.png"><img src="/content/images/2026/06/gca-01.png" alt="/content/images/2026/06/gca-01.png" /></a></p>

<p>Here you can see it easily handle a TUI Readme that Crush was having such troubles with:</p>
<video muted="" controls="">
    <source src="/content/images/2026/05/gca-02.mp4" type="video/mp4" />
</video>

<p>And perhaps it’s not really going away for <em>everyone</em>.  To be clear, the offering i had with AI Pro, a paid plan, <a href="https://developers.google.com/gemini-code-assist/resources/faqs">is just being taken away from us</a>.</p>

<p><a href="/content/images/2026/06/gca-03.png"><img src="/content/images/2026/06/gca-03.png" alt="/content/images/2026/06/gca-03.png" /></a></p>

<p>I can supposedly keep it with Gemini Code Assist for Enterprise, or Gemini CLI if I use it with a paid Vertex AI key</p>

<p><a href="/content/images/2026/06/gca-04.png"><img src="/content/images/2026/06/gca-04.png" alt="/content/images/2026/06/gca-04.png" /></a></p>

<p>part of the reason I so enjoyed GCA, even if it wasn’t as polished as other tools, was it was a way I could leverage my AI Pro accounts in Coder</p>

<p><a href="/content/images/2026/06/gca-05.png"><img src="/content/images/2026/06/gca-05.png" alt="/content/images/2026/06/gca-05.png" /></a></p>

<p>As it stands, going forward, there will be no official VS Code extension I can use and will only be able to use Gemini <em>models</em> via things like Cline and Continue.dev plugins - which is pay as you go, not leveraging the AI Pro subscription.</p>

<p>It would seem the only supported path to using AI Pro in Coder would be via <code class="language-plaintext highlighter-rouge">agy</code> CLI in the terminal which would work, but seems a “lesser” option.</p>

<p>You might say, use AI Studio - that’s an officially supported web interface.</p>

<p>But it seems really basic - like comparing KidPix to Photoshop. I mean i get “enjoy these tips while you wait” animated slides - who does that?</p>

<p><a href="/content/images/2026/06/aistudio-01.png"><img src="/content/images/2026/06/aistudio-01.png" alt="/content/images/2026/06/aistudio-01.png" /></a></p>

<p>So for example, I asked for a TUI animation for a TUI I’m working on and after 5+ minutes it made a webapp</p>

<p><a href="/content/images/2026/06/aistudio-02.png"><img src="/content/images/2026/06/aistudio-02.png" alt="/content/images/2026/06/aistudio-02.png" /></a></p>

<p>I can download that as a zip file or publish to Antigravity in Windows</p>

<p><a href="/content/images/2026/06/aistudio-03.png"><img src="/content/images/2026/06/aistudio-03.png" alt="/content/images/2026/06/aistudio-03.png" /></a></p>

<p>Or send to Github</p>

<p><a href="/content/images/2026/06/aistudio-04.png"><img src="/content/images/2026/06/aistudio-04.png" alt="/content/images/2026/06/aistudio-04.png" /></a></p>

<p>As of today, that is the only choice.  Github or nothing at all.  Don’t get the wrong idea, I don’t hate Github, but I do have issues with a lack of choice.</p>

<p>With Coder, I can can use Gitea for instance</p>

<p><a href="/content/images/2026/06/gca-06.png"><img src="/content/images/2026/06/gca-06.png" alt="/content/images/2026/06/gca-06.png" /></a></p>

<p>or Forgejo in code-server</p>

<p><a href="/content/images/2026/06/gca-07.png"><img src="/content/images/2026/06/gca-07.png" alt="/content/images/2026/06/gca-07.png" /></a></p>

<p>then I can use GCA to update the code in my private repos</p>

<p><a href="/content/images/2026/06/gca-08.png"><img src="/content/images/2026/06/gca-08.png" alt="/content/images/2026/06/gca-08.png" /></a></p>

<h1 id="summary">Summary</h1>

<p><a href="https://github.com/charmbracelet/crush">Charm Crush</a> is a pretty nice CLI.  It certainly is one of the better looking.  It worked fine with Cloud AI providers, but I really struggled getting it to work with local Ollama ones.  I tried with Linux, Windows and GPU and CPU hosted instances.  I tried small 2b models through 27b, the largest my hardware can run.  My biggest frustration was the “To-Do” list that would just die out.</p>

<p>I won’t give up - if I can get better hardware, I’ll circle back on this.  But for now, I’ll just keep it in my back pocket.</p>

<p>The other footnote on this article is saying goodbye to GCA and Gemini CLI.  Gemini CLI was (and is) one of my favourites.  I enjoyed the extensions, and OTel support.  I enjoyed the benefits of an insider track to converse with the creator on a nearly weekly basis for a while.</p>

<p>I do not have the same warm and fuzzy feeling towards Antigravity CLI.  I might use it, albeit begrudgingly, as it’s a closed-source tool that at launch, at least, had a lot of issues.  I also just tend to avoid any tool that requires a raw ‘deb’ to install.  Seems a bit shady to me.  Give me <code class="language-plaintext highlighter-rouge">npx</code>, <code class="language-plaintext highlighter-rouge">uv</code>, <code class="language-plaintext highlighter-rouge">brew</code> or even <code class="language-plaintext highlighter-rouge">choco</code> any day.</p>]]></content><author><name>Isaac Johnson</name></author><category term="GenAI" /><category term="crush" /><category term="GCA" /><category term="Geminicli" /><category term="Ollama" /><summary type="html"><![CDATA[Today we’ll take a look at Charm Crush. I love OS AI CLI’s and this one even Open Sources it’s providers. I’ll try some Cloud options but really focus on local Ollama hosted models.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://freshbrewed.science/content/images/2026/06/crushbg.png" /><media:content medium="image" url="https://freshbrewed.science/content/images/2026/06/crushbg.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Local LLM CLI: Opencode, Pi, Cline</title><link href="https://freshbrewed.science/2026/05/28/opencode.html" rel="alternate" type="text/html" title="Local LLM CLI: Opencode, Pi, Cline" /><published>2026-05-28T01:00:01+00:00</published><updated>2026-05-28T01:00:01+00:00</updated><id>https://freshbrewed.science/2026/05/28/opencode</id><content type="html" xml:base="https://freshbrewed.science/2026/05/28/opencode.html"><![CDATA[<p>It’s been a few since i used <a href="https://opencode.ai/">Opencode</a>.  When I last looked at in <a href="https://freshbrewed.science/2026/03/20/revisit.html">in March</a> i really just explored tying it to Gemini.  I want to see what I can do with local models instead.</p>

<p>Since now is a good time to revisit a few of these local Open-source CLIs, I also decided to install and try <a href="https://docs.cline.bot/cli/">Cline</a> which I had yet to do.  I’m just going to focus on the CLI this time (but it does have a VS Code setup as well).</p>

<p>Lastly, I noticed that <a href="https://pi.dev/">Pi</a> had a new root NPM package.  It seemed just as good a time to revisit that as well.</p>

<h1 id="opencode">Opencode</h1>

<p>I want to revisit <a href="https://opencode.ai/">Opencode</a> to again see if I can get it to work with local Ollama agents.  I’ve had some challenges in the past and I really wanted to give it another try.</p>

<h2 id="install">Install</h2>

<p>Let’s update our Open-Code, or install if you haven’t yet before:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/myTest$ curl -fsSL https://opencode.ai/install | bash

Installing opencode version: 1.15.10
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 100%
Successfully added opencode to $PATH in /home/builder/.bashrc

                                 ▄
█▀▀█ █▀▀█ █▀▀█ █▀▀▄ █▀▀▀ █▀▀█ █▀▀█ █▀▀█
█░░█ █░░█ █▀▀▀ █░░█ █░░░ █░░█ █░░█ █▀▀▀
▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀  ▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀


OpenCode includes free models, to start:

cd &lt;project&gt;  # Open directory
opencode      # Run command

For more information visit https://opencode.ai/docs


builder@DESKTOP-QADGF36:~/Workspaces/myTest$
</code></pre></div></div>

<p>When I fire it up, we can see it already detected my models</p>

<p><a href="/content/images/2026/05/opencode-01.png"><img src="/content/images/2026/05/opencode-01.png" alt="/content/images/2026/05/opencode-01.png" /></a></p>

<p>that’s because it pulls from the <code class="language-plaintext highlighter-rouge">~/.config/opencode/opencode.json</code> settings</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">builder@DESKTOP-QADGF</span><span class="mi">36</span><span class="err">:~/Workspaces/jekyll-blog$</span><span class="w"> </span><span class="err">cat</span><span class="w"> </span><span class="err">~/.config/opencode/opencode.json</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nl">"$schema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://opencode.ai/config.json"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"provider"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"ollama143"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"npm"</span><span class="p">:</span><span class="w"> </span><span class="s2">"@ai-sdk/openai-compatible"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"baseURL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://192.168.1.143:11434/v1"</span><span class="w">
            </span><span class="p">},</span><span class="w">
            </span><span class="nl">"models"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"qwen3:8b"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"qwen3:8b"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"deepseek-r1:7b"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"deepseek-r1:7b"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"gemma3:4b"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gemma3:4b"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"ollama121"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"npm"</span><span class="p">:</span><span class="w"> </span><span class="s2">"@ai-sdk/openai-compatible"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"baseURL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://192.168.1.121:11434/v1"</span><span class="w">
            </span><span class="p">},</span><span class="w">
            </span><span class="nl">"models"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"qwen3:8b"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"qwen3:8b"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"qwen3:14b"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"qwen3:14b"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"gemma3:4b"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gemma3:4b"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"ollama160"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"npm"</span><span class="p">:</span><span class="w"> </span><span class="s2">"@ai-sdk/openai-compatible"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"baseURL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://192.168.1.160:11434/v1"</span><span class="w">
            </span><span class="p">},</span><span class="w">
            </span><span class="nl">"models"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"gemma3:latest"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gemma3:latest"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"qwen3:latest"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"qwen3:latest"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"ollama220"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"npm"</span><span class="p">:</span><span class="w"> </span><span class="s2">"@ai-sdk/openai-compatible"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"baseURL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://192.168.1.220:11434/v1"</span><span class="w">
            </span><span class="p">},</span><span class="w">
            </span><span class="nl">"models"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"qwen3.5:latest"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"qwen3.5:latest"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"qwen3.6:27b"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"qwen3.6:27b"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"gemma4:e4b"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gemma4:e4b"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"codegemma:latest"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"codegemma:latest"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"codegemma:2b"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"codegemma:2b"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"granite4.1:8b"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"granite4.1:8b"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"aleshribar3/deepseek-r1-tool-calling:8b"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"aleshribar3/deepseek-r1-tool-calling:8b"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"reasoning"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"options"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"num_ctx"</span><span class="p">:</span><span class="w"> </span><span class="mi">65536</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>Spoiler:</strong> later I found I had better results if I included an explicit write permission in the JSON:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  "permission": {
    "edit": "allow"
  },
</code></pre></div></div>

<h2 id="init">init</h2>

<p>Now the next part is something I’m always neglecting to do - start with <code class="language-plaintext highlighter-rouge">/init</code> to tell OpenCode ‘here is my project’</p>

<p>I tried a couple different hosts and different size models, but in all cases, in WSL, it just keeps showing working with no results. I’ve let it go for hours so far</p>

<p><a href="/content/images/2026/05/opencode-02.png"><img src="/content/images/2026/05/opencode-02.png" alt="/content/images/2026/05/opencode-02.png" /></a></p>

<p>I’m wondering if it’s due to either WSL or being Ubuntu 22.02 jammy and not 22.04 noble or higher.</p>

<p>I hopped into a different Linux host with a newer Ubuntu (24.04 Noble) to try</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@bosgamerz9:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04.2 LTS
Release:        24.04
Codename:       noble
builder@bosgamerz9:~$ curl -fsSL https://opencode.ai/install | bash

Installing opencode version: 1.15.10
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 100%
Successfully added opencode to $PATH in /home/builder/.bashrc

                                 ▄
█▀▀█ █▀▀█ █▀▀█ █▀▀▄ █▀▀▀ █▀▀█ █▀▀█ █▀▀█
█░░█ █░░█ █▀▀▀ █░░█ █░░░ █░░█ █░░█ █▀▀▀
▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀  ▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀


OpenCode includes free models, to start:

cd &lt;project&gt;  # Open directory
opencode      # Run command

For more information visit https://opencode.ai/docs

</code></pre></div></div>

<p>I found <code class="language-plaintext highlighter-rouge">gemma4:e4b</code> fell down as it it didn’t see a tool it wanted to use</p>

<p><a href="/content/images/2026/05/opencode-03.png"><img src="/content/images/2026/05/opencode-03.png" alt="/content/images/2026/05/opencode-03.png" /></a></p>

<p>However, <code class="language-plaintext highlighter-rouge">Qwen3.5:latest</code> did fine</p>

<p><a href="/content/images/2026/05/opencode-04.png"><img src="/content/images/2026/05/opencode-04.png" alt="/content/images/2026/05/opencode-04.png" /></a></p>

<p>But I see no AGENT.md as I hoped</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@bosgamerz9:~/Workspaces/mytest$ ls -ltra
total 8
drwxrwxr-x 8 builder builder 4096 May 25 14:43 ..
drwxrwxr-x 2 builder builder 4096 May 25 14:43 .
</code></pre></div></div>

<p>I started down my tests of the models I had with <code class="language-plaintext highlighter-rouge">/init</code> and “build” agent mode with opencode 1.15.10.  Most of my Ollamas were 0.20.0 at this point (but i did upgrade one to 0.24.0 during the tests)</p>

<table>
  <thead>
    <tr>
      <th>model</th>
      <th>worked</th>
      <th>error</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>codegemma:2b</td>
      <td>no</td>
      <td>does not support tools</td>
    </tr>
    <tr>
      <td>codegemma:latest</td>
      <td>no</td>
      <td>does not support tools</td>
    </tr>
    <tr>
      <td>deepseek-r1-tool-calling:8b</td>
      <td>no</td>
      <td>said it would do it but did not</td>
    </tr>
    <tr>
      <td>gemma4:e4b</td>
      <td>no</td>
      <td>… error because I used a tool and the system reported it as unavailable…</td>
    </tr>
    <tr>
      <td>gemma3:4b</td>
      <td>no</td>
      <td>does not support tools</td>
    </tr>
    <tr>
      <td>granite4.1:8b</td>
      <td>no</td>
      <td>read tool was called with invalid arguments</td>
    </tr>
    <tr>
      <td>ministral-3:8b</td>
      <td>no</td>
      <td>task tool was called with invalid arguments</td>
    </tr>
    <tr>
      <td>qwen3:8b</td>
      <td>no</td>
      <td>got close, but not there</td>
    </tr>
    <tr>
      <td>qwen3:14b</td>
      <td>no</td>
      <td>read tool was called with invalid arguments</td>
    </tr>
    <tr>
      <td>qwen3.5:latest</td>
      <td>no</td>
      <td>comes back with details, but doesn’t make the AGENTS.md file.. trying with a file i did <code class="language-plaintext highlighter-rouge">touch</code> on next.. still no go</td>
    </tr>
    <tr>
      <td>qwen3.6:27b</td>
      <td>no</td>
      <td>thought then gave up with</td>
    </tr>
  </tbody>
</table>

<p>Qwen3:8b was interesting in that after some time, it put together a plan</p>

<p><a href="/content/images/2026/05/opencode-05.png"><img src="/content/images/2026/05/opencode-05.png" alt="/content/images/2026/05/opencode-05.png" /></a></p>

<p>Asked for permission to <code class="language-plaintext highlighter-rouge">/path/to</code></p>

<p><a href="/content/images/2026/05/opencode-06.png"><img src="/content/images/2026/05/opencode-06.png" alt="/content/images/2026/05/opencode-06.png" /></a></p>

<p>Then was confused when (the clearly placeholder) path didn’t exist</p>

<p><a href="/content/images/2026/05/opencode-07.png"><img src="/content/images/2026/05/opencode-07.png" alt="/content/images/2026/05/opencode-07.png" /></a></p>

<p>It then came back</p>

<p><a href="/content/images/2026/05/opencode-08.png"><img src="/content/images/2026/05/opencode-08.png" alt="/content/images/2026/05/opencode-08.png" /></a></p>

<p>I kept trying to prompt it to use the right path.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Create or update `AGENTS.md` for this repository.
The goal is a compact instruction file that helps future OpenCode sessions avoid mistakes and ramp up quickly. Every line should answer: "Would an agent likely miss this without help?" If not, leave it out.
User-provided focus or constraints (honor these):
with path set to /home/builder/Workspaces/mytest
## How to investigate
Read the highest-value sources first:
- `README*`, root manifests, workspace config, lockfiles
- build, test, lint, formatter, typecheck, and codegen config
- CI workflows and pre-commit / task runner config
- existing instruction files (`AGENTS.md`, `CLAUDE.md`, `.cursor/rules/`, `.cursorrules`, `.github/copilot-instructions.md`)
- repo-local OpenCode config such as `opencode.json`
If architecture is still unclear after reading config and docs, inspect a small number of representative code files to find the real entrypoints, package boundaries, and execution flow. Prefer reading the files that explain how the system is wired together over random leaf files.
Prefer executable sources of truth over prose. If docs conflict with config or scripts, trust the executable source and only keep what you can verify.
## What to extract
Look for the highest-signal facts for an agent working in this repo:
- exact developer commands, especially non-obvious ones
- how to run a single test, a single package, or a focused verification step
- required command order when it matters, such as `lint -&gt; typecheck -&gt; test`
- monorepo or multi-package boundaries, ownership of major directories, and the real app/library entrypoints
- framework or toolchain quirks: generated code, migrations, codegen, build artifacts, special env loading, dev servers, infra deploy flow
- repo-specific style or workflow conventions that differ from defaults
- testing quirks: fixtures, integration test prerequisites, snapshot workflows, required services, flaky or expensive suites
- important constraints from existing instruction files worth preserving
Good `AGENTS.md` content is usually hard-earned context that took reading multiple files to infer.
## Questions
Only ask the user questions if the repo cannot answer something important. Use the `question` tool for one short batch at most.
Good questions:
- undocumented team conventions
- branch / PR / release expectations
- missing setup or test prerequisites that are known but not written down
Do not ask about anything the repo already makes clear.
## Writing rules
Include only high-signal, repo-specific guidance such as:
- exact commands and shortcuts the agent would otherwise guess wrong
- architecture notes that are not obvious from filenames
- conventions that differ from language or framework defaults
- setup requirements, environment quirks, and operational gotchas
- references to existing instruction sources that matter
Exclude:
- generic software advice
- long tutorials or exhaustive file trees
- obvious language conventions
- speculative claims or anything you could not verify
- content better stored in another file referenced via `opencode.json` `instructions`
When in doubt, omit.
Prefer short sections and bullets. If the repo is simple, keep the file simple. If the repo is large, summarize the few structural facts that actually change how an agent should work.
If `AGENTS.md` already exists at `/`, improve it in place rather than rewriting blindly. Preserve verified useful guidance, delete fluff or stale claims, and reconcile it with the current codebase.
</code></pre></div></div>

<p>But it would just forget where it left off</p>

<p><a href="/content/images/2026/05/opencode-10.png"><img src="/content/images/2026/05/opencode-10.png" alt="/content/images/2026/05/opencode-10.png" /></a></p>

<p>DSr1 with tools came back after 30m but didn’t seem to do anything</p>

<p><a href="/content/images/2026/05/opencode-09.png"><img src="/content/images/2026/05/opencode-09.png" alt="/content/images/2026/05/opencode-09.png" /></a></p>

<h3 id="cloud">Cloud</h3>

<p>I didn’t want to rely on cloud, but maybe this is an Opencode vs my Ollama issue.</p>

<p>I tried the free “Big Pickle” model from OpenCode Zen</p>

<p><a href="/content/images/2026/05/opencode-11.png"><img src="/content/images/2026/05/opencode-11.png" alt="/content/images/2026/05/opencode-11.png" /></a></p>

<p>That worked fine.</p>

<h3 id="supermemory">Supermemory</h3>

<p>I next wanted to try a memory plugin for Opencode, supermemory</p>

<p>I needed to add <a href="https://bun.com/docs/installation">Bun</a> to my system, but then it was just a matter of running</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ bunx opencode-supermemory@latest install --no-tui
</code></pre></div></div>

<p>and adding my <a href="https://app.supermemory.ai/">Supermemory</a> API key (in Supermemory they call it the “Raycast Extension”) in <code class="language-plaintext highlighter-rouge">~/.config/opencode/opencode.jsonc</code> as detailed <a href="https://github.com/supermemoryai/opencode-supermemory">in this doc</a></p>

<p>But even with Supermemory, it just wont recall its step and gets stuck</p>

<p><a href="/content/images/2026/05/opencode-12.png"><img src="/content/images/2026/05/opencode-12.png" alt="/content/images/2026/05/opencode-12.png" /></a></p>

<p>I kept working on this for a while and saw a suggestion to ensure “write” was enabled in the opencode.json file.  So I added a permission block to be explicit about write</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat ~/.config/opencode/opencode.json | head -n10
{
  "$schema": "https://opencode.ai/config.json",
  "permission": {
    "edit": "allow"
  },
  "provider": {
    "ollama143": {
      "npm": "@ai-sdk/openai-compatible",
      "options": {
        "baseURL": "http://192.168.1.143:11434/v1"
</code></pre></div></div>

<p>I then managed to get granite4.1:8b to work</p>

<p><a href="/content/images/2026/05/opencode-13.png"><img src="/content/images/2026/05/opencode-13.png" alt="/content/images/2026/05/opencode-13.png" /></a></p>

<p>and actually write files</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@builder-Lenny16:~/Workspaces/mytest2$ ls -ltra
total 20
drwxrwxr-x 15 builder builder 4096 May 25 16:02 ..
-rw-rw-r--  1 builder builder   97 May 26 07:09 README.md
-rw-rw-r--  1 builder builder   50 May 26 07:26 requirements.txt
-rw-rw-r--  1 builder builder  515 May 26 07:26 server.py
drwxrwxr-x  2 builder builder 4096 May 26 07:26 .
</code></pre></div></div>

<p>However, when I had granite4.1 create a Dockerfile, it did a rather interesting markdown for using Docker, not a Dockerfile</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@builder-Lenny16:~/Workspaces/mytest2$ cat Dockerfile
# Docker Setup

This repository contains the necessary files to build and run a Docker container for `mytest2`.

## Building the Image

To build the Docker image, navigate to the project root and execute:
---bash
docker build -t mytest2 .
---

## Running the Container

After building, you can run the container with:
---bash
docker run --name mytest2_container -dit mytest2
---

## Usage

You can interact with the running container using:
---bash
docker exec -it mytest2_container /bin/sh
---

---

## Contributing

We welcome contributions! Please follow these guidelines:
1. Fork the repository.
2. Create a new branch for your feature: `git checkout -b feature/YourFeature`.
3. Make changes and commit them: `git commit -m "Add descriptive commit message"`.
4. Push to your fork: `git push origin feature/YourFeature`.
5. Open a Pull Request against the main branch.

### Code of Conduct

This project adheres to a [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you agree to uphold these guidelines.
</code></pre></div></div>

<p>However, I continue to find challenges with Opencode forgetting it’s home directory and trying to write files to <code class="language-plaintext highlighter-rouge">/</code> or other strange roots</p>

<p><a href="/content/images/2026/05/opencode-14.png"><img src="/content/images/2026/05/opencode-14.png" alt="/content/images/2026/05/opencode-14.png" /></a></p>

<h1 id="cline">Cline</h1>

<p>I wanted to test an LLM CLI I had heard about but never tried, <a href="https://docs.cline.bot/cli/">Cline</a>.</p>

<p>You just need to install with npm</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install -g cline
</code></pre></div></div>

<p>Then launch with <code class="language-plaintext highlighter-rouge">cline</code> and set the model to be Ollama</p>

<h2 id="replicating-the-opencode-init">Replicating the OpenCode init</h2>

<p>I had to run it twice but it worked just fine with qwen3:8b</p>

<p><a href="/content/images/2026/05/cline-01.png"><img src="/content/images/2026/05/cline-01.png" alt="/content/images/2026/05/cline-01.png" /></a></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@bosgamerz9:~/Workspaces/mytest$ cline

Session Summary
  ID        1779793430569_u5y6s
  Duration  526s
  Model     ollama:qwen3:8b
  CWD       /home/builder/Workspaces/mytest
  Messages  6
  Cost      $0.00
  Continue  cline --id 1779793430569_u5y6s
</code></pre></div></div>

<p>which showed a basic agents file</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@bosgamerz9:~/Workspaces/mytest$ cat AGENTS.md
## AGENTS

### Overview

This section provides an overview of the agents and their roles in the repository.

### Agents

#### 1. Cline
- **Role**: Main AI coding agent
- **Responsibilities**: Assisting users with coding tasks, generating code, testing, and maintaining code quality.
- **Functions**: Code generation, testing, file manipulation, and system interaction.

#### 2. Helper Agents
- **Role**: Sub-agents or teammates that assist with specialized tasks.
- **Responsibilities**: Performing focused tasks such as testing, documentation, and system monitoring.
- **Functions**: Task delegation, system monitoring, and documentation.

### Teammates

#### 1. Documentation Agent
- **Role**: Assisting with documentation and maintaining code quality.
- **Responsibilities**: Generating and updating documentation for code changes.
- **Functions**: Documentation generation, code quality checks.

#### 2. Testing Agent
- **Role**: Ensuring the correctness of code changes.
- **Responsibilities**: Writing and running tests for new features and bug fixes.
- **Functions**: Test generation, test execution, and test reporting.

### Conclusion

This repository uses a team of agents to ensure code quality, maintain documentation, and streamline development tasks.

**Last Updated**: 5/26/2026builder@bosgamerz9:~/Workspaces/mytest$
</code></pre></div></div>

<p>I finally had gemma4:e4b which was running on my Windows host working!  Again, I had to try twice but the second time through it worked dandy</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/mytest2$ cline

Session Summary
  ID        1779793718556_3kd9r
  Duration  270s
  Model     ollama:gemma4:e4b
  CWD       /home/builder/Workspaces/mytest2
  Messages  17
  Cost      $0.00
  Continue  cline --id 1779793718556_3kd9r
</code></pre></div></div>

<p>With an AGENTS.md</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/mytest2$ cat AGENTS.md
# Agent Guide: MyTest2 Repository Instructions

This file serves as a highly condensed, machine-readable guide for AI agents working in this repository. Every line documents a process, command, or convention that an agent might otherwise guess incorrectly or miss entirely. **Follow this guide strictly.**

## 🚀 Getting Started &amp; Setup
*   **Installation:** Always run `npm ci` (or `yarn install --frozen-lockfile`) to ensure dependencies match the lockfile exactly. Do not rely on `npm install`.
*   **Environment:** The system requires the `NODE_ENV` to be set to `development` for local server startups, even if the build process uses `production`.
*   **Required Tools:** Ensure `eslint`, `prettier`, and `typescript` are installed as dev dependencies.

## 🏗️ Development Workflow &amp; Commands
*   **Pre-flight Check Order:** When debugging or making changes, the required command order is critical and must be followed: `(1) lint -&gt; (2) typecheck -&gt; (3) test`. Never skip a step.
    1.  **Linting (Style Check):** Run `npm run lint:fix` first to automatically correct style issues. Failure here means a structural style problem exists.
    2.  **Type Checking (Structural Check):** Run `npm run typecheck`. This is a *compile-time* check and does not test runtime execution, but it catches incorrect API usage or missing types.
    3.  **Testing (Execution Check):** Only run `npm test` *after* linting and typechecking pass successfully. This ensures the test environment is clean.

*   **Running Single Tests:** To focus verification, use the specific test utility provided by the framework (e.g., `npx jest --only-tests="Feature X Test"`). Passing the test name/file path is mandatory.

## 🧩 Architecture &amp; Boundaries
*   **Monorepo Structure:** This repository is structured as a mono-repo. The core application logic resides in the `src/` directory. Specific libraries/modules are located in `packages/`.
*   **Entry Point:** The main application entry point is located at `src/index.js` (or `src/main.ts`).
*   **Data/Models:** All primary data models and interfaces are defined in `src/models/`. **Do not** write business logic directly in these files; they are purely declarative.
*   **Service Layer:** Business logic MUST be encapsulated within the `services/` directory. Services are responsible for coordinating model interactions and external API calls.

## 🔧 Toolchain Quirks &amp; Gotchas
*   **Database Migrations:** Running database changes requires the dedicated migration step: `npm run db:migrate`. This runs *before* any testing suite that depends on the updated schema.
*   **Generated Code:** Files within `src/generated/` or `packages/utils/generated/` are *never* to be edited manually. They are the output of the codegen process.
*   **Mocking:** When writing integration tests, always use the `MockService` class located in `test/mocks/` to control external dependencies (e.g., payment gateways, third-party APIs).

## ⛔ Exclusions
*   Do not rely on simple `npm run dev` for production readiness validation. It is only for local development.
*   If a feature involves state or side effects, the implementation should use the `EventBus` pattern documented in `src/utils/event.js`, not direct global modification.
</code></pre></div></div>

<p>I noted that Cline found all my Skills already installed in .agents/skills</p>

<p><a href="/content/images/2026/05/cline-02.png"><img src="/content/images/2026/05/cline-02.png" alt="/content/images/2026/05/cline-02.png" /></a></p>

<h2 id="create-a-fastmcp-server">Create a FastMCP server</h2>

<p>I decided to try Cline with a task of making a FastMCP server</p>

<p><a href="/content/images/2026/05/cline-03.png"><img src="/content/images/2026/05/cline-03.png" alt="/content/images/2026/05/cline-03.png" /></a></p>

<p>It went surprisingly smooth on my Windows host with WSL</p>

<p><a href="/content/images/2026/05/cline-04.png"><img src="/content/images/2026/05/cline-04.png" alt="/content/images/2026/05/cline-04.png" /></a></p>

<p>Creating the proper directory structure</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/mytest2$ ls
CONTRIBUTING.md  Dockerfile  README.md  mcp_server.py  requirements.txt  src
builder@DESKTOP-QADGF36:~/Workspaces/mytest2$ tree .
.
├── CONTRIBUTING.md
├── Dockerfile
├── README.md
├── mcp_server.py
├── requirements.txt
└── src
    └── tools
        └── hello_world_tool.py

2 directories, 6 files
</code></pre></div></div>

<p>and I knew what I wanted for the MCP server so I was, again, a bit surprised when it added proper tool registration and exposing on 0.0.0.0, which most default guides omit</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span> <span class="n">cat</span> <span class="n">mcp_server</span><span class="p">.</span><span class="n">py</span>
<span class="c1"># mcp_server.py
</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
<span class="kn">from</span> <span class="nn">dotenv</span> <span class="kn">import</span> <span class="n">load_dotenv</span>
<span class="kn">from</span> <span class="nn">src.tools.hello_world_tool</span> <span class="kn">import</span> <span class="n">HelloWorldTool</span>
<span class="kn">from</span> <span class="nn">fastmcp.fastapi</span> <span class="kn">import</span> <span class="n">FastMCP</span>

<span class="c1"># Load environment variables from .env file
</span><span class="n">load_dotenv</span><span class="p">()</span>

<span class="c1"># Initialize FastAPI and FastMCP
</span><span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s">"FastMCP Tool Server"</span><span class="p">)</span>
<span class="n">mcp_router</span> <span class="o">=</span> <span class="n">FastMCP</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>

<span class="o">@</span><span class="n">mcp_router</span><span class="p">.</span><span class="n">tool</span><span class="p">(</span><span class="n">HelloWorldTool</span><span class="p">())</span>
<span class="k">def</span> <span class="nf">setup_tools</span><span class="p">():</span>
    <span class="s">"""Registers all available tools with the FastMCP router."""</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Successfully registered tools."</span><span class="p">)</span>

<span class="c1"># 1. Include the FastAPI routes for the MCP framework and the registered tools
</span><span class="o">@</span><span class="n">mcp_router</span><span class="p">.</span><span class="n">api_router</span>
<span class="k">def</span> <span class="nf">include_mcp</span><span class="p">():</span>
    <span class="k">return</span> <span class="n">mcp_router</span><span class="p">.</span><span class="n">get_root_router</span><span class="p">()</span>

<span class="c1"># Simple root endpoint for health check
</span><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"/"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">read_root</span><span class="p">():</span>
    <span class="k">return</span> <span class="p">{</span><span class="s">"status"</span><span class="p">:</span> <span class="s">"FastMCP Server Running!"</span><span class="p">,</span> <span class="s">"version"</span><span class="p">:</span> <span class="s">"1.0"</span><span class="p">}</span>

<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">uvicorn</span>
    <span class="c1"># Assuming the port is read from environment variables or fallback to 80
</span>    <span class="n">port</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"PORT"</span><span class="p">,</span> <span class="mi">80</span><span class="p">))</span>
    <span class="n">uvicorn</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="s">"0.0.0.0"</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="n">port</span><span class="p">)</span>
</code></pre></div></div>

<h1 id="pi">Pi</h1>

<p>The <a href="https://pi.dev/">Pi CLI</a> has a new version (and root path).  I used <code class="language-plaintext highlighter-rouge">--force</code> to update it</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install -g --force --ignore-scripts @earendil-works/pi-coding-agent
</code></pre></div></div>

<p>Following our prior FastMCP example, I asked it to create the Dockerfile (as granite4.1 has made a mess of it)</p>

<p><a href="/content/images/2026/05/pi-01.png"><img src="/content/images/2026/05/pi-01.png" alt="/content/images/2026/05/pi-01.png" /></a></p>

<p>This file looked much better</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@builder-Lenny16:~/Workspaces/mytest2$ cat Dockerfile
# Use an official Python runtime as a parent image
FROM python:3.11-slim

# Set the working directory in the container
WORKDIR /app

# Copy the requirements file and install dependencies
# This step is cached unless requirements.txt changes
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code
COPY server.py .

# Expose the port the application runs on (from server.py: port 5000)
EXPOSE 5000

# Command to run the application
# We use the python command structure defined in server.py
CMD ["python", "server.py"]
</code></pre></div></div>

<p>Something I noted about Pi that I hadn’t seen in Opencode was that when it asked me a question, it seemed to keep going and not forget what it had asked me about:</p>

<p><a href="/content/images/2026/05/pi-02.png"><img src="/content/images/2026/05/pi-02.png" alt="/content/images/2026/05/pi-02.png" /></a></p>

<h1 id="ubuntu-speed-issue-fixed">Ubuntu Speed Issue (fixed)</h1>

<p>I noticed the performance of my ‘220’ server, the gaming laptop with the GPU was just crap.  I also didn’t hear the fans kicking in like I expected.</p>

<p>Recently I had upgraded to Ubuntu 26.04 (Resolute Raccoon).</p>

<p>when I went to test the nvidia drivers (nvidia-smi) i saw some errors.</p>

<p>reading online a bit, it suggested that is when your kernel might have old drivers loaded.</p>

<p>I did a quick</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo ubuntu-drivers install
$ sudo reboot
</code></pre></div></div>

<p>And when it came back, it was wicked fast again (and I heard the fans kick in).  Also, <code class="language-plaintext highlighter-rouge">nvidia-smi</code> showed actual content.</p>

<p>Create an MCP server using FastMCP python framework.  Include requirements.txt, a hello world tool that returns “Hello, {user}”, a Dockerfile that will build and run, a README.md and a CONTRIBUTING.md</p>

<h1 id="summary">Summary</h1>

<p>A few key things with all these tools and Ollama.  If your nvidia setup isn’t working then Ollama will fall back to CPU and give really slow results.  I found this to be the case with my laptop that had moved to Ubuntu 26 but neglected to update to the latest nVidia drivers.</p>

<p><a href="https://opencode.ai/">Opencode</a>, when using a remote model is just fine.  And <em>sometimes</em> works with small asks locally.  But overall, I’m just not finding it to be great with my hosts and Ollama.</p>

<p><a href="https://cline.bot/cli">Cline</a> was new to me.  It’s kind of fun (yes, silly i know) that it follows you while you type:</p>

<p><a href="/content/images/2026/05/cline-05.gif"><img src="/content/images/2026/05/cline-05.gif" alt="/content/images/2026/05/cline-05.gif" /></a></p>

<p>I found it worked just great for most off my asks.</p>

<p><a href="https://pi.dev/">Pi</a> seemed even better than before.  I actually like how before it does anything, when launched, it immediately reads your directory so it’s good to go with whatever your ask might be.</p>

<p>One thing you need to remember with Pi and Opencode is to keep the models file up to date. For instance, to test the new ministral-3:8b with Pi, i needed to add an entry in the models.json file</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">builder@builder-Lenny</span><span class="mi">16</span><span class="err">:~/Workspaces/mytest</span><span class="mi">2</span><span class="err">$</span><span class="w"> </span><span class="err">cat</span><span class="w"> </span><span class="err">~/.pi/agent/models.json</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"providers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"ollama"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"baseUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:11434/v1"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"api"</span><span class="p">:</span><span class="w"> </span><span class="s2">"openai-completions"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"apiKey"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ollama"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"models"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mistral-nemo:12b-instruct-2407-q4_K_M"</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"qwen2.5-coder:14b"</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gemma3:12b"</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gemma4:e4b"</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ministral-3:8b"</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"deepseek-r1:14b"</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"qwen3:14b"</span><span class="w"> </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"qwen3.5:latest"</span><span class="w"> </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Lastly, just because we were focused on local models, I felt it only fitting to generate the splash image locally with ComfyUI</p>

<p><a href="/content/images/2026/05/comfyui-01.png"><img src="/content/images/2026/05/comfyui-01.png" alt="/content/images/2026/05/comfyui-01.png" /></a></p>

<p>Which was accomplished with z-image-turbo</p>

<p><a href="/content/images/2026/05/comfyui-02.png"><img src="/content/images/2026/05/comfyui-02.png" alt="/content/images/2026/05/comfyui-02.png" /></a></p>]]></content><author><name>Isaac Johnson</name></author><category term="GenAI" /><category term="Pi" /><category term="Cline" /><category term="Opencode" /><category term="Ollama" /><summary type="html"><![CDATA[It’s been a few since i used Opencode. When I last looked at in in March i really just explored tying it to Gemini. I want to see what I can do with local models instead.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://freshbrewed.science/content/images/2026/05/opencodepiclinebg.png" /><media:content medium="image" url="https://freshbrewed.science/content/images/2026/05/opencodepiclinebg.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">OS Apps: Skysend and Fider</title><link href="https://freshbrewed.science/2026/05/26/miscapps.html" rel="alternate" type="text/html" title="OS Apps: Skysend and Fider" /><published>2026-05-26T01:00:01+00:00</published><updated>2026-05-26T01:00:01+00:00</updated><id>https://freshbrewed.science/2026/05/26/miscapps</id><content type="html" xml:base="https://freshbrewed.science/2026/05/26/miscapps.html"><![CDATA[<p>Today we’ll dig into two interesting Open-source products; <a href="https://skysend.app">SkySend</a> and <a href="https://fider.io">Fider</a>.  Skysend is all about sharing files and notes with various secure options whereas Fider is about collecting ideas from people (like a digital suggestion box).</p>

<p>Let’s start with Skysend…</p>

<h2 id="skysend">Skysend</h2>

<p>I saw <a href="https://mariushosting.com/how-to-install-skysend-on-your-synology-nas/">this Marius post</a> on <a href="https://github.com/skyfay/skysend">SkySend</a> which bills itself as “Minimalist, end-to-end encrypted, self-hostable file and note sharing. Zero-knowledge server”.</p>

<p>The docker compose yaml:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># docker-compose.yml
services:
  skysend:
    image: skyfay/skysend:latest
    container_name: skysend
    restart: always
    ports:
      - "3000:3000"
    volumes:
      - ./data:/data
      - ./uploads:/uploads
    environment:
      - BASE_URL=http://localhost:3000
      # All environment variables: https://docs.skysend.app/user-guide/configuration/environment-variables
      # There are a lot of customization options available, so make sure to check the documentation for more details.
</code></pre></div></div>

<p>I’ll immediately convert this to a Kubernetes manifest</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">skysend-deployment</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">skysend</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">replicas</span><span class="pi">:</span> <span class="m">1</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">matchLabels</span><span class="pi">:</span>
      <span class="na">app</span><span class="pi">:</span> <span class="s">skysend</span>
  <span class="na">template</span><span class="pi">:</span>
    <span class="na">metadata</span><span class="pi">:</span>
      <span class="na">labels</span><span class="pi">:</span>
        <span class="na">app</span><span class="pi">:</span> <span class="s">skysend</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">containers</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">skysend</span>
        <span class="na">image</span><span class="pi">:</span> <span class="s">skyfay/skysend:latest</span>
        <span class="na">ports</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">3000</span>
        <span class="na">envFrom</span><span class="pi">:</span>
          <span class="c1"># Reads environment variables from the ConfigMap</span>
          <span class="pi">-</span> <span class="na">configMapRef</span><span class="pi">:</span>
              <span class="na">name</span><span class="pi">:</span> <span class="s">skysend-env</span>
        <span class="na">volumeMounts</span><span class="pi">:</span>
        <span class="c1"># Mounts the PVCs to the specified paths</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">data-storage</span>
          <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/data</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">uploads-storage</span>
          <span class="na">mountPath</span><span class="pi">:</span> <span class="s">/uploads</span>
      <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">data-storage</span>
        <span class="na">persistentVolumeClaim</span><span class="pi">:</span>
          <span class="na">claimName</span><span class="pi">:</span> <span class="s">skysend-data-pvc</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">uploads-storage</span>
        <span class="na">persistentVolumeClaim</span><span class="pi">:</span>
          <span class="na">claimName</span><span class="pi">:</span> <span class="s">skysend-uploads-pvc</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">skysend-service</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">skysend</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="c1"># This service exposes the application internally via ClusterIP.</span>
  <span class="c1"># If you intend for external access directly without an Ingress, use type: LoadBalancer.</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">skysend</span>
  <span class="na">ports</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
    <span class="na">port</span><span class="pi">:</span> <span class="m">80</span>       <span class="c1"># Service port exposed</span>
    <span class="na">targetPort</span><span class="pi">:</span> <span class="m">3000</span> <span class="c1"># Container port</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Ingress</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">skysend-ingress</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">kubernetes.io/ingress.class</span><span class="pi">:</span> <span class="s">nginx</span>
    <span class="na">cert-manager.io/cluster-issuer</span><span class="pi">:</span> <span class="s">gcpleprod2</span>
    <span class="na">ingress.kubernetes.io/proxy-body-size</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0"</span>
    <span class="na">ingress.kubernetes.io/ssl-redirect</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
    <span class="na">kubernetes.io/tls-acme</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
    <span class="na">nginx.ingress.kubernetes.io/proxy-body-size</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0"</span>
    <span class="na">nginx.ingress.kubernetes.io/proxy-read-timeout</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3600"</span>
    <span class="na">nginx.ingress.kubernetes.io/proxy-send-timeout</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3600"</span>
    <span class="na">nginx.ingress.kubernetes.io/ssl-redirect</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
    <span class="na">nginx.org/client-max-body-size</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0"</span>
    <span class="na">nginx.org/proxy-connect-timeout</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3600"</span>
    <span class="na">nginx.org/proxy-read-timeout</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3600"</span>
    <span class="na">nginx.org/websocket-services</span><span class="pi">:</span> <span class="s">skysend-service</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">rules</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">host</span><span class="pi">:</span> <span class="s">skysend.steeped.icu</span>
    <span class="na">http</span><span class="pi">:</span>
      <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
        <span class="na">pathType</span><span class="pi">:</span> <span class="s">Prefix</span>
        <span class="na">backend</span><span class="pi">:</span>
          <span class="na">service</span><span class="pi">:</span>
            <span class="na">name</span><span class="pi">:</span> <span class="s">skysend-service</span>
            <span class="na">port</span><span class="pi">:</span>
              <span class="na">number</span><span class="pi">:</span> <span class="m">80</span>
  <span class="na">tls</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">skysend.steeped.icu</span>
    <span class="na">secretName</span><span class="pi">:</span> <span class="s">skysendgcp-tls</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ConfigMap</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">skysend-env</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">skysend</span>
<span class="na">data</span><span class="pi">:</span>
  <span class="na">BASE_URL</span><span class="pi">:</span> <span class="s">https://skysend.steeped.icu/</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">PersistentVolumeClaim</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">skysend-data-pvc</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">skysend</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">accessModes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">ReadWriteOnce</span>
  <span class="na">resources</span><span class="pi">:</span>
    <span class="na">requests</span><span class="pi">:</span>
      <span class="na">storage</span><span class="pi">:</span> <span class="s">5Gi</span> <span class="c1"># Adjust size as needed</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">PersistentVolumeClaim</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">skysend-uploads-pvc</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">skysend</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">accessModes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">ReadWriteOnce</span>
  <span class="na">resources</span><span class="pi">:</span>
    <span class="na">requests</span><span class="pi">:</span>
      <span class="na">storage</span><span class="pi">:</span> <span class="s">5Gi</span> <span class="c1"># Adjust size as needed</span>
</code></pre></div></div>

<p>then create an A record we can use to serve traffic</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gcloud dns --project=myanthosproject2 record-sets create skysend.steeped.icu --zone="steepedicu" --type="A" --ttl="300" --rrdatas="76.156.69.232"
NAME                  TYPE  TTL  DATA
skysend.steeped.icu.  A     300  76.156.69.232
</code></pre></div></div>

<p>I can then apply the YAML manifest</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl apply -f ./skysend.yaml
deployment.apps/skysend-deployment created
service/skysend-service created
Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
ingress.networking.k8s.io/skysend-ingress created
configmap/skysend-env created
persistentvolumeclaim/skysend-data-pvc created
persistentvolumeclaim/skysend-uploads-pvc created
</code></pre></div></div>

<p>Once I see the cert satisfied</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get cert skysendgcp-tls
NAME             READY   SECRET           AGE
skysendgcp-tls   True    skysendgcp-tls   92s
</code></pre></div></div>

<p>I can try the URL: https://skysend.steeped.icu/</p>

<p><a href="/content/images/2026/05/skysend-01.png"><img src="/content/images/2026/05/skysend-01.png" alt="/content/images/2026/05/skysend-01.png" /></a></p>

<p>I’ll try uploading an image</p>

<p><a href="/content/images/2026/05/skysend-02.png"><img src="/content/images/2026/05/skysend-02.png" alt="/content/images/2026/05/skysend-02.png" /></a></p>

<p>I then get a shareable link as well as a QR code</p>

<p><a href="/content/images/2026/05/skysend-03.png"><img src="/content/images/2026/05/skysend-03.png" alt="/content/images/2026/05/skysend-03.png" /></a></p>

<p>Trying that in an incognito window prompts for a password</p>

<p><a href="/content/images/2026/05/skysend-04.png"><img src="/content/images/2026/05/skysend-04.png" alt="/content/images/2026/05/skysend-04.png" /></a></p>

<p>Let’s try getting that image back in an incognito window and seeing what a bad password would show.  I’ll use a bad password a few times, then a good one, then refresh:</p>

<video muted="" controls="">
    <source src="/content/images/2026/05/skysend-05.mp4" type="video/mp4" />
</video>

<p>Next I’ll try a note.  I’ll use Markdown and no password, but leave the expiry</p>

<p><a href="/content/images/2026/05/skysend-06.png"><img src="/content/images/2026/05/skysend-06.png" alt="/content/images/2026/05/skysend-06.png" /></a></p>

<p>I can then use the note URL in an incognito window</p>

<p><a href="/content/images/2026/05/skysend-07.png"><img src="/content/images/2026/05/skysend-07.png" alt="/content/images/2026/05/skysend-07.png" /></a></p>

<p>Viewing it does render the markdown, and moreover the “copy” button copies the raw markdown to the user’s clipboard</p>

<p><a href="/content/images/2026/05/skysend-08.png"><img src="/content/images/2026/05/skysend-08.png" alt="/content/images/2026/05/skysend-08.png" /></a></p>

<p>Password creation is similar to note in that we can enter it and optionally password protect our new password</p>

<p><a href="/content/images/2026/05/skysend-09.png"><img src="/content/images/2026/05/skysend-09.png" alt="/content/images/2026/05/skysend-09.png" /></a></p>

<p>Loading the created link in another window suggests its a view note</p>

<p><a href="/content/images/2026/05/skysend-10.png"><img src="/content/images/2026/05/skysend-10.png" alt="/content/images/2026/05/skysend-10.png" /></a></p>

<p>Where view note then will let the user copy the password to the clipboard or use the “eye” icon to view it plain text</p>

<p><a href="/content/images/2026/05/skysend-11.png"><img src="/content/images/2026/05/skysend-11.png" alt="/content/images/2026/05/skysend-11.png" /></a></p>

<p>Code is the same, though I did just realize there is a max expires of 7d</p>

<p><a href="/content/images/2026/05/skysend-12.png"><img src="/content/images/2026/05/skysend-12.png" alt="/content/images/2026/05/skysend-12.png" /></a></p>

<p>And the view looks good with proper syntax highlighting</p>

<p><a href="/content/images/2026/05/skysend-13.png"><img src="/content/images/2026/05/skysend-13.png" alt="/content/images/2026/05/skysend-13.png" /></a></p>

<p>Lastly, there is the “SSH Key” option to generate SSH keys</p>

<p><a href="/content/images/2026/05/skysend-14.png"><img src="/content/images/2026/05/skysend-14.png" alt="/content/images/2026/05/skysend-14.png" /></a></p>

<p>You can also paste in keys to share</p>

<p><a href="/content/images/2026/05/skysend-15.png"><img src="/content/images/2026/05/skysend-15.png" alt="/content/images/2026/05/skysend-15.png" /></a></p>

<p>which works much like the note option</p>

<p><a href="/content/images/2026/05/skysend-16.png"><img src="/content/images/2026/05/skysend-16.png" alt="/content/images/2026/05/skysend-16.png" /></a></p>

<p>for any of the uploads that have not expired or in the case of “view once”, been viewed and delete, we can see active links in the “My Uploads” tab</p>

<p><a href="/content/images/2026/05/skysend-17.png"><img src="/content/images/2026/05/skysend-17.png" alt="/content/images/2026/05/skysend-17.png" /></a></p>

<p>Since there is no login, these are just cached in your browser session 
while it is active.</p>

<p>I want to see if I can enable unlimited file and note views as well as add a 28d option to the list.</p>

<p>I’ll add some configuration parameters I noted in <a href="https://docs.skysend.app/user-guide/configuration/environment-variables">the docs</a> to the ConfigMap used for env vars on the deployment:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ConfigMap</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">skysend-env</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">skysend</span>
<span class="na">data</span><span class="pi">:</span>
  <span class="na">BASE_URL</span><span class="pi">:</span> <span class="s">https://skysend.steeped.icu/</span>
  <span class="na">NOTE_MAX_SIZE</span><span class="pi">:</span> <span class="s2">"</span><span class="s">10MB"</span>
  <span class="na">NOTE_DEFAULT_EXPIRE_SEC</span><span class="pi">:</span> <span class="s2">"</span><span class="s">86400"</span>
  <span class="na">NOTE_EXPIRE_OPTIONS_SEC</span><span class="pi">:</span> <span class="s2">"</span><span class="s">300,3600,86400,604800,2419200"</span>
  <span class="na">NOTE_VIEW_OPTIONS</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0,1,2,3,5,10,20,50,100"</span>
  <span class="na">FILE_DEFAULT_EXPIRE_SEC</span><span class="pi">:</span> <span class="s2">"</span><span class="s">86400"</span>
  <span class="na">FILE_EXPIRE_OPTIONS_SEC</span><span class="pi">:</span> <span class="s2">"</span><span class="s">300,3600,86400,604800,2419200"</span>
  <span class="na">FILE_DOWNLOAD_OPTIONS</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0,1,2,3,4,5,10,20,50,100"</span>
</code></pre></div></div>

<p>Then apply the changes (we should just see the configmap change)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl apply -f ./skysend.yaml
deployment.apps/skysend-deployment unchanged
service/skysend-service unchanged
ingress.networking.k8s.io/skysend-ingress unchanged
configmap/skysend-env configured
persistentvolumeclaim/skysend-data-pvc unchanged
persistentvolumeclaim/skysend-uploads-pvc unchanged
</code></pre></div></div>

<p>However, we will need to manually rotate the pod to get the changes to take effect</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ kubectl get po | grep sky
skysend-deployment-6c886888f7-87d4x                  1/1     Running            0                  30m
builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ kubectl delete po skysend-deployment-6c886888f7-87d4x
pod "skysend-deployment-6c886888f7-87d4x" deleted
builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ kubectl get po | grep sky
skysend-deployment-6c886888f7-tn49n                  0/1     CrashLoopBackOff   1 (14s ago)        18s
</code></pre></div></div>

<p>Seems I must have made a mistake, let’s look at the logs on the crashing pod:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ kubectl logs skysend-deployment-6c886888f7-tn49n
file:///app/apps/server/dist/lib/config.js:294
    const parsed = configSchema.parse(env);
                                ^

ZodError: [
  {
    "origin": "number",
    "code": "too_small",
    "minimum": 0,
    "inclusive": false,
    "path": [
      "FILE_DOWNLOAD_OPTIONS",
      0
    ],
    "message": "Too small: expected number to be &gt;0"
  }
]
    at loadConfig (file:///app/apps/server/dist/lib/config.js:294:33)
    at file:///app/apps/server/dist/index.js:34:16
    at ModuleJob.run (node:internal/modules/esm/module_job:437:25)
    at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
    at async node:internal/modules/esm/loader:639:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v24.15.0
</code></pre></div></div>

<p>I’ll remove “0” from the list of file download options</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ConfigMap</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">skysend-env</span>
  <span class="na">labels</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">skysend</span>
<span class="na">data</span><span class="pi">:</span>
  <span class="na">BASE_URL</span><span class="pi">:</span> <span class="s">https://skysend.steeped.icu/</span>
  <span class="na">NOTE_MAX_SIZE</span><span class="pi">:</span> <span class="s2">"</span><span class="s">10MB"</span>
  <span class="na">NOTE_DEFAULT_EXPIRE_SEC</span><span class="pi">:</span> <span class="s2">"</span><span class="s">86400"</span>
  <span class="na">NOTE_EXPIRE_OPTIONS_SEC</span><span class="pi">:</span> <span class="s2">"</span><span class="s">300,3600,86400,604800,2419200"</span>
  <span class="na">NOTE_VIEW_OPTIONS</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0,1,2,3,5,10,20,50,100"</span>
  <span class="na">FILE_DEFAULT_EXPIRE_SEC</span><span class="pi">:</span> <span class="s2">"</span><span class="s">86400"</span>
  <span class="na">FILE_EXPIRE_OPTIONS_SEC</span><span class="pi">:</span> <span class="s2">"</span><span class="s">300,3600,86400,604800,2419200"</span>
  <span class="na">FILE_DOWNLOAD_OPTIONS</span><span class="pi">:</span> <span class="s2">"</span><span class="s">1,2,3,4,5,10,20,50,100"</span>
</code></pre></div></div>

<p>Then apply</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl apply -f ./skysend.yaml
deployment.apps/skysend-deployment unchanged
service/skysend-service unchanged
ingress.networking.k8s.io/skysend-ingress unchanged
configmap/skysend-env configured
persistentvolumeclaim/skysend-data-pvc unchanged
persistentvolumeclaim/skysend-uploads-pvc unchanged
</code></pre></div></div>

<p>Now the pod is happy</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get po | grep sky
skysend-deployment-6c886888f7-tn49n                  1/1     Running            4 (59s ago)        110s
$ kubectl get po | grep sky
skysend-deployment-6c886888f7-tn49n                  1/1     Running            4 (109s ago)       2m40s
</code></pre></div></div>

<p>I can now see a 28d option in files</p>

<p><a href="/content/images/2026/05/skysend-18.png"><img src="/content/images/2026/05/skysend-18.png" alt="/content/images/2026/05/skysend-18.png" /></a></p>

<p>and unlimited option for views</p>

<p><a href="/content/images/2026/05/skysend-19.png"><img src="/content/images/2026/05/skysend-19.png" alt="/content/images/2026/05/skysend-19.png" /></a></p>

<p>Because the other sections are all variants of notes, we can see the “Unlimited” option in Password, Code, etc</p>

<p><a href="/content/images/2026/05/skysend-20.png"><img src="/content/images/2026/05/skysend-20.png" alt="/content/images/2026/05/skysend-20.png" /></a></p>

<h2 id="cli-client">CLI client</h2>

<p>Fromm the <a href="https://docs.skysend.app/">main docs site</a> I noted that they even have a Linux/Mac and Windows CLI we can use</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl -fsSL https://skysend.app/install.sh | sh
Fetching latest version...
Installing SkySend CLI v2.9.3 (linux-x64)...
  From: https://github.com/skyfay/SkySend/releases/download/v2.9.3/skysend-linux-x64
  Downloading skysend-linux-x64...
######################################################################## 100.0%
  Checksum verified.
  Installing to /usr/local/bin (requires sudo)...
[sudo] password for builder:

SkySend CLI v2.9.3 installed to /usr/local/bin/skysend

Get started:
  skysend --help
  skysend config set-server https://your-instance.example.com
  skysend upload ./file.txt
</code></pre></div></div>

<p>I then uploaded the YAML I had been using</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ skysend config set-server https://skysend.steeped.icu/
Server set to: https://skysend.steeped.icu
builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ skysend upload ./skysend.yaml
Preparing encryption...
Upload complete.

Share URL: https://skysend.steeped.icu/file/f15a17bb-0be8-4659-85d5-4880134e1a79#6GWwR_cF48TmX3G1_k2C68a9FRpdW0nUIhTZ5sysPjo
Files: 1 | Size: 3.12 KB | Expires: 1 days | Downloads: 1 | Avg speed: 56.2 KB/s
</code></pre></div></div>

<p>The link then gives me a single Download URL which loads fine</p>

<p><a href="/content/images/2026/05/skysend-21.png"><img src="/content/images/2026/05/skysend-21.png" alt="/content/images/2026/05/skysend-21.png" /></a></p>

<p>I can set the expiry, max downloads and password just as easily on the CLI</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ skysend upload -d 10 -e 1h -p p@ssw0rd123 ./skysend.yaml
Preparing encryption...
Upload complete.

Share URL: https://skysend.steeped.icu/file/862c147d-d68d-40a6-abd8-fbebb1464709#ICfEI1YWEbptzRfj18eup31pVVECGu64uXedel69lDI
Files: 1 | Size: 3.12 KB | Expires: 1 hours | Downloads: 10 | Avg speed: 166 KB/s
Password protected: yes
</code></pre></div></div>

<p><a href="/content/images/2026/05/skysend-22.png"><img src="/content/images/2026/05/skysend-22.png" alt="/content/images/2026/05/skysend-22.png" /></a></p>

<p>The CLI works both ways of course.  We can use it to now download that password protected file</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ mkdir -p /tmp/testing &amp;&amp; skysend download -p p@ssw0rd123 -o /tmp/testi
ng "https://skysend.steeped.icu/file/862c147d-d68d-40a6-abd8-fbebb1464709#ICfEI1YWEbptzRfj18eup31pVVECGu64uXedel69lDI"
Fetching file info...
Deriving keys...
Downloading to: /tmp/testing/skysend.yaml
Download complete.
Saved: /tmp/testing/skysend.yaml (3.12 KB)


builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ cat /tmp/testing/skysend.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: skysend-deployment
  labels:
    app: skysend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: skysend
... snip ...

</code></pre></div></div>

<p>I also did a quick verification on what happens when we hit the max download limit</p>

<p><a href="/content/images/2026/05/skysend-23.png"><img src="/content/images/2026/05/skysend-23.png" alt="/content/images/2026/05/skysend-23.png" /></a></p>

<h1 id="fider">Fider</h1>

<p>I’ve been meaning to revisit <a href="https://www.fider.io/">Fider</a> for some time.</p>

<p><a href="/content/images/2026/05/fider-01.png"><img src="/content/images/2026/05/fider-01.png" alt="/content/images/2026/05/fider-01.png" /></a></p>

<p>I’ve had <a href="https://fider.tpk.pw/">a Fider board</a> for over a year, though no one has ever used it (though I don’t think I linked it anywhere either).</p>

<p>I’m using presently <code class="language-plaintext highlighter-rouge">0.0.6</code> version of the chart and <code class="language-plaintext highlighter-rouge">0.25.0</code> version of the app.</p>

<p>Updating my helm repos shows there are updates available:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm search repo fider
NAME                    CHART VERSION   APP VERSION     DESCRIPTION
fider/fider             0.0.7           0.28.0          An open platform to collect and prioritize feed...
fider/postgresql        12.6.4          15.3.0          PostgreSQL (Postgres) is an open source object-...
</code></pre></div></div>

<p>I pulled my current values to check which image tag I’m using</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm get values myfider -n fider -o yaml &gt; fidervals.yaml
$ cat fidervals.yaml | head -n 58 | tail -n 5
image:
  pullPolicy: IfNotPresent
  repository: getfider/fider
  tag: v0.27.0
imagePullSecrets: []
</code></pre></div></div>

<p>From dockerhub, <a href="https://hub.docker.com/r/getfider/fider/tags">I see the latest</a> is <a href="https://hub.docker.com/layers/getfider/fider/v0.35.0/images/sha256-13ffef652eb815c640f527b6f2456cb7558d539dddaff198987368bd175be701">v0.35.0</a></p>

<p>I decided to YOLO on this and just upgrade to latest and override the image tag</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm upgrade myfider -n fider -f ./fidervals.yaml --set image.tag=v0.35.0 fider/fider
Release "myfider" has been upgraded. Happy Helming!
NAME: myfider
LAST DEPLOYED: Wed May 20 06:30:17 2026
NAMESPACE: fider
STATUS: deployed
REVISION: 9
NOTES:
1. Get the application URL by running these commands:
  https://fider.tpk.pw/
</code></pre></div></div>

<p>I would say the UI is <em>interesting</em> now:</p>

<p><a href="/content/images/2026/05/fider-02.png"><img src="/content/images/2026/05/fider-02.png" alt="/content/images/2026/05/fider-02.png" /></a></p>

<p>Under user settings I now see some notification options and ability to get an API key</p>

<p><a href="/content/images/2026/05/fider-03.png"><img src="/content/images/2026/05/fider-03.png" alt="/content/images/2026/05/fider-03.png" /></a></p>

<p>In administration I can now easily make this a private board and/or set content moderation</p>

<p><a href="/content/images/2026/05/fider-04.png"><img src="/content/images/2026/05/fider-04.png" alt="/content/images/2026/05/fider-04.png" /></a></p>

<p>However, I realized that while Invitations looks good</p>

<p><a href="/content/images/2026/05/fider-05.png"><img src="/content/images/2026/05/fider-05.png" alt="/content/images/2026/05/fider-05.png" /></a></p>

<p>I set this up with Sendgrid.net which is now dead (to me).</p>

<p>I updated my SMTP settings and upgraded the chart to see it take effect</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ vi fidervals.yaml

$ helm upgrade myfider -n fider -f ./fidervals.yaml --set image.tag=v0.35.0 fider/fider
Release "myfider" has been upgraded. Happy Helming!
NAME: myfider
LAST DEPLOYED: Wed May 20 06:38:35 2026
NAMESPACE: fider
STATUS: deployed
REVISION: 10
NOTES:
1. Get the application URL by running these commands:
  https://fider.tpk.pw/

$ kubectl get po -n fider
NAME                       READY   STATUS            RESTARTS   AGE
myfider-555fd89cdc-mchdj   1/1     Running           0          8m23s
myfider-f4879649c-7d7tc    0/1     PodInitializing   0          4s
myfider-postgresql-0       1/1     Running           0          136d

$ kubectl get po -n fider
NAME                       READY   STATUS        RESTARTS   AGE
myfider-555fd89cdc-mchdj   1/1     Terminating   0          8m27s
myfider-f4879649c-7d7tc    1/1     Running       0          8s
myfider-postgresql-0       1/1     Running       0          136d
</code></pre></div></div>

<p>I then sent myself a sample invite</p>

<p><a href="/content/images/2026/05/fider-06.png"><img src="/content/images/2026/05/fider-06.png" alt="/content/images/2026/05/fider-06.png" /></a></p>

<p>Which worked just fine</p>

<p><a href="/content/images/2026/05/fider-07.png"><img src="/content/images/2026/05/fider-07.png" alt="/content/images/2026/05/fider-07.png" /></a></p>

<p>I realized there is a backup and export option for data and posts.  Next time I’ll use that before a major upgrade</p>

<p><a href="/content/images/2026/05/fider-08.png"><img src="/content/images/2026/05/fider-08.png" alt="/content/images/2026/05/fider-08.png" /></a></p>

<h1 id="summary">Summary</h1>

<video muted="" loop="" autoplay="" controls="">
    <source src="/content/images/2026/05/skysendfider.mp4" type="video/mp4" />
</video>

<p>Today we dug into <a href="https://skysend.app/">SkySend</a>, a surprisingly full featured security focused file/password/note sharing app that is fully Open-source and AGPL licensed.  The app seems pretty robust with lots of configuration and I was pleasantly surprised by the CLI.</p>

<p>We also circled back on <a href="https://www.fider.io/">Fider</a> which I last covered about <a href="https://freshbrewed.science/2025/06/03/smallos.html">a year ago</a>.  There are some nice minor updates, though I’m not sure I’m a tremendous fan of the UI changes (seems kind of ‘large’ to me).</p>

<p>If one does not want to self-host Fider, there are <a href="https://www.fider.io/pricing">hosted options</a> with a generous free tier.  I did so and you can see the board at <a href="https://freshbrewed.fider.io/">https://freshbrewed.fider.io/</a>.</p>

<p>I actually made it a private board so I could see how that works</p>

<p><a href="/content/images/2026/05/fider-09.png"><img src="/content/images/2026/05/fider-09.png" alt="/content/images/2026/05/fider-09.png" /></a></p>

<p>I’ll likely leave skysend for a while, but I do get worried about abuses so I’ll check it often.</p>]]></content><author><name>Isaac Johnson</name></author><category term="opensource" /><category term="container" /><category term="kubernetes" /><category term="fider" /><category term="skysend" /><summary type="html"><![CDATA[Today we’ll dig into two interesting Open-source products; SkySend and Fider. Skysend is all about sharing files and notes with various secure options whereas Fider is about collecting ideas from people (like a digital suggestion box).]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://freshbrewed.science/content/images/2026/05/skysendfiderbg.png" /><media:content medium="image" url="https://freshbrewed.science/content/images/2026/05/skysendfiderbg.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>