Posted on
Artificial Intelligence

Artificial Intelligence Browser Workflows

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

AI Browser Workflows from Bash: Turn Clicks into Reproducible Automation

If you live in a terminal but your day is still ruled by the browser, you’re leaving speed and repeatability on the table. Researching, filling forms, checking dashboards, copying snippets into docs—AI can do the reading and drafting, while Bash orchestrates headless browsers to click, fetch, and extract. The result: repeatable pipelines that replace “open-tab, click, skim, paste” with a single command or cron job.

This post shows how to build practical “AI + Browser” workflows using Linux tools you already trust: Bash, headless Chromium/Firefox, Playwright, curl/jq, and either a cloud LLM API or a local model via Ollama. You’ll get 3–5 actionable patterns, complete with installation commands for apt, dnf, and zypper.


Why AI Browser Workflows Are Worth Your Time

  • Repeatability beats tab chaos: Headless browsers click and render, Bash glues steps together, and AI turns raw pages into summaries, structured data, or drafts.

  • Privacy and control: Run locally with Ollama or point at your own self-hosted API. Everything remains scriptable and auditable.

  • Scale without burnout: Once you can script one page, you can script ten. Cron turns one-off tricks into nightly routines.

  • Less yak shaving: Tools like Playwright neutralize brittle DOM scraping by running a real browser engine—no guesswork on client-side rendering.


Prerequisites: Install the Building Blocks

You don’t need a desktop session; headless mode is enough. Install these packages (curl, jq, nodejs/npm, headless browsers + drivers, and lynx for plain-text extraction).

APT (Debian/Ubuntu)

sudo apt update
sudo apt install -y \
  curl jq git nodejs npm \
  chromium chromium-driver \
  firefox geckodriver \
  lynx

DNF (Fedora/RHEL/CentOS Stream)

sudo dnf install -y \
  curl jq git nodejs npm \
  chromium chromedriver \
  firefox geckodriver \
  lynx

Zypper (openSUSE)

sudo zypper refresh
sudo zypper install -y \
  curl jq git nodejs npm \
  chromium chromedriver \
  MozillaFirefox geckodriver \
  lynx

Notes:

  • Node.js 18+ is recommended. If your distro ships an older Node, consider using NodeSource or your distro’s newer module streams.

  • Playwright will download its own browser binaries by default. System browsers/drivers are still handy for other tooling and sanity checks.

Optional: Install Ollama (for local models)

curl -fsSL https://ollama.com/install.sh | sh
# Example: download a small general model
ollama pull llama3.1

Optional: Prepare Playwright (downloads browsers and dependencies)

npx playwright install chromium firefox
# Or include OS deps (uses apt/dnf/zypper under the hood where possible):
npx playwright install --with-deps

Workflow 1: Research Digest (Fetch → Read → Summarize)

Goal: Given a list of URLs, fetch readable text, summarize with an LLM, and produce a daily digest.

digest.sh

#!/usr/bin/env bash
set -euo pipefail

URLS_FILE="${1:-urls.txt}"     # one URL per line
OUTFILE="${2:-digest-$(date +%F).md}"

summarize_locally() {
  # Requires: ollama pull llama3.1
  ollama run llama3.1
}

summarize_openai() {
  # Requires: export OPENAI_API_KEY=...
  local prompt
  prompt=$(jq -Rs --arg role "user" '{model:"gpt-4o-mini",messages:[{role:$role,content:.}],temperature:0.2}')
  curl -s https://api.openai.com/v1/chat/completions \
    -H "Authorization: Bearer $OPENAI_API_KEY" \
    -H "Content-Type: application/json" \
    -d "$prompt" | jq -r '.choices[0].message.content'
}

echo "# Daily Research Digest ($(date))" > "$OUTFILE"
echo >> "$OUTFILE"

while read -r url; do
  [[ -z "$url" || "$url" =~ ^# ]] && continue
  echo "Processing: $url" 1>&2

  # Extract plain text with lynx (simple, robust)
  text="$(lynx -dump -nolist "$url" | sed 's/^[[:space:]]*[0-9]\+\.\s*//')"
  # Trim to a manageable size if needed
  text="$(printf "%s" "$text" | head -c 20000)"

  printf "## %s\n\n" "$url" >> "$OUTFILE"
  printf "Summary:\n\n" >> "$OUTFILE"

  # Choose one summarizer: local Ollama or OpenAI
  if command -v ollama >/dev/null 2>&1; then
    printf "Summarize the following webpage in 5 bullet points, then extract 3 action items if any.\n\n%s" "$text" | summarize_locally >> "$OUTFILE"
  else
    summary="$(printf "Summarize the following webpage in 5 bullet points, then extract 3 action items if any.\n\n%s" "$text" | summarize_openai)"
    printf "%s\n" "$summary" >> "$OUTFILE"
  fi

  echo -e "\n---\n" >> "$OUTFILE"
done < "$URLS_FILE"

echo "Wrote $OUTFILE"
  • Put URLs in urls.txt (one per line).

  • Run: bash digest.sh

  • Automate with cron: crontab -e0 7 * * 1-5 /path/to/digest.sh /path/to/urls.txt /path/to/daily.md

Why this works: lynx creates readable text without fragile DOM surgery; AI condenses to insight.


Workflow 2: Structured Scrape with Headless Browsers (Playwright → JSON → jq)

Goal: Visit a JS-heavy page, wait for elements, extract structured data, and print JSON for downstream Bash tooling.

create extract.mjs

import { chromium } from 'playwright';

const url = process.argv[2] || 'https://news.ycombinator.com/';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();

// Be nice to servers
await page.setExtraHTTPHeaders({ 'User-Agent': 'bash-ai-bot/1.0 (+https://example.org)' });

await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
// Wait for content (adjust selector for your target)
await page.waitForSelector('a.storylink, a.titlelink, a[href^="item?id="]');

const items = await page.$$eval('tr.athing', rows => {
  return rows.slice(0, 10).map(row => {
    const titleLink = row.querySelector('a.storylink, a.titlelink') || row.querySelector('a');
    return {
      title: titleLink?.innerText?.trim() || null,
      url: titleLink?.href || null,
      id: row.getAttribute('id') || null
    };
  });
});

console.log(JSON.stringify({ scrapedAt: new Date().toISOString(), url, items }, null, 2));
await browser.close();

Run it:

node extract.mjs "https://news.ycombinator.com/"

Pipe to jq or your LLM:

node extract.mjs | jq '.items[] | select(.title!=null) | .title'
node extract.mjs | ollama run llama3.1 <<< "Group these headlines by theme and output YAML:\n$(cat)"

Why this works: You let a real browser render JS, then reliably extract with selectors. Outputting JSON makes everything composable in Bash.


Workflow 3: Autofill Repetitive Web Forms (CSV → Playwright → Logs)

Goal: Submit the same form for multiple rows of data without clicking through.

create autofill.mjs

import { chromium } from 'playwright';
import fs from 'node:fs';

const csvPath = process.argv[2] || 'data.csv';
const url = process.argv[3] || 'https://example.com/form';

const rows = fs.readFileSync(csvPath, 'utf8').trim().split('\n').slice(1).map(line => {
  const [name, email, message] = line.split(',');
  return { name, email, message };
});

const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();

for (const [i, row] of rows.entries()) {
  console.error(`Submitting row ${i+1}: ${row.email}`);
  await page.goto(url, { waitUntil: 'networkidle' });

  await page.fill('input[name="name"]', row.name);
  await page.fill('input[name="email"]', row.email);
  await page.fill('textarea[name="message"]', row.message);
  await Promise.all([
    page.click('button[type="submit"]'),
    page.waitForNavigation({ waitUntil: 'networkidle' })
  ]);

  // Optional: confirm success
  const ok = await page.isVisible('text=Thank you') || await page.isVisible('.success');
  console.log(JSON.stringify({ index: i, email: row.email, success: !!ok }));
}

await browser.close();

Example CSV (data.csv)

name,email,message
Ada Lovelace,ada@example.com,Please send the API docs.
Linus Torvalds,linus@example.com,Following up on kernel CI.

Run it:

node autofill.mjs data.csv "https://example.com/form"

Enhancement: After submission, capture the confirmation text and send it to an LLM to draft a follow‑up email template.


Workflow 4: Page Health Check with AI Notes (Screenshot → LLM)

Goal: Nightly check a page, capture a screenshot and page text, and let AI flag regressions or missing content.

check.mjs

import { firefox } from 'playwright';
import fs from 'node:fs';

const url = process.argv[2] || 'https://example.com/';
const out = process.argv[3] || 'check';

const browser = await firefox.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });

await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 });
await page.screenshot({ path: `${out}.png`, fullPage: true });
const text = await page.locator('body').innerText();
fs.writeFileSync(`${out}.txt`, text);
await browser.close();

console.log(`Captured ${out}.png and ${out}.txt`);

Then, analyze with your LLM:

summary="$(printf "Scan this page text and flag issues:\n\n%s" "$(cat check.txt)" | ollama run llama3.1)"
printf "%s\n" "$summary" > check-report.md

Schedule via cron and alert on diffs:

0 6 * * * /usr/bin/node /path/to/check.mjs https://example.com /var/reports/homepage && /usr/bin/git -C /var/reports add . && /usr/bin/git -C /var/reports commit -m "Daily check" || true

Tips for Reliable, Respectful Automation

  • Be gentle: add waits, set a clear User-Agent, obey robots.txt and site terms.

  • Secrets: store API keys in environment variables or a password store, not in scripts.

  • Determinism: pin Node and Playwright versions; set timeouts and explicit selectors.

  • Observability: log JSON to stdout and errors to stderr; version your scripts.

  • Local-first: prefer Ollama for sensitive text; fall back to a cloud LLM when you need stronger reasoning.


Conclusion and Next Steps

You now have a pattern: headless browser fetches, Bash glues, AI interprets. Start small: 1) Pick one page you touch daily. 2) Script it with Playwright (render + extract). 3) Add an AI step to summarize or structure results. 4) Put it behind a one-liner or a cron job.

Call to Action:

  • Install the prerequisites for your distro (apt/dnf/zypper above).

  • Prototype Workflow 1 today with two URLs you actually read.

  • When it saves you 10 minutes, expand to Workflows 2–4 and share your script with your team.

Your browser doesn’t have to be a click farm. With Bash + AI, it becomes a pipeline.