KeywordSmith AI is a CLI pipeline that leverages Ollama and open-source Large Language Models to generate SEO-optimized product and category descriptions for e-commerce platforms. It fetches items from a remote API, generates HTML descriptions via a local LLM, stores results in SQLite, and can upload them back to the API. Everything runs on your machine β no cloud, no data leaks.
- 100% Local: All processing stays on your machine via Ollama β no third-party cloud calls
- Multi-type Generation: Supports product descriptions, category descriptions, and compact category summaries (
category_short) - SEO-optimized HTML Output: Clean HTML using only
<h2>,<strong>,<p>,<ul>, and<li>tags β no classes or IDs - Technical Description Awareness: Uses the supplier's sanitized source description (
source_desc) as an authoritative technical reference, weaving real specs into the prose and optionally adding a bulleted technical characteristics section for technical products - Web Crawler Fallback (opt-in): When a product has no supplier description, searches the web by EAN via Ollama Web Search, verifies the match (EAN / manufacturer code / brand), and caches a verified technical description in SQLite β so the same EAN is never searched twice
- Flexible Pipeline: Fetch -> Generate -> Store -> Upload, with CLI flags for fine-grained control
- Smart Deduplication: Skips already-generated items using SQLite tracking with upsert-on-conflict
- Upload Modes: Upload descriptions one-by-one during generation (
--upload=during) or batch after (--upload=after) - Preview Mode: Test LLM output on a sample before running the full pipeline
- Rich CLI Output: Color-coded logging with progress percentages via Chalk
- Node.js (v18 or higher)
- Ollama installed and running on your system
- A supported LLM model (recommended:
gemma4:26b, alternatives:llama3.1:8b,mixtral:8x22b,qwen2.5:7b)
- Visit Ollama.ai
- Download and install for your operating system
- Pull the recommended model:
ollama pull gemma4:26b
# Clone the repository
git clone https://github.com/AllWorkNoPlay-95/KeywordSmith-AI.git
# Navigate to the project directory
cd KeywordSmith-AI
# Install dependencies
npm install
# Create the .env file based on .env.example
cp .env.example .envEdit the .env file to configure the following parameters:
| Variable | Description | Default |
|---|---|---|
API_TOKEN |
API authentication token | β |
API_DOWN_ROOT |
Base URL for fetching data | β |
API_UP_ROOT |
Base URL for uploading descriptions | Falls back to API_DOWN_ROOT |
API_CATEGORIES_DOWN_URL |
Categories fetch endpoint path | β |
API_PRODUCTS_DOWN_URL |
Products fetch endpoint path | β |
API_CATEGORIES_UP_URL |
Categories upload endpoint path | β |
API_PRODUCTS_UP_URL |
Products upload endpoint path | β |
CATEGORIES_TARGET_KEY |
JSON key to extract category name | name |
PRODUCTS_TARGET_KEY |
JSON key to extract product name | name |
MODEL |
Ollama model to use | gemma4:26b |
LANGUAGE |
Content generation language | en |
COMPANY_NAME |
Your company name for contextual generation | β |
THINK |
Enable LLM thinking/reasoning mode | false |
SQLITE_DB_PATH |
Local database path | ./db.sqlite |
OLLAMA_API_KEY |
Ollama API key, required for Ollama's web search capability | β |
WEB_SEARCH |
Enable the web-crawler fallback inline during npm run dev (opt-in β see Web Crawler) |
false |
WEB_SEARCH_MAX_RESULTS |
Max search results requested per EAN lookup | 5 |
npm run dev # Run full pipeline (fetch + generate for all types)
npm run products # Generate product descriptions only
npm run categories # Generate category descriptions only
npm run categoriesShort # Generate compact category summaries only
npm run preview # Preview LLM output on a small product samplenpm run sendCategories # Upload all generated category descriptions to API
npm run sendProducts # Upload all generated product descriptions to API (stub)Flags are passed via node-args syntax:
# Filter to specific type(s)
npm run dev -- --only=product
npm run dev -- --only=category,category_short
# Upload as each description is generated
npm run dev -- --upload=during
# Batch upload everything after generation completes
npm run dev -- --upload=afternpm run prompts # Print all resolved prompts and current model/settings
npm run nuke # Delete the local SQLite database
npm run migrate # Apply any pending SQLite schema migrations
npm test # Run Jest testsMany products don't have a supplier-provided technical description (source_desc). For those, KeywordSmith can
search the web by EAN, verify the result actually matches the product (EAN / manufacturer code / brand
corroboration), extract a technical description, and cache it in SQLite (product_tech_sources) so the same
EAN is never searched twice. It's opt-in β it never runs during a plain npm run dev:
npm run webSearch # Backfill: search + cache for all eligible products, then exit
npm run dev -- --web-search # Search+cache inline, live, during the normal pipeline
WEB_SEARCH=true npm run dev # Same as --web-search, via .envRequires an Ollama API key set as OLLAMA_API_KEY (Ollama's web search is a
cloud capability, not local). Once a product's EAN is cached, subsequent npm run dev runs reuse the cached
result at no extra cost β no flag needed.
The pipeline follows a Fetch -> Generate -> Store -> Upload flow:
Remote API ββfetchββ> Item list ββLLMββ> HTML description ββsaveββ> SQLite
β
(optional upload)
β
v
Remote API
KeywordSmith-AI/
βββ config.ts # Central registry β env vars, API URLs, payload type configs
βββ tuning/ # Prompt templates (CommonJS)
β βββ system.js # Shared system prompt (company name, language, excluded words)
β βββ product.js # Product-specific user prompt
β βββ category.js # Category-specific user prompt
β βββ category_short.js # Compact category summary prompt
β βββ webExtract.js # Verification + extraction prompt for the web crawler
β βββ exclude.js # Words/phrases to exclude from output
βββ src/
β βββ index.ts # Main entry point & pipeline orchestrator
β βββ types/ # TypeScript type definitions (Payload, PayloadType, TechCacheRow)
β βββ cli/ # Styled CLI logging (chalk-based)
β βββ fetch/ # API data retrieval
β βββ prompts/ # LLM prompt assembly & generation orchestration
β βββ interfaces/ # Ollama, Ollama web search, and SQLite wrappers
β βββ crawl/ # EAN web-search crawler (search, verify, extract, cache)
β βββ helpers/ # Output cleaning (strips markdown fences from LLM output)
β βββ send/ # Upload generated descriptions to API
β βββ bin/ # Standalone scripts (sendCategories, sendProducts, nuke, preview, webSearch)
β βββ __tests__/ # Jest tests
βββ .env # Environment variables (not in repo)
βββ .env.example # Template for .env
βββ db.sqlite # Local SQLite database (ephemeral, deletable via npm run nuke)
Generated descriptions, with automatic schema migration for older databases:
| Column | Type | Notes |
|---|---|---|
pk |
INTEGER | Primary key |
id |
INTEGER | Item ID from API |
name |
TEXT | Item name |
output |
TEXT | Generated HTML description |
type |
VARCHAR(255) | product, category, or category_short |
ean |
TEXT | Product EAN code |
cod_produttore |
TEXT | Manufacturer code |
brand |
TEXT | Product brand |
full_desc |
TEXT | Original full description from API |
source_desc |
TEXT | Sanitized supplier technical description from API, used as an authoritative technical source in the prompt |
web_desc |
TEXT | Technical description found and verified by the web crawler, used only when source_desc is absent |
model |
TEXT | LLM model used for generation |
think |
INTEGER | Whether thinking/reasoning mode was enabled (0/1) |
created_at |
TEXT | Row creation timestamp |
updated_at |
TEXT | Last update timestamp |
Unique index on (id, type) β upserts on conflict.
Cache of web-crawler results, keyed by EAN, so a product is only searched for once (see Web Crawler Fallback):
| Column | Type | Notes |
|---|---|---|
pk |
INTEGER | Primary key |
ean |
TEXT | Search key (unique) |
product_id |
INTEGER | Product ID from API |
name, cod_produttore, brand |
TEXT | Product identity snapshot at search time |
description |
TEXT | Extracted technical description (NULL if no verified match) |
source_url |
TEXT | URL the description was extracted from |
status |
TEXT | found or not_found |
confidence |
REAL | LLM-reported match confidence (0β1) |
model |
TEXT | LLM model used for verification/extraction |
created_at / updated_at |
TEXT | Timestamps |
Unique index on ean β upserts on conflict. Negative results (not_found) are cached too, so unmatched
products aren't re-searched on every run.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the GPL-3.0 License - see the LICENSE file for details.
Samuele Mancuso - @AllWorkNoPlay-95
Project Link: https://github.com/AllWorkNoPlay-95/KeywordSmith-AI
KeywordSmith AI is actively used in production by KartoClick, powering SEO content generation for their e-commerce catalog.
</> with <3 by AllWorkNoPlay-95