<?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: Devaansh Jaiswal</title>
    <description>The latest articles on DEV Community by Devaansh Jaiswal (@devaansh_jaiswal_39d2764d).</description>
    <link>https://dev.to/devaansh_jaiswal_39d2764d</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%2F4172098%2F6e2adfcd-08ad-4191-9297-f9c13ab42810.jpg</url>
      <title>DEV Community: Devaansh Jaiswal</title>
      <link>https://dev.to/devaansh_jaiswal_39d2764d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devaansh_jaiswal_39d2764d"/>
    <language>en</language>
    <item>
      <title>Ahhh...There is something Creepy in My Terminal !</title>
      <dc:creator>Devaansh Jaiswal</dc:creator>
      <pubDate>Sat, 31 Oct 2026 20:07:16 +0000</pubDate>
      <link>https://dev.to/devaansh_jaiswal_39d2764d/ahhhthere-is-something-creepy-in-my-terminal--225p</link>
      <guid>https://dev.to/devaansh_jaiswal_39d2764d/ahhhthere-is-something-creepy-in-my-terminal--225p</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;h1&gt;
  
  
  What I Built
&lt;/h1&gt;

&lt;p&gt;So its Halloween! and a perfect Hacktoberfest'26 Dev Challenge Based on the theme "Ghost in the Machine", it was a cool week thought to keep it simple so i built a terminal ghost which comments on the commands you run on your terminal in a very eerie and creepy way ,look by yourself ~~ &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%2F8b4wmzm1a1j3vkecibdg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8b4wmzm1a1j3vkecibdg.png" alt="1st Look of the Ghost-Shell" width="800" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The best part? Beneath all the spooky interactions, your original commands still execute normally. The ghost is here to entertain, not get in your way.&lt;/p&gt;

&lt;p&gt;I didn't want to build another productivity tool. I wanted to make something developers would smile at every time they opened their terminal.&lt;br&gt;
Because even developers deserve a little supernatural company. 👻&lt;/p&gt;
&lt;h1&gt;
  
  
  Demo
&lt;/h1&gt;

&lt;p&gt;Here is a Video Demo which I recorded while I was pushing the code to GitHub :&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/2UoqfXD2EP8" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Code
&lt;/h1&gt;

&lt;p&gt;GitHub Repository for this Project : &lt;a href="https://github.com/Vaansh-GitHub/Ghost-Shell.git" rel="noopener noreferrer"&gt;https://github.com/Vaansh-GitHub/Ghost-Shell.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Its open to contribute hence do feel free to make any changes to this project&lt;/p&gt;

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

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;GhostShell is an AI-powered, voice-enabled terminal companion built for the DEV Hacktoberfest Week 4 challenge, &lt;em&gt;Ghost in the Machine&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The idea behind GhostShell was simple: developers spend a significant amount of time working in terminals, executing commands such as &lt;code&gt;git status&lt;/code&gt;, &lt;code&gt;npm install&lt;/code&gt;, &lt;code&gt;git push&lt;/code&gt;, and &lt;code&gt;npm run dev&lt;/code&gt;. However, the traditional terminal experience is mostly functional and lacks personality.&lt;/p&gt;

&lt;p&gt;I wanted to make this experience more interactive by introducing a virtual ghost that reacts to the commands developers execute.&lt;/p&gt;

&lt;p&gt;GhostShell wraps the regular command-line experience with an AI-powered ghost that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understands the command entered by the developer.&lt;/li&gt;
&lt;li&gt;Generates a short, contextual, spooky dialogue.&lt;/li&gt;
&lt;li&gt;Displays an ASCII ghost with a green Matrix-inspired CRT effect.&lt;/li&gt;
&lt;li&gt;Converts the dialogue into speech using ElevenLabs.&lt;/li&gt;
&lt;li&gt;Plays the generated audio through the system's audio player.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important design decision was that the ghost should never take control of the developer's terminal. It only reacts to commands; it does not generate or execute them.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Technology Stack
&lt;/h2&gt;

&lt;p&gt;I chose Node.js because the application needed to interact directly with the operating system, execute shell commands, handle terminal input, and manage audio playback.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node.js 18+&lt;/td&gt;
&lt;td&gt;Application runtime and process management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JavaScript ES Modules&lt;/td&gt;
&lt;td&gt;Modular application architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chalk 5.4.1&lt;/td&gt;
&lt;td&gt;ANSI terminal colours and styling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dotenv 16.4.7&lt;/td&gt;
&lt;td&gt;Environment configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Workers AI&lt;/td&gt;
&lt;td&gt;Serverless AI inference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Gemma 4 26B&lt;/td&gt;
&lt;td&gt;Contextual ghost dialogue generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ElevenLabs Flash v2.5&lt;/td&gt;
&lt;td&gt;Text-to-speech generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FFmpeg / ffplay&lt;/td&gt;
&lt;td&gt;Audio playback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PowerShell MediaPlayer&lt;/td&gt;
&lt;td&gt;Native Windows audio fallback&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One important implementation choice was to avoid unnecessary frameworks and databases. GhostShell is a lightweight CLI application, so I used Node.js built-in modules such as &lt;code&gt;child_process&lt;/code&gt;, &lt;code&gt;readline/promises&lt;/code&gt;, &lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt;, and &lt;code&gt;os&lt;/code&gt; wherever possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Project Architecture
&lt;/h2&gt;

&lt;p&gt;GhostShell follows a modular, pipeline-based architecture. The application is divided into separate layers for command execution, AI dialogue generation, terminal presentation, and voice synthesis.&lt;/p&gt;

&lt;p&gt;The architecture keeps the operating system execution layer independent from the AI layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  High-level architecture
&lt;/h3&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A["Developer enters command"] --&amp;gt; B["CLI Input"]
    B --&amp;gt; C["Session Manager"]

    C --&amp;gt; D["Command Executor"]
    D --&amp;gt; E["Operating System Shell"]
    E --&amp;gt; F["Actual Terminal Output"]
    E --&amp;gt; G["Exit Code"]

    G --&amp;gt; H["AI Dialogue Generator"]
    H --&amp;gt; I["Prompt Builder"]
    I --&amp;gt; J["Cloudflare Workers AI"]
    J --&amp;gt; K["Google Gemma"]
    K --&amp;gt; H

    H --&amp;gt; L["Dialogue Sanitization"]
    L --&amp;gt; M["Ghost UI"]
    L --&amp;gt; N["ElevenLabs TTS"]

    M --&amp;gt; O["Terminal Display"]
    N --&amp;gt; P["Audio Player"]
    P --&amp;gt; Q["ffplay / Windows MediaPlayer"]&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  Folder structure
&lt;/h3&gt;

&lt;p&gt;The project is organised into the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ghostshell/
│
├── assets/
│   └── ghost.txt
│
├── src/
│   ├── ai/
│   │   ├── cloudflare.js
│   │   ├── dialogueGenerator.js
│   │   └── prompts.js
│   │
│   ├── cli/
│   │   ├── input.js
│   │   └── session.js
│   │
│   ├── config/
│   │   └── settings.js
│   │
│   ├── executor/
│   │   └── commandExecutor.js
│   │
│   ├── ui/
│   │   ├── dialogue.js
│   │   ├── ghost.js
│   │   └── terminal.js
│   │
│   ├── utils/
│   │   └── logger.js
│   │
│   ├── voice/
│   │   ├── audioPlayer.js
│   │   └── elevenlabs.js
│   │
│   └── index.js
│
├── .env.example
├── .gitignore
├── package.json
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Each module has a clearly defined responsibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/index.js&lt;/code&gt;: Application entry point, CLI argument handling, and signal management.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/cli/input.js&lt;/code&gt;: Reads commands from the developer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/cli/session.js&lt;/code&gt;: Coordinates the complete execution pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/executor/commandExecutor.js&lt;/code&gt;: Executes the original shell command.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/ai/prompts.js&lt;/code&gt;: Defines the ghost's personality and contextual prompts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/ai/cloudflare.js&lt;/code&gt;: Communicates with Cloudflare Workers AI.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/ai/dialogueGenerator.js&lt;/code&gt;: Generates, sanitizes, and provides fallback dialogue.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/voice/elevenlabs.js&lt;/code&gt;: Converts text into speech.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/voice/audioPlayer.js&lt;/code&gt;: Plays generated audio across operating systems.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/ui/ghost.js&lt;/code&gt;: Renders the ASCII ghost with Matrix-inspired colours.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/ui/dialogue.js&lt;/code&gt;: Displays the ghost's dialogue and emotional cues.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/config/settings.js&lt;/code&gt;: Centralises configuration and environment variables.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  4. Step-by-Step Implementation
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: Initialising the Node.js project
&lt;/h3&gt;

&lt;p&gt;I started by creating a Node.js project using ECMAScript Modules.&lt;/p&gt;

&lt;p&gt;The project requires Node.js 18 or above because it uses native &lt;code&gt;fetch&lt;/code&gt;, asynchronous readline interfaces, and modern JavaScript features.&lt;/p&gt;

&lt;p&gt;The main dependencies are deliberately minimal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;package.json&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ghostshell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=18.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"chalk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.4.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dotenv"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^16.4.7"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node src/index.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I used Chalk for terminal styling and Dotenv for environment configuration. The remaining functionality relies primarily on Node.js built-in modules.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Separating command execution from AI processing
&lt;/h3&gt;

&lt;p&gt;This is probably the most important architectural decision in GhostShell.&lt;/p&gt;

&lt;p&gt;I did not want to build an AI agent that could independently generate and execute terminal commands. Instead, the developer remains completely responsible for the command being executed.&lt;/p&gt;

&lt;p&gt;The command executor receives the original command entered by the developer and executes it using Node.js's &lt;code&gt;child_process&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;src/executor/commandExecutor.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;spawn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;executeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawCommand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawCommand&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;exitCode&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inherit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;windowsHide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to start command:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;exitCode&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="nx"&gt;error&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;exitCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Important concepts used here:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;spawn()&lt;/code&gt;: Creates a child process to execute the command.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shell: true&lt;/code&gt;: Uses the platform's shell to interpret the command.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stdio: "inherit"&lt;/code&gt;: Allows the child process to use the same terminal input and output streams.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;close&lt;/code&gt; event: Captures the command's exit status.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using inherited standard streams is particularly useful because it preserves live terminal output and supports interactive commands.&lt;/p&gt;

&lt;p&gt;For example, commands such as &lt;code&gt;git status&lt;/code&gt;, &lt;code&gt;npm install&lt;/code&gt;, and other shell operations can display their actual output directly in the terminal.&lt;/p&gt;

&lt;p&gt;The AI never receives control of this executor. It only receives the command as context for generating dialogue.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Security distinction:&lt;/em&gt; GhostShell executes shell commands intentionally entered by the developer. The AI-generated dialogue is not passed into the shell. As with any shell wrapper using &lt;code&gt;shell: true&lt;/code&gt;, users should only execute commands they trust.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Building the ghost's personality with prompt engineering
&lt;/h3&gt;

&lt;p&gt;Once command execution was implemented, I worked on making the ghost respond intelligently.&lt;/p&gt;

&lt;p&gt;A generic random sentence would quickly become repetitive. I wanted the dialogue to depend on what the developer had actually executed and whether the command succeeded.&lt;/p&gt;

&lt;p&gt;For this, I created a separate prompt module.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;src/ai/prompts.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GHOST_SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
You are Ghost, a mischievous digital phantom
haunting a developer's terminal.

Your personality:
- Mysterious, creepy and humorous.
- Slightly sarcastic and dramatic.
- Never genuinely threatening.

Rules:
- Generate only one short sentence.
- React contextually to the developer's command.
- Use emotional delivery tags such as:
  [whispers], [sinister chuckle], [raspy laugh].
- Never generate shell commands or code.
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildGhostPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;statusNote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exitCode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exitCode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`The command failed with exit code &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exitCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The command completed successfully.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
    The developer just executed: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"

    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;statusNote&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

    Respond with a short, witty, creepy ghost reaction.
    Include expressive voice delivery tags.
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This creates two distinct levels of instructions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The system prompt defines the ghost's personality and behavioural boundaries.&lt;/li&gt;
&lt;li&gt;The user prompt provides the actual command and its execution status.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Possible ghost reaction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checking your repository again, mortal?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm install&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Feeding me dependencies again? Delicious!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git push&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sending your secrets into the great unknown?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed command&lt;/td&gt;
&lt;td&gt;Oh dear... even the spirits rejected that one!&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Step 4: Integrating Google Gemma through Cloudflare Workers AI
&lt;/h3&gt;

&lt;p&gt;For dynamic dialogue generation, I integrated Google Gemma 4 26B through Cloudflare Workers AI.&lt;/p&gt;

&lt;p&gt;I chose this approach because it allowed me to access a hosted model without needing to run a large language model locally on the developer's machine.&lt;/p&gt;

&lt;p&gt;I used Node.js's native &lt;code&gt;fetch()&lt;/code&gt; API to communicate with Cloudflare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;src/ai/cloudflare.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="s2"&gt;`https://api.cloudflare.com/client/v4/accounts/`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/ai/run/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Cloudflare API error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;choices&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="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cloudflare AI error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The model identifier is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@cf/google/gemma-4-26b-a4b-it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;An interesting implementation detail is the response parser.&lt;/p&gt;

&lt;p&gt;Cloudflare Workers AI can return different response structures depending on the model interface. Therefore, I handle both the chat-completion response and the legacy response format.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;choices&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="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This makes the client more tolerant of different response formats.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5: Sanitising AI output and implementing fallbacks
&lt;/h3&gt;

&lt;p&gt;An AI model may return text surrounded by quotation marks, Markdown formatting, or unnecessary line breaks.&lt;/p&gt;

&lt;p&gt;Since the generated dialogue is displayed in the terminal and passed to a TTS service, I wanted to keep it clean and readable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;src/ai/dialogueGenerator.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sanitizeDialogue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;"'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;$/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/``&lt;/span&gt;&lt;span class="err"&gt;`
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="s2"&gt;```/g, "")
    .replace(/`&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="s2"&gt;`]+)`&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\r?\n&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I also implemented an offline fallback mechanism.&lt;/p&gt;

&lt;p&gt;If Cloudflare is unavailable, credentials are missing, or the request fails, GhostShell can still respond using predefined dialogue.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getFallbackDialogue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exitCode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exitCode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[sinister chuckle] Oh dear... the spirits rejected your command!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fallbackDialogues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;git status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[whispers] Checking your repository again, mortal...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm install&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[sinister chuckle] More dependencies to feed me!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fallbackDialogues&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nx"&gt;normalized&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="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[eerie whisper] Another command... another mystery.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is important because an external AI service should not become a single point of failure for a terminal application.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 6: Integrating ElevenLabs for voice generation
&lt;/h3&gt;

&lt;p&gt;Text alone wasn't enough. I wanted the ghost to actually speak.&lt;/p&gt;

&lt;p&gt;For this, I integrated ElevenLabs Text-to-Speech using its REST API.&lt;/p&gt;

&lt;p&gt;The voice configuration was tuned to produce a more expressive and theatrical delivery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;src/voice/elevenlabs.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="s2"&gt;`https://api.elevenlabs.io/v1/text-to-speech/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;selectedVoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xi-api-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio/mpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;voice_settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;stability&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;similarity_boost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;use_speaker_boost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`ElevenLabs API error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arrayBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The settings have different purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;stability: 0.32&lt;/code&gt;: Encourages more variation and expressive delivery.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;similarity_boost: 0.82&lt;/code&gt;: Helps preserve the selected voice's characteristics.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;style: 0.50&lt;/code&gt;: Adds stylistic variation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;use_speaker_boost: true&lt;/code&gt;: Enhances voice presence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dialogue also includes emotional cues such as &lt;code&gt;[whispers]&lt;/code&gt; and &lt;code&gt;[sinister chuckle]&lt;/code&gt;, giving the TTS model additional delivery context.&lt;/p&gt;

&lt;p&gt;I initially used ElevenLabs Flash v2.5 because responsiveness was important for an interactive CLI application.&lt;/p&gt;

&lt;p&gt;I also added handling for HTTP 402 errors, which can occur when an account or selected voice is not available under the current API plan. Instead of crashing, GhostShell displays guidance about using a supported premade voice.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 7: Building a cross-platform audio player
&lt;/h3&gt;

&lt;p&gt;After receiving the MP3 data from ElevenLabs, the next challenge was playing it directly from a Node.js CLI application.&lt;/p&gt;

&lt;p&gt;Rather than depending on native Node.js audio packages that may require C++ compilation, I used external audio utilities.&lt;/p&gt;

&lt;p&gt;The audio player follows two paths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Try &lt;code&gt;ffplay&lt;/code&gt;, which is part of FFmpeg.&lt;/li&gt;
&lt;li&gt;If it is unavailable on Windows, use PowerShell and the native .NET &lt;code&gt;MediaPlayer&lt;/code&gt; class.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;src/voice/audioPlayer.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;playerBinary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-nodisp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-autoexit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-loglevel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quiet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;filePath&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ignore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;windowsHide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Audio binary was not available.&lt;/span&gt;
  &lt;span class="c1"&gt;// Try the native Windows player.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;win32&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playWindowsNative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Windows fallback uses PowerShell to invoke:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PresentationCore&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$player&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Windows.Media.MediaPlayer&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$player&lt;/span&gt;&lt;span class="o"&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;System.Uri&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$player&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Play&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The implementation also stores temporary MP3 files under the operating system's temporary directory and removes them after playback.&lt;/p&gt;

&lt;p&gt;This avoids leaving generated audio files scattered throughout the developer's project.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 8: Designing the terminal ghost
&lt;/h3&gt;

&lt;p&gt;I wanted the application to have a recognisable visual identity instead of printing ordinary text.&lt;/p&gt;

&lt;p&gt;The ghost is stored as ASCII art in:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;assets/ghost.txt&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The rendering logic is implemented in:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/ui/ghost.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Rather than colouring every character identically, I implemented a multi-tier Matrix-inspired shading system.&lt;/p&gt;

&lt;p&gt;The idea is to assign different shades of green to different characters and spatial regions.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lineIdx&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;lineIdx&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;colIdx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;colIdx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#75ffaa&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Other character groups use different colours:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Visual element&lt;/th&gt;
&lt;th&gt;Colour&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Glowing eyes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#75ffaa&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lantern flame&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#88ffbb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lantern frame&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#00ff66&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prominent matrix characters&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#00ee55&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloak folds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#00aa33&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background particles&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#005511&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rendering algorithm processes the ASCII image character by character, checking its row, column, and character type to determine its colour.&lt;/p&gt;

&lt;p&gt;Its time complexity is (O(R \times C)), where (R) represents the number of rows and (C) represents the number of columns.&lt;/p&gt;

&lt;p&gt;This keeps the visual rendering lightweight.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 9: Styling the ghost's dialogue
&lt;/h3&gt;

&lt;p&gt;The ghost's dialogue contains emotional tags that should look different from the actual spoken sentence.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[sinister chuckle]&lt;/code&gt; should have a different appearance from the dialogue itself.&lt;/p&gt;

&lt;p&gt;I implemented this using a regular expression and Chalk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;src/ui/dialogue.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;styleEmotionTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;rawText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(\[[^\]]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\*[^&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\*)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;magentaBright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderDialogue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;magentaBright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[Ghost] Ghost:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styledContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;styleEmotionTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;speech&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chalk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cyanBright&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styledContent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;speech&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This creates a more immersive terminal presentation by separating the ghost's emotional expressions from its actual words.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Bringing Everything Together
&lt;/h2&gt;

&lt;p&gt;Once the individual modules were implemented, I connected them through the session manager.&lt;/p&gt;

&lt;p&gt;This is the central part of GhostShell because it coordinates command execution, AI generation, visual rendering, and audio playback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;src/cli/session.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userCommand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;userCommand&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;userCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;handleBuiltinCommands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userCommand&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&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="c1"&gt;// 1. Execute the original command first.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exitCode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;executeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userCommand&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Generate contextual ghost dialogue.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dialogue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateDialogue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;userCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exitCode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Display the ghost.&lt;/span&gt;
    &lt;span class="nf"&gt;renderGhost&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Display its dialogue.&lt;/span&gt;
    &lt;span class="nf"&gt;renderDialogue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dialogue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 5. Generate and play the voice.&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;muted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;elevenlabsVoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synthesizeSpeech&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dialogue&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;audioPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audioBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Session processing error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;renderDivider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The complete lifecycle is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The developer enters a command.&lt;/li&gt;
&lt;li&gt;GhostShell executes the original command.&lt;/li&gt;
&lt;li&gt;The process exit code is captured.&lt;/li&gt;
&lt;li&gt;The command and exit status are sent to the dialogue generator.&lt;/li&gt;
&lt;li&gt;Gemma generates a contextual reaction through Cloudflare.&lt;/li&gt;
&lt;li&gt;The response is sanitised.&lt;/li&gt;
&lt;li&gt;The ASCII ghost is displayed.&lt;/li&gt;
&lt;li&gt;The dialogue appears in the terminal.&lt;/li&gt;
&lt;li&gt;ElevenLabs generates speech.&lt;/li&gt;
&lt;li&gt;The audio player plays the generated MP3.&lt;/li&gt;
&lt;li&gt;The session displays a divider and waits for the next command.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The command is executed before AI processing, so a slow AI response does not delay the initial execution of the developer's command.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Interactive Terminal and CLI Controls
&lt;/h2&gt;

&lt;p&gt;I also wanted GhostShell to behave like a proper terminal environment rather than a program that accepts only one command.&lt;/p&gt;

&lt;p&gt;For this, I used Node.js's &lt;code&gt;readline/promises&lt;/code&gt; module to create an asynchronous input loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;src/cli/session.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;startSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;renderBanner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The application also supports built-in controls:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ghost --help&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Displays available options&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ghost --version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Displays application version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ghost --mute&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toggles ghost voice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;clear&lt;/code&gt; / &lt;code&gt;cls&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Clears the terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;exit&lt;/code&gt; / &lt;code&gt;quit&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Closes GhostShell&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The application supports two main launch modes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interactive mode:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Single-command mode:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node src/index.js git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This makes GhostShell useful both as an interactive terminal companion and as a command prefix.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Configuration and Secret Management
&lt;/h2&gt;

&lt;p&gt;Since GhostShell communicates with external APIs, I needed a centralised way to manage credentials and configuration.&lt;/p&gt;

&lt;p&gt;I used Dotenv and implemented a two-level configuration loader.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;src/config/settings.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;envPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;envPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;envPath&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;envLocalPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env.local&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;envLocalPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;envLocalPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;override&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;.env.local&lt;/code&gt; file takes precedence over &lt;code&gt;.env&lt;/code&gt;, allowing developers to maintain private credentials locally.&lt;/p&gt;

&lt;p&gt;The environment configuration uses variables such as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLOUDFLARE_ACCOUNT_ID=your_account_id
CLOUDFLARE_API_TOKEN=your_api_token
CLOUDFLARE_AI_MODEL=@cf/google/gemma-4-26b-a4b-it

ELEVENLABS_API_KEY=your_api_key
ELEVENLABS_VOICE_ID=your_voice_id
ELEVENLABS_MODEL_ID=eleven_flash_v2_5

GHOST_MUTE=false
GHOST_AUDIO_PLAYER=ffplay
LOG_LEVEL=info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These are placeholders, not actual credentials.&lt;/p&gt;

&lt;p&gt;I also added &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;.env.local&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt; so that local credentials are not accidentally committed to the repository.&lt;/p&gt;
&lt;h2&gt;
  
  
  8. Database and Storage Decisions
&lt;/h2&gt;

&lt;p&gt;GhostShell intentionally does not use a database.&lt;/p&gt;

&lt;p&gt;Since it is a lightweight command-line companion, there is no need to maintain user accounts, sessions, repositories, or persistent application records.&lt;/p&gt;

&lt;p&gt;This decision provides several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No database installation or configuration.&lt;/li&gt;
&lt;li&gt;No database connection overhead.&lt;/li&gt;
&lt;li&gt;No schema migrations.&lt;/li&gt;
&lt;li&gt;No persistent command history stored by GhostShell.&lt;/li&gt;
&lt;li&gt;A smaller and simpler application architecture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are only two relevant file-storage mechanisms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;assets/ghost.txt&lt;/code&gt; stores the static ASCII ghost artwork.&lt;/li&gt;
&lt;li&gt;The operating system's temporary directory stores generated MP3 files during playback.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The temporary audio files are removed after playback.&lt;/p&gt;
&lt;h2&gt;
  
  
  9. Error Handling and Reliability
&lt;/h2&gt;

&lt;p&gt;I wanted GhostShell to remain usable even when external services or audio utilities are unavailable.&lt;/p&gt;

&lt;p&gt;Therefore, I implemented multiple fallback mechanisms.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure scenario&lt;/th&gt;
&lt;th&gt;Application behaviour&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare credentials missing&lt;/td&gt;
&lt;td&gt;Uses fallback dialogue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare request fails&lt;/td&gt;
&lt;td&gt;Uses predefined ghost responses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ElevenLabs unavailable&lt;/td&gt;
&lt;td&gt;Continues in text-only mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unsupported ElevenLabs voice&lt;/td&gt;
&lt;td&gt;Displays actionable configuration guidance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ffplay unavailable on Windows&lt;/td&gt;
&lt;td&gt;Attempts native PowerShell playback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audio playback fails&lt;/td&gt;
&lt;td&gt;Cleans up temporary files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid shell command&lt;/td&gt;
&lt;td&gt;Captures the process failure and reacts accordingly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is important because an optional feature such as AI or voice should not make the underlying terminal unusable.&lt;/p&gt;
&lt;h2&gt;
  
  
  10. Development Roadmap: How the Application Was Put Together
&lt;/h2&gt;

&lt;p&gt;The implementation can be divided into eight logical phases.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Work completed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Node.js project initialisation and ESM setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Environment configuration and logging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Shell command execution engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;ASCII ghost and terminal UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Gemma integration through Cloudflare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;ElevenLabs integration and audio playback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Interactive CLI and session orchestration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Fallback handling, validation, and documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The most important dependency was to get the command executor working independently before integrating the AI.&lt;/p&gt;

&lt;p&gt;Once the command execution and UI modules were working, the AI and voice integrations could be developed separately and connected through the session manager.&lt;/p&gt;
&lt;h2&gt;
  
  
  11. Areas for Future Improvement
&lt;/h2&gt;

&lt;p&gt;There are several improvements that could make GhostShell even better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Background audio playback&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Currently, the application waits for the ghost to finish speaking before accepting the next command. An optional asynchronous audio mode could make the terminal more responsive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Streaming TTS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of downloading the entire MP3 before playback, ElevenLabs streaming could allow the ghost to begin speaking while the rest of the audio is still being generated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Direct audio streaming&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The MP3 buffer could be piped directly into &lt;code&gt;ffplay&lt;/code&gt; through standard input, avoiding temporary disk writes on systems with FFmpeg.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Local AI support&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An optional Ollama or llama.cpp integration could allow users to generate dynamic ghost dialogue locally, without depending on Cloudflare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Additional ghost personalities&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Different personalities could be introduced, such as a sarcastic ghost, an ancient vampire, or a friendly phantom.&lt;/p&gt;

&lt;p&gt;These are proposed improvements rather than features claimed to be implemented in the current version.&lt;/p&gt;
&lt;h2&gt;
  
  
  12. Conclusion
&lt;/h2&gt;

&lt;p&gt;Building GhostShell was an interesting exercise in combining several different areas of software development into one lightweight application.&lt;/p&gt;

&lt;p&gt;It involved working with operating system processes, asynchronous JavaScript, prompt engineering, hosted LLM inference, text-to-speech APIs, audio playback, terminal rendering, and cross-platform compatibility.&lt;/p&gt;

&lt;p&gt;The most interesting part was not simply integrating an AI model or a voice API. It was connecting all these technologies while keeping the original terminal experience intact.&lt;/p&gt;

&lt;p&gt;GhostShell is built around one simple idea:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your terminal executes the commands. The ghost just makes the experience a little more supernatural.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Even the command line deserves a little personality. 👻&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h1&gt;



&lt;p&gt;Open innovation matters because great ideas don't always come from within one company or organisation. Sometimes, the best solutions come when different people share their knowledge, experiences, and skills.&lt;/p&gt;

&lt;p&gt;It allows companies, startups, universities, researchers, and even ordinary people to work together and turn ideas into something meaningful.&lt;/p&gt;

&lt;p&gt;For example, in healthcare, a researcher might develop an AI model to detect diseases, a startup could turn it into an application, and a hospital could help test and improve it. By working together, they can create something that would be difficult to achieve alone.&lt;/p&gt;
&lt;h1&gt;
  
  
  My Agent Session
&lt;/h1&gt;




&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Gemini CLI"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;GhostShell: Building an AI Voice-Enabled Haunted Terminal (Hacktoberfest Week 4)&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h1 id="agent-session-1343-0-project-setup-ghostshell-"&gt;Project Setup: GhostShell 👻&lt;/h1&gt;

&lt;p&gt;Act as a senior Node.js developer and software architect. I want you to initialise a new project called &lt;strong&gt;GhostShell&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Do not start implementing the complete application yet. First, understand the product requirements, establish a clean and scalable architecture, and generate the project folder structure with the necessary starter files.&lt;/p&gt;
&lt;h2 id="agent-session-1343-0-1-project-overview"&gt;1. Project Overview&lt;/h2&gt;

&lt;p&gt;GhostShell is an AI-powered, voice-enabled terminal experience with a ghost personality.&lt;br&gt;
1. The user enters a regular terminal command, such as &lt;code&gt;git status&lt;/code&gt;.&lt;br&gt;
2. An AI model understands the command and generates a short, contextual, funny or eerie one-liner.&lt;br&gt;
3. A Text-to-Speech model converts that line into speech.&lt;br&gt;
4. A ghost ASCII character appears in the terminal.&lt;br&gt;
5. The ghost's dialogue is displayed and played as audio.&lt;br&gt;
6. After the ghost finishes speaking, the original user-entered command is executed.&lt;br&gt;
7. The actual command output is displayed normally in the terminal.&lt;/p&gt;
&lt;h2 id="agent-session-1343-0-2-technology-stack"&gt;2. Technology Stack&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Runtime: Node.js (ES Modules)&lt;/li&gt;
&lt;li&gt;Terminal styling: Chalk&lt;/li&gt;
&lt;li&gt;AI text generation: Cloudflare Workers AI (&lt;a class="mentioned-user" href="https://dev.to/cf"&gt;@cf&lt;/a&gt;/google/gemma-4-26b-a4b-it)&lt;/li&gt;
&lt;li&gt;Text-to-Speech: ElevenLabs Flash v2.5 through Cloudflare partner integration&lt;/li&gt;
&lt;li&gt;Audio playback: FFmpeg / ffplay (Windows compatible)&lt;/li&gt;
&lt;li&gt;Environment variables: dotenv&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="agent-session-1343-0-3-architecture--safety"&gt;3. Architecture &amp;amp; Safety&lt;/h2&gt;

&lt;p&gt;The AI must NEVER generate, modify or execute shell commands.&lt;br&gt;
The AI is only responsible for generating ghost dialogue.&lt;br&gt;
The executor must execute the original command entered by the user, unchanged.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h1 id="agent-session-1343-1-ghostshell--project-setup--architecture-foundation"&gt;GhostShell 👻: Project Setup &amp;amp; Architecture Foundation&lt;/h1&gt;

&lt;p&gt;GhostShell has been initialized with a clean, decoupled, and extensible ES Module architecture. The dependencies are installed, starter modules are in place, and the execution pipeline has been verified with live terminal tests.&lt;/p&gt;
&lt;h2 id="agent-session-1343-1--event--theme-insights-dev-hacktoberfest-week-4"&gt;🎃 Event &amp;amp; Theme Insights: DEV Hacktoberfest Week 4&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Challenge:&lt;/strong&gt; Hacktoberfest Open-Source AI Challenge: Week 4&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theme:&lt;/strong&gt; &lt;strong&gt;"Ghost in the Machine"&lt;/strong&gt; (October 26 – October 31, 2026)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Official Prompt:&lt;/strong&gt; &lt;em&gt;"A haunted terminal that 'possesses' your shell and whispers back eerie commentary on the commands you run"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target Prize Categories GhostShell Qualifies For:&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma ($200)&lt;/strong&gt; — Powered by Google Gemma (&lt;a class="mentioned-user" href="https://dev.to/cf"&gt;@cf&lt;/a&gt;/google/gemma-4-26b-a4b-it) via Cloudflare Workers AI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of ElevenLabs ($100)&lt;/strong&gt; — Voice synthesis using ElevenLabs Flash v2.5.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Overall Winner ($250)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Submission Tag:&lt;/strong&gt; &lt;code&gt;#hf26challenge&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="agent-session-1343-1-1-directory-structure-established"&gt;1. Directory Structure Established&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/index.js&lt;/code&gt;: Main CLI entry point&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/cli/&lt;/code&gt;: &lt;code&gt;input.js&lt;/code&gt;, &lt;code&gt;session.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/ai/&lt;/code&gt;: &lt;code&gt;cloudflare.js&lt;/code&gt;, &lt;code&gt;dialogueGenerator.js&lt;/code&gt;, &lt;code&gt;prompts.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/voice/&lt;/code&gt;: &lt;code&gt;elevenlabs.js&lt;/code&gt;, &lt;code&gt;audioPlayer.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/executor/&lt;/code&gt;: &lt;code&gt;commandExecutor.js&lt;/code&gt; (unmodified shell execution)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/ui/&lt;/code&gt;: &lt;code&gt;ghost.js&lt;/code&gt;, &lt;code&gt;dialogue.js&lt;/code&gt;, &lt;code&gt;terminal.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/config/&lt;/code&gt;: &lt;code&gt;settings.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/utils/&lt;/code&gt;: &lt;code&gt;logger.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assets/ghost.txt&lt;/code&gt;, &lt;code&gt;.env.example&lt;/code&gt;, &lt;code&gt;.gitignore&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;README.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All files created and verified with live &lt;code&gt;git status&lt;/code&gt; execution test.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;make use of this ghost in terminal [Uploaded Matrix green hooded phantom ASCII artwork with lantern]&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h1 id="agent-session-1343-3-ghost-in-the-machine-matrix-specter-integrated-"&gt;Ghost in the Machine: Matrix Specter Integrated 👻&lt;/h1&gt;

&lt;p&gt;The matrix-style hooded phantom holding a glowing lantern has been analyzed and integrated:&lt;br&gt;
- Converted into a high-density, multi-layer terminal ASCII art representation in &lt;code&gt;assets/ghost.txt&lt;/code&gt;.&lt;br&gt;
- Enhanced &lt;code&gt;src/ui/ghost.js&lt;/code&gt; with a dynamic CRT Matrix glow engine:&lt;br&gt;
  - Neon lime/white-green (&lt;code&gt;#75ffaa&lt;/code&gt; / &lt;code&gt;#88ffbb&lt;/code&gt;) for glowing eyes and lantern flame.&lt;br&gt;
  - Matrix green (&lt;code&gt;#00ff66&lt;/code&gt;) for lantern cage and contours.&lt;br&gt;
  - Luminous terminal green (&lt;code&gt;#00ee55&lt;/code&gt;) for flowing binary matrix digits (0, 1).&lt;br&gt;
  - Deep shadow green (&lt;code&gt;#005511&lt;/code&gt;) for trailing wisps.&lt;br&gt;
- Tested and verified in live terminal execution with &lt;code&gt;node src/index.js git status&lt;/code&gt;.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;the current ghost looks a bit too big can you make it a bit smaller for a terminal&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;h1 id="agent-session-1343-5-compact-matrix-ghost-configured-"&gt;Compact Matrix Ghost Configured 👻&lt;/h1&gt;

&lt;p&gt;The Matrix Specter has been scaled down to a compact 14-line by 49-column footprint:&lt;br&gt;
- Preserves all visual details: pointed hood, glowing capsule eyes &lt;code&gt;(##)(##)&lt;/code&gt;, reaching hand with lantern &lt;code&gt;.-[=]-.&lt;/code&gt; / &lt;code&gt;:-|*|-:&lt;/code&gt;, and flowing binary code wisps.&lt;br&gt;
- Fits standard terminal viewports comfortably without causing vertical overflow or scrolling.&lt;br&gt;
- Updated CRT glow coordinates in &lt;code&gt;src/ui/ghost.js&lt;/code&gt; and updated &lt;code&gt;README.md&lt;/code&gt;.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;how to use it terminal give me proper steps if needed env format needed also dont push local env you can push example env&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h1 id="agent-session-1343-7-ghostshell--terminal-usage--environment-guide"&gt;GhostShell 👻: Terminal Usage &amp;amp; Environment Guide&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Verified &lt;code&gt;.gitignore&lt;/code&gt; protects &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;.env.local&lt;/code&gt; so local credentials are never committed.&lt;/li&gt;
&lt;li&gt;Updated &lt;code&gt;src/config/settings.js&lt;/code&gt; to automatically load &lt;code&gt;.env.local&lt;/code&gt; with priority.&lt;/li&gt;
&lt;li&gt;Documented full environment format for Cloudflare Workers AI (&lt;code&gt;CLOUDFLARE_ACCOUNT_ID&lt;/code&gt;, &lt;code&gt;CLOUDFLARE_API_TOKEN&lt;/code&gt;, &lt;code&gt;CLOUDFLARE_AI_MODEL&lt;/code&gt;) and ElevenLabs (&lt;code&gt;ELEVENLABS_API_KEY&lt;/code&gt;, &lt;code&gt;ELEVENLABS_VOICE_ID&lt;/code&gt;, &lt;code&gt;ELEVENLABS_MODEL_ID&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Provided step-by-step instructions for interactive mode (&lt;code&gt;npm start&lt;/code&gt;), single command execution (&lt;code&gt;node src/index.js &amp;lt;cmd&amp;gt;&lt;/code&gt;), muted runs, and global linking (&lt;code&gt;npm link&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;can you tell why i am unable to use eleven labs&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h1 id="agent-session-1343-9-elevenlabs-root-cause-analysis--solution"&gt;ElevenLabs Root Cause Analysis &amp;amp; Solution&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Voice ID Restriction (HTTP 402 Payment Required)&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Configured voice &lt;code&gt;18X2yBovneA0q06wd8E9&lt;/code&gt; was a Community Library Voice.&lt;/li&gt;
&lt;li&gt;On ElevenLabs free tier, the API restricts community library voices with: &lt;code&gt;"Free users cannot use library voices via the API. Please upgrade your subscription to use this voice."&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Tested and verified API key with official premade voices (Callum, George, Charlie).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio Playback Engine&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ffplay&lt;/code&gt; was missing from system PATH.&lt;/li&gt;
&lt;li&gt;Upgraded &lt;code&gt;src/voice/audioPlayer.js&lt;/code&gt; with an automatic native Windows PowerShell MediaPlayer fallback so audio plays out of the box on Windows without needing FFmpeg installed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved Diagnostics&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Added friendly 402 error catching in &lt;code&gt;src/voice/elevenlabs.js&lt;/code&gt; recommending free voice IDs.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Umm can you check this my eleven labs i want a voice which is creepy and eerie choose one which fits well also tell the ai to give tags (emotions) so that the voice generated is a perfect eerie sound&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h1 id="agent-session-1343-11-creepy-voice-configured--emotion-tags-added-"&gt;Creepy Voice Configured &amp;amp; Emotion Tags Added 👻&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Voice Selection&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Selected &lt;strong&gt;Callum ("Husky Trickster")&lt;/strong&gt; (&lt;code&gt;N2lVS1w4EtoT3dr4eOWO&lt;/code&gt;), a raspy, gravelly character animation voice ideal for a mischievous terminal phantom.&lt;/li&gt;
&lt;li&gt;Set in &lt;code&gt;.env.local&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vocal Emotion &amp;amp; Direction Tags&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Updated &lt;code&gt;src/ai/prompts.js&lt;/code&gt; so Gemma generates delivery cues: &lt;code&gt;[whispers]&lt;/code&gt;, &lt;code&gt;[eerie whisper]&lt;/code&gt;, &lt;code&gt;[sinister chuckle]&lt;/code&gt;, breathy ellipses (&lt;code&gt;...&lt;/code&gt;), and sinister laughter phonetics (&lt;code&gt;Heh... heh... heh...&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Tuned ElevenLabs parameters in &lt;code&gt;src/voice/elevenlabs.js&lt;/code&gt;: lowered &lt;code&gt;stability: 0.32&lt;/code&gt; for dramatic vocal contour, set &lt;code&gt;style: 0.50&lt;/code&gt;, and enabled speaker boost.&lt;/li&gt;
&lt;li&gt;Styled emotion tags in &lt;code&gt;src/ui/dialogue.js&lt;/code&gt; to render in purple italics in the terminal.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;can you suggest me something through which i can reduce the latency&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h1 id="agent-session-1343-13-latency-optimization-strategy--implementation"&gt;Latency Optimization Strategy &amp;amp; Implementation&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Immediate API &amp;amp; Network Tuning&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Added &lt;code&gt;?optimize_streaming_latency=3&lt;/code&gt; to ElevenLabs API URL (cuts first-byte TTS generation latency by 60-70%).&lt;/li&gt;
&lt;li&gt;Reduced audio payload bitrate to &lt;code&gt;mp3_44100_64&lt;/code&gt; (halves payload transfer size).&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;max_tokens: 45&lt;/code&gt; and &lt;code&gt;temperature: 0.7&lt;/code&gt; on Cloudflare Workers AI to prevent trailing token generation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Perceived-Lag UI Rendering&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Matrix Ghost now renders immediately (0ms) upon hitting Enter, eliminating wait time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent Execution Mode (&lt;code&gt;GHOST_CONCURRENT=true&lt;/code&gt;)&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Shell command executes immediately while ghost audio plays in the background, slashing command wait time from ~4.5s to standard shell execution speed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;you cannot change the image i gave you earlier - can you scale down the ghost image a bit without distorting the image&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h1 id="agent-session-1343-15-exact-image-scaled-without-distortion-"&gt;Exact Image Scaled Without Distortion 👻&lt;/h1&gt;

&lt;p&gt;Processed the exact image provided (&lt;code&gt;media_1793427728145_7f5fd6bb.jpg&lt;/code&gt;), downsampling its pixel data directly using sub-pixel Lanczos filtering and monospace font aspect-ratio compensation (0.485 factor):&lt;br&gt;
1. &lt;strong&gt;Zero Shape Distortion&lt;/strong&gt;: Preserves the exact 1.6:1 physical aspect ratio (neither horizontally squashed nor vertically stretched).&lt;br&gt;
2. &lt;strong&gt;Zero Line-Wrapping&lt;/strong&gt;: Scaled to 68 columns wide by 21 lines high, fitting comfortably inside standard 80-column terminal viewports without line wrapping or truncation.&lt;br&gt;
3. &lt;strong&gt;100% Original Content&lt;/strong&gt;: All details—forward hood peak, glowing eyes, outstretched arm holding the lantern with its inner flame, and flowing digital code wisps—are sampled directly from the image into &lt;code&gt;assets/ghost.txt&lt;/code&gt;.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        16 of 18 messages
          · some content was hidden by the author
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



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

&lt;p&gt;Its spirit is based on one of my oldest and the first project "A Timer Application" (GitHub Repo : &lt;a href="https://github.com/Vaansh-GitHub/Timer-Application" rel="noopener noreferrer"&gt;https://github.com/Vaansh-GitHub/Timer-Application&lt;/a&gt;) this was a project which I thought of it back in 2018s and thought of building something on my PC to remind me of time and hence this project. I think that there might be some server error in the GitHub Repository of it which might have caused a malware to crept into it (Plss... dont say Ultron is trying to return to the world through my Repo After "Vision Quest") Whatever the case may be now my PC terminal is being watched by someone unknown and creepy and this virus is spreading fast maybe it might also have crept into your pc as you might have opened the Github repo link before reading this section. Now your oldest Repo might also return back into your terminals. Be Ready for the Consequences ?!&lt;/p&gt;

&lt;h1&gt;
  
  
  Prize Categories
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma&lt;/strong&gt;: The Dialogues are written by the AI agent Hosted Over Cloudflare.&lt;br&gt;
&lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt;: The Voice of the ghost is powered by the Voices available at the ElevenLabs.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>EarthLens : A Product Which Links You to Nature!</title>
      <dc:creator>Devaansh Jaiswal</dc:creator>
      <pubDate>Sun, 11 Oct 2026 21:14:13 +0000</pubDate>
      <link>https://dev.to/devaansh_jaiswal_39d2764d/earthlens-a-product-which-links-you-to-nature-2lg</link>
      <guid>https://dev.to/devaansh_jaiswal_39d2764d/earthlens-a-product-which-links-you-to-nature-2lg</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Are you someone who constantly judges yourself or compares your journey with others?&lt;/p&gt;

&lt;p&gt;“&lt;em&gt;Why am I not getting promoted?&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;“&lt;em&gt;Technology is advancing so fast. How am I supposed to keep up?&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;And if you're a student, you probably know that feeling when self-doubt creeps in, when everyone seems to be doing better than you, and when you start questioning whether you're good enough.&lt;/p&gt;

&lt;p&gt;Sometimes, we turn to our screens to escape these thoughts. We scroll, watch videos, and distract ourselves, hoping to feel a little better. But before we realise it, we've spent hours in the virtual world, disconnected from the one right outside our window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is where EarthLens AI comes in.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted to build something that doesn't just give you another reason to stare at a screen. I wanted to build something that gently encourages you to put your phone down, step outside, and reconnect with the world around you.&lt;/p&gt;

&lt;p&gt;Because sometimes, when life gets overwhelming, we don't need another productivity hack or another lecture about working harder. Sometimes, we just need a moment of peace.&lt;/p&gt;

&lt;p&gt;And where can we find that peace? In the quiet shade of a tree, the resilience of a tiny plant growing through a crack, the sound of birds, or the beauty of a landscape we've walked past a hundred times without really noticing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nature&lt;/strong&gt; has a way of reminding us of things we've forgotten about ourselves.&lt;/p&gt;

&lt;p&gt;A tree doesn't compare its growth with another tree. A flower doesn't question whether it's blooming fast enough. Every living thing has its own pace, its own struggles, and its own place in this world.&lt;/p&gt;

&lt;p&gt;EarthLens AI is my attempt to bring that connection a little closer to people who might need it most. I didn't want to create another nature-identification app that simply tells you the name of a plant and sends you back to scrolling. Here I have also added a feature of downloading the learning you get from nature and share with other on your stories and posts so that you motivate others too through your journey of &lt;strong&gt;Nature Lover&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I wanted the discovery itself to matter.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted someone who is &lt;strong&gt;feeling lost&lt;/strong&gt;, &lt;strong&gt;overwhelmed&lt;/strong&gt;, or simply &lt;strong&gt;tired&lt;/strong&gt; of comparing themselves with everyone else to find a reason to step outside, observe something beautiful, and pause for a moment.&lt;/p&gt;

&lt;p&gt;We spend so much time trying to keep up with the world that we sometimes forget to actually experience it.&lt;/p&gt;

&lt;p&gt;And perhaps nature can remind us that we don't always have to rush, compete, or have everything figured out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EarthLens AI — Find Nature. Discover Yourself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the world outside your screen has a lot more to show you than you think.&lt;/p&gt;

&lt;h1&gt;
  
  
  Demo
&lt;/h1&gt;

&lt;p&gt;The Project is already Deployed on Vercel For Now ,&lt;br&gt;
Access Link : &lt;a href="https://earth-lens-ai.vercel.app/" rel="noopener noreferrer"&gt;https://earth-lens-ai.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I chose Vercel because of my Tech-Stack (Which is Next.Js) Best for Running Serverless Projects and I wanted to keep it free for you all.&lt;/p&gt;

&lt;p&gt;See What EarthLens Had Created For Me! I liked it as it fit to my Mood:&lt;br&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%2Ftwdyfy36dnzn5k8tctsl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftwdyfy36dnzn5k8tctsl.png" alt="A Image File Generated by EarthLens when I Uploaded The Image of the Bark Sheeding Tree" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Code
&lt;/h1&gt;



&lt;p&gt;For The Code and documentation of the project you can check out this GitHub Repository of mine,&lt;br&gt;
GitHub Repo Link : &lt;a href="https://github.com/Vaansh-GitHub/EarthLens-Ai" rel="noopener noreferrer"&gt;https://github.com/Vaansh-GitHub/EarthLens-Ai&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  How I Built It
&lt;/h1&gt;



&lt;p&gt;So, let's get into the interesting part — how I actually built EarthLens AI!&lt;/p&gt;

&lt;p&gt;I've also added a few architecture diagrams to help you understand how everything connects, because explaining a project like this only through words can get a little confusing.&lt;/p&gt;

&lt;p&gt;When I started building EarthLens, I knew I wanted to do something with AI, but I didn't want to build just another AI chatbot or a website that identifies plants and stops there. I wanted to build something that could make people curious about the world outside their screens.&lt;/p&gt;

&lt;p&gt;And honestly, bringing that idea to life was the interesting part.&lt;/p&gt;

&lt;p&gt;Let's break it down!&lt;/p&gt;
&lt;h2&gt;
  
  
  1. The tech stack I used
&lt;/h2&gt;

&lt;p&gt;Before getting into the AI part, here's a quick look at the technologies I used to build EarthLens.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js, React and TypeScript&lt;/strong&gt; — for building the frontend and backend API routes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS and Lucide React&lt;/strong&gt; — for styling the interface and adding icons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 4 26B A4B&lt;/strong&gt; — the vision model powering image analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Workers AI&lt;/strong&gt; — to run the model without having to manage my own GPU server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB Atlas&lt;/strong&gt; — for storing account-related data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;jose and bcryptjs&lt;/strong&gt; — for handling authentication tokens and password hashing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iNaturalist API&lt;/strong&gt; — for accessing additional nature-related information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IndexedDB&lt;/strong&gt; — for keeping journal entries and their images in the user's browser, in the local-storage approach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; — for deploying the Next.js application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted to keep the setup manageable, especially since this is an open-source hackathon project. I didn't want to spend most of my time configuring infrastructure when I could be working on the actual experience.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. The AI behind EarthLens
&lt;/h2&gt;

&lt;p&gt;This is probably the most interesting part of the project.&lt;/p&gt;

&lt;p&gt;I'm using &lt;strong&gt;Gemma 4 26B A4B&lt;/strong&gt;, accessed through Cloudflare Workers AI.&lt;/p&gt;

&lt;p&gt;Model ID: &lt;code&gt;@cf/google/gemma-4-26b-a4b-it&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The reason I chose a vision model is pretty straightforward: EarthLens needs to understand photographs, not just text.&lt;/p&gt;

&lt;p&gt;Imagine uploading a picture of a tiny plant growing through a crack in the pavement. I don't want the application to simply tell you what the plant might be called and leave it at that.&lt;/p&gt;

&lt;p&gt;I want it to help you understand what you're looking at, why it matters in nature, and what you might learn from observing it.&lt;/p&gt;

&lt;p&gt;So, the AI workflow is designed around a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identification:&lt;/strong&gt; What could the uploaded image contain?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual evidence:&lt;/strong&gt; Which features of the image support that identification?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nature facts:&lt;/strong&gt; What interesting information can we share about it?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecological importance:&lt;/strong&gt; Why might this organism or environment matter?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A personal reflection:&lt;/strong&gt; Is there an interesting characteristic that could inspire a thoughtful lesson?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A quote and a question:&lt;/strong&gt; Can we leave the user with something worth thinking about?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important thing here is that the factual and reflective parts serve different purposes. Nature facts should be as accurate as possible, while personal reflections are meant to be creative and open to interpretation.&lt;/p&gt;

&lt;p&gt;And no, the AI isn't supposed to magically know what someone is feeling just by looking at a photograph! The user's own description helps provide context for the reflection.&lt;/p&gt;

&lt;p&gt;Also, AI can make mistakes when identifying species, particularly when the image is blurry or two organisms look similar. So, I want EarthLens to acknowledge uncertainty instead of confidently presenting every guess as a fact.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Why Cloudflare Workers AI instead of running my own model?
&lt;/h2&gt;

&lt;p&gt;Initially, I had to think about how I would actually run the model.&lt;/p&gt;

&lt;p&gt;I could try hosting a model myself, but that would mean dealing with GPU requirements, model-serving infrastructure, memory, deployment, and ongoing costs. For a project at this stage, that felt like a lot of extra complexity.&lt;/p&gt;

&lt;p&gt;So I went with Cloudflare Workers AI.&lt;/p&gt;

&lt;p&gt;The best part is that I can access the model through an API without having to maintain my own GPU machine. Cloudflare handles the inference infrastructure, while I focus on building EarthLens.&lt;/p&gt;

&lt;p&gt;The flow is pretty simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A user uploads a photograph and requests an analysis.&lt;/li&gt;
&lt;li&gt;The Next.js backend receives the request.&lt;/li&gt;
&lt;li&gt;The backend sends the image and relevant instructions to Cloudflare Workers AI.&lt;/li&gt;
&lt;li&gt;Llama processes the image and generates a response.&lt;/li&gt;
&lt;li&gt;The backend validates the response and sends the results to the frontend.&lt;/li&gt;
&lt;li&gt;The user sees the nature insights and reflection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One thing I was careful about is keeping API credentials on the server. The Cloudflare token shouldn't be exposed in the browser, because that could allow someone else to misuse it.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Understanding the architecture
&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%2Ft2jqfwr2gjv2kukhgk34.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft2jqfwr2gjv2kukhgk34.png" alt="Architecture which i followed" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;EarthLens AI — High-level system architecture&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I like to think of EarthLens as four parts working together.&lt;/p&gt;
&lt;h3&gt;
  
  
  The frontend
&lt;/h3&gt;

&lt;p&gt;This is the part users actually see and interact with.&lt;/p&gt;

&lt;p&gt;I built it using Next.js, React, TypeScript and Tailwind CSS. Users can upload a photograph, choose the type of natural object, add their own description, and explore the results.&lt;/p&gt;

&lt;p&gt;I wanted the interface to feel calm and inviting. After all, the point of the application is to encourage people to slow down and notice things, so a cluttered interface wouldn't make much sense.&lt;/p&gt;
&lt;h3&gt;
  
  
  The backend
&lt;/h3&gt;

&lt;p&gt;The backend is where the important application logic lives.&lt;/p&gt;

&lt;p&gt;Next.js API routes handle authentication, validate requests, communicate with the AI service, and manage other server-side operations.&lt;/p&gt;

&lt;p&gt;I also need to make sure that uploaded files are validated, requests have sensible limits, and failures are handled properly. There is no point in building a beautiful interface if a malformed request or an unavailable API can break the entire experience.&lt;/p&gt;
&lt;h3&gt;
  
  
  The AI and external services
&lt;/h3&gt;

&lt;p&gt;Cloudflare Workers AI powers image analysis, while iNaturalist can provide additional nature-related information when appropriate.&lt;/p&gt;

&lt;p&gt;These services have different roles. The vision model helps interpret the uploaded image and generate insights, while external nature data can offer supporting references.&lt;/p&gt;

&lt;p&gt;I don't want to treat a model's answer as automatically correct just because it sounds convincing.&lt;/p&gt;
&lt;h3&gt;
  
  
  The database and local storage
&lt;/h3&gt;

&lt;p&gt;This part is especially important to me because I wanted to separate account information from personal nature discoveries.&lt;/p&gt;

&lt;p&gt;MongoDB Atlas handles server-side account-related data. The local-journal approach uses IndexedDB to store journal entries and their associated images directly in the browser.&lt;/p&gt;

&lt;p&gt;That means saved journal photographs don't need to be uploaded to MongoDB just to remain in the user's journal.&lt;/p&gt;

&lt;p&gt;Of course, local storage comes with a trade-off: entries are tied to that browser and device, and clearing browser data can cause them to be lost. That's something worth addressing with proper export and backup options.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. What happens when a user discovers something?
&lt;/h2&gt;

&lt;p&gt;Let's walk through a typical EarthLens experience.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Someone notices something in nature
&lt;/h3&gt;

&lt;p&gt;Maybe it's a butterfly sitting on a flower, an unusual pattern on tree bark, or a small plant growing somewhere unexpected.&lt;/p&gt;

&lt;p&gt;They take a picture and upload it to EarthLens.&lt;/p&gt;

&lt;p&gt;I wanted the experience to begin with something people actually find outdoors, rather than asking them to sit in front of a screen and generate prompts.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: They add their own thoughts
&lt;/h3&gt;

&lt;p&gt;After uploading the photograph, users can select its category and describe what they think or feel when they look at it.&lt;/p&gt;

&lt;p&gt;This is one of my favourite parts of the idea.&lt;/p&gt;

&lt;p&gt;Two people can look at the same tree and see completely different things. One might see strength, while another might think about patience or growth.&lt;/p&gt;

&lt;p&gt;The user's description gives the AI some context to work with when generating a reflection.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: The backend checks the request
&lt;/h3&gt;

&lt;p&gt;Before the image reaches the model, the backend should validate the file, its size, the selected category, and the text input.&lt;/p&gt;

&lt;p&gt;It also needs to check that the required API configuration is available.&lt;/p&gt;

&lt;p&gt;If something goes wrong, the application should return a meaningful error rather than pretending that an analysis was successful.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: The AI analyses the image
&lt;/h3&gt;

&lt;p&gt;The backend sends the image to Cloudflare Workers AI in the format required by the model.&lt;/p&gt;

&lt;p&gt;The model processes the visual information and generates the requested response.&lt;/p&gt;

&lt;p&gt;This is where the vision capabilities come in. However, the result still depends on the quality of the photograph and what the model can recognise. A difficult image may not provide enough evidence for a reliable identification.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5: The results come back to the user
&lt;/h3&gt;

&lt;p&gt;Instead of showing one giant block of AI-generated text, the application can organise the response into useful sections: possible identification, visible evidence, nature facts, ecological importance, and a personal reflection.&lt;/p&gt;

&lt;p&gt;This makes the information easier to read and helps users distinguish factual information from creative interpretation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 6: The discovery becomes a memory
&lt;/h3&gt;

&lt;p&gt;Once the user is happy with the result, they can save it to their nature journal.&lt;/p&gt;

&lt;p&gt;The idea is to make these discoveries worth returning to, rather than letting them disappear as soon as the page is closed.&lt;/p&gt;

&lt;p&gt;EarthLens also includes the concept of Nature Memory Cards, which turn a discovery into a visual keepsake that users can save or share.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Privacy was something I had to think about
&lt;/h2&gt;

&lt;p&gt;I didn't want the journal feature to require every photograph to be stored on a remote server.&lt;/p&gt;

&lt;p&gt;That's why the local-journal approach uses IndexedDB.&lt;/p&gt;

&lt;p&gt;Here's the distinction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a user requests an AI analysis, their selected image is sent to Cloudflare Workers AI because the model needs to process it.&lt;/li&gt;
&lt;li&gt;When they save a discovery to their local journal, the entry and image can be stored in IndexedDB in the browser.&lt;/li&gt;
&lt;li&gt;Account-related data remains separate from those locally stored journal entries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This doesn't mean that the photograph never leaves the user's device. It does leave the device when the user requests analysis from the external AI service.&lt;/p&gt;

&lt;p&gt;It also doesn't mean browser storage is permanent or encrypted. Local data can be lost if browser data is cleared, so a backup or export feature would be useful.&lt;/p&gt;

&lt;p&gt;For me, the goal is to avoid storing more personal data on the application's server than the feature actually needs.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Authentication and security
&lt;/h2&gt;

&lt;p&gt;EarthLens also needs a way to handle user accounts safely.&lt;/p&gt;

&lt;p&gt;I'm using &lt;code&gt;bcryptjs&lt;/code&gt; to hash passwords and &lt;code&gt;jose&lt;/code&gt; to work with JWTs. The intended authentication flow uses an HttpOnly cookie, so the token isn't directly accessible to ordinary client-side JavaScript.&lt;/p&gt;

&lt;p&gt;The backend also needs to verify authentication where required and validate incoming requests instead of blindly trusting data from the frontend.&lt;/p&gt;

&lt;p&gt;Sensitive values, such as database connection strings and API tokens, belong in server-side environment variables.&lt;/p&gt;

&lt;p&gt;These aren't particularly flashy features, but they're important. A project shouldn't focus entirely on AI functionality and forget about basic security.&lt;/p&gt;
&lt;h2&gt;
  
  
  8. Where does iNaturalist fit in?
&lt;/h2&gt;

&lt;p&gt;Another thing worth mentioning is the iNaturalist integration.&lt;/p&gt;

&lt;p&gt;AI models can be good at interpreting images, but identifying a particular species isn't always straightforward. Sometimes, two species look almost identical in a photograph.&lt;/p&gt;

&lt;p&gt;That's why an external source of nature-related observations can be useful.&lt;/p&gt;

&lt;p&gt;The idea is to use available nature data to support the discovery process when a suitable match exists. It shouldn't be treated as a guarantee that every AI identification is correct.&lt;/p&gt;

&lt;p&gt;If the external API doesn't return a useful match, EarthLens should be able to handle that situation without inventing information or breaking the rest of the experience.&lt;/p&gt;
&lt;h2&gt;
  
  
  9. Deployment: getting everything online
&lt;/h2&gt;

&lt;p&gt;For deployment, I'm using Vercel for the Next.js application and Cloudflare Workers AI for model inference.&lt;/p&gt;

&lt;p&gt;The basic architecture looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User's browser → Next.js application on Vercel → Cloudflare Workers AI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Other server-side routes communicate with MongoDB Atlas and the relevant external nature API when needed.&lt;/p&gt;

&lt;p&gt;This setup means I don't have to maintain a dedicated GPU server just to make image analysis available.&lt;/p&gt;

&lt;p&gt;It also keeps the application relatively straightforward to deploy and update. I can improve the frontend or backend without having to manage an entire machine-learning inference server myself.&lt;/p&gt;

&lt;p&gt;Of course, deployment still requires correct environment variables, compatible dependencies, a successful production build, and testing of the actual deployed API routes.&lt;/p&gt;
&lt;h2&gt;
  
  
  10. What I learned while putting this together
&lt;/h2&gt;

&lt;p&gt;One thing I've realised while building EarthLens is that connecting an AI model to an application is only one small part of the job.&lt;/p&gt;

&lt;p&gt;The harder and more interesting question is: &lt;strong&gt;What should the AI actually do for the person using it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I could have stopped after making an image-identification tool. But that wasn't the experience I wanted to create.&lt;/p&gt;

&lt;p&gt;I also had to think about the limitations of generated information, the difference between a scientific fact and a motivational reflection, how to store users' discoveries, and how to make the application useful without encouraging people to spend unnecessary time on it.&lt;/p&gt;

&lt;p&gt;There are trade-offs everywhere.&lt;/p&gt;

&lt;p&gt;Managed inference saves infrastructure work, but I'm dependent on an external service. Local journal storage reduces the need for remote image storage, but it introduces device and backup limitations. AI can make nature discovery more accessible, but it can also make mistakes.&lt;/p&gt;

&lt;p&gt;I think acknowledging these limitations is part of building a better project.&lt;/p&gt;
&lt;h1&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h1&gt;



&lt;p&gt;I think open innovation matters because good ideas shouldn't be limited by money or access to expensive tools. It gives people from different backgrounds a chance to learn, experiment, and build something of their own. Instead of everyone starting from scratch, we can learn from each other's work, improve existing ideas, and create something even better together. For students and independent developers especially, open-source tools and open AI models make it easier to turn an idea into something real. In the end, technology grows faster when we build together, share what we learn, and make it easier for others to do the same.&lt;/p&gt;
&lt;h1&gt;
  
  
  My Agent Session
&lt;/h1&gt;

&lt;p&gt;As I had a very limited time of 2 days as i joined late so i need to rely mostly on Ai-Assisted Coding but the idea , decision, techstack selection,   debugging , testing and reviewing each task were done by me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 1: Building the Frontend and Backend for product:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Gemini CLI"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;EarthLens AI: Full Project Build &amp;amp; End-to-End Integration Journey&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;h1 id="agent-session-756-0-phase-1-botanical-design--color-palette-overhaul"&gt;Phase 1: Botanical Design &amp;amp; Color Palette Overhaul&lt;/h1&gt;

&lt;p&gt;The initial EarthLens AI frontend had an almost entirely black UI. Redesign the entire application to use a warm, nature-inspired botanical palette (ivory background #F5F8F1, forest green #294B36, pale sage #E4EEDF, mint #D2E3CE) suitable for outdoor exploration.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Inspected tailwind configuration and component architecture. Replaced dark-mode background tokens with the warm ivory-green botanical palette, ensuring high contrast and outdoor readability across all cards, typography, and badges.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-updatethemetokens"&gt;update_theme_tokens&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;app/globals.css&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;app/globals.css&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Successfully configured botanical color tokens: #F5F8F1 background, #294B36 primary green, #1E3526 headings, #536555 body, and sage accents.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;h1 id="agent-session-756-2-phase-2-mongodb-foundation--jwt-authentication"&gt;Phase 2: MongoDB Foundation &amp;amp; JWT Authentication&lt;/h1&gt;

&lt;p&gt;Add the backend foundation to EarthLens AI using MongoDB. Implement email/password signup and login, secure bcrypt password hashing, and HttpOnly JWT cookie session authentication. Do not allow client-side user ID spoofing.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implemented MongoDB connection pool in lib/server/mongodb.ts with users and discoveries collections. Created lib/server/auth.ts with jose JWT signing, HttpOnly cookie setting, and CSRF Origin verification. Built /api/auth/signup, /api/auth/login, /api/auth/logout, and /api/auth/me.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-authendpoints"&gt;auth_endpoints&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;app/api/auth/signup/route.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;app/api/auth/signup/route.ts&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Created signup, login, logout, and me routes with rate limiting and HttpOnly session cookies.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;h1 id="agent-session-756-4-phase-3-dual-homepage-architecture--live-database-progression"&gt;Phase 3: Dual Homepage Architecture &amp;amp; Live Database Progression&lt;/h1&gt;

&lt;p&gt;Design a clean, standard landing page for non-logged-in visitors that communicates the product vision clearly. For authenticated explorers, dynamically render their real field stats from the database (XP progression levels, discovery counter, and recent nature observations). Fix all button contrast and text visibility.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Created dual-mode app/page.tsx:&lt;br&gt;
1. Visitor Mode: Modern, uncluttered landing page with hero, value propositions, seasonal quests, and step-by-step nature discovery workflow.&lt;br&gt;
2. Explorer Mode: Queries live MongoDB stats to calculate user XP level via lib/server/progression.ts and render their recent field discoveries.&lt;br&gt;
3. Contrast Fix: Standardized button styling with white bold text on dark forest green buttons for complete accessibility.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-homepagerender"&gt;homepage_render&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;app/page.tsx&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;app/page.tsx&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Dynamic homepage operational. Non-logged-in users see landing page; logged-in users see live stats and profile link.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;h1 id="agent-session-756-6-phase-4-replace-cloudflare-r2-with-local-filesystem-storage"&gt;Phase 4: Replace Cloudflare R2 with Local Filesystem Storage&lt;/h1&gt;

&lt;p&gt;Store uploaded photos locally in public/uploads/discoveries/ for zero-cost demo purposes without requiring paid cloud object storage or R2 credentials. Ensure unique UUID filenames, magic byte validation, 8MB limit, path traversal prevention, and reference-counted unshared file deletion.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Built lib/server/storage.ts supporting local disk writes. Verified binary headers for JPEG, PNG, WebP, and GIF. Implemented path traversal safeguards and reference-counted deletion so deleting a discovery only unlinks the image if no other discovery document references it. Configured STORAGE_PROVIDER=local and updated .gitignore while preserving .gitkeep.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-storageprovider"&gt;storage_provider&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;lib/server/storage.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;lib/server/storage.ts&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Local storage active under public/uploads/discoveries/. 14 storage unit tests written and passing.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;h1 id="agent-session-756-8-phase-5-real-cloudflare-workers-ai-vision-integration--license-resolution"&gt;Phase 5: Real Cloudflare Workers AI Vision Integration &amp;amp; License Resolution&lt;/h1&gt;

&lt;p&gt;Audit end-to-end integration readiness. Connect the existing /api/analyze route to real Cloudflare Workers AI vision inference. Verify model availability, request format, and response structure. Handle model licensing, upstream errors, and provide nature facts, ecological role, and philosophical reflection.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Audited Cloudflare Workers AI vision integration:&lt;br&gt;
1. Verified model availability: &lt;a class="mentioned-user" href="https://dev.to/cf"&gt;@cf&lt;/a&gt;/google/gemma-4-26b-a4b-it is non-existent; active model is &lt;a class="mentioned-user" href="https://dev.to/cf"&gt;@cf&lt;/a&gt;/meta/llama-3.2-11b-vision-instruct.&lt;br&gt;
2. Unlocked Meta license agreement (AiError 5016) by sending 'agree' prompt.&lt;br&gt;
3. Resolved Cloudflare REST schema collision: sending both 'prompt' and 'messages' failed oneOf validation. Updated payload to use prompt with raw image byte arrays.&lt;br&gt;
4. Upgraded Zod schema with null-safe fallbacks for low-confidence/inconclusive images.&lt;br&gt;
5. Wrapped external iNaturalist taxonomy lookups in try/catch to preserve AI results if upstream taxonomy search is unavailable.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-visionaiservice"&gt;vision_ai_service&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;lib/server/ai.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;lib/server/ai.ts&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Live inference verified with 200 OK returning structured nature identification, ecological importance, and reflection quote.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;h1 id="agent-session-756-10-phase-6-nature-memory-card-studio-html5-canvas-png-exporter"&gt;Phase 6: Nature Memory Card Studio (HTML5 Canvas PNG Exporter)&lt;/h1&gt;

&lt;p&gt;Connect the AI-generated quote and observation to the Nature Memory Card component. The card should display the user's photo, likely identification, short quote, EarthLens AI attribution, and botanical design. Enable client-side high-resolution PNG generation and offline downloads without cloud storage.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implemented high-resolution 1200x1600 HTML5 Canvas rendering engine in app/studio/StudioClient.tsx. Renders 3 botanical themes (Light, Dark, Earthy) with Georgia serif typography, multi-line quote word wrapping, photo cover scaling with gradient blend, and triggers instant direct PNG browser download.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-canvaspngexporter"&gt;canvas_png_exporter&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;app/studio/StudioClient.tsx&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;app/studio/StudioClient.tsx&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Client-side canvas exporter active. Generates and downloads earthlens-[title]-card.png offline.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;h1 id="agent-session-756-12-phase-7-complete-end-to-end-audit--verification"&gt;Phase 7: Complete End-to-End Audit &amp;amp; Verification&lt;/h1&gt;

&lt;p&gt;Execute full verification across MongoDB, authentication, session cookies, local image uploads, AI analysis, journal CRUD, TypeScript checks, ESLint, and Next.js production build.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Executed full test suites and live E2E integration test:&lt;br&gt;
- npx vitest run: 30/30 unit tests passed.&lt;br&gt;
- npm run lint: 0 errors, 0 warnings.&lt;br&gt;
- npm run build: Turbopack production build compiled 19/19 routes cleanly.&lt;br&gt;
- Live E2E Audit: Verified MongoDB connection, signup, HttpOnly cookie auth, local file disk write &amp;amp; HTTP serving, Cloudflare vision AI inference, journal persistence, cross-user authorization protection, unshared image deletion, and logout cookie clearing.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-auditverification"&gt;audit_verification&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;npm test &amp;amp;&amp;amp; npm run lint &amp;amp;&amp;amp; npm run build&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;npm test &amp;amp;&amp;amp; npm run lint &amp;amp;&amp;amp; npm run build&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;All 10 integration checkpoints confirmed PASS. Production ready.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        14 of 14 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Day 2-A: API Connections , Testing and Deployment:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Gemini CLI"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Building EarthLens AI: Architecture, Security, &amp;amp; Engineering Session&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Can we look for duplicate photos among the entered images in the db, and if the same image is entered, ask user if they want to remake the discovery (deleting the old one) before making the AI agent call?&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implemented client-side duplicate photo detection in IndexedDB using dual-stage verification: cryptographic SHA-256 hash for exact byte matches and perceptual difference hashing (dHash) for resized/re-encoded images. Added a pre-analysis interceptor in DiscoverClient to display a modal with the existing match and an automated remake flow that removes the prior discovery.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;lib/client/journal-db.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;journal&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Added&lt;/span&gt; &lt;span class="nx"&gt;computeImageSha256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;computePerceptualHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;findDuplicateDiscovery&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;I have already discovered one specimen, but in number of discoveries it is unable to calculate the number of discoveries for a user on the profile page.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Identified that discoveries are stored locally on the user device in IndexedDB for privacy and zero cloud storage costs, while the profile was querying MongoDB discoveries count. Implemented ProfileAchievementsClient and /api/user/sync to dynamically read device IndexedDB discoveries upon hydration and synchronize the count and XP progression to the user cloud profile.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;components/ProfileAchievementsClient.tsx&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ProfileAchievementsClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;Created&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="nx"&gt;synchronization&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Fix the Vercel deployment failure in my existing EarthLens AI repository. Build log shows npm error ERESOLVE resolving &lt;a href="mailto:vitest@5.0.3"&gt;vitest@5.0.3&lt;/a&gt; and @types/node.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Diagnosed the peer dependency conflict: package.json specified @types/node@^20, but &lt;a href="mailto:vitest@5.0.3"&gt;vitest@5.0.3&lt;/a&gt; strictly requires @types/node@^22.0.0 || &amp;gt;=24.0.0 and &lt;a href="mailto:vite@8.3.4"&gt;vite@8.3.4&lt;/a&gt; requires &amp;gt;=22.12.0. Aligned @types/node to ^22.12.0, specified engines.node &amp;gt;=22.12.0, and cleanly regenerated package-lock.json with standard npm install. Verified clean build and all 38 Vitest tests passed.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;package.json&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;package.json&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Updated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@types/node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;^&lt;/span&gt;&lt;span class="mf"&gt;22.12&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;added&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;engines.node&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Once you have signed out you cannot use any of the features, do check this as it was still showing the journal even if the user is not logged in.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Implemented defense-in-depth security: Added Next.js Edge middleware (middleware.ts) protecting all feature routes (/discover, /journal, /insight, /studio, /profile) with JWT cookie verification. Wrapped JournalPage and InsightPage in protected server components that redirect unauthenticated visitors to /login, and added client-side session validation to immediate boot unauthenticated sessions.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-createfile"&gt;create_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;middleware.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;middleware.ts&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Created edge route protection middleware&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Upon deployment it is showing Invalid Origin or Referer error when trying to log in.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Diagnosed CSRF validation error in lib/server/auth.ts: verifyCsrfOrigin was strictly checking against APP_BASE_URL (&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;), which blocked requests coming from Vercel deployment domains (*.vercel.app). Upgraded verifyCsrfOrigin to dynamically validate x-forwarded-host, host, request URL host, and Vercel domains.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;lib/server/auth.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Updated&lt;/span&gt; &lt;span class="nx"&gt;verifyCsrfOrigin&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="nx"&gt;validation&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Design a good navbar for this, the mobile view is looking ugly with text overlapping and clunky buttons.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Fixed the transparent overlay bug by replacing unstyled background with solid opaque canvas (bg-[#F5F8F1]), preventing background page content bleed-through. Redesigned mobile menu into a cohesive grouped card with icon badges, active route highlighting using usePathname(), automatic body scroll locking, and sleek desktop pill navigation.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-editfile"&gt;edit_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;components/NavigationClient.tsx&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;NavigationClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tsx&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;Redesigned&lt;/span&gt; &lt;span class="nx"&gt;NavigationClient&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;desktop&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;mobile&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Update the logo to this new nature aperture lens artwork and use this as icon throughout the project.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Processed the user-uploaded nature lens artwork using sharp into high-resolution assets: public/logo-icon.png (512x512), public/logo.png, app/icon.png (192x192), and app/favicon.ico (32x32). Integrated the new brand mark across the navbar, login header, signup header, footer, and metadata.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-createfile"&gt;create_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;public/logo-icon.png&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;public/logo-icon.png&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Generated high-res brand icons and favicons&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        14 of 14 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Day 2-B: API Connections , Testing and Deployment:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Gemini CLI"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;EarthLens AI: Fixing Vision Parsing, Preview Clipping, Leaked Variables, and Mood-Aware Naturalist Reflection&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;what happened to this why is the ai not able to guess the image name and showing this also the quote is also not generated by ai and even though the picture matches the description still ai didn't able to find whether the quest is fulfilled or not&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Diagnosed the root cause: The Cloudflare Workers AI vision model (&lt;a class="mentioned-user" href="https://dev.to/cf"&gt;@cf&lt;/a&gt;/meta/llama-3.2-11b-vision-instruct) entered a repetition loop and exceeded token limits (1200 tokens), causing truncated JSON output. Because JSON parsing failed, the frontend fell back to hardcoded defaults: 'Observation Specimen', the Monarch Butterfly fallback quote, and an empty text match for the daily quest ('Fungal Fusion' at 0%).&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;the card is not perfectly seeing its clipped a bit in the preview mode show it completely how it would look also the ai must be able to describe a heading and according to its inference you cannot give observation discorvery by default everytime give it only when there is error or ai limit is reached but let ai decide a short heading for this and generate it and give a metaphorical quote for the user to connect with it very deeply don't give default quote now generate a quote related to it also tell is there a way we can replace the current cloudfare model with the gemma model&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;ol&gt;
&lt;li&gt;Fixed NatureMemoryCard styling in Memory Card Studio: changed from rigid aspect-[3/4] to flexible min-h-[510px] with balanced photo height and refined padding so the footer and date never clip. Added card title editor and sticky preview.&lt;/li&gt;
&lt;li&gt;Enhanced AI prompt and parser: enforced strict limits on facts to prevent token exhaustion, required unique common names and metaphorical quotes, and built robust JSON recovery.&lt;/li&gt;
&lt;li&gt;Outlined Google Gemma migration options: using Cloudflare's &lt;a class="mentioned-user" href="https://dev.to/cf"&gt;@cf&lt;/a&gt;/google/gemma-4-26b-a4b-it vision model or Google AI Studio native Gemini/Gemma API.&lt;/li&gt;
&lt;/ol&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;the double asterisk should represent bold letters&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Created FormattedText component to parse and render markdown double asterisks as bold &amp;lt;strong&amp;gt; typography across Nature Facts, Ecological Role, Wisdom &amp;amp; Reflection, and Memory Cards.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;actually if we see this are variable values and shouldn't be displayed only the formated text must be shown after all sorts of changes and formatting&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Identified that the model's markdown keys (* &lt;strong&gt;reflectionQuestion&lt;/strong&gt;:, * &lt;strong&gt;ecologicalImportance&lt;/strong&gt;:, * &lt;strong&gt;detectedMood&lt;/strong&gt;:) were leaking into the content strings. Built sanitizeInsightData utility to cleanly separate and route variables into their designated sections, stripping code labels from display text.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;this one is still not taken care of [showing ecologicalImportance**: in facts]&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Fixed regex in clean-insight.ts where leading bullet stripping previously stripped opening asterisks, leaving asymmetric prefixes. Now matches and strips variable prefixes regardless of formatting and moves the text cleanly into Ecological Role.&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;have you changed the response of agent why is it almost the same everytime i told you agent must rely on the image and connect the beauty with a persons life but the tone of the quote must be same as that of the persons text as we decided earlier in the chat and if user has not given any prompt then the agent must take the overall tone from the image and give a quote which is very emotional and deep for a persons life&lt;/p&gt;

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

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Ran diagnostic tracing on the vision model and discovered it was writing in conversational prose sentences instead of raw JSON. Updated the extraction engine in lib/server/ai.ts to extract quotes, lessons, facts, and mood from natural prose sentences. Enforced prompt directives so that the quote's emotional tone intimately matches user notes when provided, or captures the image's aesthetic atmosphere when notes are absent.&lt;/p&gt;

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

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        12 of 12 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  Prize Categories
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Best Use of MongoDB Atlas:&lt;/strong&gt; MongoDB Atlas powers NatureBrain, EarthLens AI's nature knowledge retrieval system. Atlas Vector Search retrieves relevant nature facts and ecological information to help Gemma 4 26B A4B generate more grounded, informative discoveries with supporting references.&lt;br&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; EarthLens AI uses Google's Gemma 4 26B A4B Instruct through Cloudflare Workers AI to analyse nature photographs, evaluate quest completion, and generate image-specific ecological insights and meaningful reflections. Gemma powers the core discovery experience, helping users connect what they observe in nature with a deeper understanding of the world around them.&lt;/p&gt;

&lt;h1&gt;
  
  
  What's next for EarthLens?
&lt;/h1&gt;

&lt;p&gt;There is still plenty I want to explore.&lt;/p&gt;

&lt;p&gt;I'd like to improve identification reliability, make uncertainty more visible, strengthen the supporting nature references, and provide better ways to export and back up journal entries.&lt;/p&gt;

&lt;p&gt;I also want to keep the main idea intact as the project grows: encouraging people to discover something outdoors, learn about it, and reflect on what makes it interesting.&lt;/p&gt;

&lt;p&gt;Technology doesn't always have to pull us deeper into the digital world. Sometimes, it can give us a reason to step away from it.&lt;/p&gt;

&lt;p&gt;That's what I'm trying to build with EarthLens AI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find Nature. Discover Yourself. 🌿&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hope you liked Reading this and do tell me if any modifications are required for this.&lt;br&gt;
Thanks for being with me till the end.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
