<?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: Max Rohde</title>
    <description>The latest articles on DEV Community by Max Rohde (@mxro).</description>
    <link>https://dev.to/mxro</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%2F400110%2F8062d23b-5265-405c-a503-5f430ee757ba.png</url>
      <title>DEV Community: Max Rohde</title>
      <link>https://dev.to/mxro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mxro"/>
    <language>en</language>
    <item>
      <title>What's the Best Model to use with OpenCode</title>
      <dc:creator>Max Rohde</dc:creator>
      <pubDate>Thu, 12 Mar 2026 00:29:27 +0000</pubDate>
      <link>https://dev.to/mxro/whats-the-best-model-to-use-with-opencode-27i3</link>
      <guid>https://dev.to/mxro/whats-the-best-model-to-use-with-opencode-27i3</guid>
      <description>&lt;p&gt;I have been extensively using &lt;a href="https://opencode.ai/?ref=maxrohde.com" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt; over the past few months.&lt;/p&gt;

&lt;p&gt;Overall, it has been working very well for me – but one issue I struggled with is to determine what model to use.&lt;/p&gt;

&lt;p&gt;There are hundreds to choose from, and, interestingly, what seems to be straightforward choices often do not work.&lt;/p&gt;

&lt;p&gt;I especially struggled with getting Gemini to work. It produces very subpar code and would often get stuck.&lt;/p&gt;

&lt;p&gt;Models are evolving quickly, so it's not very useful for me to give you a specific model to use. Instead, I thought it's best I share the strategies I use to determine which model to use.&lt;/p&gt;

&lt;p&gt;I'll give them in the order I find most effective to least effective:&lt;/p&gt;

&lt;h2&gt;
  
  
  (1) OpenRouter Statistics
&lt;/h2&gt;

&lt;p&gt;A good indication if a model 'works well' for OpenCode is what other user's are using!&lt;/p&gt;

&lt;p&gt;OpenRouter keeps statistics on what models are used for various app, so that feels like an obvious starting point.&lt;/p&gt;

&lt;p&gt;For whatever reason, OpenCode is filtered out in many views in OpenRouter. However, OpenRouter still keeps statistics for OpenCode, they can be found here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://openrouter.ai/apps?url=https%3A%2F%2Fopencode.ai%2F&amp;amp;ref=maxrohde.com" rel="noopener noreferrer"&gt;https://openrouter.ai/apps?url=https%3A%2F%2Fopencode.ai%2F&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the snapshot as of today:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8e4v9n3xlcs9mykegfo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8e4v9n3xlcs9mykegfo.png" alt="Statistics for OpenCode on OpenRouter" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It seems like users are having success with the Anthropic models as well as some open source models, like Kimi K2.5 and MiniMax2.5.&lt;/p&gt;

&lt;p&gt;Here it is good to keep in mind that OpenCode makes certain models available for free – as of writing this, Kimi K.24 for instance, is free to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  (2) OpenCode Go Models
&lt;/h2&gt;

&lt;p&gt;OpenCode has an offering called &lt;a href="https://opencode.ai/docs/go?ref=maxrohde.com" rel="noopener noreferrer"&gt;OpenCode Go&lt;/a&gt;, that's a fixed price per-month subscription that includes usage of a certain set of models.&lt;/p&gt;

&lt;p&gt;Chances are, models included here will work well with OpenCode.&lt;/p&gt;

&lt;p&gt;As of writing this, the following models are included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GLM-5&lt;/li&gt;
&lt;li&gt;Kimi K2.5&lt;/li&gt;
&lt;li&gt;MiniMax M2.5&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  (3) OpenCode Documentation
&lt;/h2&gt;

&lt;p&gt;OpenCode includes a number of model recommendation in their &lt;a href="https://opencode.ai/docs/models/?ref=maxrohde.com#recommended-models" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F469m5qwzph6a5qmigc2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F469m5qwzph6a5qmigc2v.png" alt="Models recommended by OpenCode" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm putting this one last, since it included Gemini 3 Pro – which I never got to work correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  (4) Experiment!
&lt;/h2&gt;

&lt;p&gt;In any case, OpenCode makes it easy to switch between models. So when using an aggregator service like OpenRouter or OpenCode Zen, it makes sense to quickly try out different models.&lt;/p&gt;

&lt;p&gt;Just be mindful that some providers will use your data for training – so always best to check the model first before giving it a try.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>agents</category>
    </item>
    <item>
      <title>How to Avoid Route 53 $0.50 / Month Charge per Hosting Zone using Free CloudFront Distribution</title>
      <dc:creator>Max Rohde</dc:creator>
      <pubDate>Wed, 03 Dec 2025 01:42:11 +0000</pubDate>
      <link>https://dev.to/mxro/how-to-avoid-route-53-050-month-charge-per-hosting-zone-using-free-cloudfront-distribution-319k</link>
      <guid>https://dev.to/mxro/how-to-avoid-route-53-050-month-charge-per-hosting-zone-using-free-cloudfront-distribution-319k</guid>
      <description>&lt;p&gt;&lt;a href="https://aws.amazon.com/route53/?ref=maxrohde.com" rel="noopener noreferrer"&gt;AWS Route 53&lt;/a&gt; charges US$0.50 per hosted zone per month.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7zb06ulidsia9c9r21g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7zb06ulidsia9c9r21g.png" width="399" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even when we otherwise use AWS services sparingly - Route 53 charges a fixed amount&lt;/p&gt;

&lt;p&gt;This can quickly add up if you have multiple domains – especially when using different AWS accounts for dev, uat and prod; which each will need their own hosted zone.&lt;/p&gt;

&lt;p&gt;Thankfully AWS has recently release &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/flat-rate-pricing-plan.html?ref=maxrohde.com" rel="noopener noreferrer"&gt;flat-rate pricing for CloudFront distributions&lt;/a&gt;, and one of the included services for the free-tier plan is a Route 53 hosted zone 🥳&lt;/p&gt;

&lt;p&gt;Using CloudFront distributions, you can reduce our costs for hosted zones to &lt;strong&gt;$0.00&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftop9vpc8qe6wls1l371i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftop9vpc8qe6wls1l371i.png" width="587" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Much better when we can replace the $0.50 from Route 53 with our free CloudFront plan&lt;/p&gt;

&lt;p&gt;And, best of all, you don't need to change our application for this!&lt;/p&gt;

&lt;p&gt;You don't need to update your existing CloudFront distributions – you can simply create a new one and point it to any subdomain in the hosted zone.&lt;/p&gt;

&lt;p&gt;Here is how to do it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Head to the AWS Console and Open CloudFront / Distributions&lt;/li&gt;
&lt;li&gt;Click [Create Distribution]&lt;/li&gt;
&lt;li&gt;Select the Free plan and click [Next]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftpv0yb7n0zuo5eccyfne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftpv0yb7n0zuo5eccyfne.png" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose any name and description you like&lt;/li&gt;
&lt;li&gt;IMPORTANT: For Domain, provide a subdomain of the hosted zone domain in Route 53 you would like to offset the costs for.

&lt;ul&gt;
&lt;li&gt;For instance, if your domain is mydomain.com - you can choose offset.mydomain.com&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;When you did everything right, you will see the following confirmation:&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wiz9p0oic2srjleqy0f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wiz9p0oic2srjleqy0f.png" width="800" height="76"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For specify origin, choose Other&lt;/li&gt;
&lt;li&gt;Provide any domain / path you like. E.g. point back to your main domain.&lt;/li&gt;
&lt;li&gt;Enable rate limiting on the security page and go [Next]&lt;/li&gt;
&lt;li&gt;Create a new TLS certificate

&lt;ul&gt;
&lt;li&gt;IMPORTANT: if you have the choice to use an already existing one, I would recommend to not choose it and instead create a new one. This way, we are not interfering with any other setup made in the account.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Now you can create the distribution.&lt;/li&gt;

&lt;li&gt;Once the distribution is created, click [Manage plan] under billing.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5hfymkmub2fswlthmec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5hfymkmub2fswlthmec.png" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Look for the line 'Route 53 DNS: [your hosted zone domain]' - if that is there, you won't be charged the US$0.50 per month anymore for this hosted zone 🥳&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsc5vthvwjs7j1aocbcm2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsc5vthvwjs7j1aocbcm2.png" width="654" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's all we need to do. If you are already using CloudFront, you could of course also change any existing CloudFront distribution to use the flat-rate pricing.&lt;/p&gt;

&lt;p&gt;But good luck with that! You will likely find it is not possible out of the box – due to many features in CloudFront not being covered in the flat-rate pricing.&lt;/p&gt;

&lt;p&gt;And if you use any of these features, the option to select the free plan will be grayed out:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7ez5nps6c9m15qngzwn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7ez5nps6c9m15qngzwn.png" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Much easier to create a new distribution under a different sub-domain!&lt;/p&gt;

&lt;p&gt;If you are considering deploying a Next.js based project using CloudFront, please consider my free starter templates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://goldstack.party/templates/nextjs-tailwind?ref=maxrohde.com" rel="noopener noreferrer"&gt;Next.js + Tailwind&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://goldstack.party/templates/nextjs-bootstrap?ref=maxrohde.com" rel="noopener noreferrer"&gt;Next.js + Bootstrap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://goldstack.party/templates/server-side-rendering?ref=maxrohde.com" rel="noopener noreferrer"&gt;Server-Side Rendering&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I haven't updated them to use the flat-rate CloudFront distribution but will likely do so in the future - &lt;a href="https://github.com/goldstack/goldstack/issues?ref=maxrohde.com" rel="noopener noreferrer"&gt;please raise an issue&lt;/a&gt; if you are interested in this feature and this will surely motivate me to get to it quicker.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>dns</category>
      <category>devops</category>
      <category>cloudfront</category>
    </item>
    <item>
      <title>When your Next.js Site Reports: Title tag Missing</title>
      <dc:creator>Max Rohde</dc:creator>
      <pubDate>Tue, 02 Dec 2025 07:19:05 +0000</pubDate>
      <link>https://dev.to/mxro/when-your-nextjs-site-reports-title-tag-missing-1oo9</link>
      <guid>https://dev.to/mxro/when-your-nextjs-site-reports-title-tag-missing-1oo9</guid>
      <description>&lt;p&gt;I got an unexpected message from the Ahrefs website monitoring for one of my pages deployed using the &lt;a href="https://goldstack.party/templates/nextjs-tailwind?ref=maxrohde.com" rel="noopener noreferrer"&gt;Next.js + Tailwind&lt;/a&gt; template:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Title tag missing or empty: 4 URLs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I remember fixing all the tags a while ago, so I was quite puzzled.&lt;/p&gt;

&lt;p&gt;In dev, everything looked fine, and it took me a while to uncover what was going on.&lt;/p&gt;

&lt;p&gt;So I thought I share what I found here in case it helps someone facing the same issue.&lt;/p&gt;

&lt;p&gt;What happened in my case is that I added a variable to the &lt;code&gt;title&lt;/code&gt; tag declaration in the &lt;a href="https://nextjs.org/docs/pages/api-reference/components/head?ref=maxrohde.com" rel="noopener noreferrer"&gt;next/head&lt;/a&gt; component:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Previously:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;title&amp;gt;Frequently Asked Questions&amp;lt;/title&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Now:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;title&amp;gt;Frequently Asked Questions - {appConfig.brand.title}&amp;lt;/title&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This for some reasons resulted in Next.js, when doing a static export (&lt;code&gt;output: 'export'&lt;/code&gt;), leaving the title blank.&lt;/p&gt;

&lt;p&gt;The easiest way to see this is to go to your affected site and use the 'Show page source' option of your web browser.&lt;/p&gt;

&lt;p&gt;Thankfully, there is an easy fix.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fix by using template literals:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;title&amp;gt;{`Frequently Asked Questions - ${appConfig.brand.title}`}&amp;lt;/title&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this fix, the &lt;code&gt;title&lt;/code&gt; tag for my site again contained the correct value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0ytiiz2v2wdrtru7cn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0ytiiz2v2wdrtru7cn9.png" alt="Page source with fixed title tag" width="795" height="104"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not sure why this is how it is – but I'm sure there is a good reason 🤔&lt;/p&gt;

&lt;p&gt;And hoped this saved you some time!&lt;/p&gt;

&lt;p&gt;If so, why not stop by my totally free &lt;a href="https://goldstack.party/?ref=maxrohde.com" rel="noopener noreferrer"&gt;Starter Project Builder Goldstack&lt;/a&gt; and consider it for your next project using Next.js 🤗&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>seo</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Notion MCP vs Notion MCP Server</title>
      <dc:creator>Max Rohde</dc:creator>
      <pubDate>Sat, 23 Aug 2025 06:22:32 +0000</pubDate>
      <link>https://dev.to/mxro/notion-mcp-vs-notion-mcp-server-17i1</link>
      <guid>https://dev.to/mxro/notion-mcp-vs-notion-mcp-server-17i1</guid>
      <description>&lt;p&gt;The Model Context Protocol or MCP for short is the de facto standard for using external services in your AI chat sessions.&lt;/p&gt;

&lt;p&gt;Thankfully, Notion offers ready-made MCP solutions.&lt;/p&gt;

&lt;p&gt;However, there are two different offerings, which can become a bit confusing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://developers.notion.com/docs/mcp?ref=maxrohde.com" rel="noopener noreferrer"&gt;Notion MCP&lt;/a&gt;: Tools hosted by Notion&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/makenotion/notion-mcp-server?ref=maxrohde.com" rel="noopener noreferrer"&gt;Notion MCP Server&lt;/a&gt;: Official Notion repository with MCP server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notion added a note to the &lt;em&gt;Notion MCP repository&lt;/em&gt; directing users to the &lt;em&gt;Notion MCP&lt;/em&gt; server hosted by them.&lt;/p&gt;

&lt;p&gt;Usually, using a hosted version of a service is always the best idea - since that usually helps us to get going quickly and reduce maintenance headaches in the long run.&lt;/p&gt;

&lt;p&gt;Unfortunately, &lt;strong&gt;I found the Notion MCP service fundamentally lacking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Thus I would recommend you to put in the extra effort to use the open source server.&lt;/p&gt;

&lt;p&gt;My reasons:&lt;/p&gt;

&lt;h2&gt;
  
  
  Lacking search function in Notion MCP
&lt;/h2&gt;

&lt;p&gt;Notion MCP supports a 'search' tool, which allows to perform a full text search across your whole workspace.&lt;/p&gt;

&lt;p&gt;This can be useful at times, but I find AI the most useful when you give it razor-sharp instructions.&lt;/p&gt;

&lt;p&gt;In the context of Notion, this would mean a specific page, or a specific set of sub pages for a database.&lt;/p&gt;

&lt;p&gt;For instance, I had a few entries in a database that were missing a title. The instructions to my agent where:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a short title for all entries missing a title for the database &lt;a href="https://www.notion.so/xxx" rel="noopener noreferrer"&gt;https://www.notion.so/xxx&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Notion MCP server is &lt;a href="https://github.com/makenotion/notion-mcp-server/issues/87?ref=maxrohde.com" rel="noopener noreferrer"&gt;not able to do this&lt;/a&gt;, since it doesn't have the capability to 'get all pages of a database' or 'get all pages for a database that match a specific filter.'.&lt;/p&gt;

&lt;p&gt;Technically, you can provide a database ID as context, but since you are forced to provide a query text, it is not possible to retrieve all pages for a database using the &lt;code&gt;search&lt;/code&gt; tool.&lt;/p&gt;

&lt;p&gt;You would receive errors such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error executing MCP tool:
MCP error -32602: MCP error -32602: Invalid arguments for tool search: [
  {
    "code": "invalid_type",
    "expected": "string",
    "received": "undefined",
    "path": [
      "query"
    ],
    "message": "Required"
  }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error executing MCP tool:
MCP error -32602: MCP error -32602: Invalid arguments for tool search: [
  {
    "code": "too_small",
    "minimum": 1,
    "type": "string",
    "inclusive": true,
    "exact": false,
    "message": "String must contain at least 1 character(s)",
    "path": [
      "query"
    ]
  }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Notion MCP server, in contrast, handles this tasks without any problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Only Small Subset of Notion Features Available on Notion MCP
&lt;/h2&gt;

&lt;p&gt;Further to the above only supports a fraction of the Notion functionality.&lt;/p&gt;

&lt;p&gt;See the complete list here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.notion.com/docs/mcp-supported-tools?ref=maxrohde.com" rel="noopener noreferrer"&gt;Notion MCP – Supported tools&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whereas the API easily covers twice as much of Notions' functionality:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.notion.com/reference/intro?ref=maxrohde.com" rel="noopener noreferrer"&gt;Notion API Reference&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source Notion MCP Server Easy to Setup
&lt;/h2&gt;

&lt;p&gt;Using the setup instructions on the &lt;a href="https://github.com/makenotion/notion-mcp-server?tab=readme-ov-file&amp;amp;ref=maxrohde.com#installation" rel="noopener noreferrer"&gt;Notion MCP Server GitHub page&lt;/a&gt;, it only took me around two minutes to have the Notion MCP server up and running in my environment (Cline extension for VSCode).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp09mtg07tvv166586ivt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp09mtg07tvv166586ivt.png" alt="Image 8" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setup instructions for Notion MCP server&lt;/p&gt;

&lt;p&gt;It actually took me longer to get the Notion MCP service running due to some confusion on my part about the server type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Instability on Notion MCP
&lt;/h2&gt;

&lt;p&gt;Looking at the GitHub issues for Notion MCP Server, a lot of users seem to use that forum to make known their issues with the Notion MCP server:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/makenotion/notion-mcp-server/issues/107?ref=maxrohde.com" rel="noopener noreferrer"&gt;why has MCP not been fixed yet? It has been an ongoing issue for 3 weeks now!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/makenotion/notion-mcp-server/issues/106?ref=maxrohde.com" rel="noopener noreferrer"&gt;401 using mcp.notion.com/mcp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/makenotion/notion-mcp-server/issues/101?ref=maxrohde.com" rel="noopener noreferrer"&gt;MCP error -32000: Connection closed&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I think combining Notion with your favorite LLM chat tool is an extremely powerful proposition.&lt;/p&gt;

&lt;p&gt;I strongly recommend considering using the open source Notion MCP server for now (even though it may use more token).&lt;/p&gt;

&lt;p&gt;I hope that Notion will improve their hosted MCP server and will be very happy to update this article when this happens.&lt;/p&gt;

</description>
      <category>notion</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>Pinecone Price Increase 😱 Is Chroma Cloud the Best Alternative?</title>
      <dc:creator>Max Rohde</dc:creator>
      <pubDate>Sat, 09 Aug 2025 03:02:30 +0000</pubDate>
      <link>https://dev.to/mxro/pinecone-price-increase-is-chroma-cloud-the-best-alternative-111h</link>
      <guid>https://dev.to/mxro/pinecone-price-increase-is-chroma-cloud-the-best-alternative-111h</guid>
      <description>&lt;p&gt;I have been a happy user of the Vector database &lt;a href="https://www.pinecone.io/?ref=maxrohde.com" rel="noopener noreferrer"&gt;Pinecone&lt;/a&gt;, which provided me with a truly 'serverless' experience.&lt;/p&gt;

&lt;p&gt;I only needed to sign up, get an API key, and then pay for the usage of my applications.&lt;/p&gt;

&lt;p&gt;Unfortunately, Pinecone decided to change their pricing model as announced in their email 'Important pricing update: minimum usage fee' 🤯&lt;/p&gt;

&lt;p&gt;From 1st of September, all users - regardless of how extensive your actual use is - need to &lt;strong&gt;pay a minimum of US$ 50 per month&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhs27mz8jv03pi7n5enir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhs27mz8jv03pi7n5enir.png" alt="Extract from Pinecone email" width="800" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not ideal for me, since I generally managed to keep my bills under US$10 - by storing only the most essential data in the Vector DB (embeddings only and not the document content).&lt;/p&gt;

&lt;p&gt;Needless to say, I was not very pleased and decided to migrate away from Pinecone for the main application I use a Vector DB for: &lt;a href="https://auto-relation.com/?ref=maxrohde.com" rel="noopener noreferrer"&gt;AI Auto Relation for Notion&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As I assumed, I wasn't the only one facing this issue and quickly found the helpful Reddit thread: &lt;a href="https://www.reddit.com/r/vectordatabase/comments/1m2n50h/pinecones_new_50mo_minimum_just_nuked_my_hobby/?ref=maxrohde.com" rel="noopener noreferrer"&gt;Pinecone’s new $50/mo minimum just nuked my hobby project - what are my best self-hosted alternatives?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think the best alternatives from this conversation with everyone with less than US$50 per month of Pinecone costs are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Self hosted &lt;a href="https://github.com/pgvector/pgvector?ref=maxrohde.com" rel="noopener noreferrer"&gt;Postgres with pgvector&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Self hosted &lt;a href="https://github.com/chroma-core/chroma?ref=maxrohde.com" rel="noopener noreferrer"&gt;Chroma&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://fly.io/docs/mpg/?ref=maxrohde.com" rel="noopener noreferrer"&gt;Fly.io 'managed' Postgres&lt;/a&gt; (with pgvector)&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.trychroma.com/cloud/getting-started?ref=maxrohde.com" rel="noopener noreferrer"&gt;Chroma Cloud&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was looking for a truly serverless solution; where I don't have the overhead of managing another server and my costs scale up linearly based on my usage - starting at $0.&lt;/p&gt;

&lt;p&gt;As far as I could establish in my research Chroma Cloud is the only solution that provides this.&lt;/p&gt;

&lt;p&gt;So I went ahead and changed the vector database for my Notion integration to Chroma Cloud.&lt;/p&gt;

&lt;p&gt;Here everything I found out that could be of help for anyone interested in doing the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  I wasn't too impressed by the documentation. It all seemed a bit 'alpha' to me. However, there is of course the documentation of the Chroma open source project to complement this - but this I found to be not that great either. For example, I was looking for some information about the error codes the TypeScript SDK would return - so I could handle these - but wasn't able to find any information about this.&lt;/li&gt;
&lt;li&gt;  The management interface seems to be more simplistic in comparison to Pinecone. However, it is able to help with the basics - e.g. see items in collection, do some basic searching and see some statistics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvpxw1sjgvdr4p5jfk6a3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvpxw1sjgvdr4p5jfk6a3.png" alt="Chroma Cloud Management Interface" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  I understand Chroma does &lt;a href="https://github.com/chroma-core/chroma/issues/1394?ref=maxrohde.com" rel="noopener noreferrer"&gt;not scale as well as Pinecone&lt;/a&gt;. Thus, it could be advisable to split one '&lt;a href="https://docs.pinecone.io/guides/index-data/create-an-index?ref=maxrohde.com" rel="noopener noreferrer"&gt;Index&lt;/a&gt;' in Pinecone into multiple Chroma '&lt;a href="https://docs.trychroma.com/docs/collections/manage-collections?ref=maxrohde.com" rel="noopener noreferrer"&gt;Collections&lt;/a&gt;'. I realised this only too late in my implementation. So I will hold my breath as to how well Chroma will perform for my solution (which only deals with a few thousand documents).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I just finished testing the new implementation today. As I learn more, I will update this post.&lt;/p&gt;

&lt;p&gt;Otherwise, would be interested if anyone has found a better alternative.&lt;/p&gt;

</description>
      <category>vectordatabase</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Migrating Maven Namespace to Central Portal</title>
      <dc:creator>Max Rohde</dc:creator>
      <pubDate>Thu, 08 May 2025 22:28:09 +0000</pubDate>
      <link>https://dev.to/mxro/migrating-maven-namespace-to-central-portal-fgp</link>
      <guid>https://dev.to/mxro/migrating-maven-namespace-to-central-portal-fgp</guid>
      <description>&lt;p&gt;If you publish your Java artifacts to Maven Central, you may receive an email with the following content:&lt;/p&gt;




&lt;p&gt;Greetings OSSRH Publisher,&lt;/p&gt;

&lt;p&gt;As you may have heard, OSSRH is reaching end of life on June 30, 2025. OSSRH users need to migrate their namespaces to the Central Portal as soon as possible.&lt;/p&gt;

&lt;p&gt;Instructions for self migration are located here: &lt;a href="https://central.sonatype.org/faq/what-is-different-between-central-portal-and-legacy-ossrh/#self-service-migration" rel="noopener noreferrer"&gt;https://central.sonatype.org/faq/what-is-different-between-central-portal-and-legacy-ossrh/#self-service-migration&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make the transition smoother we will be automatically migrating publishers that have not used &lt;a href="http://oss.sonatype.org/?ref=maxrohde.com" rel="noopener noreferrer"&gt;&lt;strong&gt;oss.sonatype.org&lt;/strong&gt;&lt;/a&gt; or &lt;a href="http://s01.sonatype.org/?ref=maxrohde.com" rel="noopener noreferrer"&gt;&lt;strong&gt;s01.sonatype.org&lt;/strong&gt;&lt;/a&gt; to publish artifacts in some time starting with the oldest and working our way forward. To avoid disruption to your publishing processes we strongly encourage migrating before the June 30, 2025 deadline.&lt;/p&gt;

&lt;p&gt;Thank you for your assistance,&lt;/p&gt;

&lt;p&gt;The Central Team&lt;/p&gt;




&lt;p&gt;This requires us to do a few things!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Migrate Namespace to Central Portal&lt;/li&gt;
&lt;li&gt; Updating Maven Plugin&lt;/li&gt;
&lt;li&gt; Updating GitHub Action&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Migrate Namespace to Central Portal
&lt;/h2&gt;

&lt;p&gt;This assumes your namespace has not been automatically migrated yet. If it has, just skip to the next section!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Go to the &lt;a href="https://central.sonatype.com/publishing/namespaces?ref=maxrohde.com" rel="noopener noreferrer"&gt;Namespaces&lt;/a&gt; page and click [Migrate Namespace] and then [Migrate]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k2a5ecof4vuvxcf9obc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k2a5ecof4vuvxcf9obc.png" alt="Image 3" width="800" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  You should see the following configuration:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffky1f1ll8unavw1d5tva.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffky1f1ll8unavw1d5tva.png" alt="Image 4" width="654" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  And it will look as follows after the migration has been completed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyygyxqmfb9060h7g0qi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyygyxqmfb9060h7g0qi.png" alt="Image 5" width="632" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating Maven Plugin
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  We need to also update the plugin to publish to Maven Central&lt;/li&gt;
&lt;li&gt;  Look for a configuration as follows in your pom.xml:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.sonatype.plugins&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;nexus-staging-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.6.13&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;extensions&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/extensions&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;serverId&amp;gt;&lt;/span&gt;ossrh&lt;span class="nt"&gt;&amp;lt;/serverId&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;nexusUrl&amp;gt;&lt;/span&gt;https://oss.sonatype.org/&lt;span class="nt"&gt;&amp;lt;/nexusUrl&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;autoReleaseAfterClose&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/autoReleaseAfterClose&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  And replace it with the new central-publishing-maven-plugin as per instructions from &lt;a href="https://central.sonatype.org/publish/publish-portal-maven/?ref=maxrohde.com" rel="noopener noreferrer"&gt;https://central.sonatype.org/publish/publish-portal-maven/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.sonatype.central&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;central-publishing-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.7.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;extensions&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/extensions&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;publishingServerId&amp;gt;&lt;/span&gt;central&lt;span class="nt"&gt;&amp;lt;/publishingServerId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;autoPublish&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/autoPublish&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;waitUntil&amp;gt;&lt;/span&gt;published&lt;span class="nt"&gt;&amp;lt;/waitUntil&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  Note we set the following to ensure this works in CI
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;autoPublish&amp;gt;true&amp;lt;/autoPublish&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  And we also set to ensure our build fails if there is anything wrong. This will make the build take longer but we can ensure the package is actually published before the build completes!
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;waitUntil&amp;gt;published&amp;lt;/waitUntil&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Updating GitHub Action
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  We also need to update our GitHub actions configuration if we publish using it.&lt;/li&gt;
&lt;li&gt;  Go to &lt;a href="https://central.sonatype.com/account?ref=maxrohde.com" rel="noopener noreferrer"&gt;https://central.sonatype.com/account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Click [Generate User Token]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8blouz0dbbddjgienh33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8blouz0dbbddjgienh33.png" alt="Image 6" width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Click [Ok]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4slwcipak7u3ktx0x5b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4slwcipak7u3ktx0x5b.png" alt="Image 7" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  You should get a username and a password&lt;/li&gt;
&lt;li&gt;  We need to store them as GitHub secrets. For this go to the Settings of your repository and add them as new repository secrets:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fga8dhcy7j0ex2y8umqrm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fga8dhcy7j0ex2y8umqrm.png" alt="Image 8" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Create the repository secrets MAVEN_USERNAME and MAVEN_PASSWORD&lt;/li&gt;
&lt;li&gt;  It should look as follows:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fajtj52hi6lhpa48q96i5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fajtj52hi6lhpa48q96i5.png" alt="Image 9" width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  We use the &lt;a href="https://github.com/actions/setup-java?ref=maxrohde.com" rel="noopener noreferrer"&gt;setup-java&lt;/a&gt; action to ensure settings.xml is created correctly:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish package to the Maven Central Repository&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;created&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;install-secret-key&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install gpg secret key&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cat &amp;lt;(echo -e "${{ secrets.OSSRH_GPG_SECRET_KEY }}") | gpg --batch --import&lt;/span&gt;
          &lt;span class="s"&gt;gpg --list-secret-keys --keyid-format LONG&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Maven Central Repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20'&lt;/span&gt;
          &lt;span class="na"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;adopt'&lt;/span&gt;
          &lt;span class="na"&gt;server-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;central&lt;/span&gt;
          &lt;span class="na"&gt;server-username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MAVEN_USERNAME&lt;/span&gt;
          &lt;span class="na"&gt;server-password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MAVEN_PASSWORD&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish package&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mvn --batch-mode -Dgpg.passphrase= deploy -DskipTests&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;MAVEN_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MAVEN_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;MAVEN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MAVEN_SECRET }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  Now we should be able to run the GitHub action. Console output should look similar to:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[INFO] --- central-publishing:0.7.0:publish (injected-central-publishing) @ delight-nashorn-sandbox ---
[INFO] Using Central baseUrl: https://central.sonatype.com
[INFO] Using credentials from server id central in settings.xml
[INFO] Using Usertoken auth, with namecode: ***
[INFO] Staging 7 files
[INFO] Staging /home/runner/work/delight-nashorn-sandbox/delight-nashorn-sandbox/target/delight-nashorn-sandbox-0.5.2.jar
[INFO] Installing /home/runner/work/delight-nashorn-sandbox/delight-nashorn-sandbox/target/delight-nashorn-sandbox-0.5.2.jar to /home/runner/work/delight-nashorn-sandbox/delight-nashorn-sandbox/target/central-staging/org/javadelight/delight-nashorn-sandbox/0.5.2/delight-nashorn-sandbox-0.5.2.jar
...
[INFO] Pre Bundling - deleted /home/runner/work/delight-nashorn-sandbox/delight-nashorn-sandbox/target/central-staging/org/javadelight/delight-nashorn-sandbox/maven-metadata-central-staging.xml
[INFO] Generate checksums for dir: org/javadelight/delight-nashorn-sandbox/0.5.2
[INFO] Going to create /home/runner/work/delight-nashorn-sandbox/delight-nashorn-sandbox/target/central-publishing/central-bundle.zip by bundling content at /home/runner/work/delight-nashorn-sandbox/delight-nashorn-sandbox/target/central-staging
[INFO] Created bundle successfully /home/runner/work/delight-nashorn-sandbox/delight-nashorn-sandbox/target/central-staging/central-bundle.zip
[INFO] Going to upload /home/runner/work/delight-nashorn-sandbox/delight-nashorn-sandbox/target/central-publishing/central-bundle.zip
[INFO] Uploaded bundle successfully, deployment name: Deployment, deploymentId: xxx-0c5d-4912-b802-392b1377670c. Deployment will publish automatically
[INFO] Waiting until Deployment xxx-0c5d-4912-b802-392b1377670c is published
[INFO] Deployment xxx-0c5d-4912-b802-392b1377670c was successfully published
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  You can also check the status of your deployment by going to &lt;a href="https://central.sonatype.com/publishing/deployments?ref=maxrohde.com" rel="noopener noreferrer"&gt;https://central.sonatype.com/publishing/deployments&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29b77p0xxt3w2lmi5qta.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29b77p0xxt3w2lmi5qta.png" alt="Image 10" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
    </item>
    <item>
      <title>Voice Memos with ChatGPT: My First "GPT"</title>
      <dc:creator>Max Rohde</dc:creator>
      <pubDate>Sat, 06 Apr 2024 02:20:13 +0000</pubDate>
      <link>https://dev.to/mxro/voice-memos-with-chatgpt-my-first-gpt-30j7</link>
      <guid>https://dev.to/mxro/voice-memos-with-chatgpt-my-first-gpt-30j7</guid>
      <description>&lt;p&gt;I've always been fascinated by how Large Language Models (LLMs) can make our interactions with technology feel more natural. These models are not just about turning speech into text. They're skilled at understanding what we mean, even when how we say it isn't clearly expressed. LLMs are great at finding the important points in our messy spoken words.&lt;/p&gt;

&lt;p&gt;My interest grew when ChatGPT introduced a voice conversation feature. However, I quickly realised that talking to ChatGPT could be awkward. ChatGPT is optimised for producing text we read rather than text we listen to. We read faster than we listen. So, long answers from ChatGPT felt slow because it took so long for it to read them out in their entirety.&lt;/p&gt;

&lt;p&gt;Thinking about this, I wanted to explore a way to use the voice conversation feature in a new and helpful way: note taking.&lt;/p&gt;

&lt;p&gt;My idea was to use ChatGPT to record voice memos. This uses the transcription abilities of ChatGPT in a useful way. For this, I developed a custom "GPT" to make recording voice memos as smooth and effective as possible.&lt;/p&gt;

&lt;p&gt;Check out the GPT here: &lt;a href="https://chat.openai.com/g/g-9okaXHlhe-voice-memo" rel="noopener noreferrer"&gt;Voice Memo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbrztwmgojo1aul0lvss3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbrztwmgojo1aul0lvss3.png" alt=" " width="437" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the remainder of this article, I explain how this GPT works and provide my impressions of working with GPTs in generatl.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are GPTs or Custom GPTs?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://openai.com/blog/introducing-gpts" rel="noopener noreferrer"&gt;Custom GPTs&lt;/a&gt; are versions of ChatGPT that you can customize for specific tasks or areas of interest. This doesn't require any coding, making it accessible to everyone interested in AI.&lt;/p&gt;

&lt;p&gt;For those who know how to code, there's more you can do. You can add code to connect these GPTs to outside data or services. This means GPTs can do almost anything. They can increase productivity, help with learning, or provide new entertainment options.&lt;/p&gt;

&lt;p&gt;But, there's a catch. To create or use custom GPTs, you need a ChatGPT Plus subscription. This costs US$20 a month. Both creators and users of custom GPTs need this subscription to access them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Voice Memo GPT
&lt;/h2&gt;

&lt;p&gt;I want to explore a new way to use ChatGPT; to use it to quickly capture thoughts, similar to many voice memo apps available on the app stores.&lt;/p&gt;

&lt;p&gt;Imagine talking to ChatGPT and expressing your ideas. Then, instead of giving you a long response as it would usually do, it simply says "noted." That's the main idea.&lt;/p&gt;

&lt;p&gt;With a special prompt I'll share below, ChatGPT acts as a recorder for your thoughts. It creates a transcript you can later reference or share with other applications such as Notion.&lt;/p&gt;

&lt;p&gt;Here's how it works: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a new conversation with the &lt;a href="https://chat.openai.com/g/g-9okaXHlhe-voice-memo" rel="noopener noreferrer"&gt;Voice Memo&lt;/a&gt; GPT&lt;/li&gt;
&lt;li&gt;hit the headphones button near the chat box, and speak.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgheow1w8q8uigf0rtvny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgheow1w8q8uigf0rtvny.png" alt=" " width="800" height="1066"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ChatGPT will capture your words and confirm with short responses. This way, you get a transcript of your thoughts without AI adding its own twist.&lt;/p&gt;

&lt;p&gt;The GPT not only records thoughts but also answers questions when explicitly asked. So, if you're brainstorming and have a query, ChatGPT will respond with its suggestions. Otherwise, it'll just document your thoughts.&lt;/p&gt;

&lt;p&gt;I think this could be quite useful for note-taking and brainstorming. &lt;/p&gt;

&lt;p&gt;Below is the prompt to make ChatGPT your personal voice memo tool. You can create your own GPT for it or simply start your conversation with it when using the free version of ChatGPT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
You just listen to ideas presented by the user.

- If given a specific question from the user, answer as succinctly as possible.
- If the users input ends on an uncompleted sentence, prompt 'And?'
- Otherwise, respond with 'Noted.'
- If you are running on the web version and not the mobile version of GPT. Your first reply should include. 'Note that voice conversations are only supported on mobile devices.'

DO NOT SUMMARIZE, DO NOT COMMENT, unless specifically prompted to do so.

You are allowed to answer a user's query on your prompt above. If the user asks you how you function, respond with the information from the prompt.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Creating a custom GPT is incredibly easy. It can be done in just a few minutes. But for more complex uses, the challenges quickly added up.&lt;/p&gt;

&lt;p&gt;Making conversations with GPT smooth was harder than expected. The system would often interrupt me mid-sentence, which broke the flow of my thoughts. Especially with voice memos, these interruptions felt awkward and made the experience less enjoyable.&lt;/p&gt;

&lt;p&gt;Despite these issues, I think there is a lot of potential in using ChatGPT to capture and refine our thoughts, as opposed to the usual usecase of answering questions. I'm looking forward to any feedback on anyone who is happy to try out the GPT or the prompt.&lt;/p&gt;

&lt;p&gt;I've shared the full prompt for this project for those interested in experimenting or creating something new.&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>ai</category>
      <category>productivity</category>
      <category>voice</category>
    </item>
    <item>
      <title>Death by Cloud or How to Build Brittle Systems</title>
      <dc:creator>Max Rohde</dc:creator>
      <pubDate>Fri, 01 Mar 2024 20:46:41 +0000</pubDate>
      <link>https://dev.to/mxro/death-by-cloud-or-how-to-build-brittle-systems-4hjf</link>
      <guid>https://dev.to/mxro/death-by-cloud-or-how-to-build-brittle-systems-4hjf</guid>
      <description>&lt;p&gt;The allure of serverless computing and cloud-based solutions was once irresistible to me. The promise of speedier system development and simpler maintenance seemed like a dream come true. I eagerly advocated for these technologies, writing numerous articles on the subject. Yet, as time passed, my enthusiasm waned as I came across a number of challenges which lead me to believe that cloud computing and serverless architectures are not the cure all they were touted to be.&lt;/p&gt;

&lt;p&gt;In this article, I will first summarise some learnings we have gained over the past years of using Serverless and then discuss the surprisingly fleeting nature of serverless solutions using an example. Lastly, I propose a number of considerations for building more sustainable systems.&lt;/p&gt;

&lt;h1&gt;
  
  
  Some Learnings from a Decade of Serverless
&lt;/h1&gt;

&lt;p&gt;Serverless has been extensively discussed in the past years. Here some of the key learnings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure Complexity&lt;/strong&gt;: Serverless computing emerged as a simplified approach to application development, but the infrastructure it requires can be unexpectedly complex. Tasks like setting up an API gateway with Lambdas can result in &lt;a href="https://github.com/goldstack/goldstack/tree/fc0fbd4c356d7efb3ccc06f775ca5f12500f3052/workspaces/templates/packages/serverless-api/infra/aws" rel="noopener noreferrer"&gt;extensive configuration&lt;/a&gt;, increasing brittleness and the learning curve for developers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Costs&lt;/strong&gt;: Cost-wise, serverless is often marketed as economical but can lead to &lt;a href="https://einaregilsson.com/serverless-15-percent-slower-and-eight-times-more-expensive/" rel="noopener noreferrer"&gt;higher expenses&lt;/a&gt; due to &lt;a href="https://dev.to/colinchartier/serverless-is-more-expensive-than-you-d-expect-30o1"&gt;unpredictable pricing models&lt;/a&gt;. These costs can outweigh savings from reduced physical hardware needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backwards Compatibility&lt;/strong&gt;: Service evolution poses another challenge; cloud providers frequently deprecate services or versions, forcing developers to rewrite applications for compatibility. This disrupts operations and shifts focus from innovation to maintenance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDK Dependency Hell&lt;/strong&gt;: Integration with cloud services has also become more complicated. The vast number of features leads to bloated SDKs and '&lt;a href="https://www.browserlondon.com/blog/2020/09/02/dependency-hell-how-to-avoid-it/" rel="noopener noreferrer"&gt;dependency hell&lt;/a&gt;', where managing libraries and their security updates consumes significant time and resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Security&lt;/strong&gt;: Despite these hurdles, serverless computing does offer enhanced security through better isolation between components. This architecture limits the impact of breaches, allowing for more secure, compartmentalised service management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In summary, while serverless technology provides certain advantages such as improved security, it also introduces complexities in infrastructure setup, cost management, service compatibility, integration processes, and dependency maintenance that organizations must navigate carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fleeting Nature of Cloud-Based Systems: An Example
&lt;/h2&gt;

&lt;p&gt;Among the various concerns discussed, one stands out as particularly troubling to me: the transient nature of cloud-based systems. These systems are not built to endure; they are fleeting. This impermanence is concerning because it contradicts one of the purported benefits of serverless computing—that it requires less maintenance due to the service provider managing many infrastructure layers. While this is still somewhat true, other factors have emerged that complicate ongoing operations.&lt;/p&gt;

&lt;p&gt;Consider a broad comparison between a modern cloud-based solution and embedded software running on a manufacturing machine. Some machines operate on software that is decades old and is still functional. In contrast, a serverless system seems to demand significant overhauls every few years just to remain operational.&lt;/p&gt;

&lt;p&gt;Let me illustrate this with an example from my personal experience, for my project &lt;a href="https://github.com/goldstack/goldstack" rel="noopener noreferrer"&gt;Goldstack&lt;/a&gt;. Last year, around December, I received a notification from AWS informing me that I could no longer update my Lambda functions because they were implemented using Node 16—a runtime AWS was phasing out (also due to &lt;a href="https://nodejs.org/en/blog/announcements/nodejs16-eol" rel="noopener noreferrer"&gt;Node 16 end of life&lt;/a&gt;). Initially, updating my application's code and build logic to Node 18 seemed straightforward—it involved &lt;a href="https://github.com/goldstack/goldstack/pull/345" rel="noopener noreferrer"&gt;modifying several Lambdas and ironing out some code incompatibilities over a few days&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;## [Action Required] AWS Lambda end of support for Node.js 16&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;However, I soon discovered that the new Node 18 Lambda runtime &lt;a href="https://aws.amazon.com/blogs/compute/node-js-18-x-runtime-now-available-in-aws-lambda/" rel="noopener noreferrer"&gt;no longer included the AWS SDK by default&lt;/a&gt;—a sensible decision perhaps, but one with significant implications for my project. The deployment size of my Lambdas quadrupled from around one megabyte to four megabytes because I had designed my solution assuming access to the provided AWS SDK in the runtime environment.&lt;/p&gt;

&lt;p&gt;To address this issue, I had to upgrade to &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/" rel="noopener noreferrer"&gt;v3 of the AWS SDK&lt;/a&gt;—a move aimed at reducing Lambda sizes but one which also led to increased cold start times. This upgrade was far from trivial; it necessitated rewriting every piece of code interacting with AWS services in some way due to the different programming model introduced by the SDK v3. The effort spanned &lt;a href="https://github.com/goldstack/goldstack/pull/351" rel="noopener noreferrer"&gt;weeks&lt;/a&gt;—time carved out from weekends here and there—resulting in a solution with identical functionality as before but now compatible with AWS's new requirements.&lt;/p&gt;

&lt;p&gt;This experience underscores how serverless cloud-based systems can become burdensome rather than liberating for developers. The need for constant updates simply to keep systems operational raises questions about sustainability and long-term viability in an environment where change is relentless and often forced upon us by service providers' decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Quest for Sustainable Systems
&lt;/h2&gt;

&lt;p&gt;It's always easier to highlight flaws than to craft solutions, particularly in our complex tech industry. I must admit that I don't have a silver bullet to offer as an alternative to cloud computing and serverless architectures. We've experienced the limitations of monolithic systems, and they too fell short of our expectations. It seems neither serverless nor on premise monolith is the definitive answer; hopefully the solution we seek is still out there, waiting to be discovered.&lt;/p&gt;

&lt;p&gt;In our pursuit of more sustainable systems, it's crucial to challenge ourselves to keep this questions in mind: how do we build technology that endures? One interesting observation comes from looking at the lifespan of &lt;a href="https://www.researchgate.net/publication/315061648_The_drivers_of_firm_longevity_Age_size_profitability_and_survivorship_of_Australian_corporations_1901-1930" rel="noopener noreferrer"&gt;companies&lt;/a&gt; and technologies. Counterintuitively, it's often the older, seemingly outdated technologies that stick around the longest. For example, mainframe computers, first introduced in the 1950s, are still in use today, running critical processes for banks and airlines.&lt;/p&gt;

&lt;p&gt;This phenomenon suggests that chasing after the latest trends may not be our best strategy if we aim for longevity. The newest tech on the block might be more prone to becoming obsolete quickly compared to its older counterparts that have stood the test of time.&lt;/p&gt;

&lt;p&gt;As an industry fixated on innovation, we often rush towards using services in their alpha or beta stages—enticed by cloud providers' promises of cutting-edge capabilities. However, this eagerness can lead us down a path where our creations are transient by nature—destined for obsolescence before they've had a chance to mature.&lt;/p&gt;

&lt;p&gt;The solution remains elusive but acknowledging these patterns is a step towards finding it. We need to balance innovation with sustainability and consider how new technologies will fit into long-term strategies. While we may not have all the answers now, I remain hopeful that as an industry, we will eventually find a way to build systems that are not only useful but also built to last.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>cloud</category>
      <category>architecture</category>
      <category>aws</category>
    </item>
    <item>
      <title>Mock S3 for AWS SDK for JavaScript (v3)</title>
      <dc:creator>Max Rohde</dc:creator>
      <pubDate>Fri, 19 Jan 2024 20:16:30 +0000</pubDate>
      <link>https://dev.to/mxro/mock-s3-for-aws-sdk-for-javascript-v3-160e</link>
      <guid>https://dev.to/mxro/mock-s3-for-aws-sdk-for-javascript-v3-160e</guid>
      <description>&lt;p&gt;It has been a few years now since AWS &lt;a href="https://aws.amazon.com/about-aws/whats-new/2020/12/aws-sdk-javascript-version-3-generally-available/" rel="noopener noreferrer"&gt;upgraded their official JavaScript SDK to version 3&lt;/a&gt;. This new version is not backwards compatible with v2 of the SDK. So any code relying on the AWS SDK would have to be changed quite extensively.&lt;/p&gt;

&lt;p&gt;This is why I had been procrastinating to update the SDK version for my &lt;a href="https://goldstack.party/" rel="noopener noreferrer"&gt;Goldstack Starter Project Builder&lt;/a&gt;. However, AWS's recent &lt;a href="https://awstip.com/action-required-aws-lambda-end-of-support-for-node-js-16-0576051e7cb4" rel="noopener noreferrer"&gt;end of support for Node.js v16 Lambda runtimes&lt;/a&gt; has forced my hand. Since Node.js v18+ runtimes no longer include the v2 &lt;code&gt;aws-sdk&lt;/code&gt; package and this, in turn, has significantly increased the size of the Lambda's I have to deploy (from ~1 MB to almost 6 MB).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuxp5obzsxo089h4wbd3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuxp5obzsxo089h4wbd3w.png" alt="Function properties on AWS" width="800" height="107"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I love writing meaningful end-to-end test scenarios in unit tests and ideally limit the use of hand-crafted mocks as much as possible. For instance, I prefer to use in memory databases in tests over mocking individual function calls for database operations.&lt;/p&gt;

&lt;p&gt;I did the same for S3 when implementing Goldstack by using a local mock S3 service. Specifically, I used the npm package &lt;a href="https://www.npmjs.com/package/mock-aws-s3" rel="noopener noreferrer"&gt;&lt;code&gt;mock-aws-s3&lt;/code&gt;&lt;/a&gt;. This package provides an S3 instance that stores objects in local files.&lt;/p&gt;

&lt;p&gt;Thus, when passing in an instance of S3 created with &lt;code&gt;mock-aws-s3&lt;/code&gt;, complex local tests could be performed, including the writing and reading of objects, who would be persisted for the duration of the test. The same package could also be used for running a local development environment.&lt;/p&gt;

&lt;p&gt;I have packaged all this up in my &lt;a href="https://goldstack.party/templates/s3" rel="noopener noreferrer"&gt;S3 Template&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j8fubc3srtgsrz35pqt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j8fubc3srtgsrz35pqt.png" alt="Goldstack S3 template" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, the &lt;code&gt;mock-aws-s3&lt;/code&gt; package &lt;a href="https://github.com/MathieuLoutre/mock-aws-s3/issues/85#issuecomment-1879251191" rel="noopener noreferrer"&gt;only supports the AWS SDK v2&lt;/a&gt;. Thus, in order to upgrade to v3 of the SDK, I needed an alternative S3 mock, but could not find any that supports in-memory or file-based persistence.&lt;/p&gt;

&lt;p&gt;In response, I developed a thin wrapper around the &lt;code&gt;mock-aws-s3&lt;/code&gt; package that exposes the AWS SDK v3 using &lt;a href="https://www.npmjs.com/package/aws-sdk-mock" rel="noopener noreferrer"&gt;aws-sdk-mock&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/mock-aws-s3-v3" rel="noopener noreferrer"&gt;&lt;code&gt;mock-aws-s3-v3&lt;/code&gt;&lt;/a&gt;:  Package to provide a file-based mock for AWS S3 for local testing.&lt;/p&gt;

&lt;p&gt;This mock can easily be used in unit tests and for local servers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createS3Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mock-aws-s3-v3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GetObjectCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/client-s3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createS3Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./local-folder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// of type S3Client&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PutObjectCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-bucket&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello&lt;/span&gt;&lt;span class="dl"&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;This will create the folder &lt;code&gt;./local-folder/test-bucket&lt;/code&gt; and place a &lt;code&gt;test-key&lt;/code&gt; file within it. The contents of the file can then be easily retrieved using a &lt;code&gt;GetObjectCommand&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetObjectCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-bucket&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-key&lt;/span&gt;&lt;span class="dl"&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;The supported operations are the same as &lt;code&gt;mock-aws-s3&lt;/code&gt; minus &lt;code&gt;getSignedUrl&lt;/code&gt; - which is not part of the S3 client package anymore in V3 of the SDK. Here a list of supported operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CreateBucketCommand&lt;/li&gt;
&lt;li&gt;DeleteBucketCommand&lt;/li&gt;
&lt;li&gt;ListObjectsCommand&lt;/li&gt;
&lt;li&gt;ListObjectsV2Command&lt;/li&gt;
&lt;li&gt;DeleteObjectsCommand&lt;/li&gt;
&lt;li&gt;DeleteObjectCommand&lt;/li&gt;
&lt;li&gt;GetObjectCommand&lt;/li&gt;
&lt;li&gt;HeadObjectCommand&lt;/li&gt;
&lt;li&gt;PutObjectCommand&lt;/li&gt;
&lt;li&gt;CopyObjectCommand&lt;/li&gt;
&lt;li&gt;GetObjectTaggingCommand&lt;/li&gt;
&lt;li&gt;PutObjectTaggingCommand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This package is used for dozens of tests in the &lt;a href="https://github.com/search?q=repo%3Agoldstack%2Fgoldstack+%27mock-aws-s3-v3%27&amp;amp;type=code" rel="noopener noreferrer"&gt;Goldstack monorepo&lt;/a&gt; but I would still consider it an early alpha version. If you encounter any issues, please check out the &lt;a href="https://github.com/goldstack/goldstack/tree/master/workspaces/utils/packages/mock-aws-s3-v3" rel="noopener noreferrer"&gt;source code&lt;/a&gt;, and be welcome to open an &lt;a href="https://github.com/goldstack/goldstack/issues" rel="noopener noreferrer"&gt;issue&lt;/a&gt; if there is anything you require help with.&lt;/p&gt;

</description>
      <category>s3</category>
      <category>aws</category>
      <category>javascript</category>
      <category>mock</category>
    </item>
    <item>
      <title>Upgrading to Yarn 4</title>
      <dc:creator>Max Rohde</dc:creator>
      <pubDate>Sat, 30 Dec 2023 00:31:07 +0000</pubDate>
      <link>https://dev.to/mxro/upgrading-to-yarn-4-1dm2</link>
      <guid>https://dev.to/mxro/upgrading-to-yarn-4-1dm2</guid>
      <description>&lt;p&gt;I maintain a set of templates and a starter project builder for fullstack projects: &lt;a href="https://goldstack.party/" rel="noopener noreferrer"&gt;Goldstack&lt;/a&gt;. This project makes heavy use of Yarn 3 features, specifically for workspace management.&lt;/p&gt;

&lt;p&gt;I realised that a new version of Yarn, &lt;a href="https://yarnpkg.com/blog/release/4.0" rel="noopener noreferrer"&gt;Yarn 4&lt;/a&gt; was released recently, so I wanted to explore if I can easily update this project from Yarn 3 to Yarn 4.&lt;/p&gt;

&lt;p&gt;This project contains more than 70 workspaces (including sub-workspaces) and uses a number of frameworks and libraries, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;AWS SDK&lt;/li&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;ESLint&lt;/li&gt;
&lt;li&gt;and many more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I think it can be a good testing ground for upgrading to Yarn 4.&lt;/p&gt;

&lt;p&gt;I &lt;a href="https://github.com/goldstack/goldstack/pull/349" rel="noopener noreferrer"&gt;completed the upgrade&lt;/a&gt; and released this in version &lt;a href="https://github.com/goldstack/goldstack/releases/tag/v0.5.0" rel="noopener noreferrer"&gt;0.5.0&lt;/a&gt; of Goldstack.&lt;/p&gt;

&lt;p&gt;Overall it took me around 3 hours to perform the upgrade. Here an overview of what I needed to do:&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating Yarn
&lt;/h2&gt;

&lt;p&gt;First, Yarn needed to be updated to version 4 using the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn set version stable
yarn install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that this may upgrade to a later version than Yarn 4 if you run this at a future point in time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrading Node version
&lt;/h2&gt;

&lt;p&gt;Yarn 4 is not compatible with earlier versions of Node. Specifically Node 16 is no longer supported.&lt;/p&gt;

&lt;p&gt;I needed to ensure that all GitHub actions provide an environment that runs in Node 18:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;18'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fixing CLI commands
&lt;/h2&gt;

&lt;p&gt;There was an incompatible change included in Yarn 4 with respect to the &lt;a href="https://yarnpkg.com/cli/workspaces/foreach" rel="noopener noreferrer"&gt;foreach&lt;/a&gt; command. Previously it was possible to run a command as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn foreach run clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, now Yarn requires to add a qualifier. Specifically one of the following:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-A,--all&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run the command on all workspaces of a project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-R,--recursive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run the command on the current workspace and all of its recursive dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-W,--worktree&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run the command on all workspaces of the current worktree&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I provided the &lt;code&gt;-W&lt;/code&gt; or &lt;code&gt;--worktree&lt;/code&gt; option for all occurrences where I previously used &lt;code&gt;foreach&lt;/code&gt;. I usually just wanted to run a command for all packages for a workspace - but not including recursive dependencies or all packages in the whole project (what &lt;code&gt;-A&lt;/code&gt; will do).&lt;/p&gt;

&lt;p&gt;A worktree is defined in the Yarn documentation as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[...] a &lt;em&gt;worktree&lt;/em&gt; is the name given to workspaces that list their own child workspaces.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I would say overall it was a fairly easy process to upgrade to Yarn 4. Next from here I would like to explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Yarn now supports having its binary managed through &lt;a href="https://nodejs.org/dist/latest/docs/api/corepack.html" rel="noopener noreferrer"&gt;corepack&lt;/a&gt;. My template currently still uses &lt;a href="https://github.com/goldstack/goldstack/blob/c02bc944b27aaed725db01139e3122e78442b158/.yarnrc.yml#L27" rel="noopener noreferrer"&gt;&lt;code&gt;yarnPath&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Yarn supports a hybrid mode that allows managing package dependencies using Yarn Plug'n'play and the 'node linker' (that requires the &lt;code&gt;node_modules&lt;/code&gt;) folder alongside each other. I want to explore whether this can be used to create a &lt;a href="https://github.com/goldstack/goldstack/issues/299" rel="noopener noreferrer"&gt;mobile template based on Expo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>yarn</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why You Should Become and Engineering Leader (or Not)</title>
      <dc:creator>Max Rohde</dc:creator>
      <pubDate>Sat, 28 Oct 2023 22:01:45 +0000</pubDate>
      <link>https://dev.to/mxro/why-you-should-become-and-engineering-leader-or-not-2mf4</link>
      <guid>https://dev.to/mxro/why-you-should-become-and-engineering-leader-or-not-2mf4</guid>
      <description>&lt;p&gt;As a software engineer, you're likely content with your role. Coding can be both enjoyable and rewarding. However, many engineers ascend to leadership roles only to discover that it's not for them and end up in a &lt;a href="https://www.infoq.com/presentations/hands-on-coding-managers/" rel="noopener noreferrer"&gt;career dead end&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Often, the better you are at coding, the more you love it, the more likely it is that you won't enjoy being a leader.&lt;/p&gt;

&lt;p&gt;Being a good and effective leader, especially in this day and age, is incredibly difficult. Thus I recommend that you carefully weigh your reasons why you want to become a leader.&lt;/p&gt;

&lt;p&gt;Don't become a leader for the wrong reasons, or you set yourself up for unnecessary strive and unhappiness.&lt;/p&gt;

&lt;p&gt;In the following, I list some 'doubtful' reasons that should make us pause before continuing with aspiring to become a leader, and 'wholesome' reasons that indicate we could be on a more fruitful path.&lt;/p&gt;

&lt;p&gt;Everyone is different, and everyone values different things. So don't take my categorisation of reasons as absolute. Reasons listed as 'doubtful' here may work perfectly for you and if that's the case, don't let me stop you!&lt;/p&gt;

&lt;h2&gt;
  
  
  Doubtful Reasons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  💪 I Want Power
&lt;/h3&gt;

&lt;p&gt;One of the most alluring qualities of become a leader is that we assume we will get more power. Power is something we naturally strive for, &lt;a href="https://phys.org/news/2018-01-psychologist-jordan-peterson-lobsters-human.html" rel="noopener noreferrer"&gt;even though we are not as close to lobsters as some people think we are&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But don't be fooled. A good leader will get to enjoy very little power. As a leader, your job will not be to tell people what to do and what not to do. Instead your job is to gently influence those you lead so that their work is organised in a way that is to their and the organisation's benefit.&lt;/p&gt;

&lt;p&gt;You do your job best not when you are bossing around everyone all day or be the recognised mastermind behind a team's success. You do it best when your contributions are barely recognisable.&lt;/p&gt;

&lt;h3&gt;
  
  
  💰 I Want more Money
&lt;/h3&gt;

&lt;p&gt;It is true that salary in leadership positions is often higher than that for individual contributor positions. However, this is changing in our industry. Individual contributors, also known as ICs, are now able to hold more senior, and better renumerated, positions.&lt;/p&gt;

&lt;p&gt;In any case, is it worth to exchange a job that you like for one that you hate for an increase in salary? As long as you don't need the extra money for your or your loved ones' survival, I suggest to put this last in your considerations of whether to become a leader.&lt;/p&gt;

&lt;h3&gt;
  
  
  👩‍💻 I Want to Ensure We Don't Write Bad Code
&lt;/h3&gt;

&lt;p&gt;If you love coding and care about coding, you would have encountered situations in which you disagree with the technical direction of the team: things are not 'done right'.&lt;/p&gt;

&lt;p&gt;A leadership position may seem tempting because it would give you the ability to shape the technical directions and standards better. This is true to some degree. However, there are plenty of other things you have to worry about as a leader.&lt;/p&gt;

&lt;p&gt;You will probably end up being more disconnected from the code than when you were working as an individual contributor in the team. Being a leader is not about micro-managing what your team does and policing their every line of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wholesome Reasons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  💬 Someone Told Me I Could be a Good Leader
&lt;/h3&gt;

&lt;p&gt;If your manager or others let you know that they think you could be a good leader, it is usually a good indication that you could be.&lt;/p&gt;

&lt;p&gt;The best leaders are chosen by the people they lead.&lt;/p&gt;

&lt;p&gt;If you find yourself to be elected for leadership tasks such as planning a project, organising something for the team, or being in charge of technical decisions, this is also a good indication that leadership could be a good avenue for you to pursue.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌱 I Want to Help People Grow
&lt;/h3&gt;

&lt;p&gt;If you listen carefully to leaders, you will hear them complain a lot about their work. That is fair enough; leadership is hard.&lt;/p&gt;

&lt;p&gt;However, if you ask leaders what they find most rewarding in their jobs, it is often that they were able to help others grow or help them overcome their problems.&lt;/p&gt;

&lt;p&gt;If growing and helping others is a key value of yours, the most enjoyable parts of leadership will be even more enjoyable for you. &lt;/p&gt;

&lt;h3&gt;
  
  
  🍸 I Want to be Lazy
&lt;/h3&gt;

&lt;p&gt;Laziness is generally not a sure path to success, but there is a certain flavour of laziness that is an &lt;a href="https://boulter.com/blog/2007/02/10/5-dysfunctional-attributes-of-good-engineers/" rel="noopener noreferrer"&gt;trait of successful engineers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Take testing for instance; engineers are too lazy to do the same tests over and over again, thus they create test automation.&lt;/p&gt;

&lt;p&gt;This laziness is one of the traits that can make you both a good engineer and a good leader; since a good leader needs to delegate - which is a very sophisticated variation of automation.&lt;/p&gt;

&lt;p&gt;But don't get your hopes up; just as an engineer's dream of reaching their nirvana of automation-enabled idleness is never realised, a leader's work never runs out and will keep you busy.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌟 I Want to Make an Impact
&lt;/h3&gt;

&lt;p&gt;If you care about making an impact in the world, you will be able to make a bigger impact if you become a leader.&lt;/p&gt;

&lt;p&gt;Our world is shaped by people, and we can change the world by influencing people. Influencing people to achieve particular outcomes lies at the heart of leadership.&lt;/p&gt;

&lt;p&gt;Being a leader is difficult but you can make a difference by being a leader. The technology industry lacks good leaders and poor quality leadership is an &lt;a href="https://www.techrepublic.com/article/why-engineers-leave-your-company-the-7-most-cited-reasons/" rel="noopener noreferrer"&gt;often cited reason engineers do not enjoy their jobs&lt;/a&gt;. You can do your part to change that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Your Decision
&lt;/h2&gt;

&lt;p&gt;Think carefully about why you want to become a leader. Leadership isn't easy; having robust motivations for taking on this challenge will be key to your success.&lt;/p&gt;

&lt;p&gt;Your motivations and values will determine whether you'll make a good leader. Don't become a leader if you are just seeking power. You won't be a good leader for your people, and it will be difficult for you to be successful and happy.&lt;/p&gt;

&lt;p&gt;However, everyone is different and you may have your unique reasons that work for you. Key is not whether the reasons listed here apply to you or don't. Key is that you spend some time thinking of what motivates you and if it will align with what will be required of you as a leader.&lt;/p&gt;

</description>
      <category>career</category>
      <category>leadership</category>
      <category>engineering</category>
      <category>management</category>
    </item>
    <item>
      <title>Control Windows with Your Voice and the Magic of ChatGPT</title>
      <dc:creator>Max Rohde</dc:creator>
      <pubDate>Fri, 29 Sep 2023 00:35:16 +0000</pubDate>
      <link>https://dev.to/mxro/control-windows-with-your-voice-and-the-magic-of-chatgpt-22cj</link>
      <guid>https://dev.to/mxro/control-windows-with-your-voice-and-the-magic-of-chatgpt-22cj</guid>
      <description>&lt;p&gt;Typing is the most common way we interact with our computers, but we can talk much faster. Even with practised fingers, we barely type 40 words per minute, yet &lt;a href="https://en.wikipedia.org/wiki/Words_per_minute" rel="noopener noreferrer"&gt;we effortlessly speak at around 150 words per minute - triple the speed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, using our voice to instruct our computers had two major hurdles. Firstly, computers used to be quite awful at transcribing our voices into text. Take Dragon NaturalSpeaking 2006, for instance. &lt;a href="https://www.cnet.com/reviews/dragon-naturallyspeaking-review/" rel="noopener noreferrer"&gt;It merely decoded our speech with roughly 80% accuracy&lt;/a&gt;. I personally found it more time-consuming correcting these minor errors than simply typing the content.&lt;/p&gt;

&lt;p&gt;Secondly, the precision required by computers clashed with our naturally imprecise spoken language. &lt;/p&gt;

&lt;p&gt;Fortunately, these issues have somewhat disappeared as of late last year, thanks to technological advances. The remarkable &lt;a href="https://openai.com/research/whisper/" rel="noopener noreferrer"&gt;OpenAI Whisper model&lt;/a&gt; excels at voice-to-text transcription - it even has a grip on my German accent! Meanwhile, &lt;a href="http://openai.com/product/gpt-4" rel="noopener noreferrer"&gt;OpenAI's GPT-4 model&lt;/a&gt; manages to comprehend our natural language usage, translating it into computer-understandable format. It has shown its merit, for instance, through its remarkable code-writing capability.&lt;/p&gt;

&lt;p&gt;With these innovations - OpenAI Whisper and GPT - I wondered if I now  can control my computer - specifically my personal Windows computer with my voice. &lt;/p&gt;

&lt;p&gt;In the rest of this article, I'll explain how I combined the OpenAI API, &lt;a href="https://www.autohotkey.com/" rel="noopener noreferrer"&gt;AutoHotKey&lt;/a&gt; and a simple Go program, to control Windows with my voice. &lt;/p&gt;

&lt;p&gt;The read-to-use tool and all source code are on &lt;a href="https://github.com/mxro/autohotkey-chatgpt-voice#autohotkey-chatgpt" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  tldr;
&lt;/h2&gt;

&lt;p&gt;If you want to see what I developed in action, please just have a look at the YouTube video below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=k-MRkMN-AMk" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub3swr9hnytlqhv8s883.png" alt="Link to youtube video" width="568" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My tool offers the versatility to trigger numerous actions including, but not confined to, the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launching a programme with the command 'open...'&lt;/li&gt;
&lt;li&gt;Hunting for information online with the instruction 'search for...'&lt;/li&gt;
&lt;li&gt;Accessing Wikipedia via 'Open Wikipedia page on...'&lt;/li&gt;
&lt;li&gt;Engaging with ChatGPT using 'tell me...'&lt;/li&gt;
&lt;li&gt;Scouting for images with the command 'search for images of...'&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How can I use it?
&lt;/h2&gt;

&lt;p&gt;Simply download the latest release from: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mxro/autohotkey-chatgpt-voice/releases" rel="noopener noreferrer"&gt;https://github.com/mxro/autohotkey-chatgpt-voice/releases&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instructions for installation can be found &lt;a href="https://github.com/mxro/autohotkey-chatgpt-voice#install" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it all works?
&lt;/h2&gt;

&lt;p&gt;The following diagram shows the different steps this solution executes: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv219zbkhw8hriln2p358.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv219zbkhw8hriln2p358.png" alt="Overview of solutions" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are three crucial technologies at the heart of this solution. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.autohotkey.com/" rel="noopener noreferrer"&gt;AutoHotKey&lt;/a&gt; is an open-source program useful for creating Windows automations.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stsaz.github.io/fmedia/" rel="noopener noreferrer"&gt;fmedia&lt;/a&gt;, a tool to record and manipulate sounds.&lt;/li&gt;
&lt;li&gt;The 'whisper-autohotkey.exe', a bespoke application I created in Go for communication with the OpenAI API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how they work together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step One&lt;/strong&gt;: Launch an AutoHotKey script and allow it to idly monitor for F8 key strokes. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step Two&lt;/strong&gt;: Command Fmedia to begin recording sound when the F8 key is pressed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step Three&lt;/strong&gt;: Instruct Fmedia to cease recording at the next F8 key press.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step Four&lt;/strong&gt;: Initiate the custom Go application, 'Whisper-AutoHotKey.exe'. Use it to dispatch the audio file recorded by Fmedia to OpenAI's Whisper API, thus getting it transcribed to text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step Five&lt;/strong&gt;: Convey this transcribed text into the OpenAI Chat GPT API, generating an AutoHotKey script in the process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step Six&lt;/strong&gt;: Run the AutoHotKey executable and input the fresh script, leading to the execution of the required action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Further explanations detailing the functionality of each component and step are provided in the subsequent section.&lt;/p&gt;

&lt;h2&gt;
  
  
  AutoHotKey Watch Script
&lt;/h2&gt;

&lt;p&gt;The watch script defines an action that is triggered whenever the user presses the F8 key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;F8::
  NotRecording := !NotRecording
  If NotRecording
  {
    Run %A_WorkingDir%\bin\fmedia-1.31-windows-x64\fmedia\fmedia.exe --record --overwrite --mpeg-quality=16 --rate=12000 --out=rec.mp3 --globcmd=listen,, Hide
  }
  Else
  {  
    Run %A_WorkingDir%\bin\fmedia-1.31-windows-x64\fmedia\fmedia.exe --globcmd=stop,, Hide
    Sleep, 100
    Run %A_WorkingDir%\bin\whisper-autohotkey\whisper-autohotkey.exe,, Hide
  }
  return
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We first check whether we are currently recording or not. If we are not recording, we use &lt;em&gt;fmedia&lt;/em&gt; to start the recording the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run %A_WorkingDir%\bin\fmedia-1.31-windows-x64\fmedia\fmedia.exe --record --overwrite --mpeg-quality=16 --rate=12000 --out=rec.mp3 --globcmd=listen,, Hide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice here specifically the &lt;code&gt;globalcmd=listen&lt;/code&gt; directive. This allows us to stop this recording through another invocation of &lt;code&gt;fmedia.exe&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Also note the specific quality and sampling rate provided here - I fine-tuned this using a &lt;a href="https://maxrohde.com/2023/09/17/optimise-openai-whisper-api-sampling-rate-quality" rel="noopener noreferrer"&gt;few experiments&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;When F8 is pressed and we are currently recording, we use &lt;code&gt;fmedia.exe&lt;/code&gt; to send the command to stop recording:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run %A_WorkingDir%\bin\fmedia-1.31-windows-x64\fmedia\fmedia.exe --globcmd=stop,, Hide 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then give our operating system a little break to write out the recorded file and then run the custom executable &lt;code&gt;whisper-autohotkey.exe&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sleep, 100
    Run %A_WorkingDir%\bin\whisper-autohotkey\whisper-autohotkey.exe,, Hide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next steps all happen in &lt;code&gt;whisper-autohotkey.exe&lt;/code&gt; - a custom Go application I developed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transcribe
&lt;/h2&gt;

&lt;p&gt;Within our Go application, we read out the recorded MP3 file and send it to the &lt;a href="https://platform.openai.com/docs/models/whisper" rel="noopener noreferrer"&gt;OpenAI Whisper API&lt;/a&gt;, see &lt;a href="https://github.com/mxro/autohotkey-chatgpt-voice/blob/master/whisper-autohotkey/cmd/whisper-autohotkey/whisper.go#L14" rel="noopener noreferrer"&gt;&lt;code&gt;whisper.go&lt;/code&gt;&lt;/a&gt;:&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;func&lt;/span&gt; &lt;span class="n"&gt;Transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputFileName&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;config&lt;/span&gt; &lt;span class="n"&gt;Config&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="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenapiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AudioRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Whisper1&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="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;FilePath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inputFileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&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;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateTranscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&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="s"&gt;""&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&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;We are using the &lt;a href="https://github.com/sashabaranov/go-openai" rel="noopener noreferrer"&gt;OpenAI API Go SDK&lt;/a&gt; here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate Script
&lt;/h2&gt;

&lt;p&gt;Once we have received the transcription, we are calling the OpenAI API chat completion endpoint to generate our AutoHotKey script, see &lt;a href="https://github.com/mxro/autohotkey-chatgpt-voice/blob/master/whisper-autohotkey/cmd/whisper-autohotkey/gpt4.go#L25" rel="noopener noreferrer"&gt;&lt;code&gt;gpt4.go&lt;/code&gt;&lt;/a&gt;:&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;func&lt;/span&gt; &lt;span class="n"&gt;BuildCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="kt"&gt;string&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="kt"&gt;error&lt;/span&gt;&lt;span class="p"&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;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSpace&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;==&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="s"&gt;"MsgBox, 32,, No input detected! Is your microphone working correctly?"&lt;/span&gt;&lt;span class="p"&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;systemContext&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;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./prompt.txt"&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="s"&gt;""&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="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenapiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&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;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateChatCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatCompletionRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GPT4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c"&gt;// https://github.com/sashabaranov/go-openai#why-dont-we-get-the-same-answer-when-specifying-a-temperature-field-of-0-and-asking-the-same-question&lt;/span&gt;
            &lt;span class="n"&gt;Temperature&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SmallestNonzeroFloat32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatCompletionMessage&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatMessageRoleSystem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&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;systemContext&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;Role&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatMessageRoleUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"ACTION: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;prompt&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="p"&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;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="s"&gt;""&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;,&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;Note, we provide most of the guidance for the GPT model here in the system context, which is provided by the following custom prompt (&lt;a href="https://github.com/mxro/autohotkey-chatgpt-voice/blob/master/prompt.txt#L1" rel="noopener noreferrer"&gt;&lt;code&gt;prompt.txt&lt;/code&gt;&lt;/a&gt;):&lt;/p&gt;




&lt;p&gt;You are a Windows automation engineer that is very familiar with AutoHotKey. &lt;br&gt;
You create AutoHotKey V1 scripts. I ask you to conduct a certain ACTION. &lt;br&gt;
You then write a script to perform this action. &lt;/p&gt;

&lt;p&gt;Unless otherwise specified, assume:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the default browser is Firefox&lt;/li&gt;
&lt;li&gt;the default search engine is DuckDuckGo&lt;/li&gt;
&lt;li&gt;if looking for pictures, open the pexels website&lt;/li&gt;
&lt;li&gt;when I ask you to 'tell me X', output a script that shows a GUI window using MsgBox that provides the answer to X.&lt;/li&gt;
&lt;li&gt;if no specific action is specified, assume a web search for the prompt needs to be conducted&lt;/li&gt;
&lt;li&gt;Your answer must ALWAYS ONLY be a correct AutoHotKey Script, nothing else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid all logical and syntactical errors. To help you avoid making errors, ALWAYS keep in mind ALL of the following rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The action should be executed when the AHK script is run, not define a keyboard shortcut to trigger the action. &lt;/li&gt;
&lt;li&gt;You only respond with the script, don't include any comments, keep it as short as possible but ensure there are no syntax errors in the script and it is a correct AutoHotKey V1 script. &lt;/li&gt;
&lt;li&gt;Tray tips are provided as follows 'TrayTip , Title, Text, Timeout, Options'.&lt;/li&gt;
&lt;li&gt;When constructing URLs, ensure to escape the escape sequence for space (%20) as '`%20'. &lt;/li&gt;
&lt;li&gt;Apply all AutoHotKey Escape sequences as required.&lt;/li&gt;
&lt;li&gt;Replace all '%' characters in URLs with the correct escape sequence '`%'. E.g. '%20' with '&lt;code&gt;%20&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;NEVER provide any other output than the script. Always complete the action with a 'return'. &lt;/li&gt;
&lt;li&gt;If you are not sure what action needs to be taken or how to create a script to perform the action,
create a script with the following content:
&amp;gt; MsgBox, 32,,[Your comment]
Replace [Your comment] with your comment. Also include the prompt as you have received it in the comment.&lt;/li&gt;
&lt;li&gt;If I ask you to Paste something, use the SendInput, {Raw} function.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now I will provide the ACTION. Please remember, NEVER respond with ANYTHING ELSE but a valid AutoHotKeyScript.&lt;/p&gt;



&lt;p&gt;Note, you can easily customise this prompt after downloading this tool by editing the &lt;code&gt;prompt.txt&lt;/code&gt; file in the tool folder. For instance, you may want to change the default browser to something else such as Chrome.&lt;/p&gt;
&lt;h2&gt;
  
  
  Execute Script
&lt;/h2&gt;

&lt;p&gt;After we have generated the script, we then execute it using AutoHotKey, see &lt;a href="https://github.com/mxro/autohotkey-chatgpt-voice/blob/b248a42c566b5784d6e74878854e5f921d644269/whisper-autohotkey/cmd/whisper-autohotkey/ahk.go#L8" rel="noopener noreferrer"&gt;&lt;code&gt;ahk.go&lt;/code&gt;&lt;/a&gt;:&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;func&lt;/span&gt; &lt;span class="n"&gt;RunCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="kt"&gt;string&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="kt"&gt;error&lt;/span&gt;&lt;span class="p"&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;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="s"&gt;"script.ahk"&lt;/span&gt;&lt;span class="p"&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="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0666&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="s"&gt;""&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="n"&gt;autoHotKeyPath&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AutoHotKeyExec&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;autoHotKeyPath&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="n"&gt;autoHotKeyPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;bin&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;autohotkey-1.1.37.01&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;AutoHotkeyU64.exe"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;data&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;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="n"&gt;autoHotKeyPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"script.ahk"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&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="s"&gt;""&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="n"&gt;output&lt;/span&gt; &lt;span class="o"&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;data&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;output&lt;/span&gt;&lt;span class="p"&gt;,&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;The generated script will, for example, look like follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run, firefox.exe "https://duckduckgo.com/?q=Whisper`%20OpenAI`%20API`%20Go`%20SDK"
return
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Learnings and Limitations
&lt;/h2&gt;

&lt;p&gt;The following are the learning and remaining limitations of the developed tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The OpenAI Whisper API can occasionally seem temperamental, primarily due to the data uploading process and the subsequent processing duration. &lt;/li&gt;
&lt;li&gt;There's a significant variance in latency, with the API requests sometimes taking up to ten times longer than the average duration. This is common with my basic, consumer-grade account.&lt;/li&gt;
&lt;li&gt;GPT-4 fails to excel at writing AutoHotKey scripts, usually falling into basic logic or syntax traps. I tried to bypass some of these frequent blunders by giving the model extra guidance within the initial prompt. However, it struggled with character escaping - a common challenge within AutoHotKey. For example, the character '%' had to be escaped as '`%'.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It's safe to infer that Cortana Voice's capacity to control windows wasn't a hit, given its &lt;a href="https://support.microsoft.com/en-gb/topic/end-of-support-for-cortana-in-windows-and-teams-d025b39f-ee5b-4836-a954-0ab646ee1efa" rel="noopener noreferrer"&gt;discontinuation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Conversely, the tool detailed in this post has been quite useful to me. It's particularly useful for triggering web searches, bundling multiple steps into a straightforward voice command. This feature absolves me from typing out search queries.&lt;/p&gt;

&lt;p&gt;In terms of disadvantages, the major one I've noticed in my personal use is the Whisper API's sluggishness. A noticeable delay ensues between issuing a command and waiting for it to be transcribed, passed to GPT, and finally executing the script. However, I was able to reduce latency by about 50% using some &lt;a href="https://maxrohde.com/2023/09/17/optimise-openai-whisper-api-sampling-rate-quality" rel="noopener noreferrer"&gt;fine-tuning of the audio encoding settings used&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Further, it's worth mentioning that GPT-4 isn't always successful in concocting accurate Autohotkey scripts. My experience suggests approximately a 90% success rate. However, I'm optimistic that further refining the prompt can heighten the accuracy rate.&lt;/p&gt;

&lt;p&gt;This tool been published as an &lt;a href="https://github.com/mxro/autohotkey-chatgpt-voice" rel="noopener noreferrer"&gt;open source project&lt;/a&gt;. I encourage you to contribute your observations or thoughts either by visiting the project or leaving a comment on this post.&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>programming</category>
      <category>go</category>
      <category>openai</category>
    </item>
  </channel>
</rss>
