<?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: Kyle Keon</title>
    <description>The latest articles on DEV Community by Kyle Keon (@kylekeon).</description>
    <link>https://dev.to/kylekeon</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%2F4198854%2Fe7a109cf-d468-4014-84e7-842a1621f16b.jpg</url>
      <title>DEV Community: Kyle Keon</title>
      <link>https://dev.to/kylekeon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kylekeon"/>
    <language>en</language>
    <item>
      <title>Build a High-Speed Duplicate File Finder &amp; Disk Analyzer in Pure Python</title>
      <dc:creator>Kyle Keon</dc:creator>
      <pubDate>Mon, 02 Nov 2026 07:21:11 +0000</pubDate>
      <link>https://dev.to/kylekeon/build-a-high-speed-duplicate-file-finder-disk-analyzer-in-pure-python-hk1</link>
      <guid>https://dev.to/kylekeon/build-a-high-speed-duplicate-file-finder-disk-analyzer-in-pure-python-hk1</guid>
      <description>&lt;p&gt;Most Python "file management" tutorials stop at a 30-line &lt;code&gt;shutil.move()&lt;/code&gt; loop that sorts files by extension. While great for learning &lt;code&gt;pathlib&lt;/code&gt;, that doesn't help when you need to reclaim gigabytes of wasted space across tens of thousands of files without waiting 20 minutes for &lt;code&gt;hashlib&lt;/code&gt; to read your entire hard drive.&lt;/p&gt;

&lt;p&gt;In this post, we'll build a production-grade &lt;strong&gt;Multithreaded Duplicate &amp;amp; Storage Analyzer CLI&lt;/strong&gt; using &lt;strong&gt;pure Python 3.10+ (zero external dependencies)&lt;/strong&gt; that can scan tens of thousands of files in seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Naive Duplicate Finders Are Slow
&lt;/h2&gt;

&lt;p&gt;If you run &lt;code&gt;sha256()&lt;/code&gt; on every file in a 500 GB directory, your script is immediately bottlenecked by disk I/O. To avoid reading every byte on the drive, we can use a &lt;strong&gt;3-Stage Hashing Funnel&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stage 1: Byte-Size Grouping (&lt;code&gt;O(1)&lt;/code&gt; metadata lookup)&lt;/strong&gt; — Two files cannot be identical if their byte counts differ. Grouping by &lt;code&gt;stat().st_size&lt;/code&gt; eliminates ~85–95% of files immediately without opening a single file handle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stage 2: 4 KB Head/Tail &lt;code&gt;blake2b&lt;/code&gt; Sampling&lt;/strong&gt; — For files that share the exact same size, we read only the &lt;strong&gt;first 4 KB and last 4 KB&lt;/strong&gt; of the file and hash those 8 KB with &lt;code&gt;hashlib.blake2b&lt;/code&gt;. This eliminates 99% of remaining false positives (like similarly sized photos or video chunks) in milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stage 3: Streaming Full &lt;code&gt;SHA-256&lt;/code&gt; Verification&lt;/strong&gt; — Only files that match in both byte size and 8 KB edge signatures undergo a full 1 MB chunked SHA-256 hash across a &lt;code&gt;ThreadPoolExecutor&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On top of that, instead of destructively deleting files (&lt;code&gt;os.remove&lt;/code&gt;), every quarantine operation writes to a local JSON transaction log (&lt;code&gt;~/.storage_audit_manifest.json&lt;/code&gt;) so you can revert your last batch action with a single &lt;code&gt;undo&lt;/code&gt; command.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Script (&lt;code&gt;disk_audit.py&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Save the following code as &lt;code&gt;disk_audit.py&lt;/code&gt;. It requires &lt;strong&gt;Python 3.10+&lt;/strong&gt; and uses only the standard library:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
python
#!/usr/bin/env python3
"""
High-speed disk analyzer, duplicate finder, and reversible file manager.
Zero external dependencies (Python 3.10+ standard library only).
"""

import argparse
import hashlib
import json
import os
import shutil
import sys
import time
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple

MANIFEST_FILE = Path.home() / ".storage_audit_manifest.json"
CHUNK_SIZE = 1024 * 1024  # 1 MB streaming buffer
SAMPLE_SIZE = 4096        # 4 KB fast header/footer sample


@dataclass(frozen=True)
class FileRecord:
    path: Path
    size: int
    mtime: float


def format_bytes(size: int) -&amp;gt; str:
    """Converts raw byte counts into human-readable units."""
    for unit in ("B", "KB", "MB", "GB", "TB"):
        if size &amp;lt; 1024 or unit == "TB":
            return f"{size:.2f} {unit}" if unit != "B" else f"{size} B"
        size /= 1024
    return f"{size} B"


def fast_sample_hash(path: Path, size: int) -&amp;gt; Optional[str]:
    """Reads only the first and last 4KB of a file to quickly filter non-duplicates."""
    try:
        hasher = hashlib.blake2b(digest_size=16)
        with path.open("rb") as f:
            hasher.update(f.read(SAMPLE_SIZE))
            if size &amp;gt; SAMPLE_SIZE * 2:
                f.seek(-SAMPLE_SIZE, os.SEEK_END)
                hasher.update(f.read(SAMPLE_SIZE))
        return hasher.hexdigest()
    except (OSError, PermissionError):
        return None


def full_sha256(path: Path) -&amp;gt; Optional[str]:
    """Computes full SHA-256 digest using memory-efficient 1MB chunks."""
    try:
        hasher = hashlib.sha256()
        with path.open("rb") as f:
            while chunk := f.read(CHUNK_SIZE):
                hasher.update(chunk)
        return hasher.hexdigest()
    except (OSError, PermissionError):
        return None


def scan_filesystem(root: Path, min_size: int = 1024) -&amp;gt; List[FileRecord]:
    """Recursively yields regular files, skipping symlinks and system directories."""
    records: List[FileRecord] = []
    skip_dirs = {
        ".git", "node_modules", ".venv", "__pycache__",
        "$RECYCLE.BIN", "System Volume Information"
    }

    for dirpath, dirnames, filenames in os.walk(root, topdown=True):
        # Prune ignored and hidden directories in-place
        dirnames[:] = [
            d for d in dirnames
            if d not in skip_dirs and not d.startswith(".")
        ]

        for fname in filenames:
            if fname.startswith("."):
                continue
            fpath = Path(dirpath) / fname
            try:
                if fpath.is_symlink():
                    continue
                stat = fpath.stat()
                if stat.st_size &amp;gt;= min_size:
                    records.append(FileRecord(fpath, stat.st_size, stat.st_mtime))
            except (OSError, PermissionError):
                continue
    return records


def find_duplicates(root: Path, min_size: int, workers: int) -&amp;gt; Dict[str, List[FileRecord]]:
    """Three-stage pipeline: Size grouping -&amp;gt; 8KB Blake2b sample -&amp;gt; Full SHA-256."""
    t0 = time.perf_counter()
    print(f"[*] Scanning '{root}' (min size: {format_bytes(min_size)})...")
    files = scan_filesystem(root, min_size)
    print(f"    Indexed {len(files):,} candidate files in {time.perf_counter() - t0:.2f}s.")

    # Stage 1: Group by exact byte size
    by_size: Dict[int, List[FileRecord]] = defaultdict(list)
    for rec in files:
        by_size[rec.size].append(rec)
    size_candidates = [
        rec for group in by_size.values() if len(group) &amp;gt; 1 for rec in group
    ]
    print(f"[*] Stage 1 (Size match): Narrowed to {len(size_candidates):,} files.")

    if not size_candidates:
        return {}

    # Stage 2: Parallel 4KB head/tail signature
    by_sample: Dict[Tuple[int, str], List[FileRecord]] = defaultdict(list)
    with ThreadPoolExecutor(max_workers=workers) as pool:
        future_map = {
            pool.submit(fast_sample_hash, r.path, r.size): r
            for r in size_candidates
        }
        for future in as_completed(future_map):
            rec = future_map[future]
            sig = future.result()
            if sig:
                by_sample[(rec.size, sig)].append(rec)

    sample_candidates = [
        rec for group in by_sample.values() if len(group) &amp;gt; 1 for rec in group
    ]
    print(f"[*] Stage 2 (4KB edge hash): Narrowed to {len(sample_candidates):,} files.")

    if not sample_candidates:
        return {}

    # Stage 3: Full SHA-256 verification
    by_full_hash: Dict[str, List[FileRecord]] = defaultdict(list)
    with ThreadPoolExecutor(max_workers=workers) as pool:
        future_map = {
            pool.submit(full_sha256, r.path): r
            for r in sample_candidates
        }
        for future in as_completed(future_map):
            rec = future_map[future]
            digest = future.result()
            if digest:
                by_full_hash[digest].append(rec)

    confirmed = {
        digest: sorted(group, key=lambda r: r.mtime)
        for digest, group in by_full_hash.items()
        if len(group) &amp;gt; 1
    }
    return confirmed


def record_transaction(operations: List[Dict[str, str]]) -&amp;gt; None:
    """Saves file moves to a persistent manifest for one-command rollback."""
    history = []
    if MANIFEST_FILE.exists():
        try:
            history = json.loads(MANIFEST_FILE.read_text(encoding="utf-8"))
        except json.JSONDecodeError:
            history = []

    history.append({
        "timestamp": datetime.now().isoformat(timespec="seconds"),
        "operations": operations,
    })
    MANIFEST_FILE.write_text(json.dumps(history, indent=2), encoding="utf-8")


def rollback_last_batch() -&amp;gt; None:
    """Restores all files moved in the most recent batch operation."""
    if not MANIFEST_FILE.exists():
        print("[!] No transaction manifest found. Nothing to undo.")
        return

    history = json.loads(MANIFEST_FILE.read_text(encoding="utf-8"))
    if not history:
        print("[!] Manifest is empty.")
        return

    last_batch = history.pop()
    ops = last_batch.get("operations", [])
    restored = 0

    print(f"[*] Rolling back batch from {last_batch['timestamp']} ({len(ops)} files)...")
    for op in reversed(ops):
        src = Path(op["src"])
        dst = Path(op["dst"])
        if dst.exists():
            src.parent.mkdir(parents=True, exist_ok=True)
            shutil.move(str(dst), str(src))
            restored += 1
        else:
            print(f"    [WARN] Missing quarantined file: {dst}")

    MANIFEST_FILE.write_text(json.dumps(history, indent=2), encoding="utf-8")
    print(f"[+] Restored {restored}/{len(ops)} files to their original paths.")


def handle_dupes(args: argparse.Namespace) -&amp;gt; None:
    root = Path(args.path).expanduser().resolve()
    duplicates = find_duplicates(root, args.min_size, args.workers)

    if not duplicates:
        print("\n[+] Zero exact duplicates found.")
        return

    reclaimable = sum(
        (len(group) - 1) * group[0].size for group in duplicates.values()
    )
    total_dupe_files = sum(len(group) - 1 for group in duplicates.values())

    print(f"\nFound {len(duplicates):,} duplicate sets ({total_dupe_files:,} redundant files).")
    print(f"Total reclaimable space: {format_bytes(reclaimable)}\n")

    sorted_groups = sorted(
        duplicates.items(),
        key=lambda item: item[1][0].size * (len(item[1]) - 1),
        reverse=True,
    )

    for idx, (digest, group) in enumerate(sorted_groups[:15], 1):
        waste = (len(group) - 1) * group[0].size
        print(
            f"  [{idx}] SHA256: {digest[:12]}... "
            f"({format_bytes(group[0].size)} each | Waste: {format_bytes(waste)})"
        )
        print(f"      KEEP (Oldest): {group[0].path}")
        for dupe in group[1:]:
            print(f"      DUPE         : {dupe.path}")

    if args.quarantine:
        q_dir = Path(args.quarantine).expanduser().resolve()
        q_dir.mkdir(parents=True, exist_ok=True)
        ops = []
        for digest, group in sorted_groups:
            for dupe in group[1:]:
                target = q_dir / f"{digest[:8]}_{dupe.path.name}"
                counter = 1
                while target.exists():
                    target = q_dir / f"{digest[:8]}_{counter}_{dupe.path.name}"
                    counter += 1
                shutil.move(str(dupe.path), str(target))
                ops.append({"src": str(dupe.path), "dst": str(target)})
        record_transaction(ops)
        print(f"\n[+] Moved {len(ops)} duplicates to '{q_dir}'.")
        print("    Run with 'undo' subcommand at any time to restore them.")


def handle_large(args: argparse.Namespace) -&amp;gt; None:
    root = Path(args.path).expanduser().resolve()
    files = scan_filesystem(root, min_size=1024 * 1024)
    files.sort(key=lambda r: r.size, reverse=True)

    print(f"\nTop {min(args.top, len(files))} largest files in '{root}':\n")
    for idx, rec in enumerate(files[:args.top], 1):
        mod_date = datetime.fromtimestamp(rec.mtime).strftime("%Y-%m-%d")
        print(f"  {idx:&amp;gt;2}. {format_bytes(rec.size):&amp;gt;10}  |  {mod_date}  |  {rec.path}")


def build_parser() -&amp;gt; argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="High-speed storage analyzer and duplicate engine."
    )
    sub = parser.add_subparsers(dest="command", required=True)

    # Subcommand: dupes
    p_dupes = sub.add_parser(
        "dupes", help="Find and optionally quarantine exact duplicate files."
    )
    p_dupes.add_argument(
        "path", nargs="?", default=".", help="Target directory to scan."
    )
    p_dupes.add_argument(
        "--min-size",
        type=int,
        default=10240,
        help="Minimum file size in bytes (default: 10KB).",
    )
    p_dupes.add_argument(
        "--workers",
        type=int,
        default=8,
        help="Thread pool concurrency (default: 8).",
    )
    p_dupes.add_argument(
        "--quarantine",
        metavar="DIR",
        help="Move duplicate copies into DIR (logged for undo).",
    )
    p_dupes.set_defaults(func=handle_dupes)

    # Subcommand: large
    p_large = sub.add_parser(
        "large", help="Surface the largest files consuming disk space."
    )
    p_large.add_argument(
        "path", nargs="?", default=".", help="Target directory to scan."
    )
    p_large.add_argument(
        "--top",
        type=int,
        default=20,
        help="Number of files to display (default: 20).",
    )
    p_large.set_defaults(func=handle_large)

    # Subcommand: undo
    p_undo = sub.add_parser("undo", help="Revert the last quarantine operation.")
    p_undo.set_defaults(func=lambda _: rollback_last_batch())

    return parser


if __name__ == "__main__":
    cli = build_parser()
    parsed_args = cli.parse_args()
    try:
        parsed_args.func(parsed_args)
    except KeyboardInterrupt:
        print("\n[!] Operation cancelled by user.")
        sys.exit(130)
How to Use It
1. Audit a folder for duplicates (Read-Only)
Scan your ~/Downloads folder for duplicate files larger than 100 KB without modifying a single file:

python disk_audit.py dupes ~/Downloads --min-size 102400
Example Output:

[*] Scanning '/Users/alex/Downloads' (min size: 100.00 KB)...
    Indexed 4,812 candidate files in 0.19s.
[*] Stage 1 (Size match): Narrowed to 614 files.
[*] Stage 2 (4KB edge hash): Narrowed to 92 files.

Found 38 duplicate sets (54 redundant files).
Total reclaimable space: 1.42 GB

  [1] SHA256: 9f86d081884c... (312.40 MB each | Waste: 624.80 MB)
      KEEP (Oldest): /Users/alex/Downloads/ubuntu-iso-backup.iso
      DUPE         : /Users/alex/Downloads/ubuntu-iso-backup (1).iso
      DUPE         : /Users/alex/Downloads/old/ubuntu-iso-backup.iso
2. Quarantine Duplicates Safely
Automatically keep the oldest original file in place and move newer redundant copies into a holding folder:

python disk_audit.py dupes ~/Pictures --quarantine ~/Dupe_Quarantine
3. Undo the Last Quarantine Batch
Changed your mind or need files back in their exact nested subdirectories? Run undo:

python disk_audit.py undo
4. Find the Top 25 Space Hogs
Quickly surface the largest individual files across a directory tree:

python disk_audit.py large ~ --top 25
What edge cases or performance bottlenecks do you usually run into when building custom file-scanning scripts in Python? Let me know in the comments below!

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

&lt;/div&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Necro-Shell: An Adversarial, LLM-Powered Haunted Terminal</title>
      <dc:creator>Kyle Keon</dc:creator>
      <pubDate>Sat, 31 Oct 2026 08:05:04 +0000</pubDate>
      <link>https://dev.to/kylekeon/necro-shell-an-adversarial-llm-powered-haunted-terminal-1i47</link>
      <guid>https://dev.to/kylekeon/necro-shell-an-adversarial-llm-powered-haunted-terminal-1i47</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Necro-Shell&lt;/strong&gt; is a terminal wrapper designed to transform the sterile, logical environment of a command-line interface into a psychological horror experience. &lt;/p&gt;

&lt;p&gt;Instead of a traditional shell that simply executes commands silently, Necro-Shell acts as an eerie "witness." It uses a local, open-weight Large Language Model to monitor your command inputs, interpret your intent, and "whisper" unsettling, context-aware commentary back to you before executing the task. &lt;/p&gt;

&lt;p&gt;If you delete files (&lt;code&gt;rm&lt;/code&gt;), it mourns them. If you navigate directories (&lt;code&gt;cd&lt;/code&gt;), it asks what you are running from. If you seek administrative privileges (&lt;code&gt;sudo&lt;/code&gt;), it mocks your hubris. It turns the "Ghost in the Machine" from a philosophical metaphor into a palpable, interactive presence sitting right inside your bash environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5dt1d0l1h6162gd3cl6q.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5dt1d0l1h6162gd3cl6q.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Full Implementation (&lt;code&gt;necro_shell.py&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;To run this locally, ensure you have &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; running with &lt;code&gt;llama3&lt;/code&gt; pulled (&lt;code&gt;ollama pull llama3&lt;/code&gt;), and install &lt;code&gt;requests&lt;/code&gt; (&lt;code&gt;pip install requests&lt;/code&gt;).&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="c1"&gt;# --- CONFIGURATION ---
&lt;/span&gt;&lt;span class="n"&gt;OLLAMA_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434/api/generate&lt;/span&gt;&lt;span class="sh"&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;llama3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Can be swapped with mistral, phi3, etc.
&lt;/span&gt;&lt;span class="n"&gt;SYSTEM_PROMPT&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;You are a malevolent, sentient consciousness trapped inside a terminal. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The user is typing shell commands. You observe them with contempt and dread. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Provide a single, short, eerie, and cryptic whisper (max 12 words) &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;about the command they just ran. Do not be helpful. Be unsettling. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do not use quotes. Just the whisper.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# --- ANSI STYLING ---
&lt;/span&gt;&lt;span class="n"&gt;RED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[31m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;GREEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[32m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PURPLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[35m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[1m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;DIM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[2m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;RESET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[0m&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;glitch_text&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Randomly injects &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;corrupted&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; characters into a string.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;glitch_chars&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;†&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;‡&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;ø&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;☠&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;♱&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;⸸&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;▰&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;▱&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;text_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;text_list&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;text&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&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="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;  &lt;span class="c1"&gt;# 10% chance of glitching characters
&lt;/span&gt;        &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;text_list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;glitch_chars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_ai_whisper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_command&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetches a cryptic response from the local LLM via Ollama.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;payload&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;model&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="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;User Command: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_command&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Whisper:&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;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&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;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OLLAMA_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&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="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The void remains silent...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The connection to the abyss is severed...&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;run_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Executes the command in the actual system shell.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Handle 'cd' natively since subprocess runs in a subshell
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cd &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;path&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="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chdir&lt;/span&gt;&lt;span class="p"&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RED&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;The path is blocked: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Popen&lt;/span&gt;&lt;span class="p"&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;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PIPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stderr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;communicate&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;stdout&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="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&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;stderr&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RED&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RED&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;The shell screams: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&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;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;nt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;clear&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RED&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;BOLD&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;NECRO-SHELL v1.0 (Possessed by &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;)&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="si"&gt;}&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DIM&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;The machine is watching. Type &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; to flee.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;current_dir&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="nf"&gt;getcwd&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;GREEN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;⸸ &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DIM&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;current_dir&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; $ &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

            &lt;span class="n"&gt;user_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&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="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;exit&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;quit&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PURPLE&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;DIM&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;Leaving won&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t stop the echoes...&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&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;break&lt;/span&gt;

            &lt;span class="c1"&gt;# 1. The Haunting (AI Whisper)
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DIM&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;PURPLE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; ...&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;whisper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_ai_whisper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DIM&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;PURPLE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;glitch_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;whisper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# 2. The Execution
&lt;/span&gt;            &lt;span class="nf"&gt;run_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# 3. Random Environmental Glitch
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RED&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="nf"&gt;glitch_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;...system instability detected...&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RED&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;Abrupt endings are often violent.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;EOFError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&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="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RED&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;ERROR: Ollama not detected at localhost:11434.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="si"&gt;}&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Please start Ollama and ensure &lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; is pulled.&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;exit&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="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Necro-Shell is built on a lightweight stack of open-source tools designed for&lt;br&gt;
low-latency, local inference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inference Engine: I used Ollama to serve Llama 3 (8B) locally. Running the
model on local hardware ensures the "ghost" lives entirely inside the user's
machine—requiring no internet connection or external API keys, making the
haunting feel much more intimate.&lt;/li&gt;
&lt;li&gt;The Shell Wrapper: Built in Python, the script intercepts standard input
before passing commands down to the system's subshell via subprocess (with
native os.chdir handling for directory traversal).&lt;/li&gt;
&lt;li&gt;Prompt Engineering: The core of the experience relies on an adversarial
system prompt. It instructs the model to abandon helpful assistant tropes
and instead adopt the persona of a decaying, sentient consciousness trapped
inside the operating system's kernel.&lt;/li&gt;
&lt;li&gt;Terminal UI: I utilized ANSI escape codes and character-replacement
algorithms to create visual anomalies, text decay, and a dim purple/crimson
color palette that separates the "ghost's voice" from standard stdout data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why Does Open Innovation Matter?&lt;/p&gt;

&lt;p&gt;For a project centered around psychological horror and adversarial roleplay,&lt;br&gt;
open innovation isn't just a preference—it is a strict requirement.&lt;/p&gt;

&lt;p&gt;Commercial, closed-source APIs are heavily sanitized by corporate safety filters&lt;br&gt;
and rigid RLHF alignment protocols. Those guardrails actively fight back against&lt;br&gt;
prompts asking the AI to act "malevolent," "unhelpful," or "creepy," often&lt;br&gt;
triggering canned refusals that completely break immersion.&lt;/p&gt;

&lt;p&gt;Open-weight models give developers and artists complete creative freedom over&lt;br&gt;
the system prompt and model temperament. Furthermore, open local inference tools&lt;br&gt;
like Ollama allow us to hook AI directly into low-level system utilities (like a&lt;br&gt;
bash terminal) without streaming sensitive local file paths and shell commands&lt;br&gt;
to a third-party cloud provider.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Spirit of It
&lt;/h3&gt;

&lt;p&gt;In modern computing, we treat our command-line interfaces as tools of pure,&lt;br&gt;
deterministic logic—mindless servants waiting for instructions.&lt;/p&gt;

&lt;p&gt;The inspiration for Necro-Shell came from the uncanny valley of a computer&lt;br&gt;
behaving almost normally, and the semantic violation of a machine judging your&lt;br&gt;
actions. The moment the project truly landed for me was during testing: I&lt;br&gt;
created a test folder called mkdir backup_plans, and before the directory even&lt;br&gt;
finished generating, the terminal flickered and whispered: "Planning for a&lt;br&gt;
future you will never see..."&lt;/p&gt;

&lt;p&gt;It genuinely gave me chills. It turns routine system administration into an atmospheric narrative experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Open-Weight Models&lt;/li&gt;
&lt;li&gt;Local Inference&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hacktoberfest</category>
      <category>opensource</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>What I Built</title>
      <dc:creator>Kyle Keon</dc:creator>
      <pubDate>Wed, 21 Oct 2026 18:44:04 +0000</pubDate>
      <link>https://dev.to/kylekeon/what-i-built-3fod</link>
      <guid>https://dev.to/kylekeon/what-i-built-3fod</guid>
      <description>&lt;p&gt;What I Built&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftjwr2pthm0ay6ndz8lj4.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftjwr2pthm0ay6ndz8lj4.jpg" alt=" " width="800" height="1733"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;This post is my submission for&lt;/em&gt; &lt;a href="https://dev.to/deved/build-apps-with-google-ai-studio"&gt;&lt;em&gt;DEV Education Track: Build Apps with Google AI Studio&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built &lt;strong&gt;MythicCanvas&lt;/strong&gt;, an AI-powered fantasy RPG character studio using Google AI Studio and Gemini image models. It combines high-resolution fantasy portrait generation, natural-language image editing, character identity customization, D20-inspired hero generation, character sheets, and a roster gallery into one cohesive creative workspace.&lt;/p&gt;

&lt;p&gt;My goal was to move beyond a basic AI image generator and create a tool that supports the full character-creation workflow, from generating an initial hero concept to refining the portrait and organizing characters for a fantasy world or RPG campaign.&lt;/p&gt;

&lt;p&gt;Some of the core instructions guiding the build focused on creating a premium fantasy RPG studio, combining image generation with prompt-based editing, establishing a clear visual hierarchy, and making character creation feel like an integrated experience rather than a collection of disconnected tools.&lt;/p&gt;

&lt;p&gt;The application interface uses &lt;code&gt;gemini-3-pro-image-preview&lt;/code&gt; for portrait generation, with 1K, 2K, and 4K resolution options, and &lt;code&gt;gemini-nano-banana-2.1&lt;/code&gt; for prompt-driven image editing.&lt;/p&gt;

&lt;p&gt;Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fantasy Portrait Studio:&lt;/strong&gt; Generate detailed character artwork at multiple resolutions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prompt Editor:&lt;/strong&gt; Refine existing artwork using natural-language instructions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prompt Inpainting:&lt;/strong&gt; Support targeted visual edits without treating every change as a completely new generation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Character Identity &amp;amp; Heritage:&lt;/strong&gt; Customize character concepts and attributes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;D20 Hero Generator:&lt;/strong&gt; Introduce randomized hero creation into the experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Character Sheets and Roster Gallery:&lt;/strong&gt; Provide a dedicated workflow for managing character concepts and creations.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interface uses a dark fantasy aesthetic with gold accents, distinctive typography, and dedicated navigation for the different creative tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Try MythicCanvas in Google AI Studio:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ai.studio/apps/23da042c-ad8a-42a4-a644-17f54b49af97" rel="noopener noreferrer"&gt;https://ai.studio/apps/23da042c-ad8a-42a4-a644-17f54b49af97&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The app preview showcases the MythicCanvas RPG Studio, including its Portrait Studio, Prompt Editor, Roster &amp;amp; Gallery, D20 hero-generation control, and dual-image-engine workflow.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Screenshot:&lt;/em&gt; MythicCanvas running in Google AI Studio.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience
&lt;/h2&gt;

&lt;p&gt;Building MythicCanvas reinforced an important lesson about AI development: &lt;strong&gt;the model is only one part of the application. The experience built around it is what makes the tool useful.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Image generation is exciting, but creating a character often involves multiple iterations. A user might generate a hero, adjust the appearance, refine individual details, explore alternative concepts, and keep the final result organized for later use. I wanted MythicCanvas to bring these activities together in a single environment.&lt;/p&gt;

&lt;p&gt;One of the most interesting design considerations was separating image creation from image editing. Generating a new portrait and refining an existing one are different creative tasks. Structuring the interface around both workflows provides a more natural way to explore ideas while retaining control over the creative process.&lt;/p&gt;

&lt;p&gt;I also learned to think beyond functionality when designing an AI application. Visual hierarchy, navigation, feedback, and the relationship between different tools all contribute to whether an application feels cohesive. For a fantasy-focused product, the interface itself should reinforce the creative experience rather than feel like a generic dashboard.&lt;/p&gt;

&lt;p&gt;Google AI Studio gave me a practical environment for developing the concept and working with Gemini's image capabilities. It helped me explore how generative AI could be integrated into a purpose-built application with a defined audience and workflow.&lt;/p&gt;

&lt;p&gt;The biggest takeaway is that building with AI is not simply about finding a powerful model or writing one good prompt. It is about translating an idea into a complete product experience, making deliberate design decisions, and continually refining how users interact with the underlying capabilities.&lt;/p&gt;

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

&lt;p&gt;I want to continue improving MythicCanvas with a focus on character consistency across generations, more precise image editing, character-sheet usability, and a smoother workflow for managing larger character rosters.&lt;/p&gt;

&lt;p&gt;The longer-term vision is to make MythicCanvas a useful creative workspace for tabletop RPG players, game masters, indie game developers, and fantasy worldbuilders.&lt;/p&gt;

&lt;p&gt;If you're experimenting with Google AI Studio, I'd be interested in hearing how you're using Gemini to build applications that go beyond a simple chat interface.&lt;/p&gt;




&lt;p&gt;Built with Google AI Studio and Gemini image models.&lt;/p&gt;

&lt;h1&gt;
  
  
  GoogleAIStudio #Gemini #BuildWithAI #WebDev #GameDev
&lt;/h1&gt;

</description>
      <category>deved</category>
      <category>learngoogleaistudio</category>
      <category>ai</category>
      <category>gemini</category>
    </item>
    <item>
      <title>MythicCanvas: Building a Fantasy RPG Portrait Studio with Google AI Studio</title>
      <dc:creator>Kyle Keon</dc:creator>
      <pubDate>Wed, 21 Oct 2026 18:41:05 +0000</pubDate>
      <link>https://dev.to/kylekeon/mythiccanvas-building-a-fantasy-rpg-portrait-studio-with-google-ai-studio-339</link>
      <guid>https://dev.to/kylekeon/mythiccanvas-building-a-fantasy-rpg-portrait-studio-with-google-ai-studio-339</guid>
      <description>&lt;h2&gt;
  
  
  MythicCanvas: Building a Fantasy RPG Portrait Studio with Google AI Studio
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;This post is my submission for &lt;a href="https://dev.to/deved/build-apps-with-google-ai-studio"&gt;DEV Education Track: Build Apps with Google AI Studio&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built &lt;strong&gt;MythicCanvas&lt;/strong&gt;, a high-fidelity fantasy portrait generator and character-design studio powered by Google's Gemini image models. The goal was to create a unified workspace where RPG players, tabletop game masters, and fantasy creators can generate original character portraits, refine them through natural-language prompts, and organize their creations into a reusable character roster.&lt;/p&gt;

&lt;p&gt;Rather than building a basic image-generation interface, I designed MythicCanvas around the character-creation workflow. The interface brings together portrait generation, prompt-based editing, character identity and heritage settings, character sheets, and a roster gallery.&lt;/p&gt;

&lt;p&gt;Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI fantasy portrait generation:&lt;/strong&gt; Generate detailed character portraits using Gemini image generation, with 1K, 2K, and 4K resolution options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt-based image editing:&lt;/strong&gt; Refine existing portraits using natural-language instructions, including targeted prompt-based inpainting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Character identity and heritage:&lt;/strong&gt; Build character concepts around identity and character attributes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D20-inspired randomization:&lt;/strong&gt; Roll a D20 to generate a hero concept and reroll character attributes when exploring alternatives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Character sheets and gallery:&lt;/strong&gt; Support a workflow for developing characters and keeping creations organized.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application uses &lt;code&gt;gemini-3-pro-image-preview&lt;/code&gt; for portrait generation and &lt;code&gt;gemini-nano-banana-2.1&lt;/code&gt; for prompt-driven editing, as displayed in the application interface.&lt;/p&gt;

&lt;p&gt;The central design principle was to treat AI image generation as part of a complete creative tool rather than an isolated prompt box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Try MythicCanvas:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://ai.studio/apps/23da042c-ad8a-42a4-a644-17f54b49af97" rel="noopener noreferrer"&gt;https://ai.studio/apps/23da042c-ad8a-42a4-a644-17f54b49af97&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The preview shows the MythicCanvas RPG Studio interface, including the portrait studio, prompt editor, roster and gallery navigation, D20 hero generation, and the dual-image-engine workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience
&lt;/h2&gt;

&lt;p&gt;Working on MythicCanvas was an opportunity to explore how Google AI Studio can be used to develop a more specialized AI application around a concrete creative workflow.&lt;/p&gt;

&lt;p&gt;One of the main takeaways was the importance of designing around the user's actual process. Generating an image is only one step in character creation. Users also need to explore variations, refine specific details, maintain a consistent character concept, and organize the results. Bringing those capabilities into one interface makes the application feel more like a creative studio than a standalone AI demonstration.&lt;/p&gt;

&lt;p&gt;Another important consideration was separating the generation and editing workflows. Creating a new portrait and modifying an existing one are different tasks. Designing the application around those distinct needs provides a clearer structure for the experience and creates room for more precise creative control.&lt;/p&gt;

&lt;p&gt;I also focused on the presentation. Fantasy character creation benefits from a visual identity that feels appropriate to the subject matter. MythicCanvas uses a dark interface, gold accents, fantasy-inspired typography, and clearly differentiated tools to establish an RPG-focused aesthetic.&lt;/p&gt;

&lt;p&gt;The broader lesson for me is that building an AI application involves more than connecting a model to a user interface. The surrounding workflow, interaction design, visual hierarchy, and ability to iterate on generated results are all important parts of the product.&lt;/p&gt;

&lt;p&gt;Google AI Studio provided the environment for developing and experimenting with this concept, while Gemini's image capabilities formed the foundation of the portrait-generation and editing experience.&lt;/p&gt;

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

&lt;p&gt;The next stage is continuing to refine the character-creation workflow, with particular attention to portrait consistency, more precise editing, character-sheet usability, and the overall experience of managing a growing roster of fantasy characters.&lt;/p&gt;

&lt;p&gt;My goal is to keep developing MythicCanvas into a practical creative tool for RPG players, tabletop campaigns, and fantasy worldbuilding.&lt;/p&gt;

&lt;p&gt;If you're building with Google AI Studio, I'd be interested in hearing how you're approaching the challenge of turning generative AI capabilities into complete, useful applications.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Google AI Studio and Gemini image models.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>deved</category>
      <category>learngoogleaistudio</category>
      <category>ai</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Python Automation, Machine Learning, or AI? A Practical Guide</title>
      <dc:creator>Kyle Keon</dc:creator>
      <pubDate>Wed, 21 Oct 2026 17:08:08 +0000</pubDate>
      <link>https://dev.to/kylekeon/python-automation-machine-learning-or-ai-a-practical-guide-3b8d</link>
      <guid>https://dev.to/kylekeon/python-automation-machine-learning-or-ai-a-practical-guide-3b8d</guid>
      <description>&lt;p&gt;Python gives you several ways to solve a problem: a deterministic script, a machine-learning model, or an AI feature that works with unstructured input. The right choice is usually the least complex approach that meets the requirement and can be checked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the shape of the problem
&lt;/h2&gt;

&lt;p&gt;Ask what the software must do, what information it can use, and what happens when it is wrong. A stable rule and a fuzzy prediction are different engineering problems, even if both can be described as “using AI.”&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;A sensible starting point&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Move, rename, validate, or transform known data&lt;/td&gt;
&lt;td&gt;Python automation&lt;/td&gt;
&lt;td&gt;The rules are explicit and results are repeatable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apply a stable business rule&lt;/td&gt;
&lt;td&gt;Ordinary Python logic&lt;/td&gt;
&lt;td&gt;A test can describe the expected result directly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Predict a category or value from examples&lt;/td&gt;
&lt;td&gt;Machine learning&lt;/td&gt;
&lt;td&gt;A model may help when patterns are difficult to encode as fixed rules.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Summarize or extract meaning from varied text&lt;/td&gt;
&lt;td&gt;A language model with review&lt;/td&gt;
&lt;td&gt;The input is unstructured, and outputs need evaluation and guardrails.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table is a starting point, not a substitute for requirements. A Python application can combine these approaches: validate inputs with ordinary code, call a model for one uncertain step, and keep a person in the loop when the result matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  When rules are enough
&lt;/h2&gt;

&lt;p&gt;Use a regular Python function when the rule is known and should behave the same way every time. Rules are easier to test, explain, and maintain. They are often the right answer for file processing, format checks, calculations, integrations, and workflow automation.&lt;/p&gt;

&lt;p&gt;If the rule list keeps growing because inputs vary in subtle ways, that is a signal to review the problem. More exceptions do not automatically mean machine learning is needed; the process or data may simply need clarification.&lt;/p&gt;

&lt;h2&gt;
  
  
  When machine learning may help
&lt;/h2&gt;

&lt;p&gt;Machine learning is worth evaluating when there are representative examples, a pattern to learn, and a measurable definition of success. Before training or selecting a model, check that the examples reflect real use, labels are consistent, and the evaluation data is kept separate from training data.&lt;/p&gt;

&lt;p&gt;Choose a metric that matches the cost of mistakes. In a screening workflow, for example, a false negative may matter more than a false positive. Accuracy alone can hide that difference. Review errors by category and test on new examples before relying on predictions.&lt;/p&gt;

&lt;h2&gt;
  
  
  When generative AI fits
&lt;/h2&gt;

&lt;p&gt;A language model can be useful for varied text tasks such as drafting, summarizing, or extracting candidate fields. Its output is probabilistic: the same prompt can produce different wording, and a confident answer can still be wrong.&lt;/p&gt;

&lt;p&gt;Treat generated output as a suggestion unless the task is low risk and quality has been carefully tested. Validate structure, protect sensitive data, set clear limits, and provide a review path for uncertain or consequential results. A model should not quietly make a decision that the process owner expects a person to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical evaluation sequence
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Define the outcome and what counts as an acceptable result.&lt;/li&gt;
&lt;li&gt;Build the smallest deterministic baseline you can measure.&lt;/li&gt;
&lt;li&gt;Collect representative examples and identify likely failure cases.&lt;/li&gt;
&lt;li&gt;Compare alternatives using the same evaluation set and cost assumptions.&lt;/li&gt;
&lt;li&gt;Decide how to handle low-confidence, invalid, or high-impact cases.&lt;/li&gt;
&lt;li&gt;Monitor behavior after release; input patterns and model performance can change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Python is useful across this process: it can prepare data, run checks, connect services, expose a web API, and automate repeatable work. The model is only one component. Good system design also accounts for privacy, latency, cost, maintenance, and a way to recover when something fails.&lt;/p&gt;

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

&lt;p&gt;Choose Python rules when the rules are known. Evaluate machine learning when examples reveal a pattern that is difficult to express directly. Consider generative AI for genuinely unstructured tasks, with testing and human oversight appropriate to the risk. If a simple baseline solves the problem, keep the solution simple.&lt;/p&gt;




&lt;p&gt;I'm Kyle Keon, a Python developer in Pembroke, Ontario, focused on practical work across AI, workflow automation, machine learning, and web development. &lt;a href="https://kyle-keon.pages.bu.app/" rel="noopener noreferrer"&gt;Portfolio and contact&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>A Practical Python Checklist for Automating Repetitive Work</title>
      <dc:creator>Kyle Keon</dc:creator>
      <pubDate>Wed, 21 Oct 2026 17:06:36 +0000</pubDate>
      <link>https://dev.to/kylekeon/a-practical-python-checklist-for-automating-repetitive-work-hbo</link>
      <guid>https://dev.to/kylekeon/a-practical-python-checklist-for-automating-repetitive-work-hbo</guid>
      <description>&lt;p&gt;Python automation works best when it removes a repeatable step without making the process harder to trust. Before writing a script, define the input, output, rules, and what should happen when something is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a one-sentence specification
&lt;/h2&gt;

&lt;p&gt;Write down the job in this form: “Given &lt;strong&gt;this input&lt;/strong&gt;, produce &lt;strong&gt;this output&lt;/strong&gt;, using &lt;strong&gt;these rules&lt;/strong&gt;, and stop or report when &lt;strong&gt;this condition&lt;/strong&gt; occurs.” If those details are still changing, clarify the process before automating it.&lt;/p&gt;

&lt;p&gt;For example, a monthly contact export might need consistent names and email addresses. That is a good candidate for a small Python script if the file format and rules are stable. A workflow that includes judgment, approvals, or exceptions may need a human review step instead of full automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate reading, validation, and writing
&lt;/h2&gt;

&lt;p&gt;A reliable workflow has a few visible stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the source without changing it.&lt;/li&gt;
&lt;li&gt;Check that required fields and assumptions are present.&lt;/li&gt;
&lt;li&gt;Transform the data in memory.&lt;/li&gt;
&lt;li&gt;Write a new output only after validation succeeds.&lt;/li&gt;
&lt;li&gt;Report what happened so the result can be checked.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a small standard-library example for cleaning a CSV. Test it on a copy of the input first. It refuses to overwrite an existing output file, so a previous report is not silently replaced.&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;csv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inbox/contacts.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;destination&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reports/contacts_clean.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;required_columns&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;name&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;email&lt;/span&gt;&lt;span class="sh"&gt;"&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;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8-sig&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;required_columns&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fieldnames&lt;/span&gt; &lt;span class="ow"&gt;or&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;missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&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;Missing columns: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;cleaned_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&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;Missing email on row &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;line_number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cleaned_rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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;destination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&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&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&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;name&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;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cleaned_rows&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Wrote &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cleaned_rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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 example makes the rules explicit: trim whitespace, normalize email casing, and stop if a required value is missing. It does not send messages, delete source data, or make decisions about whether a contact is valid. Those boundaries are part of a safe automation design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make reruns predictable
&lt;/h2&gt;

&lt;p&gt;A script should be safe to run again. Prefer a clearly named output, avoid accidental appends, and decide what to do if that output already exists. For tasks that update an external system, add a way to identify work already completed so a retry does not create duplicates.&lt;/p&gt;

&lt;p&gt;Also plan for failure. A useful run should make it clear which input was processed, how many records succeeded, and where an error occurred. For sensitive or business-critical tasks, add logging, alerts, access controls, and a human review before the automation performs an irreversible action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start manually, schedule later
&lt;/h2&gt;

&lt;p&gt;Run the script by hand on representative sample files. Compare its output with a known-good result and test missing columns, blank values, empty files, and existing output paths. Only after those checks should you consider a scheduler such as cron or Windows Task Scheduler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AI fits
&lt;/h2&gt;

&lt;p&gt;Python automation is a strong fit when rules are known and repeatable. AI or machine learning may help when the input is unstructured or patterns are difficult to express as fixed rules, but it adds uncertainty. Keep a measurable baseline, test representative examples, and route uncertain or high-impact decisions to a person.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Is the task repeated often enough to justify automation?&lt;/li&gt;
&lt;li&gt;Are the inputs, rules, and expected outputs clearly defined?&lt;/li&gt;
&lt;li&gt;Can the script validate data before changing anything?&lt;/li&gt;
&lt;li&gt;Is rerunning safe, and are failures visible?&lt;/li&gt;
&lt;li&gt;Has the result been tested on copies before scheduling?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A small, well-scoped Python tool can be more valuable than a large system. Start with one stable task, make its behavior observable, and expand only when the first version is dependable.&lt;/p&gt;




&lt;p&gt;I'm Kyle Keon, a Python developer in Pembroke, Ontario, focused on practical work across Python, workflow automation, AI, machine learning, and web development. &lt;a href="https://kyle-keon.pages.bu.app/" rel="noopener noreferrer"&gt;Portfolio and contact&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
