<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Akhyar Amarullah</title>
    <description>The latest articles on DEV Community by Akhyar Amarullah (@akhy).</description>
    <link>https://dev.to/akhy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F426859%2F649cf442-bc91-4833-8478-b725c67fa11d.png</url>
      <title>DEV Community: Akhyar Amarullah</title>
      <link>https://dev.to/akhy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akhy"/>
    <language>en</language>
    <item>
      <title>Wrote my thought process on redesigning my tiny CLI tool.

https://akhy.my.id/posts/fixing-nested-shells-ksw-kubeconfig-switcher/

#go #kubectl #unix</title>
      <dc:creator>Akhyar Amarullah</dc:creator>
      <pubDate>Sun, 19 Oct 2025 05:28:52 +0000</pubDate>
      <link>https://dev.to/akhy/wrote-my-thought-process-on-redesigning-my-tiny-cli-tool-5a6d</link>
      <guid>https://dev.to/akhy/wrote-my-thought-process-on-redesigning-my-tiny-cli-tool-5a6d</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://akhy.my.id/posts/fixing-nested-shells-ksw-kubeconfig-switcher/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;akhy.my.id&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




</description>
    </item>
    <item>
      <title>Fixing Nested Shells in KSW Kubeconfig Switcher: My Thought Process</title>
      <dc:creator>Akhyar Amarullah</dc:creator>
      <pubDate>Sat, 18 Oct 2025 05:32:00 +0000</pubDate>
      <link>https://dev.to/akhy/fixing-nested-shells-in-ksw-kubeconfig-switcher-my-thought-process-151p</link>
      <guid>https://dev.to/akhy/fixing-nested-shells-in-ksw-kubeconfig-switcher-my-thought-process-151p</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/chickenzord/ksw" rel="noopener noreferrer"&gt;ksw&lt;/a&gt; (Kubeconfig SWitcher) is a &lt;a href="https://akhy.my.id/posts/ksw-kubeconfig-switcher" rel="noopener noreferrer"&gt;small CLI tool I built&lt;/a&gt; to help me work with multiple Kubernetes contexts across different terminal windows. It creates isolated shell sessions, each using a different context, so I can have one terminal connected to production and another to staging without them interfering with each other. I use it every day at work.&lt;/p&gt;

&lt;p&gt;It's a tiny project. I don't know if anyone else is using it besides me, but I wanted to improve it anyway. When I started working on this refactor, I had one clear goal: make the process tree as flat as possible. The nested shell problem was bothering me, and I wanted to fix it, even if there were catches along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Nested Shell Problem
&lt;/h2&gt;

&lt;p&gt;The original implementation spawned a subprocess for each ksw invocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;you@machine$ ksw production
  └─ ksw process (waiting)
      └─ zsh
          $ ksw staging
            └─ ksw process (waiting)
                └─ zsh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each switch added another level. The &lt;code&gt;KSW_LEVEL&lt;/code&gt; environment variable tracked how deep you were. It worked, but it felt wrong. I wanted a flat process tree with minimal nesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Attempt: Parent-Managed Switching
&lt;/h2&gt;

&lt;p&gt;My first thought was: what if the child shell exits and delegates the switching to the parent ksw process? The parent could detect the exit, switch the context, and spawn a new shell.&lt;/p&gt;

&lt;p&gt;I quickly abandoned this idea. It was too complex. Coordinating between parent and child, passing signals or exit codes to communicate the desired context, handling edge cases. The complexity wasn't worth it.&lt;/p&gt;

&lt;p&gt;(Side note: I might revisit this inter-process delegation approach later just for the sake of it. It's an interesting problem even if the simpler solution works fine.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Discovery: syscall.Exec
&lt;/h2&gt;

&lt;p&gt;Then I found &lt;a href="https://pkg.go.dev/syscall#Exec" rel="noopener noreferrer"&gt;&lt;code&gt;syscall.Exec()&lt;/code&gt;&lt;/a&gt;. Instead of &lt;a href="https://akhy.my.id/posts/automating-interactive-shell-input-in-go" rel="noopener noreferrer"&gt;spawning a subprocess&lt;/a&gt;, it replaces the current process entirely. The ksw process doesn't wait around, it becomes the shell.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Replace ksw process with shell&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environ&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to exec shell: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;// This line never executes - ksw is gone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now the process tree looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;you@machine$ ksw production
  └─ zsh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what about switching contexts? If you run &lt;code&gt;ksw staging&lt;/code&gt; from within that shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;you@machine$ ksw production
  └─ zsh
      $ ksw staging
        └─ zsh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still nested, but shallower. No idle ksw processes sitting around at each level. All ksw processes get replaced by shells at the same depth. Better, but not quite there yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Workaround: Shell exec Command
&lt;/h2&gt;

&lt;p&gt;I realized you could use the shell's built-in &lt;code&gt;exec&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;production&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;exec &lt;/span&gt;ksw staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This replaces the current shell process with ksw, which then replaces itself with a new shell for the staging context. No nesting, but requiring users to type &lt;code&gt;exec ksw&lt;/code&gt; instead of just &lt;code&gt;ksw&lt;/code&gt; felt clunky. Not a good experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Breakthrough: In-Place Updates
&lt;/h2&gt;

&lt;p&gt;Then it hit me: if the current shell is already in a ksw session, I don't need a new shell at all. Each ksw session uses an isolated temporary kubeconfig file, that's the whole point of ksw. Other terminals aren't affected.&lt;/p&gt;

&lt;p&gt;So why not just update that temp file in place?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// In main.go - detect if already in a session&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"KSW_KUBECONFIG_ORIGINAL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;switchContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contextName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// switchContext overwrites the existing temp file&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;switchContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contextName&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;kubeconfigOriginal&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"KSW_KUBECONFIG_ORIGINAL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;existingKubeconfig&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"KSW_KUBECONFIG"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;generateKubeconfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kubeconfigOriginal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contextName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Overwrite existing temp file with new context&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existingKubeconfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;logf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"switched to context %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contextName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since kubectl reads the &lt;code&gt;KUBECONFIG&lt;/code&gt; environment variable on every invocation, it immediately sees the new context. No new process, no nesting, just an updated file.&lt;/p&gt;

&lt;p&gt;Now the flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;you@machine$ ksw production
  └─ zsh (KUBECONFIG=/tmp/production.xyz.yaml)
      $ ksw staging        # Updates /tmp/production.xyz.yaml in-place
      $ kubectl get pods   # Reads the updated file, sees staging context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same shell, same process, different context. Flat process tree achieved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;The combination of &lt;code&gt;syscall.Exec()&lt;/code&gt; for initial sessions and in-place updates for context switching solved both problems (see the &lt;a href="https://github.com/chickenzord/ksw/blob/main/shell.go" rel="noopener noreferrer"&gt;full implementation&lt;/a&gt;):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;First time&lt;/strong&gt;: ksw replaces itself with your shell, no idle process&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context switching&lt;/strong&gt;: ksw updates the temp file and returns, no new shell, no nesting&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;KSW_LEVEL&lt;/code&gt; tracking is gone. The nested shell problem is gone. The process tree stays flat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs
&lt;/h2&gt;

&lt;p&gt;There's one trade-off: temp file cleanup now relies on the OS cleaning up &lt;code&gt;/tmp&lt;/code&gt;. Previously, the ksw process could delete the temp file when the subprocess exited. In practice, this hasn't been an issue. The files are tiny (~1-2KB) and OS temp cleanup handles them fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Sometimes the best solution comes from realizing you don't need to do something at all. I was so focused on how to make process spawning better that I almost missed the obvious: when you're already in a ksw session, you don't need another process. Just update the file.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;syscall.Exec()&lt;/code&gt; change made the architecture cleaner, but the real win was recognizing that in-place updates were possible because of ksw's isolated temp file design. The solution was already there in the architecture. I just needed to see it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;If you work with multiple Kubernetes contexts, give ksw a try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;chickenzord/tap/ksw
ksw my-context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switch contexts as many times as you want, your process tree stays flat.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;ksw is open source at &lt;a href="https://github.com/chickenzord/ksw" rel="noopener noreferrer"&gt;github.com/chickenzord/ksw&lt;/a&gt;. The exec refactor was implemented in &lt;a href="https://github.com/chickenzord/ksw/pull/19" rel="noopener noreferrer"&gt;PR #19&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>tooling</category>
      <category>kubectl</category>
      <category>syscall</category>
    </item>
    <item>
      <title>Vibe-Coding My First MCP Server</title>
      <dc:creator>Akhyar Amarullah</dc:creator>
      <pubDate>Fri, 05 Sep 2025 09:00:00 +0000</pubDate>
      <link>https://dev.to/akhy/vibe-coding-my-first-mcp-server-4gb2</link>
      <guid>https://dev.to/akhy/vibe-coding-my-first-mcp-server-4gb2</guid>
      <description>&lt;p&gt;I recently built my first Model Context Protocol (MCP) server, and honestly? I had no idea what I was doing. The whole thing was pure vibe-coding. Throw ideas at Claude, see what works, and somehow end up with working software. The result was &lt;a href="https://github.com/chickenzord/linkding-mcp" rel="noopener noreferrer"&gt;linkding-mcp&lt;/a&gt;, and I'm surprised it actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Even Is MCP?
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol is Anthropic's way of letting AI assistants connect to external tools and data sources. Think of it as giving Claude access to your stuff. Instead of just chatting, it can actually work with your real data. When I heard about it, my first thought was "I want my bookmarks accessible to Claude."&lt;/p&gt;

&lt;p&gt;I use Linkding for bookmark management as part of my &lt;a href="https://akhy.my.id/posts/my-self-hosting-setup" rel="noopener noreferrer"&gt;self-hosted setup&lt;/a&gt;, and the idea of asking Claude "find that article about Go testing I bookmarked last month" seemed too good to pass up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vibe-Coding Approach
&lt;/h2&gt;

&lt;p&gt;I've been experimenting with what I call "vibe-coding." Basically, let AI do most of the heavy lifting while I provide direction and fix the issues. For this project, I took it to the extreme.&lt;/p&gt;

&lt;p&gt;My process was basically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tell Claude "I want an MCP server for Linkding"&lt;/li&gt;
&lt;li&gt;Paste the Linkding API docs from their website&lt;/li&gt;
&lt;li&gt;Let Claude generate the entire Go client&lt;/li&gt;
&lt;li&gt;Ask Claude to build the MCP server on top of that&lt;/li&gt;
&lt;li&gt;Ask Claude to generate the GitHub workflow for CI/CD&lt;/li&gt;
&lt;li&gt;Debug and iterate until it worked&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No careful planning, no detailed design docs, just pure experimentation guided by AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Magic Moment
&lt;/h2&gt;

&lt;p&gt;What surprised me was watching Claude generate a complete Go client library just from reading Linkding's API documentation. I copy-pasted the docs from their website, and within minutes I had working code that could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate with Linkding&lt;/li&gt;
&lt;li&gt;Create and retrieve bookmarks&lt;/li&gt;
&lt;li&gt;Search through my bookmark collection&lt;/li&gt;
&lt;li&gt;Handle pagination and error cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, even more impressively, Claude built an entire MCP server on top of that client. The server implements the MCP protocol, handles tool registration, and provides clean interfaces for bookmark operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Final Product Does
&lt;/h2&gt;

&lt;p&gt;The linkding-mcp server gives Claude direct access to my Linkding bookmarks. I can now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search bookmarks&lt;/strong&gt; : "Find bookmarks about Kubernetes monitoring"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create bookmarks&lt;/strong&gt; : "Bookmark this URL with these tags"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get bookmark details&lt;/strong&gt; : "Show me the details of that bookmark about MCP"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's surprisingly useful. Instead of opening Linkding in a browser and manually searching, I just ask Claude. For someone who bookmarks everything but rarely organizes it properly, having AI help navigate my collection is really helpful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI-Assisted Development Experience
&lt;/h2&gt;

&lt;p&gt;This project made me realize how much AI has changed software development. I'm experienced with Go, but I've never built an MCP server before. In the old days, I would have spent hours reading specs, looking at examples, and carefully implementing each piece.&lt;/p&gt;

&lt;p&gt;Instead, I described what I wanted and let Claude figure out the implementation details. When something didn't work, I'd paste the error and get a fix. When I wanted to add features, I'd describe them and get working code.&lt;/p&gt;

&lt;p&gt;The ratio was probably 90% AI-generated code, 10% me providing direction and debugging. The whole thing took about 3 hours from inception to a fully working Docker image automatically built and pushed on GitHub releases. And the result serves its purpose well as an experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Surprises
&lt;/h2&gt;

&lt;p&gt;A few things that surprised me during this experiment:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP is surprisingly straightforward&lt;/strong&gt; - The protocol is well-designed and Claude understood it from the specs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API client generation is magic&lt;/strong&gt; - Feed API docs to AI and get working client code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error handling just worked&lt;/strong&gt; - I expected to spend time debugging edge cases, but the generated code handled errors well from the start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance was fine&lt;/strong&gt; - No optimization needed, it just works fast enough for personal use.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for Development
&lt;/h2&gt;

&lt;p&gt;This project has me thinking about the future of software development. When AI can generate working code from documentation and natural language descriptions, what does that mean for how we build things?&lt;/p&gt;

&lt;p&gt;I'm not ready to vibe-code everything, but for exploratory projects and personal tools? It's pretty powerful. The barrier between "I wish this existed" and "I have working software" has basically disappeared.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;The entire &lt;a href="https://github.com/chickenzord/linkding-mcp" rel="noopener noreferrer"&gt;linkding-mcp server is on GitHub&lt;/a&gt;. Fair warning, it's mostly AI-generated code with minimal polishing. But it works, and that's what matters for a personal tool.&lt;/p&gt;

&lt;p&gt;If you're using Linkding and want to try it out, the setup is straightforward. Just don't expect production-grade code, this was pure experimentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;This experience convinced me that MCP servers are worth exploring further. My next project was &lt;a href="https://akhy.my.id/posts/portosync" rel="noopener noreferrer"&gt;PortoSync&lt;/a&gt; (connecting Indonesian investment data to Claude), and I'm already thinking about what other data sources would be useful to connect.&lt;/p&gt;

&lt;p&gt;The ability to give AI assistants access to your actual data, rather than just generic knowledge, opens up a lot of possibilities. And if AI can help build the connections themselves, the barrier to experimentation drops to basically zero.&lt;/p&gt;

&lt;p&gt;Vibe-coding might not be the future of all software development, but for personal tools and rapid prototyping? It's pretty amazing.&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>mcp</category>
      <category>go</category>
      <category>claude</category>
    </item>
    <item>
      <title>KSW (Kubeconfig Switcher)</title>
      <dc:creator>Akhyar Amarullah</dc:creator>
      <pubDate>Sat, 25 Nov 2023 11:34:00 +0000</pubDate>
      <link>https://dev.to/akhy/ksw-kubeconfig-switcher-2ic9</link>
      <guid>https://dev.to/akhy/ksw-kubeconfig-switcher-2ic9</guid>
      <description>&lt;p&gt;A while ago I created and published my own CLI tool to switch Kubeconfig. I wrote it in Go and it's installable easily using Homebrew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install chickenzord/tap/ksw

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Project repo: &lt;a href="https://github.com/chickenzord/ksw" rel="noopener noreferrer"&gt;https://github.com/chickenzord/ksw&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;When you run the command and pass the context's name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ksw context-name

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Try loading kubeconfig file from these locations:

&lt;ol&gt;
&lt;li&gt;Path set in &lt;code&gt;KSW_KUBECONFIG_ORIGINAL&lt;/code&gt; (more on this below)&lt;/li&gt;
&lt;li&gt;Path set in &lt;code&gt;KUBECONFIG&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Default location &lt;code&gt;$HOME/.kube/config&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Minify and flatten the config so it only contains clusters and users used by the specificed "context-name", then put it in a temp file&lt;/li&gt;

&lt;li&gt;Start a new shell (&lt;a href="https://github.com/riywo/loginshell" rel="noopener noreferrer"&gt;same with the currently used&lt;/a&gt;) with &lt;code&gt;KUBECONFIG&lt;/code&gt; set to the temp file&lt;/li&gt;

&lt;li&gt;Additionally, these environment variables also set in the sub-shell:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;KSW_KUBECONFIG_ORIGINAL&lt;/code&gt;: To keep track of original kubeconfig file when starting recursive shells&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KSW_KUBECONFIG&lt;/code&gt;: Same value as KUBECONFIG&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KSW_ACTIVE&lt;/code&gt;: Always set to "true"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KSW_SHELL&lt;/code&gt;: Path to the shell (e.g. &lt;code&gt;/bin/zsh&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KSW_LEVEL&lt;/code&gt;: Nesting level of the shell, starting at 1 when first running ksw&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KSW_CONTEXT&lt;/code&gt;: Kube context name used when running ksw&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Supports recursive shell (starting ksw shell within ksw shell)&lt;/li&gt;
&lt;li&gt;Shows a built-in fuzzy finder (like fzf) when no contexts specified in the argument&lt;/li&gt;
&lt;li&gt;No automatic indicator in prompt, use the provided environment variables to set it depending on your setup&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Some thoughts on the reason
&lt;/h3&gt;

&lt;p&gt;You might think why am I reinventing the wheel? Some tools solve the same problem already.&lt;/p&gt;

&lt;p&gt;I want a Kubeconfig switcher that simple (as in Unix philosophy) and can integrate easily with my existing ZSH and Prezto setup without getting in the way. Must also be able to integrate with other Kubernetes tools without many changes.&lt;/p&gt;

&lt;p&gt;Other existing solutions I have tried:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kubectx&lt;/code&gt; and &lt;code&gt;kubens&lt;/code&gt;: They are good, but I switch and use multiple contexts concurrently a lot. Changing the context in one terminal will change other terminals as well because they share the same kubeconfig file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubie&lt;/code&gt;: Took a lot of inspiration from this project. But somehow it's doing too much and messing with ZDOTDIR breaking my ZSH setup.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kube_ps1&lt;/code&gt;: I am still using this for showing current context, and it integrates well with &lt;code&gt;ksw&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, this project has also taught me several interesting things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://akhy.my.id/posts/automating-interactive-shell-input-in-go" rel="noopener noreferrer"&gt;Spawning and interacting with sub-processes in Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Understanding kubectl configurations. I dived into Kubernetes source code to get an idea of how it is working. And if you checked my code, it's reusing Kubectl's code as a dependency to mimic its config handling behavior.&lt;/li&gt;
&lt;li&gt;Automatic tests, build, and release using Goreleaser in GitHub. It was such a breeze I used Goreleaser in all of my Go projects.&lt;/li&gt;
&lt;li&gt;Managing and publishing my own Homebrew tap. It allows more people to try and use my tools quickly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have been using it on my own for several months. Using separate shells for different contexts seems a good fit for my &lt;a href="https://akhy.my.id/categories/workflow" rel="noopener noreferrer"&gt;workflow&lt;/a&gt;. I set the default context in the default kubeconfig (&lt;code&gt;~/.kube/config&lt;/code&gt;) to the non-production cluster I'm working on the most. Anytime I need to work on another cluster (especially the production one), I just need to run &lt;code&gt;ksw&lt;/code&gt; to start a new shell with that specific cluster. This conscious effort to switch the context has reduced the risk of doing something bad on the wrong cluster.&lt;/p&gt;

</description>
      <category>go</category>
      <category>kubectl</category>
      <category>cli</category>
    </item>
    <item>
      <title>Automating interactive shell input in Go</title>
      <dc:creator>Akhyar Amarullah</dc:creator>
      <pubDate>Mon, 06 Jul 2020 05:08:00 +0000</pubDate>
      <link>https://dev.to/akhy/automating-interactive-shell-input-in-go-30c1</link>
      <guid>https://dev.to/akhy/automating-interactive-shell-input-in-go-30c1</guid>
      <description>&lt;p&gt;Once every several weeks, I take a moment to review and optimize my daily workflow, identifying bottlenecks or repetitive tasks that can be automated. This time I’m trying to tackle one of most tedious task that I do a lot on daily basis: &lt;strong&gt;logging in to VPN using OTP sent via email&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Context-switching&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have several VPN profiles and need to switch back and forth between them at work. Opening email and hunting down OTP code for each VPN session really slow me down, especially in tight situation like when firefighting in an incident.&lt;/p&gt;

&lt;p&gt;I know automating 2FA sounds like defeating it’s own very purpose, but I still do it anyway for convenience &lt;del&gt;and educational purpose&lt;/del&gt;. Let’s see how I’m doing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analyze before automate
&lt;/h2&gt;

&lt;p&gt;Most of the times, automating shell input can be as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'y'&lt;/span&gt; | ./interactive-setup.sh

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or even better, keep sending &lt;code&gt;y&lt;/code&gt; to the process’ stdin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;yes&lt;/span&gt; | ./interactive-setup.sh

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, &lt;a href="https://en.wikipedia.org/wiki/Yes_(Unix)" rel="noopener noreferrer"&gt;“yes” is a standard unix command&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, why can’t we just do something like this? (note that oathtool is a CLI tool for generating time-based OTP)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oathtool &lt;span class="nt"&gt;--totp&lt;/span&gt; &lt;span class="nt"&gt;--base64&lt;/span&gt; OTPKEY | openfortivpn &lt;span class="nt"&gt;-c&lt;/span&gt; prod.cfg

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can’t be done because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I don’t have the key used to generate the OTP&lt;/li&gt;
&lt;li&gt;The new OTP generated EVERYTIME AFTER connection initiated&lt;/li&gt;
&lt;li&gt;The generated OTP are only sent via email, there is no other way&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s see how openfortivpn command get invoked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ sudo openfortivpn -c prod.cfg
INFO: Connected to gateway.
Two-factor authentication token:🔑

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server generates and sends the OTP via email right before prompting for it. Also because it gets regenerated for each connection attempt, wrong input will somehow “invalidate” the already sent OTP (i.e. after a typo and fail, you cannot simply reconnect and input the correct OTP from previous attempt).&lt;/p&gt;

&lt;h1&gt;
  
  
  My solution
&lt;/h1&gt;

&lt;p&gt;I have looked at Unix’s &lt;a href="https://core.tcl-lang.org/expect" rel="noopener noreferrer"&gt;Expect&lt;/a&gt; also its &lt;a href="https://github.com/google/goexpect" rel="noopener noreferrer"&gt;Go implementation by Google&lt;/a&gt;, but not interested in both. I decided to implement my own logic in Go for learning purpose.&lt;/p&gt;

&lt;p&gt;The program will execute openfortivpn and automatically input the OTP from my email (via IMAP). I already set the filter in my Gmail account so all OTP emails will be automatically moved to dedicated label/folder named “OTP”.&lt;/p&gt;

&lt;p&gt;Here’s the logic outline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Spawn openfortivpn client in the background (let’s call it “the process”)&lt;/li&gt;
&lt;li&gt;Forward process stdout to terminal while monitoring it for the input prompt (“Two-factor authentication token:” string)&lt;/li&gt;
&lt;li&gt;When the prompt detected, run a function to fetch email and extract the OTP&lt;/li&gt;
&lt;li&gt;Write the OTP to the process stdin, then send newline (like pressing Return)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It seems simple, but I learned quite a lot along the way. It taught me about how to interact with subprocess’s IO stream in Go, also how to properly use goroutines and channels (the hardest part of Go for me to understand).&lt;/p&gt;

&lt;p&gt;Here’s the simplified code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"bufio"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"io"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"os/exec"&lt;/span&gt;
    &lt;span class="s"&gt;"path/filepath"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;configFile&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"prod.cfg"&lt;/span&gt;
    &lt;span class="n"&gt;promptString&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"Two-factor authentication token:"&lt;/span&gt;

    &lt;span class="c"&gt;// Prepare command&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"openfortivpn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;configFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdoutPipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdinPipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;

    &lt;span class="c"&gt;// Start command&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Wait for OTP prompt&lt;/span&gt;
    &lt;span class="n"&gt;promptDetected&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;frags&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;frags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;promptString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;scanner&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bufio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewScanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bufio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ScanBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;buff&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;buff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;promptDetected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Getting OTP"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fetchOtpFromEmail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// delegate it to another function&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Send input to the prompt&lt;/span&gt;
    &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/chickenzord/empatpuluh" rel="noopener noreferrer"&gt;The complete code is on my GitHub project&lt;/a&gt;. It’s usable and configurable for your own use.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>automation</category>
    </item>
    <item>
      <title>Minimal Makefile to Run Java Projects</title>
      <dc:creator>Akhyar Amarullah</dc:creator>
      <pubDate>Wed, 18 Dec 2019 08:12:00 +0000</pubDate>
      <link>https://dev.to/akhy/minimal-makefile-to-run-java-projects-4a4i</link>
      <guid>https://dev.to/akhy/minimal-makefile-to-run-java-projects-4a4i</guid>
      <description>&lt;p&gt;Recently, a conversation with my SO reminded me about a piece of code I write long time ago. &lt;a href="https://github.com/akhy/hanoi" rel="noopener noreferrer"&gt;It was a college assignment on Data Structure course&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I immediately dug my email and found the project compressed in a RAR archive. The project was written using Java with &lt;a href="https://netbeans.org" rel="noopener noreferrer"&gt;NetBeans IDE&lt;/a&gt; default folder structure. There was no build configurtions like Maven, Ant, Makefile, or whatsoever. Only NetBeans project config and I don’t want to install it just for the sake of running the code.&lt;/p&gt;

&lt;p&gt;After Googled a bit, I came with a quick and simple Makefile to run the code. Luckily there was no external dependencies to deal with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;SRC&lt;/span&gt; &lt;span class="o"&gt;?=&lt;/span&gt; src
&lt;span class="nv"&gt;DST&lt;/span&gt; &lt;span class="o"&gt;?=&lt;/span&gt; build/classes
&lt;span class="nv"&gt;MAIN&lt;/span&gt; &lt;span class="o"&gt;?=&lt;/span&gt; Main

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;clean compile run&lt;/span&gt;

&lt;span class="nl"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;find &lt;span class="p"&gt;$(&lt;/span&gt;DST&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.class&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;compile&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;DST&lt;span class="p"&gt;)&lt;/span&gt;
    javac &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;DST&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;$$(&lt;/span&gt;find &lt;span class="p"&gt;$(&lt;/span&gt;SRC&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.java&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    java &lt;span class="nt"&gt;-cp&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;DST&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;MAIN&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>makefile</category>
      <category>java</category>
    </item>
  </channel>
</rss>
