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>, and<p>tags β no classes or IDs - 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 |
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 test # Run Jest testsThe 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
β βββ exclude.js # Words/phrases to exclude from output
βββ src/
β βββ index.ts # Main entry point & pipeline orchestrator
β βββ types/ # TypeScript type definitions (Payload, PayloadType)
β βββ cli/ # Styled CLI logging (chalk-based)
β βββ fetch/ # API data retrieval
β βββ prompts/ # LLM prompt assembly & generation orchestration
β βββ interfaces/ # Ollama and SQLite wrappers
β βββ helpers/ # Output cleaning (strips markdown fences from LLM output)
β βββ send/ # Upload generated descriptions to API
β βββ bin/ # Standalone scripts (sendCategories, sendProducts, nuke, preview)
β βββ __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)
Single table ai_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 |
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.
- 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