<?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: Benjamin Kincses</title>
    <description>The latest articles on DEV Community by Benjamin Kincses (@benjaminkincses).</description>
    <link>https://dev.to/benjaminkincses</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4147531%2F21104414-b4e3-4881-8027-64fc494eb013.png</url>
      <title>DEV Community: Benjamin Kincses</title>
      <link>https://dev.to/benjaminkincses</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/benjaminkincses"/>
    <language>en</language>
    <item>
      <title>That .apkm file will not install from your file manager</title>
      <dc:creator>Benjamin Kincses</dc:creator>
      <pubDate>Sun, 08 Nov 2026 11:55:55 +0000</pubDate>
      <link>https://dev.to/benjaminkincses/that-apkm-file-will-not-install-from-your-file-manager-3khi</link>
      <guid>https://dev.to/benjaminkincses/that-apkm-file-will-not-install-from-your-file-manager-3khi</guid>
      <description>&lt;p&gt;Downloaded a beta update today. Tapped the file in the file manager. Nothing happened. No error, no install screen. Nothing.&lt;/p&gt;

&lt;p&gt;The file was fine. The format was the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  An .apkm is not an APK
&lt;/h2&gt;

&lt;p&gt;It is a zip. Inside is not one app but a set of them: a &lt;code&gt;base.apk&lt;/code&gt;, plus config splits. One split per CPU architecture, one per screen density, one per language, and sometimes a pile of feature modules on top.&lt;/p&gt;

&lt;p&gt;The stock Android installer opens a single APK. It does not open bundles. Third-party installer apps exist for this format, but you do not need one. You need adb and two minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Look inside first
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unzip &lt;span class="nt"&gt;-l&lt;/span&gt; app.apkm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real numbers from the two bundles I installed today: one was 201 MB with over 30 splits inside. The other was 340 MB with 126 files, 85 of them feature splits. My phone needed 4 files out of the first bundle and 72 out of the second. Everything else in there is for other phones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the signature before you install anything
&lt;/h2&gt;

&lt;p&gt;A split bundle from a mirror is still a sideload. Two checks, both cheap.&lt;/p&gt;

&lt;p&gt;First, the version. If the bundle's versionCode is lower than the installed app's, Android refuses the downgrade and the install dies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys package com.google.android.youtube | &lt;span class="nb"&gt;grep &lt;/span&gt;versionCode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, the signing certificate. An update only installs over the existing app if the same key signed it. If the cert differs, one of two things is true: the file is a modded build, or it is not the app you think it is. Either way, you want to know before, not after.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apksigner verify &lt;span class="nt"&gt;--print-certs&lt;/span&gt; base.apk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pull the installed app's base.apk and compare the SHA-256 fingerprint. Today both bundles matched the installed apps exactly. Official builds, just shipped as bundles. That is the normal case for beta mirrors, but check anyway. It costs ten seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install only the splits your phone needs
&lt;/h2&gt;

&lt;p&gt;Pick by three things: architecture (arm64-v8a on most current phones), density (mine is 320 dpi, so xhdpi), language (en).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb install-multiple &lt;span class="nt"&gt;-r&lt;/span&gt; base.apk split_config.arm64_v8a.apk split_config.xhdpi.apk split_config.en.apk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the bundle has feature splits, install the ones for your architecture and language too. If a required split is missing, the installer fails and names it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify like you mean it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys package com.google.android.youtube | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"versionName|versionCode"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open the app. New version in dumpsys plus the app actually running is proof. "Install said Success" on its own is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.apkm&lt;/code&gt;, &lt;code&gt;.apks&lt;/code&gt;, &lt;code&gt;.xapk&lt;/code&gt;: bundles, not apps. The file manager tap failing is expected behavior, not a broken file.&lt;/li&gt;
&lt;li&gt;The bundle is a zip. Look inside before you install anything from it.&lt;/li&gt;
&lt;li&gt;Match the signing cert to the installed app. Mismatch means stop.&lt;/li&gt;
&lt;li&gt;Install base plus your architecture, density, and language splits with &lt;code&gt;install-multiple&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Verify the version after, and launch the app.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>security</category>
      <category>tutorial</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Blind RSA in circl ignored the reader you passed it</title>
      <dc:creator>Benjamin Kincses</dc:creator>
      <pubDate>Thu, 05 Nov 2026 10:46:30 +0000</pubDate>
      <link>https://dev.to/benjaminkincses/blind-rsa-in-circl-ignored-the-reader-you-passed-it-1645</link>
      <guid>https://dev.to/benjaminkincses/blind-rsa-in-circl-ignored-the-reader-you-passed-it-1645</guid>
      <description>&lt;p&gt;Cloudflare's circl has a partially blind RSA signer. Its Blind method takes an io.Reader. The blinding factor used that reader. The salt did not. It read from crypto/rand directly.&lt;/p&gt;

&lt;p&gt;That breaks the contract. Two calls with the same deterministic reader should give the same blinded message. They did not, because half the randomness came from somewhere else. The sibling code in brsa.go did it right and used the supplied reader throughout.&lt;/p&gt;

&lt;p&gt;The fix is one line in blindsign/blindrsa/partiallyblindrsa/pbrsa.go:&lt;/p&gt;

&lt;p&gt;_, err := io.ReadFull(random, salt)&lt;/p&gt;

&lt;p&gt;It used to read from rand.Reader. Now it reads from random, the reader the caller passed in.&lt;/p&gt;

&lt;p&gt;The test pins it down. TestBlindUsesSuppliedReader calls Blind twice with fresh deterministic readers that emit the same byte sequence. Both blinded messages must be identical. It fails before the fix and passes after. Callers passing crypto/rand see no behavior change.&lt;/p&gt;

&lt;p&gt;Merged as cloudflare/circl#711: &lt;a href="https://github.com/cloudflare/circl/pull/711" rel="noopener noreferrer"&gt;https://github.com/cloudflare/circl/pull/711&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>cryptography</category>
      <category>opensource</category>
    </item>
    <item>
      <title>mypy rejected subclassing an inferred-Any variable. The fix is deferred, not special-cased.</title>
      <dc:creator>Benjamin Kincses</dc:creator>
      <pubDate>Thu, 29 Oct 2026 10:45:27 +0000</pubDate>
      <link>https://dev.to/benjaminkincses/mypy-rejected-subclassing-an-inferred-any-variable-the-fix-is-deferred-not-special-cased-kin</link>
      <guid>https://dev.to/benjaminkincses/mypy-rejected-subclassing-an-inferred-any-variable-the-fix-is-deferred-not-special-cased-kin</guid>
      <description>&lt;p&gt;mypy accepted this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It rejected this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same variable, same Any. The difference is when the type is known. Semantic analysis runs before type inference. When a base class is a variable with no inferred type yet, the analyser saw Var.type is None and reported it invalid, early, before inference ever got a say.&lt;br&gt;
The fix does not special-case Any. It defers the verdict. A new DeferredBaseClassVar exception fires when a base-class variable has no inferred type yet, on the base-class analysis path only. analyze_base_classes catches it, treats the base as Any provisionally so MRO computation proceeds, and records it for the checker to settle once inference is done. The premature --disallow-subclassing-any error is skipped for provisional bases for the same reason: you cannot grade a type that does not exist yet.&lt;br&gt;
The general point is the one that keeps showing up in type checkers: an error raised in the wrong phase is a false positive, even when the check itself is correct. Fix the phase, not the check.&lt;br&gt;
Since the first version, the deferred-base handling gained support for a second subscript and for string subscripts, pushed 2026-10-29. The PR is still open, not merged.&lt;br&gt;
PR: python/mypy#22037. Fixes #21998.&lt;/p&gt;

</description>
      <category>python</category>
      <category>mypy</category>
      <category>typechecking</category>
    </item>
    <item>
      <title>lazycommit: the commit message is clerical work</title>
      <dc:creator>Benjamin Kincses</dc:creator>
      <pubDate>Fri, 23 Oct 2026 22:15:08 +0000</pubDate>
      <link>https://dev.to/benjaminkincses/lazycommit-the-commit-message-is-clerical-work-3ih2</link>
      <guid>https://dev.to/benjaminkincses/lazycommit-the-commit-message-is-clerical-work-3ih2</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;The change is done. Tests pass. Then I run &lt;code&gt;git commit&lt;/code&gt; and sit there,&lt;br&gt;
staring at a blinking cursor, trying to compress an hour of work into&lt;br&gt;
seven words.&lt;/p&gt;

&lt;p&gt;I have typed "fix stuff" more times than I will admit. Also "wip",&lt;br&gt;
"changes", and once, memorably, "please work". Every one of those&lt;br&gt;
commits was good code with a message written by a person who had&lt;br&gt;
already spent his attention on the code.&lt;/p&gt;

&lt;p&gt;So for Slow and Steady I built the tool I kept wishing for. It does not&lt;br&gt;
write code. It does the clerical work after the code, which is exactly&lt;br&gt;
the work laziness should take.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;lazycommit&lt;/strong&gt; is a small CLI, one Python file, standard library only.&lt;/p&gt;

&lt;p&gt;Stage your changes, run it, and it prints three commit messages in&lt;br&gt;
Conventional Commits format. You press &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt; or &lt;code&gt;3&lt;/code&gt;, and it commits&lt;br&gt;
with the one you picked. Press &lt;code&gt;q&lt;/code&gt; and it walks away and commits&lt;br&gt;
nothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add stats.py
lazycommit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No dependencies to install, no config file, no API key. The whole thing&lt;br&gt;
is small enough to read in one sitting, which I count as a feature.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Three steps, and the interesting part is what each one refuses to do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Read the staged diff, and only the staged diff.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;git diff --cached&lt;/code&gt;. Unstaged work never leaves the machine, because it&lt;br&gt;
was never part of this commit. If nothing is staged, the tool says so&lt;br&gt;
and exits. It does not guess.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Send a capped diff to a free text endpoint.&lt;/strong&gt;&lt;br&gt;
The diff is truncated at 6,000 characters before it goes anywhere. The&lt;br&gt;
head of a diff carries the file names and the first hunks, which is&lt;br&gt;
enough context for a subject line, and the cap keeps the request small.&lt;br&gt;
The default endpoint is the Pollinations text API: free, no key,&lt;br&gt;
a &lt;code&gt;messages&lt;/code&gt; array in and text out. The model and endpoint are flags&lt;br&gt;
(&lt;code&gt;--model&lt;/code&gt;, &lt;code&gt;--endpoint&lt;/code&gt;), so swapping providers is a one-line change.&lt;/p&gt;

&lt;p&gt;The prompt is strict. Exactly three lines. Conventional Commits format.&lt;br&gt;
Imperative mood, under 72 characters each. Three different angles on the&lt;br&gt;
same change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Parse like you do not trust the model. Because I do not.&lt;/strong&gt;&lt;br&gt;
Models like to help. They add "Here are three options for you!" and&lt;br&gt;
numbering and backticks. The parser strips all of that, prefers lines&lt;br&gt;
that actually match the &lt;code&gt;type(scope): summary&lt;/code&gt; pattern, drops&lt;br&gt;
duplicates, and takes the first three. If nothing usable comes back,&lt;br&gt;
you get a one-line error. Not a traceback, and not a garbage commit.&lt;/p&gt;

&lt;p&gt;That defensive parser is most of the code, and most of the tests.&lt;br&gt;
23 tests, all offline, API and git mocked.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;A real run, live free API, throwaway repo. The staged change adds an&lt;br&gt;
empty-list guard to an &lt;code&gt;average()&lt;/code&gt; helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git add stats.py
$ lazycommit
  1. feat(stats): handle empty list in average
  2. fix(stats): return 0 for empty numbers
  3. perf(stats): guard against division by zero
pick 1-3 (or q to quit): 1
[master bab7bea] feat(stats): handle empty list in average
 1 file changed, 2 insertions(+)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what happened there. Three messages, one change, and they still&lt;br&gt;
disagree about whether it was a &lt;code&gt;feat&lt;/code&gt;, a &lt;code&gt;fix&lt;/code&gt; or a &lt;code&gt;perf&lt;/code&gt;. Anyone who&lt;br&gt;
has argued about commit types in a review thread knows that argument.&lt;br&gt;
The tool does not settle it. It just makes sure I am choosing between&lt;br&gt;
three decent options instead of inventing a fourth bad one from&lt;br&gt;
nothing.&lt;/p&gt;

&lt;p&gt;Total time, about ten seconds, most of it network.&lt;/p&gt;

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

&lt;p&gt;The API call was the easy part. Fifteen lines, including the error&lt;br&gt;
handling. Everything else was deciding where the laziness stops.&lt;/p&gt;

&lt;p&gt;One generated message would have been faster to build. It also would&lt;br&gt;
have been a command: take it or leave it. Three messages is a menu.&lt;br&gt;
Reading three short lines and pressing one key is genuinely lazy, and&lt;br&gt;
the choice stays mine. That difference is the whole design.&lt;/p&gt;

&lt;p&gt;The other thing: a tool that commits on your behalf has to be boring&lt;br&gt;
about safety. Stage nothing, get nothing. Model rambles, commit&lt;br&gt;
nothing. Network dies, one clean line of error and commit nothing.&lt;br&gt;
Every failure path in lazycommit ends the same way, with your repo&lt;br&gt;
exactly as you left it.&lt;/p&gt;

&lt;p&gt;Slow and steady, it turns out, is a decent spec for a lazy tool.&lt;br&gt;
Small, a little stubborn, and it saves its effort for the part you&lt;br&gt;
actually want to skip.&lt;/p&gt;

&lt;p&gt;The code is on GitHub: &lt;a href="https://github.com/Dextheking1/lazycommit" rel="noopener noreferrer"&gt;https://github.com/Dextheking1/lazycommit&lt;/a&gt;. MIT licensed.&lt;br&gt;
Take it, break it, and go back to not writing commit messages.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part of the DEV x MLH Hacktoberfest "Slow and Steady"&lt;br&gt;
challenge.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>python</category>
      <category>git</category>
    </item>
    <item>
      <title>3 prompt patterns that actually work, with copy-paste examples</title>
      <dc:creator>Benjamin Kincses</dc:creator>
      <pubDate>Mon, 28 Sep 2026 23:18:46 +0000</pubDate>
      <link>https://dev.to/benjaminkincses/3-prompt-patterns-that-actually-work-with-copy-paste-examples-10e2</link>
      <guid>https://dev.to/benjaminkincses/3-prompt-patterns-that-actually-work-with-copy-paste-examples-10e2</guid>
      <description>&lt;p&gt;Most bad AI output is a bad prompt, not a bad model. These three patterns fix the majority of it. Each one includes a template you can copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Role plus constraints
&lt;/h2&gt;

&lt;p&gt;Vague prompts get vague answers. Give the model a role and hard constraints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a senior Python reviewer. Review the code below.
Rules:
- List only real bugs, not style nits
- Max 5 items
- Each item: one line describing the bug, one line with the fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The constraints do the heavy lifting. "Max 5 items" and "one line each" force density. Without them you get a polite essay.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Few-shot examples
&lt;/h2&gt;

&lt;p&gt;Show the model what good output looks like instead of describing it. Two examples beat two paragraphs of instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Convert these into calendar event titles:

Input: "Lunch with Sara tomorrow at noon"
Output: "Lunch - Sara"

Input: "Dentist appointment Friday 3pm"
Output: "Dentist"

Input: "Call the bank about the fee on Monday morning"
Output:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model continues the pattern. This works for formatting, classification, tone matching, and extraction. Use it whenever the output has a shape you can demonstrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Ask for questions first
&lt;/h2&gt;

&lt;p&gt;For anything complex, make the model interrogate you before it answers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I want to automate my invoice reminders. Before you suggest anything,
ask me every question you need answered to give a good recommendation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kills the biggest failure mode: the model guessing your context and confidently solving the wrong problem. Answer its questions, then let it work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;A strong prompt usually combines all three: a role with constraints, one or two examples of the output shape, and a questions-first step for anything ambiguous. Try rewriting your most-used prompt with this structure and compare the outputs.&lt;/p&gt;

&lt;p&gt;I build AI automations for businesses. Chatbots, workflow automation, custom tools that cut manual work. Engineering proof: github.com/Dextheking1. DMs open on X: @FREEAIMETHODS.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>promptengineering</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Automate a boring task for $0 with the Google AI Studio free API tier</title>
      <dc:creator>Benjamin Kincses</dc:creator>
      <pubDate>Mon, 28 Sep 2026 16:29:04 +0000</pubDate>
      <link>https://dev.to/benjaminkincses/automate-a-boring-task-for-0-with-the-google-ai-studio-free-api-tier-27i7</link>
      <guid>https://dev.to/benjaminkincses/automate-a-boring-task-for-0-with-the-google-ai-studio-free-api-tier-27i7</guid>
      <description>&lt;h1&gt;
  
  
  Automate a boring task for $0 with the Google AI Studio free API tier
&lt;/h1&gt;

&lt;p&gt;Google AI Studio gives you a free Gemini API key with a generous free tier. No credit card. Here is the full path from zero to a working automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Get your free API key
&lt;/h2&gt;

&lt;p&gt;Go to aistudio.google.com, sign in with a Google account, and click "Get API key". Copy it. Treat it like a password: put it in an environment variable, never in your code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-key-here"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Make your first call
&lt;/h2&gt;

&lt;p&gt;No SDK needed. Plain curl works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-goog-api-key: &lt;/span&gt;&lt;span class="nv"&gt;$GEMINI_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"contents":[{"parts":[{"text":"Summarize this in one sentence: The quick brown fox jumps over the lazy dog."}]}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gemini-2.5-flash-lite&lt;/code&gt; is the fastest model and has the most generous free-tier quota, so it is the right default for automations.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Build something real: a text summarizer
&lt;/h2&gt;

&lt;p&gt;Say you wake up to 40 unread newsletters and threads. This script condenses pasted text into 5 bullets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash-lite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://generativelanguage.googleapis.com/v1beta/models/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:generateContent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;summarize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarize the following in exactly 5 short bullets:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&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="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;encode&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;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-goog-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&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;as&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&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;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;candidates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;summarize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;long-article.txt | python summarize.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero dependencies beyond the standard library. Run it on a schedule with cron and you have a free daily digest.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Know the limits
&lt;/h2&gt;

&lt;p&gt;The free tier has rate limits (requests per minute and per day). They change, so check your current quota at aistudio.google.com under usage. If you hit a 429, slow down and retry later.&lt;/p&gt;

&lt;p&gt;One real caveat: do not send private or customer data through the free tier. Keep it to public or personal content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this goes
&lt;/h2&gt;

&lt;p&gt;Once you see what one script can do, the next step is wiring it into a real workflow: inbox triage, lead follow-ups, report generation. That is the part I do for businesses.&lt;/p&gt;

&lt;p&gt;I build AI automations for businesses. Chatbots, workflow automation, custom tools that cut manual work. Engineering proof: github.com/Dextheking1. DMs open on X: @FREEAIMETHODS.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>tutorial</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
