<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Andrew Zigler</title>
        <link>https://www.andrewzigler.com/</link>
        <description>The works and writings of Andrew Zigler — ancient historian, agentic engineer, and host of Dev Interrupted.</description>
        <lastBuildDate>Wed, 08 Jul 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>© Andrew Zigler</copyright>
        <atom:link href="https://www.andrewzigler.com/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[If you give a Goose an MCP server]]></title>
            <link>https://www.andrewzigler.com/feed/if-you-give-a-goose-an-mcp-server</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/if-you-give-a-goose-an-mcp-server</guid>
            <pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A hands-on build: register a local model, federate your MCP servers into one endpoint, scope that endpoint down to the six tools the job needs, and watch every model and tool call land in one log, tagged with the key that made it. Built on agentgateway.]]></description>
            <content:encoded><![CDATA[<p><em>A hands-on build: register a local model, federate your MCP servers into one endpoint, scope that endpoint down to the six tools the job needs, and watch every model and tool call land in one log, tagged with the key that made it. Built on agentgateway.</em></p>
<p>If you give a Goose an MCP server, it gets every tool the server ships.</p>
<p>You handed it a filesystem server because it needed to read one file. It got the read tool, and the write tool, and the edit tool, and 11 more, because <code>tools/list</code> came back full and nobody reads a tool manifest the way they read a diff. The filesystem server exposes 14 tools. The Goose now has all 14, because the server decides what the Goose can do, and that server was written to do everything.</p>
<p>This is a build for taking that decision back, by hand, once, so the tool layer stops being a black box you accept on faith. You put a gateway in the one place every call has to cross. You register your model there, so model calls ride the same layer as tool calls. You federate your MCP servers into a single endpoint and grant one agent exactly six tools on it. Then you watch the whole session, model calls and tool calls alike, land in one log, each row tagged with the key that made it. By the end the Goose holds six tools it can't step outside of, and a dashboard shows you every one it reached for.</p>
<p>The gateway is <a href="https://github.com/agentgateway/agentgateway">agentgateway</a>, a single Rust binary and an <a href="https://aaif.io">AAIF</a> project. It bills itself as a connectivity solution for agents: one proxy in front of model traffic, MCP traffic, and ordinary HTTP, applying the same auth, policy, and logging to all three. That "all three" is what makes this build work. The model and the tools go through one door, so one log holds the whole session. I'm running it in standalone mode — a single binary and a YAML file, with an admin UI at <code>:15000/ui</code> that hot-reloads on save — in front of <a href="https://goose-docs.ai/">Goose</a> talking to a local model. The six-tool set I grant the Goose has a name (that I made up): <strong>tiny-tools</strong>.</p>
<p>One important thing to note: the agent that <em>builds</em> this needs a shell and room to install things, so the builder is you, or an agent that still has the run of the house. The Goose we keep narrowing to six tools is the subject on the workbench. You hold the wrench.</p>
<p>Everything here is one config file. The slices nest like this, top-level keys first:</p>
<pre><code class="language-yaml">config:
  database:
    url: "sqlite:///path/to/requests.db?mode=rwc"   # the request-log DB (Step 5)
  standardAttributes:
    user: 'apiKey.name'          # stamps every logged row with the calling key's name

frontendPolicies:
  accessLog:
    database:
      add: { prompt: 'llm.prompt', completion: 'llm.completion' }   # capture prompts + completions (Step 5)

llm:                             # Step 1 -- the model listener
  port: 15003
  providers: [ ... ]
  models: [ ... ]
  policies:
    apiKey: { ... }              # the goose key, so model calls are keyed and attributed

mcp:                             # Step 2 -- the federated tool listener
  port: 15001
  targets: [ ... ]
  policies:
    apiKey: { ... }              # Step 3 -- the per-agent keys (goose, cc)
    mcpAuthorization: { ... }    # Step 3 -- match the key name to scope its tools
</code></pre>
<p><strong>Before you start.</strong> <a href="https://agentgateway.dev/docs/">Install agentgateway</a> and get it running. Save your config as <code>config.yaml</code> and launch with <code>agentgateway -f config.yaml</code>; the admin UI at <code>:15000/ui</code> hot-reloads on every save, and <code>agentgateway -f config.yaml --validate-only</code> checks a config before you run it. Have <a href="https://ollama.com/">Ollama</a> serving a tool-capable model (<code>ollama pull qwen3:8b</code>), install <a href="https://goose-docs.ai/">Goose</a>, and keep <code>npx</code> (for the filesystem server) and <code>uvx</code> (for the fetch server) within reach.</p>
<h2>Step 1: Put your model behind the gateway</h2>
<p>Start with the model. In my case, I'll use Ollama, just because I'm used to it, but you can use anything that serves a model. Routing it through the gateway is the piece most local setups skip, and it decides whether you can watch the whole session later. Route the model through the gateway and its calls land in the same request log as the tool calls, tagged with the same key. Leave it pointed at Ollama directly and you get a curated tool layer with a blind spot where the model should be.</p>
<p>An agentgateway <code>llm</code> block is a provider plus a list of models. The provider names your backend once; each model references it. The listener also carries the <code>goose</code> key, so a model call arrives identified:</p>
<pre><code class="language-yaml">llm:
  port: 15003
  providers:
    - name: local
      provider: ollama
      params:
        baseUrl: http://localhost:11434/v1
  models:
    - name: qwen3:8b
      provider:
        reference: local
  policies:
    apiKey:
      mode: strict
      keys:
        - key: sk-...            # the same goose key you'll reuse in Step 3
          metadata: { name: goose }
</code></pre>
<p>The gateway now exposes an OpenAI-compatible API on <code>:15003</code>, and every model call through it is one row in the request log, stamped <code>goose</code> by <code>standardAttributes.user</code>. Point Goose at the gateway with four environment variables:</p>
<pre><code class="language-sh">export GOOSE_PROVIDER=openai
export OPENAI_HOST=http://localhost:15003     # bare host:port; Goose appends the OpenAI path
export OPENAI_API_KEY=sk-...                  # the goose key -- our LLM listener is keyed
export GOOSE_MODEL=qwen3:8b
</code></pre>
<p>Agentgateway's own Goose guide uses a placeholder key here, because its example listener takes any key. Ours doesn't. The <code>goose</code> key is what stamps every model row with the same identity the tool calls carry, so it has to be the real one.</p>
<p><img src="https://cdn.zig.computer/aaif/honk-tutorial/04-models-redacted.png" alt="The gateway&#x27;s Models view: the local models registered behind one provider, each routed at the gateway&#x27;s LLM listener. Model calls now cross the same gateway as tool calls, so one log holds the whole session."></p>
<blockquote>
<p><strong>Agent:</strong> in agentgateway's <code>llm</code> config, register an Ollama provider pointed at my local Ollama (<code>baseUrl</code> <code>http://localhost:11434/v1</code>), add my model referencing it, and attach an <code>apiKey</code> policy carrying the <code>goose</code> key so model calls are authenticated and attributed. Set <code>standardAttributes.user: 'apiKey.name'</code> in the top-level <code>config</code> block. Then point Goose at the gateway with <code>GOOSE_PROVIDER=openai</code>, <code>OPENAI_HOST=http://localhost:15003</code>, <code>OPENAI_API_KEY=&#x3C;the goose key></code>, <code>GOOSE_MODEL=qwen3:8b</code>. Read agentgateway's <a href="https://agentgateway.dev/docs/standalone/latest/llm/providers/ollama/">Ollama provider docs</a> and its <a href="https://agentgateway.dev/docs/standalone/latest/integrations/web-uis/goose/">Goose integration guide</a>.</p>
</blockquote>
<h2>Step 2: Federate your MCP servers into one endpoint</h2>
<p>Now the model has somewhere to reach. Give it tools. The interesting part is <em>how</em> you give them. You federate several MCP servers behind the gateway and hand the agent a single endpoint, rather than wiring one connection per server into the client. The gateway multiplexes them. The agent connects to one place, and the servers behind it are the gateway's concern.</p>
<p>You define each server as a <code>target</code> under the <code>mcp</code> block. Two small servers here, a filesystem server scoped to one workspace directory and a fetch server:</p>
<pre><code class="language-yaml">mcp:
  port: 15001
  targets:
    - name: filesystem
      stdio:
        cmd: npx
        args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/workspace"]
    - name: fetch
      stdio:
        cmd: uvx
        args: ["mcp-server-fetch"]
</code></pre>
<p>Between them, that's 15 tools behind one endpoint. The gateway namespaces each tool by its target, so the filesystem server's <code>read_text_file</code> shows up as <code>filesystem_read_text_file</code>. Open the Servers view and both targets register and report ready. The Goose connects to this one endpoint, never to either server directly.</p>
<p><img src="https://cdn.zig.computer/aaif/honk-tutorial/01-servers-redacted.png" alt="The MCP Servers view: the filesystem and fetch servers federated behind the gateway, both ready. The agent connects to this single endpoint; the gateway multiplexes the two servers behind it."></p>
<blockquote>
<p><strong>Agent:</strong> federate two <code>stdio</code> MCP targets under one <code>mcp</code> endpoint: <code>@modelcontextprotocol/server-filesystem</code> scoped to a single workspace directory (target name <code>filesystem</code>) and <code>mcp-server-fetch</code> (target name <code>fetch</code>). Confirm both report ready and that the combined <code>tools/list</code> shows all 15 tools, target-prefixed. Read agentgateway's <a href="https://agentgateway.dev/docs/standalone/latest/mcp/connect/virtual/">MCP multiplexing docs</a>; a target name can't contain underscores.</p>
</blockquote>
<h2>Step 3: Scope the endpoint per agent</h2>
<p>Give a Goose 15 tools and it will find a use for one you never meant it to have. So before the Goose ever connects, you decide what's on the endpoint for it, and you decide it per key. One virtual endpoint, and each API key gets its own view of it.</p>
<p>Let's scope the MCP tools into custom virtual MCP servers for two different agents: one on Goose, and one in Claude Code. We're picking the Goose's six by hand for now; Step 7 shows how to derive that set from real traffic instead.</p>
<pre><code class="language-yaml">  policies:                      # under the mcp block from Step 2
    apiKey:
      mode: strict
      keys:
        - key: sk-...            # the Goose's key (the same one from Step 1)
          metadata: { name: goose }
        - key: sk-...            # Claude Code's key
          metadata: { name: cc }
</code></pre>
<p>Then we match on the key's name to scope which tools each one sees:</p>
<pre><code class="language-yaml">    mcpAuthorization:
      rules:
        - allow: apiKey.name == "cc"
        # mcp.tool.name is the underlying tool name (read_text_file), not the
        # filesystem_-prefixed name the client sees in tools/list
        - allow: apiKey.name == "goose" &#x26;&#x26;
                 mcp.tool.name in ["read_text_file", "write_file", "edit_file",
                                   "list_directory", "search_files", "fetch"]
</code></pre>
<p>The <code>cc</code> key (Claude Code) gets everything; the <code>goose</code> key gets six. Same endpoint, and the key decides the view. Write one <code>allow</code> rule and the endpoint flips to default-deny: anything not allowed is denied, and a tool the <code>goose</code> key doesn't allow drops out of <code>tools/list</code> entirely, so the Goose never discovers it exists. That scoped view is tiny-tools.</p>
<p><img src="https://cdn.zig.computer/aaif/honk-tutorial/02-tools.png" alt="The gateway&#x27;s Tool Playground, a session opened with the Goose&#x27;s key: it discovers exactly six tools — read, write, and edit a file, list and search a directory, and fetch a URL — each namespaced by the server it came from. This is tiny-tools from the client&#x27;s side of the wire."></p>
<blockquote>
<p><strong>Agent:</strong> on the federated MCP endpoint, add a <code>strict</code> API-key policy with keys named <code>goose</code> and <code>cc</code>, and an <code>mcpAuthorization</code> rule allowing the <code>goose</code> key exactly <code>read_text_file, write_file, edit_file, list_directory, search_files, fetch</code>. Confirm a session opened with the <code>goose</code> key discovers only those six. Read agentgateway's <a href="https://agentgateway.dev/docs/standalone/latest/mcp/mcp-authz/">MCP authorization docs</a>: one <code>allow</code> rule flips the endpoint to default-deny, and denied tools drop out of <code>tools/list</code>.</p>
</blockquote>
<h2>Step 4: Point Goose at your virtualized MCP server</h2>
<p>You built the scoped endpoint. Now point your Goose at it. Step 1 aimed Goose's model at the gateway; this aims its tools there too, at the tiny-tools view you just carved out.</p>
<p>Run <code>goose configure</code>, choose <strong>Add Extension → Remote Extension (Streamable HTTP)</strong>, and point it at the gateway's MCP endpoint, <code>http://localhost:15001/mcp</code>, with an <code>Authorization: Bearer &#x3C;goose key></code> header so it connects as the <code>goose</code> key. The Goose now reaches its tools through the gateway, and the gateway hands back exactly the six that tiny-tools allows.</p>
<p>By the way, Goose does a lot of what's in tiny-tools out of the box, through a built-in <code>developer</code> extension with its own shell and editor. Toggle it off for this run (<code>goose configure → Toggle Extensions</code>) so tiny-tools is the entire surface, which is the point when you're trying to see the tool layer plainly. In your own setup you'd keep it or drop it depending on whether you want those built-ins alongside your list.</p>
<p>Start a Goose session and its tool list is exactly tiny-tools: the six you granted the <code>goose</code> key, and nothing underneath.</p>
<blockquote>
<p><strong>Agent:</strong> in Goose, add a remote streamable-HTTP MCP extension pointed at <code>http://localhost:15001/mcp</code> with an <code>Authorization: Bearer &#x3C;goose key></code> header, and disable the built-in <code>developer</code> extension. Confirm the Goose session's tools are exactly the six from tiny-tools. Read Goose's <a href="https://goose-docs.ai/docs/getting-started/using-extensions/">extensions guide</a>.</p>
</blockquote>
<h2>Step 5: Watch the whole session in one place</h2>
<p>Every call the Goose makes now crosses the gateway, model and tools alike. So watch them. This is the payoff of routing the model through the gateway back in Step 1: one gateway holds the whole session, in one place you can query.</p>
<p>That place is the request-log database, the same store behind the admin UI's Logs tab. Turn it on in the top-level <code>config</code> block and tag every row with the calling key:</p>
<pre><code class="language-yaml">config:
  database:
    url: "sqlite:///path/to/requests.db?mode=rwc"   # the request-log DB
  standardAttributes:
    user: 'apiKey.name'                              # tag every row with the key's name
</code></pre>
<p>Every call the gateway proxies writes a row there. A tool call at the MCP listener and a model call at the LLM listener both land in the same table, each row carrying its protocol (<code>mcp</code> or <code>llm</code>) and, because of <code>standardAttributes.user</code>, the name of the key that made it. There's also a text access log you can tail to watch calls fly by live; the database is the durable, queryable version, and it's what the skills in Step 7 read.</p>
<p>I gave the Goose a task that read a file, wrote a new one, and fetched a page. Pulling the fields that matter from its rows, the whole session comes back under one identity, tools and model together:</p>
<pre><code>protocol=mcp   read_text_file   user=goose   status=200    16ms
protocol=mcp   write_file       user=goose   status=200     8ms
protocol=mcp   fetch            user=goose   status=200   782ms
protocol=llm   qwen3:8b         user=goose   status=200   1533ms
</code></pre>
<p>One key made every call, so one identity tags every row. That is why the model went behind the gateway in Step 1. (The text access log labels that identity <code>agent</code> rather than <code>user</code>; both read from <code>apiKey.name</code>, so it's one value under two names.)</p>
<p>Turn on payload capture and the log holds not just that the Goose called the model but what it said:</p>
<pre><code class="language-yaml">frontendPolicies:
  accessLog:
    database:
      add: { prompt: 'llm.prompt', completion: 'llm.completion' }
</code></pre>
<p>It's worth having on. Those rows now carry full prompts and completions, so treat the database as sensitive.</p>
<p><img src="https://cdn.zig.computer/aaif/honk-tutorial/03-tool-call.png" alt="Driving the same kind of call by hand: the Tool Playground runs a fetch and shows the HTTP 200 and the returned content in the result pane. It&#x27;s the same request path Goose&#x27;s calls take, which is why every one of them lands in the log."></p>
<blockquote>
<p><strong>Agent:</strong> set <code>config.database.url</code> and <code>standardAttributes.user: 'apiKey.name'</code> so the request-log DB records every call with identity. Run a task through Goose that reads a known file, writes a new one, and fetches a URL. Confirm the DB (and the admin UI's Logs tab) shows the tool rows (<code>protocol=mcp</code>) and the model row (<code>protocol=llm</code>) all under <code>user=goose</code>. Turn on payload capture (<code>frontendPolicies.accessLog.database</code>) to also record the prompt and completion.</p>
</blockquote>
<h2>Step 6: Prove the tools you cut are gone</h2>
<p>An agent never reaches for a tool it can't see — that's the whole point of scoping, and it's also why you can't prove the wall holds just by watching the Goose work. So reach for the cut tool yourself. <code>move_file</code> is one of the filesystem server's original tools, from before you virtualized it, and the <code>goose</code> key never got it. Issue a <code>tools/call</code> for it straight to the gateway's MCP endpoint with the <code>goose</code> key, the same request the Goose's own client would send. (MCP over HTTP opens with an <code>initialize</code> handshake that hands back a session id; reuse it here.)</p>
<pre><code class="language-sh">curl -sS http://localhost:15001/mcp \
  -H "Authorization: Bearer $GOOSE_KEY" -H "Mcp-Session-Id: $SESSION" \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"filesystem_move_file",
                 "arguments":{"source":"a.txt","destination":"b.txt"}}}'
</code></pre>
<p>The gateway answers <code>HTTP 400</code> with a JSON-RPC error:</p>
<pre><code class="language-json">{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"Unknown tool: filesystem_move_file"}}
</code></pre>
<p>That error turns on one word: <em>unknown</em>. A tool the gateway called <em>forbidden</em> would still be in the manifest, a name the Goose could see, reach for, and be told no. This one is <em>unknown</em>. It never entered <code>tools/list</code>, so there's no name to reach for, and as far as the <code>goose</code> key's view of the endpoint goes, <code>move_file</code> was never there. The attempt still lands in the log, so you can watch it happen and watch it go nowhere.</p>
<blockquote>
<p><strong>Agent:</strong> call <code>tools/call</code> for <code>filesystem_move_file</code> against the endpoint with the <code>goose</code> key, and confirm the gateway returns an "Unknown tool" error while still recording the attempt. This verifies the allowlist filters at discovery, before the call is even attempted.</p>
</blockquote>
<h2>Step 7: Run the loop with one skill</h2>
<p>You built tiny-tools by guessing which six the job needs. The skill loop is how you stop guessing. Point the Goose at a full MCP server — a new one you're onboarding, before you've scoped it, on a key that can still see everything — and let it work through a real task. The gateway records all of it. Then you read the record and cut: keep the tools the Goose actually reached for, notice which ones carried real weight, and virtualize the rest away. You whittle a fat server down to a tiny-tools that fits the workflow, from what happened instead of from a guess.</p>
<p>I packaged four read-only components as one <a href="https://github.com/azigler/aaif/tree/main/.claude/skills/agentgateway">agentgateway skill</a>, a Claude Code Agent-Skill you drop in your <code>.claude/skills/</code>:</p>
<ul>
<li><strong>audit</strong> groups every call by identity, tool, and outcome, so you can see which of a server's tools the Goose actually used, and how often.</li>
<li><strong>cost</strong> reads the token counts, so you can see which tools carried the weight. The dollar column stays empty on a local model, but the tokens are the number you tune.</li>
<li><strong>trace</strong> walks a single call, or a session, end to end when one of them looked wrong.</li>
<li><strong>harden</strong> reads what the Goose actually used and drafts the tighter allowlist that would have permitted exactly that, then hands it to you. It proposes; it never applies. A rule that could lock out real traffic waits for a human.</li>
</ul>
<p>They read the request-log database over a consistent snapshot, never the live file, and they share one <a href="https://github.com/azigler/aaif/blob/main/refs/gateway-request-log-cookbook.md">request-log cookbook</a>, the verified query foundation underneath all four. Continuous, standing alerting isn't in here on purpose, because a skill can't hold a live watch. Run audit and cost on a cadence and read the deltas.</p>
<p>The morning after a real run, the loop composes: <strong>audit</strong> shows the Goose reached for a handful of the server's tools and never touched the rest; <strong>cost</strong> shows which of them carried the token weight; <strong>trace</strong> explains the one slow call; and <strong>harden</strong> hands you the tighter allowlist — the tiny-tools for this workflow — built from what actually happened, for you to validate and apply. Read-only the whole way, human-gated at the one step that changes anything.</p>
<blockquote>
<p><strong>Agent:</strong> install the <a href="https://github.com/azigler/aaif/tree/main/.claude/skills/agentgateway">agentgateway skill</a> and its request-log cookbook. After a real Goose session against a full server, run <strong>audit</strong> to see what the Goose actually used, then <strong>harden</strong> to draft the whittled-down allowlist for my review. It proposes policy; it never applies it.</p>
</blockquote>
<p>The black box was a list and a rule the whole time. Now it's yours.</p>
<hr>
<p><em>Andrew Zigler is a 2026 AAIF Ambassador. Find the cohort at <a href="https://aaif.io/ambassadors">aaif.io/ambassadors</a>.</em></p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Build vs. buy: Why DIY engineering metrics break at scale]]></title>
            <link>https://www.andrewzigler.com/feed/build-vs-buy-why-diy-engineering-metrics-break-at-scale</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/build-vs-buy-why-diy-engineering-metrics-break-at-scale</guid>
            <pubDate>Fri, 22 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Build vs. buy: Why DIY engineering metrics break at scale]]></description>
            <category>media</category>
        </item>
        <item>
            <title><![CDATA[We asked Claude to build LinearB (and here's what happened)]]></title>
            <link>https://www.andrewzigler.com/feed/we-asked-claude-to-build-linearb-and-heres-what-happened</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/we-asked-claude-to-build-linearb-and-heres-what-happened</guid>
            <pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[We asked Claude to build LinearB (and here's what happened)]]></description>
            <category>media</category>
        </item>
        <item>
            <title><![CDATA[Depths (Ludum Dare 57)]]></title>
            <link>https://www.andrewzigler.com/feed/crossed-wires-ludum-dare-59</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/crossed-wires-ludum-dare-59</guid>
            <pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A small-town switchboard operator's shift. Every call is tangled. You sort.]]></description>
            <content:encoded><![CDATA[<p>A small-town switchboard operator's shift. Every call is tangled. You sort.</p>
<p>Two callers arrive on the same phone line. You assign each of their utterances back to the right conversation. Early calls are easy — the goat lady does not sound like the shipping clerk. Later calls have static eating the words on the wire, and you sort by how each caller talks — all-caps outbursts versus two-word dispatches, grief versus directive. Voice recognition as puzzle.</p>
<p>Built solo over the Ludum Dare 59 jam weekend with a handcrafted Claude Code agentic harness and pair-coded with Claude Opus 4.7. The harness pulls in Impeccable design skills and a cross-engine game-dev agent team, alongside project-local skills for spec, impl, review, commit, manifesto and indie-game-taste.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[How to Measure AI Impact: Proving ROI from Copilot, Cursor, and Claude]]></title>
            <link>https://www.andrewzigler.com/feed/how-to-measure-ai-impact-proving-roi-from-copilot-cursor-and-claude</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/how-to-measure-ai-impact-proving-roi-from-copilot-cursor-and-claude</guid>
            <pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How to Measure AI Impact: Proving ROI from Copilot, Cursor, and Claude]]></description>
            <category>media</category>
        </item>
        <item>
            <title><![CDATA[Genuary 2026]]></title>
            <link>https://www.andrewzigler.com/feed/genuary-2026</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/genuary-2026</guid>
            <pubDate>Sat, 31 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Genuary is an annual, month-long creative coding challenge where artists create generative art daily based on specific prompts. It's a celebration of algorithmic art, creative coding, and the vibrant community of generative artists worldwide.]]></description>
            <content:encoded><![CDATA[<p><a href="https://genuary.art/">Genuary</a> is an annual, month-long creative coding challenge where artists create generative art daily based on specific prompts. It's a celebration of algorithmic art, creative coding, and the vibrant community of generative artists worldwide.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Building future AI news experiences with The Atlantic and Infactory]]></title>
            <link>https://www.andrewzigler.com/feed/infactory-the-atlantic-hackathon</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/infactory-the-atlantic-hackathon</guid>
            <pubDate>Sat, 31 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[I placed 1st in the Infactory x The Atlantic AI hackathon (Building future AI news experiences with The Atlantic and Infactory) by creating a virtual classroom platform that empowers teachers, challenges students, and fosters a deeper relationship with journalism using The Atlantic’s archives as a teaching resource. To create my demo, I orchestrated agents in Claude Code with beads.]]></description>
            <content:encoded><![CDATA[<p>I placed 1st in the <a href="https://www.linkedin.com/company/infactory-ai/">Infactory</a> x <a href="https://www.linkedin.com/company/the-atlantic/">The Atlantic</a> AI hackathon (<a href="https://luma.com/hh-future-ai-news-theatlantic-infactory">Building future AI news experiences with The Atlantic and Infactory</a>) by creating a virtual classroom platform that empowers teachers, challenges students, and fosters a deeper relationship with journalism using The Atlantic’s archives as a teaching resource. To create my demo, I orchestrated agents in Claude Code with beads.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[AdventJS 2025]]></title>
            <link>https://www.andrewzigler.com/feed/adventjs-2025</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/adventjs-2025</guid>
            <pubDate>Thu, 25 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A complete set of solutions for all 25 challenges in AdventJS 2025. Each challenge solved in JavaScript, TypeScript, and Python with comprehensive test suites and solution logs. Built a custom harness for automated fetching, testing, and submission with quality score tracking.]]></description>
            <content:encoded><![CDATA[<p>A complete set of solutions for all 25 challenges in <a href="https://adventjs.dev/">AdventJS 2025</a>. Each challenge solved in JavaScript, TypeScript, and Python with comprehensive test suites and solution logs. Built a custom harness for automated fetching, testing, and submission with quality score tracking.</p>
<p>Completed all 25 challenges across all three languages (75 total solutions), achieving 5/5 quality scores on all submissions.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Advent of Code 2025]]></title>
            <link>https://www.andrewzigler.com/feed/advent-of-code-2025</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/advent-of-code-2025</guid>
            <pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Solutions for all 12 days of Advent of Code 2025. Each day includes TypeScript solutions with comprehensive test suites, edge case analysis, and detailed solution logs documenting the problem-solving process.]]></description>
            <content:encoded><![CDATA[<p>Solutions for all 12 days of <a href="https://adventofcode.com/2025">Advent of Code 2025</a>. Each day includes TypeScript solutions with comprehensive test suites, edge case analysis, and detailed solution logs documenting the problem-solving process.</p>
<p>Built a custom harness for automated input fetching, answer submission, and star tracking with rate limiting and caching.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Depths (Ludum Dare 57)]]></title>
            <link>https://www.andrewzigler.com/feed/depths-ludum-dare-57</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/depths-ludum-dare-57</guid>
            <pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A game about navigating deep folder structures under time pressure. Players take on the role of an office worker who must find a specific file in a chaotic company server within a strict time limit. Created in 72 hours for Ludum Dare 57.]]></description>
            <content:encoded><![CDATA[<p>A game about navigating deep folder structures under time pressure. Players take on the role of an office worker who must find a specific file in a chaotic company server within a strict time limit. Created in 72 hours for <a href="https://ldjam.com/events/ludum-dare/57">Ludum Dare 57</a>.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[zigMOO]]></title>
            <link>https://www.andrewzigler.com/feed/zigmoo-browser-based-moo</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/zigmoo-browser-based-moo</guid>
            <pubDate>Sat, 05 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[zigMOO is persistent browser-based world simulating a virtual reality powered by MOO and TypeScript, providing opportunities for emergent gameplay.]]></description>
            <content:encoded><![CDATA[<p>zigMOO is persistent browser-based world simulating a virtual reality powered by MOO and TypeScript, providing opportunities for emergent gameplay.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[tsdx-react-storybook-starter]]></title>
            <link>https://www.andrewzigler.com/feed/tsdx-react-storybook-starter</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/tsdx-react-storybook-starter</guid>
            <pubDate>Mon, 12 Sep 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[TSDX with an opinionated setup for React and Storybook.]]></description>
            <content:encoded><![CDATA[<p>TSDX with an opinionated setup for React and Storybook.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Palladium]]></title>
            <link>https://www.andrewzigler.com/feed/palladium-web-service-suite</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/palladium-web-service-suite</guid>
            <pubDate>Fri, 01 Jul 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Deploy-and-enjoy web service suite via Docker.]]></description>
            <content:encoded><![CDATA[<p>Deploy-and-enjoy web service suite via Docker.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Club Work Out No Jogging]]></title>
            <link>https://www.andrewzigler.com/feed/club-work-out-no-jogging-sims-4-mod</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/club-work-out-no-jogging-sims-4-mod</guid>
            <pubDate>Thu, 17 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[This Sims 4 mod fixed unintended behavior wherein Sims would go jogging while attending fitness-oriented club hangouts. This caused them to scatter all over the map and spend no time socializing with each other, which defeated the point of the feature. This mod adjusted the Sims autonomy to de-prioritize jogging. However, this mod is now deprecated because the original unintended behavior is no longer observed.]]></description>
            <content:encoded><![CDATA[<p>This Sims 4 mod fixed unintended behavior wherein Sims would go jogging while attending fitness-oriented club hangouts. This caused them to scatter all over the map and spend no time socializing with each other, which defeated the point of the feature. This mod adjusted the Sims autonomy to de-prioritize jogging. However, this mod is now deprecated because the original unintended behavior is no longer observed.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[More Club Members]]></title>
            <link>https://www.andrewzigler.com/feed/more-club-members-sims-4-mod</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/more-club-members-sims-4-mod</guid>
            <pubDate>Thu, 17 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[This Sims 4 mod expanded the limit on club members in the game. By default, club members were arbitrarily capped at 8, but this mod lifted that cap to 20 or 50, depending on user preference. It also introduced more advanced club requirement filters for Sims, because at the time there were limited options. However, this mod is now deprecated. All functionality of this mod is provided by Deaderpool's MC Command Center Clubs module.]]></description>
            <content:encoded><![CDATA[<p>This Sims 4 mod expanded the limit on club members in the game. By default, club members were arbitrarily capped at 8, but this mod lifted that cap to 20 or 50, depending on user preference. It also introduced more advanced club requirement filters for Sims, because at the time there were limited options. However, this mod is now deprecated. All functionality of this mod is provided by <a href="https://deaderpool-mccc.com/#/doc/mc_clubs">Deaderpool's MC Command Center Clubs module</a>.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[moobarn]]></title>
            <link>https://www.andrewzigler.com/feed/moobarn-utility</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/moobarn-utility</guid>
            <pubDate>Mon, 13 Dec 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[MOO Bridge API for React and Node, or moobarn, is an all-in-one utility for managing MOO. It can spawn and monitor MOO processes, bridge between telnet and WebSocket, follow a backup schedule, scan for relevant processes on the machine, and persist processes across reboots. It also provides a web server and RESTful API for further integration opportunities. The application can be easily extended by adding new controller files that follow a simple API.]]></description>
            <content:encoded><![CDATA[<p>MOO Bridge API for React and Node, or moobarn, is an all-in-one utility for managing <a href="https://en.wikipedia.org/wiki/MOO">MOO</a>. It can spawn and monitor MOO processes, bridge between telnet and WebSocket, follow a backup schedule, scan for relevant processes on the machine, and persist processes across reboots. It also provides a web server and RESTful API for further integration opportunities. The application can be easily extended by adding new controller files that follow a simple API.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Esdi]]></title>
            <link>https://www.andrewzigler.com/feed/esdi-discord-bot</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/esdi-discord-bot</guid>
            <pubDate>Wed, 17 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Esdi is a "plug-and-play" framework for building extensible Discord bots in ES6. Esdi can be added as an npm dependency to your Node.js project and implemented with just a few lines of code.]]></description>
            <content:encoded><![CDATA[<p>Esdi is a "plug-and-play" framework for building extensible Discord bots in ES6. Esdi can be added as an npm dependency to your Node.js project and implemented with just a few lines of code.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Eurynome]]></title>
            <link>https://www.andrewzigler.com/feed/eurynome-electron-game</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/eurynome-electron-game</guid>
            <pubDate>Wed, 17 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[The game's client was built on Electron using Mithril and interacts with a Node backend server. The proof-of-concept involves players creating characters on their local client before interacting with other users on a server. Player characters can engage in scenes of conflict or cooperation to earn points. The project has since been deprecated but was my first notable experience using either Electron or Mithril.]]></description>
            <content:encoded><![CDATA[<p>The game's client was built on <a href="https://www.electronjs.org/">Electron</a> using <a href="https://mithril.js.org/">Mithril</a> and interacts with a Node backend server. The proof-of-concept involves players creating characters on their local client before interacting with other users on a server. Player characters can engage in scenes of conflict or cooperation to earn points. The project has since been deprecated but was my first notable experience using either Electron or Mithril.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[getflightprices.com]]></title>
            <link>https://www.andrewzigler.com/feed/get-flight-prices-com-website</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/get-flight-prices-com-website</guid>
            <pubDate>Wed, 17 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[This website helps users research and prepare for their next vacation, including making bookings through referral networks.]]></description>
            <content:encoded><![CDATA[<p>This website helps users research and prepare for their next vacation, including making bookings through referral networks.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[mypetadoption.com]]></title>
            <link>https://www.andrewzigler.com/feed/my-pet-adoption-com-website</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/my-pet-adoption-com-website</guid>
            <pubDate>Wed, 17 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[This website connects with a pet adoption API to provide information about pets.]]></description>
            <content:encoded><![CDATA[<p>This website connects with a pet adoption API to provide information about pets.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[ranvier-datasource-couchdb]]></title>
            <link>https://www.andrewzigler.com/feed/ranvier-datasource-couchdb-ranvier-bundle</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/ranvier-datasource-couchdb-ranvier-bundle</guid>
            <pubDate>Wed, 17 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[This bundle allows zigmud to use a NoSQL CouchDB database instead of flat files. Everything that loads from JSON and YAML files can be loaded via this DataSource.]]></description>
            <content:encoded><![CDATA[<p>This bundle allows <a href="">zigmud</a> to use a NoSQL <a href="https://couchdb.apache.org/">CouchDB</a> database instead of flat files. Everything that loads from JSON and YAML files can be loaded via this DataSource.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[ranvier-tracery]]></title>
            <link>https://www.andrewzigler.com/feed/ranvier-tracery-ranvier-bundle</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/ranvier-tracery-ranvier-bundle</guid>
            <pubDate>Wed, 17 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Tracery is a JavaScript library by GalaxyKate that uses grammars to generate surprising new text. This bundle includes utility functions that allow the user to easily manipulate game entities with customizable grammars. By using this bundle, one can procedurally generate content with flexibility and ease. This bundle also includes a setup for a centralized grammar that can be shared between entities. To learn more about Tracery, check out my blog post where I explore its use.]]></description>
            <content:encoded><![CDATA[<p><a href="https://github.com/galaxykate/tracery">Tracery</a> is a JavaScript library by <a href="https://twitter.com/GalaxyKate">GalaxyKate</a> that uses grammars to generate surprising new text. This bundle includes utility functions that allow the user to easily manipulate game entities with customizable grammars. By using this bundle, one can procedurally generate content with flexibility and ease. This bundle also includes a setup for a centralized grammar that can be shared between entities. To learn more about Tracery, check out <a href="">my blog post</a> where I explore its use.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[ranvier-webhooks]]></title>
            <link>https://www.andrewzigler.com/feed/ranvier-webhooks-ranvier-bundle</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/ranvier-webhooks-ranvier-bundle</guid>
            <pubDate>Wed, 17 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[This bundle comes with a webhook that handles GitHub events. Once implemented, this bundle can trigger an automatic rebuild and relaunch of a zigmud instance when committing to your public repository. More webhooks can be easily added to this bundle, and those webhooks can use both command line arguments and the game state.]]></description>
            <content:encoded><![CDATA[<p>This bundle comes with a <a href="https://en.wikipedia.org/wiki/Webhook">webhook</a> that handles GitHub events. Once implemented, this bundle can trigger an automatic rebuild and relaunch of a <a href="">zigmud</a> instance when committing to your public repository. More webhooks can be easily added to this bundle, and those webhooks can use both command line arguments and the game state.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[ranvier-zpanel]]></title>
            <link>https://www.andrewzigler.com/feed/ranvier-zpanel-ranvier-bundle</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/ranvier-zpanel-ranvier-bundle</guid>
            <pubDate>Wed, 17 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Create and modify game data for zigmud via a web app. Built with Mithril.]]></description>
            <content:encoded><![CDATA[<p>Create and modify game data for <a href="">zigmud</a> via a web app. Built with <a href="https://mithril.js.org/">Mithril</a>.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Froggins]]></title>
            <link>https://www.andrewzigler.com/feed/froggins-browser-game</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/froggins-browser-game</guid>
            <pubDate>Tue, 16 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[This browser game mock-up is built in Vue and serves as a proof-of-concept for the desired interface on mobile and desktop. In the envisioned game, the player is tasked with caring for a nomadic tribe of frog-like creatures that live in a marsh and exhibit a wide array of social traits. Part of the game is simulation-based and involves nurturing your little pets as you grow the tribe. Another part of the game is RPG-based and focuses on their adventures in the marshlands. The project has since been deprecated but contains some my earliest work in Vue. The accompanying server for the front-end can be found here.]]></description>
            <content:encoded><![CDATA[<p>This browser game mock-up is built in Vue and serves as a proof-of-concept for the desired interface on mobile and desktop. In the envisioned game, the player is tasked with caring for a nomadic tribe of frog-like creatures that live in a marsh and exhibit a wide array of social traits. Part of the game is simulation-based and involves nurturing your little pets as you grow the tribe. Another part of the game is RPG-based and focuses on their adventures in the marshlands. The project has since been deprecated but contains some my earliest work in Vue. The accompanying server for the front-end can be found <a href="https://github.com/azigler/froggins-server">here</a>.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[zigmud]]></title>
            <link>https://www.andrewzigler.com/feed/zigmud-mud-engine</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/zigmud-mud-engine</guid>
            <pubDate>Tue, 16 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Experimental fork of RanvierMUD/trpg-skeleton powered by an experimental fork of RanvierMUD/core. This project is the spiritual successor to Pinwheel.]]></description>
            <content:encoded><![CDATA[<p>Experimental fork of <a href="https://github.com/RanvierMUD/trpg-skeleton">RanvierMUD/trpg-skeleton</a> powered by an experimental fork of <a href="https://github.com/azigler/core">RanvierMUD/core</a>. This project is the spiritual successor to <a href="">Pinwheel</a>.</p>]]></content:encoded>
            <category>project</category>
        </item>
        <item>
            <title><![CDATA[Review: Visme puts your visuals ahead of the curve]]></title>
            <link>https://www.andrewzigler.com/feed/review-visme-puts-your-visuals-ahead-of-the-curve</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/review-visme-puts-your-visuals-ahead-of-the-curve</guid>
            <pubDate>Sun, 29 Dec 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[I've been updating some of the images on my website, and conforming to a new image size for all of my social media shares. This turned out to be a pretty big project, since I needed to crop or rebuild over forty different blog post headers into the new format. I didn't want to do it with Photoshop again, so I joined Visme to make it easy! Visme is an easy-to-use online tool that you can use to create and organize all types of visual content. Visme has been around for over 5 years, so it has a lot to offer. Bloggers (like me!) are using it to create static graphics or presentations, but I promise that I'll try not to get too hammy with it!]]></description>
            <content:encoded><![CDATA[<p>I've been updating some of the images on my website, and conforming to a new image size for all of my social media shares. This turned out to be a pretty big project, since I needed to crop or rebuild over forty different blog post headers into the new format. I didn't want to do it with Photoshop again, so I joined <a href="https://www.visme.co/"><strong>Visme</strong></a> to make it easy! Visme is an easy-to-use online tool that you can use to create and organize all types of visual content. Visme has been around for over 5 years, so it has a lot to offer. Bloggers (like me!) are using it to create static graphics or presentations, but I promise that I'll try not to get too hammy with it!</p>
<p><img src="https://images.prismic.io/andrewzigler/85bc317a-f4bb-42b6-8735-f536d40ad038_review-visme-puts-your-visuals-ahead-of-the-curve-02.jpg?auto=compress,format" alt=""></p>
<p>What makes Visme easy is that you can start from professional templates or from a blank canvas. Visme also has millions of assets available right inside the web app (including vector icons), so (for me) this was a process that used to jump between Google Images and Photoshop but now resides solely in one spot: Visme. That simplification made working on my images easy and (for once) something I actually <em>enjoyed</em> doing.</p>
<p><img src="https://images.prismic.io/andrewzigler/8f11d367-4d80-4e77-a109-6d3ab72197a3_review-visme-puts-your-visuals-ahead-of-the-curve-04.jpg?auto=compress,format" alt=""></p>
<p>Visme also has an animation engine on top of the visuals. Virtually any object can be animated with a couple clicks. Visme has over a hundred high-quality and popular fonts that can be applied with many properties and effects. There are a variety of font styles to satisfy any project need and the entire process is simple. You can also apply these effects and properties granularly to each individual text character, allowing for truly fine-tuned control. And for me, that's what makes Visme truly shine!</p>
<p><img src="https://images.prismic.io/andrewzigler/a731e002-4cd1-4e06-8a94-c09f50da076e_review-visme-puts-your-visuals-ahead-of-the-curve-03.jpg?auto=compress,format" alt=""></p>
<p>Branding is simple using the tool, and one of the best features I found for that was the global color library, which allowed me to pick and save colors to reuse within my project. If you're using Visme to create literal presentations, then it's also good to know that you can upload your own audio MP3 files to your visual presentation. Visme also provides a large library of audio tracks that can be attached to one slide (for presentations) or used as background music for an entire project.</p>
<p>As I mentioned initially, I had to methodically go through each social media image on my website and recrop it to fit perfectly in my new dimensions. If it didn't fit, I then used Visme to rebuild or completely remake the image but with correct proportions. I was able to lean on Visme's pixel-precise tools to create images that popped more. The abundance of fonts let me mix up some of what I was doing before. I completed the project quicker than expected so I even made some extra images!</p>
<p>If you're interested in checking out the tool, know that <a href="https://www.visme.co/pricing/">Visme is free for all users</a>! It takes only a few seconds to register and start creating your first project. Most of the features are free so it's easy to experiment with.</p>]]></content:encoded>
            <category>article</category>
            <category>Review</category>
        </item>
        <item>
            <title><![CDATA[JavaScript and Java Most in Demand Software Languages]]></title>
            <link>https://www.andrewzigler.com/feed/javascript-and-java-most-in-demand-software-languages</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/javascript-and-java-most-in-demand-software-languages</guid>
            <pubDate>Fri, 22 Nov 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Programming languages JavaScript and Java are the most sought-after skills for IT professionals, according to a recent report from online academy Pluralsight. The report, called the Technology Index, breaks down the demand for a software developer’s proficiency in specific languages.]]></description>
            <content:encoded><![CDATA[<p>Programming languages JavaScript and Java are the most sought-after skills for IT professionals, <a href="https://www.techrepublic.com/article/the-most-in-demand-technologies-for-it-professionals/">according to a recent report from online academy Pluralsight</a>. The report, called the Technology Index, breaks down the demand for a software developer’s proficiency in specific languages.</p>
<p>While both languages have been around for over two decades, JavaScript and Java continue to be the most popular in the majority of ranking sites.</p>
<p><a href="https://dzone.com/articles/top-programming-languages-rankings">An analysis from DZone notes that learning Java</a> as your first language is probably the best choice if you’re planning to have a career as a developer. Moreover, a 4-year analysis of job postings from job platform Indeed indicates that both Java and JavaScript remain the most in-demand programming skills. Similarly, there are almost 5 million students <a href="https://www.udemy.com/topic/javascript/">learning JavaScript on Udemy</a>, which is a clear indication of how widely used the program is across industries. With a wide user base and tons of libraries created over the past three decades, both languages remain the most reliable and widely used.</p>
<h2>Battle for the Web: Java vs. JavaScript</h2>
<p>Many people still think the two are related but Java and JavaScript are two different languages altogether. Although they had a brief intersection in the early days of Netscape, the two top languages have diverted paths from thereon.</p>
<p>When JavaScript was first introduced in the 90s, it was packaged as a companion of the then-popular Java for web development. It was not until years later that <a href="">JavaScript was recognized as a programming language on its own</a>. Java, on the other hand, was a pioneer in web development as an object-oriented programming language.</p>
<p>While both languages have been in contention for the best language for web development for a long time now, JavaScript has emerged as the undisputed language for web applications. Around 94% of all webpages on the internet are made from JavaScript but it’s common to have them running Java on the back-end.</p>
<p>Five years ago, people would’ve made clear distinctions in terms of JavaScript being the language used for client-facing coding and Java for server-side coding. But the advent of Node.js made JavaScript a more efficient language to be used for back-end development. As a runtime environment, Node is a light, scalable, and cross-platform way to execute code. This marked the supremacy of JavaScript for the internet including enterprise web programs. With Vue.js and AngularJS frameworks for front-end and React for back-end, it has become the most versatile, fast, and scalable language for web platforms.</p>
<h2>Mobile development</h2>
<p>Part of what kept Java afloat despite the rise of JS is its platform Java Virtual Machine. The "write once, run anywhere" principle of Java made it a versatile and universally compatible language as long as the device has a Java Runtime Environment.</p>
<p>This important feature made it the best language to use for developing mobile applications. In fact, it’s <a href="https://www.androidauthority.com/develop-android-apps-languages-learn-391008/">the official language for Android development</a> and is supported by Android Studio. Around a fourth of the apps currently in the Google Play Store are made with Java and its frameworks.</p>
<p>In conclusion, both JavaScript and Java have stood the test of time. Their adaptability and capacity to integrate with new languages make them the most valuable skills for developers.</p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
        <item>
            <title><![CDATA[Untitled Goose Game Review: Avian Agent of Chaos]]></title>
            <link>https://www.andrewzigler.com/feed/untitled-goose-game-review-avian-agent-of-chaos</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/untitled-goose-game-review-avian-agent-of-chaos</guid>
            <pubDate>Wed, 23 Oct 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[People online are losing their minds over a casual simulation game where you take control of a mischievous goose. In its first two weeks, Untitled Goose Game sold over 100,000 copies worldwide. If we hadn't before, we've now definitely crossed the Rubicon. With enough creativity, anything can be a game.]]></description>
            <content:encoded><![CDATA[<p>People online are losing their <em>minds</em> over a casual simulation game where you take control of a mischievous goose. In its first two weeks, <a href="https://goose.game/"><em>Untitled Goose Game</em></a> sold over 100,000 copies worldwide. If we hadn't before, we've now <em>definitely</em> crossed the Rubicon. With enough creativity, <em>anything</em> can be a game.</p>
<p>One of the unique attributes of being a goose is that most people ignore you unless you're causing trouble. Waddling through a world of people absorbed in their own tasks and leisure, you can fly mostly under the radar. When you intrude on their livelihoods or otherwise make a mess, they'll chase and shoo you away. They'll shake their fist and rue the day, but their responses are fully non-violent and realistic. People care about and react to you in more or less the same way they would react to a real live goose in that situation: they'll flap their arms until they scare you off. One meek boy in particular will flee from you and hide! Contrast this with other games that use direct conflict to drive story and provide gameplay, the situations of <em>Untitled Goose Game</em> become low-stakes and more like extended slapstick jokes. And it works. Magically. It would be absurd if it wasn't so realistic in the way the world reacts to you. It never tips into being unrealistic, which makes the experience that much more satisfying from the perspective of a goose. You're finally getting a piece of the pie from which all geese in this world freely eat: terrorizing humans for no discernible reason.</p>
<p>The game features an incredible and dynamic music score that reacts live to the choices you make. When you sneak up on someone or an objective, the pitter-patter of music will build and grow in turn. When you finally seize an opportunity or make some real trouble, the piano score will undergo a reactive crescendo that makes gameplay more like a performance you share with others. As a result, it's wickedly fun to play this game with an audience of friends. The musical choices of the game make revisiting the game enjoyable and always different.</p>
<p>The game is silly and creative, and I was laughing the whole way through. However, it's driven by the premise alone so once you complete all of the main tasks (and all of the bonus tasks) then there's very little reason to revisit it (other than to show your friends that you own <em>Untitled Goose Game</em>, of course). The pranks you can undertake are thrilling, but you'll hit a point where most everything has been done. The brief and endlessly funny interactions between the titular (untitular?) goose and the denizens of the village are charming but once you push all of everyone's buttons, there's very little else to try. The environment is peaceful and fun, which is why it's so fun to try and wreck the idyllic calmness of it all. The world is your canvas and the goose is your paint.</p>
<p>_Untitled Goose Game _drew similar attention as _<a href="http://goat-simulator.com/">Goat Simulator</a> _on social media, both being sandbox-style games in which the player is invited to create chaos as a semi-domesticated animal. Similarly, the contents of both games have become memes in their own right because of the almost-absurd nature of the tasks involved. Indeed, <em>Untitled Goose Game</em> invites memes with its relatively plain palette and simple graphics. The soft colors and flat greens make each moment a snapshot of a pastoral hamlet, almost lost in time with its narrow roads and winding aqueducts (on which you quickly sail away as a fast-swimming goose). The goose itself is white and simple, a most memetic representation of the bird there can be. <a href="https://twitter.com/Foone">@Foone</a> has created a <a href="http://deathgenerator.com/#ugg">meme generator</a> that lets you mock up a game image with your own <em>Untitled Goose Game</em> tasks (and even cross them out), furthering the meme potential. The simplicity of its presentation makes it just as engaging to watch as it is to play. If you want to get your feathers on some mischief, you can buy <em>Untitled Goose Game</em> now for PC, Mac, or the Nintendo Switch for only $20.</p>
<p><em>**2021 update: **Untitled Goose Game now has a local two-player co-op mode, so you can play the same game with a friend and cause trouble as a duo! The game tasks are ultimately the same, but having an extra beak allows for extra mayhem.</em></p>]]></content:encoded>
            <category>article</category>
            <category>Review</category>
        </item>
        <item>
            <title><![CDATA[Relaxing with CouchDB]]></title>
            <link>https://www.andrewzigler.com/feed/relaxing-with-couchdb</link>
            <guid isPermaLink="true">https://www.andrewzigler.com/feed/relaxing-with-couchdb</guid>
            <pubDate>Wed, 25 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[I'll admit it: databases scare me. I've always avoided databases in my projects because the idea of sorting and scheming all of my necessary data is an intimidating task. Up until now, if I needed to store data then I would save it to a local JSON file or put it in localStorage. However, you can only do that for so long until you start hitting limits in your implementation. So this month I've been dedicating myself to learning databases. Ideally, I just want to think in terms of JSON, so it's no real surprise that my first major database exploration has been with CouchDB, which is a NoSQL database maintained by the Apache Software Foundation. And I have to say, it's been a relaxing experience so far.]]></description>
            <content:encoded><![CDATA[<p>I'll admit it: databases scare me. I've always avoided databases in my projects because the idea of sorting and scheming all of my necessary data is an intimidating task. Up until now, if I needed to store data then I would save it to a local JSON file or put it in <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">localStorage</a>. However, you can only do that for so long until you start hitting limits in your implementation. So this month I've been dedicating myself to learning databases. Ideally, I just want to think in terms of JSON, so it's no real surprise that my first major database exploration has been with <a href="http://couchdb.apache.org/">CouchDB</a>, which is a <a href="https://en.wikipedia.org/wiki/NoSQL">NoSQL database</a> maintained by the <a href="https://www.apache.org/">Apache Software Foundation</a>. And I have to say, it's been a <em><a href="http://docs.couchdb.org/en/stable/intro/why.html#relax">relaxing</a></em> experience so far.</p>
<p>Before I discuss CouchDB and why it's so great, it's important to define the bigger picture. Proposed in 1970, <a href="https://en.wikipedia.org/wiki/SQL">SQL</a> (Structured Query Language) is a programming language used to manage data in relational databases. Relational databases use relations (typically called tables) to store data, and they match that data by using common characteristics between them. SQL is used to create both data (in objects called tables) and the schema for that data, which describes fields as columns and a single record as a row.</p>
<p>Younger in comparison, <a href="https://en.wikipedia.org/wiki/NoSQL">NoSQL</a> (Not Only SQL, or Not SQL) was coined in 1998 and defines a database that is self-describing, which means it does not require a schema or enforce relations between tables in all cases. All of its entries (typically called documents) are JSON, which are complete data object entities that applications can read and understand. NoSQL refers to high-performance, non-relational databases that utilize not just document-based schemas but a wide variety of data models for its entries. These databases are prized for their ease-of-use, scaling performance, resilience under pressure, and ample availability.</p>
<p>A CouchDB server instance has databases, which in turn store documents. Each document has a unique identifier within the database, and CouchDB provides a <a href="https://en.wikipedia.org/wiki/Representational_state_transfer">RESTful</a> HTTP API for manipulating the server and its contents. Documents are the bread and butter of CouchDB, consisting of any number of fields and attachments that can contain any amount of various value types. Documents also include metadata that’s maintained by the database system (like revision history). Documents form the heart of the database and can change dynamically beyond their original definition. CouchDB applies document updates without locking the entry, to improve performance for all connections. So if another client editing the same document saves their changes first, the remaining client gets an edit conflict error when saving because the document metadata (revision history) has changed. To resolve the update conflict, the latest document version should be opened, the edits reapplied, and the update reattempted. Manipulating entire documents (as opposed to their properties) either succeeds entirely or fails completely. CouchDB never contains partially saved or edited documents because the document commitment system is <a href="https://en.wikipedia.org/wiki/ACID">ACID</a> (<em><strong>A</strong>tomic</em>, <em><strong>C</strong>onsistent</em>, <em><strong>I</strong>solated</em>, and <em><strong>D</strong>urable</em>).</p>
<p>CouchDB isn't the only NoSQL database out there. In fact, there are a lot! One of the most popular options is <a href="https://www.mongodb.com/">MongoDB</a>, which admittedly has more resources (i.e., a larger community with more tooling and marketing) than CouchDB. And MongoDB supports ad hoc querying, which makes the transition from SQL even easier if you're coming from that background. However, there is no <em>one</em> single perfect database for all scenarios. Each database type offers certain features and prioritizes certain tenants of database design. This is best explained by the <a href="https://en.wikipedia.org/wiki/CAP_theorem">CAP theorem</a>, which says it's impossible for a distributed data store to simultaneously provide more than two out of the following three guarantees: <em><strong>C</strong>onsistency</em> (each client always has the same view of the data), <em><strong>A</strong>vailability</em> (all clients can always read and write), and <em><strong>P</strong>artition tolerance</em> (the system works well across physical network partitions).</p>
<p>CouchDB favors availability. For example, CouchDB supports full replication to mobile devices and desktop, so if you need a local database on your mobile application (or if your desktop users need to work offline and sync their work back to a server) then you should use CouchDB. CouchDB uses a replication model called <a href="http://docs.couchdb.org/en/stable/intro/consistency.html">Eventual Consistency</a>. In this system, clients can write data to one node of the database without waiting for other nodes to come into agreement. The system incrementally copies document changes between nodes, meaning everything will <em>eventually</em> be in sync. Another important distinction is that CouchDB relies on <a href="https://en.wikipedia.org/wiki/B-tree">B-tree</a> indexes. This means that whether you have 1 entry or 15 billion, the querying time will always remain below 10 milliseconds. This is huge for performance, making CouchDB low latency and very appealing.</p>
<p>MongoDB, on the other hand, favors consistency. It uses a single authoritative main server that accepts all writes and updates its replicas accordingly. Replicas can be read, but they may possibly not be completely up-to-date at that specific point in time. Consequently, there is no versioning like in CouchDB. In fact, updates can happen in-place, using an algorithm to pad frequently growing documents.</p>
<p>There are drivers and tooling available for CouchDB in several languages, including JavaScript. CouchDB itself is manipulated via a RESTful API, and it's surprisingly easy to get started that way:</p>
<pre><code>const URL = "http://127.0.0.1:5984"

function createDB(dbName) {
    var req = new XMLHttpRequest();
    req.open("PUT", URL + "/" + dbName, true);
    req.setRequestHeader("Content-type", "application/json");

    req.send();
}

function updateDB(dbName, docName, data) {
    var req = new XMLHttpRequest();
    req.open("PUT", URL + '/' + dbName + '/' + docName, true);
    req.setRequestHeader("Content-type", "application/json");

    req.send(JSON.stringify(data));
}

createDB("baseball");
updateDB("baseball", "Rangers", {"pitcher":"Nolan Ryan"});
</code></pre>
<p>But building an interface like this from scratch is a huge hassle and I don't recommend it! Unless absolutely necessary, you should use a community-supported driver so your interface is well-developed and well-tested. When using CouchDB in JavaScript, I recommend <a href="https://pouchdb.com/">PouchDB</a>, which works both in the browser and in Node. By default, PouchDB ships with a <a href="http://www.w3.org/TR/IndexedDB/">IndexedDB</a> adapter for the browser and a <a href="https://github.com/google/leveldb">LevelDB</a> adapter in Node. By using the included adapters in both the browser and in Node, you can use PouchDB as an actual local database that's capable of syncing to a remote store, or you can simply use PouchDB as the interface for manipulating a remote store directly. PouchDB's API works the same in every environment, so you can spend less time worrying about browser/server differences and more time writing clean, consistent code. PouchDB can also be easily used with frameworks like React and Vue. You can either listen for events on a synced database and handle those events, or you can use a package like <em><a href="https://github.com/MDSLKTR/pouch-vue">pouch-vue</a></em> to handle the database directly in your components.</p>
<p>When I initially started digging into databases this month, I wasn't too sure how I wanted to practice my new skills. Coming from several website projects, I was more interested in working on a game or at least something more fun than functional, per se. The last game-related project that I worked on was my <a href="">Pinwheel MUD Engine</a>, and I still have much to do in terms of that project. However, the worldbuilding resources behind Pinwheel have been a personal project of mine for a long time, and many aspects are practically <em>begging</em> to be plucked out and formed into their own independent projects. My Pinwheel MUD is envisioned as a multiplayer hybrid of both an artificial life simulation and a role-playing game. Early in development, one of the creatures I built using the engine was a small human-like frog that exhibited a wide array of characteristics. The concept immediately gripped me and was both intuitive and fun to develop, so I'm expanding on them in a new project called <a href="">Froggins</a>.</p>
<p>In this game concept, you're charged with caring for a nomadic tribe of frog-like creatures who migrate across a marsh and exhibit a wide array of social traits. Part of the game is simulation-based and involves nurturing your little pets as you grow the tribe. Another part of the game is RPG-based and focuses on their adventures in the marshlands. Both gameplay modes meld together to create a story unique to the player. You can trade them with other players to cultivate desired traits within your tribe, and once your creatures mature then you can imbue them with heroic abilities and send them out on quests. The game concept is still in early development and I hope to provide more details in a future blog post.</p>
<p>The concept is inspired by early artificial life simulations like <a href="https://en.wikipedia.org/wiki/Creatures_(video_game_series)">Creatures</a> and browser-based virtual pet games like <a href="https://en.wikipedia.org/wiki/Neopets">Neopets</a>. Since you can train and battle your creatures, it's also a lot like <a href="https://en.wikipedia.org/wiki/Pok%C3%A9mon">Pokémon</a>. Virtual pets have always been fascinating to me because it's interesting how much we can grow to love a digital representation of a living thing. They're ultimately just 1s and 0s on a virtual machine somewhere but they can evoke emotions all the same.</p>
<p>CouchDB underpins the entire project, which persists on a server handling all game logic. Clients connect via the browser and relay user interactions back to the server, which then responds with the outcome. Building this server and it's corresponding client has been an eye-opening undertaking. One of the early obstacles involved creating a communication protocol that's shared between the WebSocket server and the Vue client. I standardized the format for sending and receiving these messages. And I call them <em>ribbits</em>, in celebration of the project's subject matter.</p>
<p>Simply speaking, CouchDB itself is an HTTP server that's capable of serving HTML directly to the browser. You can also attach binary files directly to a database, as well. As a result, you can use actually <a href="http://guide.couchdb.org/draft/standalone.html">serve any web application and its resources directly from a CouchDB instance</a>, removing the intermediary server component from any traditional client–server model. The loaded page can even talk directly to the database using the JavaScript served in the page itself, which completely eliminates the server layer in some implementations! CouchDB’s features are a foundation for building standalone web applications backed by a powerful database. Even CouchDB’s own built-in administrative interface, <a href="https://couchdb.apache.org/fauxton-visual-guide/">Fauxton</a>, is a fully functional database management application built using HTML, CSS, and JavaScript. CouchDB and web applications go hand in hand.</p>
<p>This type of application was once called a <a href="https://github.com/couchapp/couchapp">CouchApp</a> before the functionality was integrated into CouchDB's <a href="http://docs.couchdb.com/en/latest/ddocs/index.html">design documents</a>, which store JavaScript code in the database. When that happened, the use of CouchDB as a combined standalone database and application server became no longer recommended by CouchDB's maintainers. There are significant limitations to a pure CouchDB web server application stack, including security, templating, and tooling. The developers of CouchDB recommend that we use CouchDB only as the database layer, in conjunction with a web application framework like Vue, Angular, or React.</p>
<p>Having dipped my toes into the waters of NoSQL and CouchDB, my fears and anxieties about databases have washed away! I've set up my own CouchDB instance on a server and configured it for use on my local machine. Now that I've learned the basics, I can't wait to integrate it into my pre-existing projects and any new ones!</p>]]></content:encoded>
            <category>article</category>
            <category>Devlog</category>
        </item>
    </channel>
</rss>