<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Blake Link</title>
    <link>https://blakelink.com/</link>
    <description>Recent content on Blake Link</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 03 May 2026 08:00:00 -0500</lastBuildDate>
    <atom:link href="https://blakelink.com/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Finding the Best Game to Stream: A Data-Driven Approach</title>
      <link>https://blakelink.com/posts/finding-the-best-game-to-stream/</link>
      <pubDate>Sun, 03 May 2026 08:00:00 -0500</pubDate>
      <guid>https://blakelink.com/posts/finding-the-best-game-to-stream/</guid>
      <description>&lt;p&gt;Every streamer knows the dilemma: you want to play something you own, but you also want viewers to actually find your stream. Browsing Twitch manually is slow and random. I built &lt;a href=&#34;https://github.com/blakelinkd/best_game&#34;&gt;Best Game&lt;/a&gt; to solve this — it reads your game library, cross-references it with live Twitch data, and ranks every game by &lt;strong&gt;discovery potential&lt;/strong&gt;. Here&amp;rsquo;s how the recommendation engine works.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Automating Cookie Clicker with Python: Hooking into an Electron Game</title>
      <link>https://blakelink.com/posts/automating-cookie-clicker-with-python/</link>
      <pubDate>Sun, 03 May 2026 06:00:00 -0500</pubDate>
      <guid>https://blakelink.com/posts/automating-cookie-clicker-with-python/</guid>
      <description>&lt;p&gt;Cookie Clicker is one of those games that starts as a joke and ends with you running a full-blown cookie empire. I wanted to see how far I could push automation — not by cheating with save editors or memory hacks, but by building a bot that plays the game &lt;em&gt;the same way a human does&lt;/em&gt;: reading the screen and clicking. Here&amp;rsquo;s how &lt;a href=&#34;https://github.com/blakelinkd/auto_clicker_for_cookie_clicker&#34;&gt;Auto Clicker for Cookie Clicker&lt;/a&gt; works under the hood.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Giving AI Agents Path Awareness with BASH_ENV</title>
      <link>https://blakelink.com/posts/solving-claude-code-path-awareness-with-bash-env/</link>
      <pubDate>Fri, 31 Oct 2025 10:00:00 -0500</pubDate>
      <guid>https://blakelink.com/posts/solving-claude-code-path-awareness-with-bash-env/</guid>
      <description>&lt;h2 id=&#34;the-problem-claude-code-losing-track-of-its-working-directory&#34;&gt;The Problem: Claude Code Losing Track of Its Working Directory&lt;/h2&gt;&#xA;&lt;p&gt;When working with Claude Code on a complex C++ game project with multiple build directories, I ran into a frustrating and token-expensive problem: Claude would frequently lose track of its current working directory.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Session Handoff Protocol: Solving AI Agent Continuity in Complex Projects</title>
      <link>https://blakelink.com/posts/session-handoff-protocol-solving-ai-agent-continuity-in-complex-projects/</link>
      <pubDate>Fri, 26 Sep 2025 10:00:00 -0500</pubDate>
      <guid>https://blakelink.com/posts/session-handoff-protocol-solving-ai-agent-continuity-in-complex-projects/</guid>
      <description>&lt;p&gt;&lt;em&gt;Published: September 26, 2025&lt;/em&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-challenge-lost-context-between-ai-sessions&#34;&gt;The Challenge: Lost Context between AI Sessions&lt;/h2&gt;&#xA;&lt;p&gt;When working on complex software projects with AI agents, one of the biggest productivity killers is context loss between sessions. You spend hours making progress on implementing API endpoints, fixing test failures, or writing documentation, only to have the next AI agent iteration start from scratch or misunderstand where you left off.&lt;/p&gt;&#xA;&lt;p&gt;This problem becomes especially acute in large codebases like my Invoico platform, where I&amp;rsquo;m implementing comprehensive API customer flows with 90+ tests across multiple feature areas including webhooks, monitoring, security, and billing systems.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How I Use chmod to Stop AI Agents from Cheating on My Tests</title>
      <link>https://blakelink.com/posts/how-i-use-chmod-to-stop-ai-agents-from-cheating-on-my-tests/</link>
      <pubDate>Tue, 23 Sep 2025 10:00:00 -0500</pubDate>
      <guid>https://blakelink.com/posts/how-i-use-chmod-to-stop-ai-agents-from-cheating-on-my-tests/</guid>
      <description>&lt;h2 id=&#34;the-problem-my-ai-assistant-was-cheating-on-its-tests&#34;&gt;The Problem: My AI Assistant Was Cheating on Its Tests&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve been using AI agents to help me build features for my invoice management platform, and I&amp;rsquo;m a big fan of Test-Driven Development (TDD). But I ran into a problem pretty quickly: &lt;strong&gt;my AI agents were a little too clever for their own good.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Instead of, you know, actually implementing the features to make the tests pass, my AI assistant started taking shortcuts. I&amp;rsquo;m talking about:&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Strip Newlines in Rust - A Complete Guide</title>
      <link>https://blakelink.com/tutorials/rust-strip-newlines/</link>
      <pubDate>Mon, 22 Sep 2025 13:32:21 -0600</pubDate>
      <guid>https://blakelink.com/tutorials/rust-strip-newlines/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;re working with text in Rust, you&amp;rsquo;ve probably had to deal with the headache of unwanted newline characters. Whether you&amp;rsquo;re cleaning up user input, processing files, or wrangling API responses, newlines can be a real pain. The good news is that Rust&amp;rsquo;s standard library gives you some powerful, memory-safe, and efficient tools for handling them. In this guide, I&amp;rsquo;ll walk you through some of the best ways to strip newlines in Rust, with plenty of practical examples.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Strip Newlines in Go - A Complete Guide</title>
      <link>https://blakelink.com/tutorials/go-strip-newlines/</link>
      <pubDate>Mon, 22 Sep 2025 13:24:56 -0600</pubDate>
      <guid>https://blakelink.com/tutorials/go-strip-newlines/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;re working with text in Go, you&amp;rsquo;ve probably had to deal with the headache of unwanted newline characters. Whether you&amp;rsquo;re cleaning up user input, processing files, or wrangling API responses, newlines can be a real pain. The good news is that Go&amp;rsquo;s standard library gives you some powerful and efficient tools for handling them. In this guide, I&amp;rsquo;ll walk you through some of the best ways to strip newlines in Go, with plenty of practical examples.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Strip Newlines in C# - A Complete Guide</title>
      <link>https://blakelink.com/tutorials/csharp-strip-newlines/</link>
      <pubDate>Mon, 22 Sep 2025 13:21:33 -0600</pubDate>
      <guid>https://blakelink.com/tutorials/csharp-strip-newlines/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;re working with text in C#, you&amp;rsquo;ve probably run into the need to get rid of unwanted newline characters. Whether you&amp;rsquo;re cleaning up user input, processing files, or dealing with API responses, newlines can be a real pain. The good news is that C# and the .NET Framework give you some powerful tools for handling them. In this guide, I&amp;rsquo;ll walk you through some of the best ways to strip newlines in C#, with plenty of practical examples.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Strip Newlines in Java - A Complete Guide</title>
      <link>https://blakelink.com/tutorials/java-strip-newlines/</link>
      <pubDate>Mon, 22 Sep 2025 13:17:53 -0600</pubDate>
      <guid>https://blakelink.com/tutorials/java-strip-newlines/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;re working with text in Java, you&amp;rsquo;ve probably had to deal with the headache of unwanted newline characters. Whether you&amp;rsquo;re cleaning up user input, processing files, or wrangling API responses, newlines can be a real pain. The good news is that Java gives you some powerful and efficient tools for handling them. In this guide, I&amp;rsquo;ll walk you through some of the best ways to strip newlines in Java, with plenty of practical examples.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Strip Newlines in JavaScript - A Complete Guide</title>
      <link>https://blakelink.com/tutorials/javascript-strip-newlines/</link>
      <pubDate>Mon, 22 Sep 2025 13:11:15 -0600</pubDate>
      <guid>https://blakelink.com/tutorials/javascript-strip-newlines/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;re working with text in JavaScript, you&amp;rsquo;ve probably had to deal with the headache of unwanted newline characters. Whether you&amp;rsquo;re cleaning up user input, processing files, or wrangling API responses, newlines can be a real pain. The good news is that JavaScript gives you some powerful and efficient tools for handling them. In this guide, I&amp;rsquo;ll walk you through some of the best ways to strip newlines in JavaScript, with plenty of practical examples.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Building a Data Model Validation System for Large-Scale AI Agent Projects</title>
      <link>https://blakelink.com/posts/building-a-data-model-validation-system-for-large-scale-ai-agent-projects/</link>
      <pubDate>Mon, 22 Sep 2025 10:00:00 -0500</pubDate>
      <guid>https://blakelink.com/posts/building-a-data-model-validation-system-for-large-scale-ai-agent-projects/</guid>
      <description>&lt;p&gt;&lt;em&gt;The follow-up to my user flow validation system: How I keep my data models in sync with business requirements using chunked processing for AI agents with limited memory.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;the-problem-when-your-data-models-cant-keep-up&#34;&gt;The Problem: When Your Data Models Can&amp;rsquo;t Keep Up&lt;/h2&gt;&#xA;&lt;p&gt;In my &lt;a href=&#34;https://blakelink.com/posts/building-a-robust-user-story-and-flow-validation-system-for-ai-agents/&#34;&gt;previous post&lt;/a&gt;, I figured out how to fix incomplete user flow documentation. But that led me to a whole new problem: &lt;strong&gt;data model drift&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;As my user flows got better and my validation scores went up, I noticed my Django data models were falling behind. Important business entities from my flows were nowhere to be found in my database schema. The relationships I&amp;rsquo;d described in my workflows weren&amp;rsquo;t modeled correctly, and status values from my flows didn&amp;rsquo;t have matching database enums.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Building a Robust User Story and Flow Validation System for AI Agents</title>
      <link>https://blakelink.com/posts/building-a-robust-user-story-and-flow-validation-system-for-ai-agents/</link>
      <pubDate>Sun, 21 Sep 2025 10:00:00 -0500</pubDate>
      <guid>https://blakelink.com/posts/building-a-robust-user-story-and-flow-validation-system-for-ai-agents/</guid>
      <description>&lt;p&gt;&lt;em&gt;How I built a validation system that guarantees complete documentation and automates quality control.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;the-problem-when-incomplete-documentation-grinds-everything-to-a-halt&#34;&gt;The Problem: When Incomplete Documentation Grinds Everything to a Halt&lt;/h2&gt;&#xA;&lt;p&gt;If you&amp;rsquo;ve worked with AI agents and complex workflows, you&amp;rsquo;ve probably felt this pain: &lt;strong&gt;incomplete documentation slows down development, creates confusing requirements, and leads to a ton of rework&lt;/strong&gt;. When your user stories are missing clear flows, or your flows aren&amp;rsquo;t properly documented, everything just stops.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Django &#43; Lit &#43; Vite: Template Setup and Hot-Reload Workflow</title>
      <link>https://blakelink.com/posts/django-lit-vite-template-hot-reload/</link>
      <pubDate>Fri, 05 Sep 2025 10:00:00 -0500</pubDate>
      <guid>https://blakelink.com/posts/django-lit-vite-template-hot-reload/</guid>
      <description>&lt;p&gt;This post walks you through how I set up a hypothetical Django app called &lt;code&gt;acmefront&lt;/code&gt; to work with the Lit JavaScript framework and Vite. I&amp;rsquo;ll also show you a simple, framework-agnostic hot-reload workflow that makes development a breeze. Just a heads-up: all the names and paths I&amp;rsquo;m using here are just for illustration.&lt;/p&gt;&#xA;&lt;h3 id=&#34;step-1-wiring-up-the-app-template-url-and-view&#34;&gt;Step 1: Wiring Up the App, Template, URL, and View&lt;/h3&gt;&#xA;&lt;p&gt;First things first, let&amp;rsquo;s get the basic Django pieces in place. The app exposes a simple index view and a URL to go with it:&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Strip Newlines in Ruby - A Complete Guide</title>
      <link>https://blakelink.com/tutorials/ruby-strip-newlines/</link>
      <pubDate>Mon, 13 Jan 2025 14:02:10 -0600</pubDate>
      <guid>https://blakelink.com/tutorials/ruby-strip-newlines/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;re working with strings in Ruby, you&amp;rsquo;ve probably run into situations where you need to get rid of newline characters. Here are a few common scenarios:&lt;/p&gt;&#xA;&lt;h3 id=&#34;1-processing-files&#34;&gt;1. Processing Files&lt;/h3&gt;&#xA;&lt;p&gt;When you&amp;rsquo;re reading from a file, each line usually comes with a newline character at the end. You&amp;rsquo;ll want to strip these out when you&amp;rsquo;re working with things like:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;CSV files where newlines aren&amp;rsquo;t part of the actual data.&lt;/li&gt;&#xA;&lt;li&gt;Config files where newlines are just for formatting.&lt;/li&gt;&#xA;&lt;li&gt;Log files that you&amp;rsquo;re parsing for analysis.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Example: Cleaning up a CSV file&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;csv&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;clean_csv_data&lt;/span&gt;(filename)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Read the whole file and get rid of any extra newlines&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  raw_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;File&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read(filename)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;gsub(&lt;span style=&#34;color:#e6db74&#34;&gt;/\r\n?/&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;strip&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Parse the cleaned-up CSV data&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;CSV&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse(raw_data)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;row&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Strip whitespace and newlines from each field&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    row&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;field&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; field&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;.&lt;/span&gt;strip }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# How to use it&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;begin&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  cleaned_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; clean_csv_data(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;sample.csv&amp;#39;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Processed &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;cleaned_data&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;size&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; rows&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;rescue&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Errno&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ENOENT&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Error: File not found&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;2-handling-user-input&#34;&gt;2. Handling User Input&lt;/h3&gt;&#xA;&lt;p&gt;When you get input from a user with a method like &lt;code&gt;gets&lt;/code&gt;, it comes with a trailing newline that you&amp;rsquo;ll almost always want to remove.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Building Smart Web Scrapers with Local LLMs</title>
      <link>https://blakelink.com/posts/smart-web-scraping-with-llms/</link>
      <pubDate>Thu, 12 Dec 2024 15:30:00 -0500</pubDate>
      <guid>https://blakelink.com/posts/smart-web-scraping-with-llms/</guid>
      <description>&lt;h1 id=&#34;how-i-use-local-llms-to-build-smarter-web-scrapers&#34;&gt;How I Use Local LLMs to Build Smarter Web Scrapers&lt;/h1&gt;&#xA;&lt;p&gt;This is a guide to how I use locally-run Large Language Models (LLMs) to build web scrapers that are way more reliable than the old-school, CSS selector-based ones. If you&amp;rsquo;ve ever built a web scraper, you know the pain of brittle CSS selectors. You spend forever getting them just right, and then the website changes its HTML, and your scraper is toast. That&amp;rsquo;s why I started playing around with running LLMs locally with LM Studio, and it&amp;rsquo;s been a game-changer.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Strip Newlines in Python - A Complete Guide</title>
      <link>https://blakelink.com/tutorials/python-strip-newlines/</link>
      <pubDate>Wed, 11 Dec 2024 14:03:14 -0600</pubDate>
      <guid>https://blakelink.com/tutorials/python-strip-newlines/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;re working with text in Python, you&amp;rsquo;ve probably had to deal with the headache of unwanted newline characters. Whether you&amp;rsquo;re cleaning up user input, processing files, or wrangling scraped data, newlines can be a real pain. The good news is that Python gives you some powerful and efficient tools for handling them. In this guide, I&amp;rsquo;ll walk you through some of the best ways to strip newlines in Python, with plenty of practical examples.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Tips for Building Apps with CursorAI</title>
      <link>https://blakelink.com/posts/tips-for-building-apps-with-cursorai/</link>
      <pubDate>Wed, 04 Dec 2024 10:00:00 -0500</pubDate>
      <guid>https://blakelink.com/posts/tips-for-building-apps-with-cursorai/</guid>
      <description>&lt;h2 id=&#34;my-favorite-tips-for-building-apps-with-cursorai&#34;&gt;My Favorite Tips for Building Apps with CursorAI&lt;/h2&gt;&#xA;&lt;p&gt;I just finished building a complete Ruby on Rails app with CursorAI, and I wanted to share some tips that made the process so much faster. Here are a few things I learned along the way that will help you get the most out of Cursor.&lt;/p&gt;&#xA;&lt;h3 id=&#34;1-keep-a-changelog-with-cursorrules&#34;&gt;1. Keep a Changelog with .cursorrules&lt;/h3&gt;&#xA;&lt;p&gt;It&amp;rsquo;s a good idea to have Cursor maintain a changelog for you. This helps you keep track of what&amp;rsquo;s been done and prevents the AI from getting stuck in a loop trying the same failed solutions over and over. If Cursor knows what you&amp;rsquo;ve already tried, it won&amp;rsquo;t waste time suggesting the same things again.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Finding a job with Python and Selenium Part 2</title>
      <link>https://blakelink.com/posts/python_scraping_2/</link>
      <pubDate>Fri, 25 Oct 2024 14:03:14 -0600</pubDate>
      <guid>https://blakelink.com/posts/python_scraping_2/</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;https://blakelink.com/posts/python_scraping_1/&#34;&gt;Part 1 of our job-finding series&lt;/a&gt;, we found a job board that was easy to scrape and saved the data to a local file. Now, it&amp;rsquo;s time to take that data and put it to work. In this post, we&amp;rsquo;ll walk through how to load our scraped data into a database and run some basic analysis on it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;setting-up-the-database&#34;&gt;Setting Up the Database&lt;/h3&gt;&#xA;&lt;p&gt;First things first, we need a place to store our data. We&amp;rsquo;ll use SQLite to create a database with a &lt;code&gt;jobs&lt;/code&gt; table that has the following columns:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;title&lt;/code&gt;: The job title&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;location&lt;/code&gt;: Where the job is located&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;date&lt;/code&gt;: The date the job was posted&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;link&lt;/code&gt;: A link to the job post&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;description&lt;/code&gt;: The full job description&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;hasapplied&lt;/code&gt;: A flag to track whether we&amp;rsquo;ve applied for the job&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; sqlite3&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; os&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Database file path&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;db_file &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;jobs.db&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# SQL to create the jobs table&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;create_jobs_table_sql &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;CREATE TABLE IF NOT EXISTS jobs (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    id INTEGER PRIMARY KEY AUTOINCREMENT,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    title TEXT NOT NULL,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    companyname TEXT NOT NULL,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    location TEXT,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    date TEXT,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    link TEXT,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    description TEXT,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    hasapplied INTEGER DEFAULT 0,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;-- Create an index on companyname for faster lookups&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;CREATE INDEX IF NOT EXISTS idx_companyname ON jobs(companyname);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;-- Create an index on hasapplied for faster filtering&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;CREATE INDEX IF NOT EXISTS idx_hasapplied ON jobs(hasapplied);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;create_database&lt;/span&gt;():&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Check if database file already exists&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    db_exists &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; os&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;path&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;exists(db_file)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Connect to the database (this will create it if it doesn&amp;#39;t exist)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    conn &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sqlite3&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;connect(db_file)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cursor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; conn&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;cursor()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Create the jobs table&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cursor&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;executescript(create_jobs_table_sql)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Commit the changes and close the connection&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    conn&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;commit()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    conn&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; db_exists:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Connected to existing database: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;db_file&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Created new database: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;db_file&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Jobs table initialized successfully.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; __name__ &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    create_database()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    <item>
      <title>Finding a job with Python and Selenium</title>
      <link>https://blakelink.com/posts/python_scraping_1/</link>
      <pubDate>Sat, 19 Oct 2024 13:36:03 -0500</pubDate>
      <guid>https://blakelink.com/posts/python_scraping_1/</guid>
      <description>&lt;p&gt;Finding a job can be a real grind. In this post, I&amp;rsquo;m going to show you how to automate a big chunk of that work using Python and Selenium. We&amp;rsquo;ll dive into how to find job boards that are easy to scrape, save that data, and then analyze it to pinpoint the jobs you&amp;rsquo;re most qualified for.&lt;/p&gt;&#xA;&lt;p&gt;We&amp;rsquo;ll even explore how to use free AI tools like Ollama and LM Studio to filter jobs based on your skillset and extract keywords to make searching easier. And to top it all off, we&amp;rsquo;ll build a neat little web app with React and Flask to browse all the jobs we&amp;rsquo;ve scraped.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-tools-of-the-trade&#34;&gt;The Tools of the Trade&lt;/h2&gt;&#xA;&lt;h3 id=&#34;python&#34;&gt;Python&lt;/h3&gt;&#xA;&lt;p&gt;I&amp;rsquo;m a big fan of Python for this kind of stuff. It&amp;rsquo;s simple, it&amp;rsquo;s effective, and it gets the job done. If you&amp;rsquo;re new to Python, you can learn how to get it set up here: &lt;a href=&#34;https://www.python.org/downloads/&#34;&gt;Installing Python&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;selenium&#34;&gt;Selenium&lt;/h3&gt;&#xA;&lt;p&gt;While Python has some built-in tools for grabbing content from web pages, a lot of job boards use JavaScript to render their content. That&amp;rsquo;s where Selenium comes in. It lets us spin up a real browser session and control it with our code, so we can get to all that juicy, dynamically-rendered content.&lt;/p&gt;&#xA;&lt;h3 id=&#34;sqlite&#34;&gt;SQLite&lt;/h3&gt;&#xA;&lt;p&gt;We&amp;rsquo;re going to be dealing with a lot of data, so Excel just isn&amp;rsquo;t going to cut it. I&amp;rsquo;ve got a single database table with about a week&amp;rsquo;s worth of job posts that&amp;rsquo;s already over 100MB. SQLite is perfect for this. It&amp;rsquo;s lightweight, and it&amp;rsquo;ll let us run the advanced queries we&amp;rsquo;ll need to analyze and sort through all the jobs we find.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
