Metadata-Version: 2.4
Name: greenprompt
Version: 0.2.0
Summary: A green prompt tool to estimate energy usage of LLM prompts
License: MIT
License-File: LICENSE
Keywords: llm,energy,monitoring,ollama,cli
Author: Uday udapra@gmail.com
Requires-Python: >=3.9,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: Flask (>=3.1.0,<4.0.0)
Requires-Dist: flask-cors (>=6.0.0,<7.0.0)
Requires-Dist: nltk (>=3.9.1,<4.0.0)
Requires-Dist: pandas (>=2.2.3,<3.0.0)
Requires-Dist: plotly (>=6.1.1,<7.0.0)
Requires-Dist: psutil (>=7.0.0,<8.0.0)
Requires-Dist: py-cpuinfo (>=9.0.0,<10.0.0)
Requires-Dist: requests (>=2.32.3,<3.0.0)
Requires-Dist: tiktoken (>=0.9.0,<0.10.0)
Project-URL: Homepage, https://github.com/uday1201/greenprompt
Project-URL: Repository, https://github.com/uday1201/greenprompt
Description-Content-Type: text/markdown

# GreenPrompt 🌱

[![PyPI version](https://badge.fury.io/py/greenprompt.svg)](https://badge.fury.io/py/greenprompt)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)

**Track and reduce the real-world energy cost of your AI prompts.**

GreenPrompt is a local-first tool that intercepts Ollama LLM calls, measures CPU/GPU power consumption in real time, logs every prompt run to a local SQLite database, and provides a CLI and interactive web dashboard for energy analysis and reporting.

---

## Table of Contents

- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [CLI Reference](#cli-reference)
- [REST API](#rest-api)
- [Web Dashboard](#web-dashboard)
- [Prompt Scoring](#prompt-scoring)
- [Architecture](#architecture)
- [Configuration](#configuration)
- [Platform Support](#platform-support)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)

---

## Features

- **Real-time power sampling** — Continuously samples system CPU/GPU power every second, retaining a 10-minute sliding window. macOS uses `powermetrics`; Linux uses Intel/AMD RAPL energy counters, an ARM big.LITTLE frequency model, or `nvidia-smi` for GPU power.
- **Per-prompt energy measurement** — Computes watt-hours consumed during each LLM call against a 1-minute idle baseline.
- **Token tracking** — Records prompt tokens, completion tokens, and total tokens per run from Ollama's response metadata.
- **Dual estimation** — Provides both hardware-measured power and token-based estimates for cross-validation and offline comparison.
- **Persistent logging** — Stores all run data in a local SQLite database (`greenprompt_usage.db`).
- **Prompt quality scoring** — Automatically scores every prompt on 18 dimensions using offline NLTK heuristics (RTCF structure, clarity, conciseness, energy awareness, and more).
- **Interactive dashboard** — Plotly-powered web dashboard with 6 chart types: energy timeline, CPU/GPU breakdown, model comparison, estimated vs. actual, and baseline overlay.
- **REST API** — Flask server exposing endpoints for running prompts and querying usage history.
- **Ollama proxy** — Optional transparent reverse proxy to intercept existing Ollama traffic without code changes.

---

## Requirements

- Python 3.9 or higher
- [Ollama](https://ollama.com) installed and running locally
- **Live power sampling on macOS and Linux** — `powermetrics` on macOS (Apple Silicon or Intel); RAPL, ARM big.LITTLE, or `nvidia-smi` on Linux
- Windows: scoring, logging, and token tracking work fully; power sampling returns zero (see [Platform Support](#platform-support))

---

## Installation

### From PyPI

```bash
pip install greenprompt
```

### With Poetry

```bash
poetry add greenprompt
```

### Development Installation

```bash
git clone https://github.com/uday1201/greenprompt.git
cd greenprompt
poetry install
```

---

## Quick Start

```bash
# 1. Initialize (first time only; on macOS run it once with sudo so that
#    powermetrics can be used without a password prompt afterwards)
sudo greenprompt setup

# 2. Start the API server (required for prompt and dashboard commands)
greenprompt run --port 5000

# 3. Send a prompt and see energy stats
greenprompt prompt "Explain quantum entanglement in one sentence." --model llama3.2:latest

# 4. Open the analytics dashboard
greenprompt dashboard
```

---

## CLI Reference

GreenPrompt installs three equivalent command aliases: `greenprompt`, `gprompt`, and `greenp`.

### Command Overview

No GreenPrompt command needs to be run as root. The one exception is the
first `setup` on macOS: running it once with `sudo` installs a sudoers rule so
that `powermetrics` can be sampled without a password prompt from then on.
Without it, GreenPrompt still runs — energy readings are simply zero.

| Command | Description | Requires sudo |
|---|---|---|
| `setup` | Write config, download NLTK data, verify Ollama, create DB | Once, on macOS |
| `run` | Start the Flask API server in the background | No |
| `prompt` | Send a prompt; print response and energy stats | No |
| `monitor` | Display the last N prompt usage entries from DB | No |
| `score` | Score a prompt without sending it to a model | No |
| `dashboard` | Open the analytics dashboard in a browser | No |
| `stop` | Stop the running API server | No |
| `log_api` | Tail the API server log file | No |

---

### `greenprompt setup`

Initializes the environment: detects CPU power capabilities and writes them to the user config file (`~/.greenprompt/config.json` by default), downloads NLTK resources, verifies Ollama, and creates the SQLite database.

On macOS it also installs `/etc/sudoers.d/greenprompt`, which allows `powermetrics` to be sampled without a password. That step is the only reason to use `sudo` here, and it is skipped with an explanatory message if you run without it.

```bash
sudo greenprompt setup [--ollama-port PORT]
```

| Flag | Default | Description |
|---|---|---|
| `--ollama-port` | `11434` | Port where Ollama is running |

> **Note:** Run `setup` from the directory where you want the database (`greenprompt_usage.db`) to live. All subsequent commands should be run from the same directory.

---

### `greenprompt run`

Starts the GreenPrompt Flask API server as a background process, along with the power sampler for the current platform: `PowerMonitor` on macOS, `LinuxPowerMonitor` on Linux. Neither requires root.

```bash
greenprompt run [--port PORT]
```

> **Note:** Wait 10–15 seconds after starting before sending the first prompt, so the sampler has collected enough readings to average over.

| Flag | Default | Description |
|---|---|---|
| `--port` | `5000` | Port for the Flask server |

---

### `greenprompt prompt`

Sends a prompt to Ollama via the running API server, prints the LLM response, and displays detailed energy and token statistics.

```bash
greenprompt prompt "Your prompt here" [--model MODEL]
```

| Flag | Default | Description |
|---|---|---|
| `--model` | `llama3.2:latest` | Ollama model name to use |

**Example:**

```bash
greenprompt prompt "List three benefits of solar energy. Format: bullet points." --model llama2
```

**Output:**

```
Response:
• Solar energy is renewable and inexhaustible.
• It reduces electricity bills significantly over time.
• It produces no greenhouse gas emissions during operation.

--- Prompt usage data ---
Prompt tokens:       14
Completion tokens:   43
Total tokens:        57
Duration (sec):      3.21
Baseline power (W):  4.20
Baseline energy (Wh):0.000070
CPU power (W):       8.60
GPU power (W):       0.00
Combined power (W):  8.60
Energy used (Wh):    0.000767
```

---

### `greenprompt monitor`

Displays the last N prompt usage entries from the local SQLite database.

```bash
greenprompt monitor [--count N]
```

| Flag | Default | Description |
|---|---|---|
| `--count` | `10` | Number of recent entries to show |

---

### `greenprompt score`

Scores a prompt on 18 quality dimensions without sending it to any model. Entirely offline and instant.

```bash
greenprompt score "Your prompt here"
```

**Example:**

```bash
greenprompt score "You are a physics expert. Explain Newton's second law to a high school student. Format: bullet points."
```

**Output:**

```python
{
  'total_score': 34,
  'max_score': 50,
  'score_percent': 68.0,
  'details': {
    'RTCF Structure': 3,
    'Clarity & Specificity': 5,
    'Conciseness': 5,
    'Contextual Priming': 0,
    'Output Specification': 5,
    'Instructional Tone': 3,
    ...
  }
}
```

---

### `greenprompt dashboard`

Opens the Plotly analytics dashboard at `http://localhost:5000/dashboard` in the default browser. Requires the API server to be running.

```bash
greenprompt dashboard
```

---

### `greenprompt stop`

Stops the Flask API server by killing the process on the given port.

```bash
greenprompt stop [--port PORT]
```

| Flag | Default | Description |
|---|---|---|
| `--port` | `5000` | Port where the API server is running |

---

### `greenprompt log_api`

Tails the API server log file at `/tmp/api.log`.

```bash
greenprompt log_api [--follow]
```

| Flag | Default | Description |
|---|---|---|
| `--follow` | `False` | Follow (tail -f) the log output |

---

## REST API

The GreenPrompt Flask server exposes the following endpoints after `greenprompt run`.

### `POST /api/prompt`

Run a prompt through Ollama, measure energy, and log the result.

**Request body:**
```json
{
  "prompt": "Explain photosynthesis.",
  "model": "llama2"
}
```

**Response:**
```json
{
  "prompt": "Explain photosynthesis.",
  "prompt_score": 36.0,
  "prompt_score_details": {
    "RTCF Structure": 1,
    "Clarity & Specificity": 5
  },
  "response": "Photosynthesis is the process by which plants...",
  "model": "llama2",
  "prompt_tokens": 5,
  "completion_tokens": 82,
  "total_tokens": 87,
  "total_energy (Wh)": 0.000512,
  "duration_sec": 4.13,
  "combined_power_w (W)": 0.446,
  "cpu_power_w (W)": 0.312,
  "gpu_power_w (W)": 0.134,
  "energy_estimate_tokens": 0.00087,
  "energy_estimate_prompt": 0.000005,
  "baseline_energy (Wh)": 0.000067,
  "baseline_power (W)": 4.03,
  "gpu_usage": "No GPU detected",
  "system_info": { "OS": "Darwin", "CPU": "Apple M2", ... }
}
```

### `GET /api/usage/all`

Retrieve all prompt usage records as a JSON array.

```bash
curl http://localhost:5000/api/usage/all
```

### `GET /api/usage/model/<model>`

Filter usage records by model name.

```bash
curl http://localhost:5000/api/usage/model/llama2
```

### `GET /api/usage/timeframe?start=ISO&end=ISO`

Filter usage records by ISO 8601 timestamp range.

```bash
curl "http://localhost:5000/api/usage/timeframe?start=2024-01-01T00:00:00&end=2024-12-31T23:59:59"
```

### `GET /dashboard`

Serves the interactive analytics dashboard.

### `ANY /ollama/api/<path>`

Transparent reverse proxy to the local Ollama server at `http://localhost:11434`. Preserves method, headers, query params, and body.

```bash
# Example: list models via proxy
curl http://localhost:5000/ollama/api/tags
```

---

## Web Dashboard

The dashboard at `http://localhost:5000/dashboard` provides six interactive Plotly charts:

| Chart | Description |
|---|---|
| Overview indicators | Total prompts, total energy (Wh), total CPU/GPU watts, total tokens, energy per token |
| Energy usage timeline | Line chart of energy (Wh) per prompt over time |
| CPU vs GPU power | Grouped bar chart of CPU and GPU watts per prompt |
| Estimated vs actual energy | Comparison of token-estimate, prompt-estimate, and hardware-measured energy |
| Baseline vs total energy | Overlay of idle baseline and total energy per prompt |
| Model comparison | Average energy per model (bar chart) |

---

## Prompt Scoring

Every prompt sent through GreenPrompt is automatically scored on 18 quality dimensions. Use `greenprompt score` to evaluate prompts standalone.

The scorer is entirely offline — no API call is made. It uses NLTK POS tagging and regex pattern matching.

### Scoring Dimensions (50 points total)

| Dimension | Max | Detection method |
|---|---|---|
| RTCF Structure | 4 | Role pattern + task verb + context marker + format spec (1 pt each) |
| Clarity & Specificity | 5 | Task verb present and prompt ≤ 400 chars = 5; task verb only = 3 |
| Conciseness | 5 | Starts at 5; minus 1 per filler phrase ("please", "could you", "just", etc.) |
| Contextual Priming | 3 | Matches `context:`, `background:`, `for <word>`, `audience:` |
| Output Specification | 5 | Matches `format:`, `output as`, or table/bullet/list/json/csv/markdown |
| Instructional Tone | 3 | Any imperative verb detected via POS tagging |
| Examples & Few-Shot | 2 | Matches `example:`, `Q:`, `A:`, `sample output`, `e.g.` |
| Task Decomposition | 2 | Matches `first...then` sequence or `step N` numbering |
| Positive/Negative Examples | 2 | Matches `do not`, `exclude`, `not include`, `except` |
| Iterative Refinement | 2 | Matches `revise`, `improve`, `refine`, `rewrite`, `repeat` |
| Creativity Control | 2 | Matches `creative`, `imaginative`, `unusual`, `inventive` |
| Tone & Style | 2 | Matches `tone:`, `style:`, `formal`, `casual`, `humorous`, `professional` |
| Error Prevention | 2 | Matches `do not guess`, `only answer if sure`, `if unsure, say so` |
| Evaluation & Validation | 2 | Matches `double-check`, `verify`, `validate`, `cross-check` |
| Sensitivity & Inclusivity | 2 | Matches `inclusive`, `avoid bias`, `unbiased`, `sensitive to` |
| Efficiency & Sustainability | 2 | Matches `concise`, `briefly`, `max N words`, `minimize tokens` |
| Energy Awareness | 2 | Matches `energy usage`, `carbon`, `footprint`, `sustainable`, `green` |
| Keyword Richness | 2 | ≥5 unique non-stopword tokens = 2; ≥2 = 1; else 0 |

See [docs/prompt-scoring.md](docs/prompt-scoring.md) for full details and optimization examples.

---

## Architecture

```
greenprompt/
├── cli.py           Entry point — argparse subcommands, starts API subprocess
├── api.py           Flask server — REST endpoints, Ollama proxy, power monitor init
├── core.py          run_prompt() — orchestrates Ollama call, power measurement, scoring
├── dbconn.py        SQLite — init_db, save_prompt_usage, get_prompt_usage
├── samplerMac.py    PowerMonitor — daemon thread sampling powermetrics every second
├── samplerLinux.py  LinuxPowerMonitor — RAPL / ARM / TDP CPU sampling + nvidia-smi GPU
├── sysUsage.py      OS-agnostic wrappers — system info, power measurement, GPU detection
├── scoreBasic.py    Prompt scorer — 18-dimension offline NLTK/regex analysis
├── analytics.py     Plotly chart functions for the dashboard
├── constants.py     Tracked source — defaults, live platform values, user-config overlay
├── setup.py         Setup routine — writes ~/.greenprompt/config.json, DB init, NLTK download
└── templates/
    └── dashboard.html   Dashboard HTML with embedded Plotly JS
```

### Request Flow

```
User
  │
  ▼
greenprompt prompt "..."
  │  (CLI sends HTTP POST)
  ▼
POST /api/prompt  (api.py Flask server)
  │
  ▼
core.py: run_prompt(prompt, model)
  ├─ PowerMonitor.samples  ──► baseline avg (1 min before prompt)
  ├─ POST http://127.0.0.1:11434/api/generate  (Ollama)
  ├─ PowerMonitor.samples  ──► during-prompt avg
  ├─ energy_wh = (avg_combined_w × duration_sec) / 3600
  ├─ scoreBasic.score_prompt(prompt)
  └─ dbconn.save_prompt_usage(result)
        │
        ▼
    greenprompt_usage.db (SQLite)
```

### Power Sampling

On macOS and Linux, a daemon thread samples once a second into a `collections.deque` holding the last 600 readings (10 minutes). When `run_prompt()` completes, the matching `measure_power_*()` function filters samples by `[start_time, end_time]` to compute average watts and energy, and separately averages the 60 seconds before the prompt as the idle baseline.

**macOS** — `PowerMonitor` (`samplerMac.py`) calls:
```bash
sudo powermetrics --samplers cpu_power -n 1 -i 1000
```

**Linux** — `LinuxPowerMonitor` (`samplerLinux.py`) picks the best available CPU source at startup:

| Mode | Condition | Method |
|---|---|---|
| `rapl` | `/sys/class/powercap/intel-rapl*/energy_uj` present | Intel/AMD energy counter delta — measured, not estimated |
| `arm_biglittle` | `psutil.cpu_freq(percpu=True)` reports more than one distinct max frequency | Per-cluster frequency-squared model over `scaling_cur_freq` (power ∝ V²f) |
| `linear_tdp` | Neither of the above | `cpu_percent / 100 × CPU_TDP_W` |

GPU power comes from a single long-running `nvidia-smi dmon -s p -d 1` process, with a per-call subprocess fallback. If the prompt is shorter than one sample interval, `measure_power_linux()` interpolates from neighbouring samples and flags the result with `"extrapolated": true`.

For detailed architecture documentation see [docs/architecture.md](docs/architecture.md).

---

## Configuration

Settings live in a JSON file written by `greenprompt setup`, by default `~/.greenprompt/config.json` (override with `$GREENPROMPT_CONFIG` or `$GREENPROMPT_HOME`).

| Key | Default | Description |
|---|---|---|
| `OLLAMA_URL` | `http://127.0.0.1:11434` | Ollama server URL |
| `CPU_TDP_W` | `40.0` | CPU TDP in watts; used only by the Linux `linear_tdp` fallback |
| `CPU_POWER_SOURCE` | `estimated` | Informational; `rapl` when direct energy counters were found |

```bash
# where is my config?
python -c "from greenprompt import constants; print(constants.config_path())"
```

Platform values (`OS`, `MACHINE`, `PLATFORM`, ...) are derived live on every import by `greenprompt/constants.py` and are never stored, so they always match the machine actually running.

**Database location:** Created in the working directory where `setup` was run:
```
<cwd>/greenprompt_usage.db
```
Always run GreenPrompt commands from the same directory to use the same database file.

See [docs/configuration.md](docs/configuration.md) for the full configuration reference.

---

## Platform Support

| Feature | macOS Apple Silicon | macOS Intel | Linux | Windows |
|---|---|---|---|---|
| Live CPU power sampling | ✅ powermetrics | ✅ powermetrics | ✅ RAPL / ARM / TDP | 🔜 Intel Power Gadget |
| Live GPU power sampling | ✅ powermetrics | ✅ powermetrics | ✅ nvidia-smi | 🔜 nvidia-smi |
| GPU detection | ✅ system_profiler | ✅ system_profiler | ✅ nvidia-smi | ✅ nvidia-smi |
| GPU utilization stats | — | — | ✅ nvidia-smi | ✅ nvidia-smi |
| Token counting | ✅ | ✅ | ✅ | ✅ |
| Prompt scoring | ✅ | ✅ | ✅ | ✅ |
| Database logging | ✅ | ✅ | ✅ | ✅ |
| REST API & dashboard | ✅ | ✅ | ✅ | ✅ |
| Token-based energy estimate | ✅ | ✅ | ✅ | ✅ |
| `run` / `stop` server lifecycle | ✅ | ✅ | ✅ | ❌ needs `lsof` |

Windows users will see `energy_wh = 0` for hardware power measurement; all other features are fully functional. See [docs/platform-support.md](docs/platform-support.md) for the implementation roadmap.

---

## Troubleshooting

**`❌ Could not connect to Ollama at http://127.0.0.1:11434`**

Ollama is not running. Start it:
```bash
ollama serve
```

**`Error connecting to API`**

The GreenPrompt API server is not running:
```bash
greenprompt run --port 5000
```

**Power readings are all zero on macOS**

The passwordless `powermetrics` rule is not installed, so the sampler cannot read power. Run setup once with sudo:
```bash
sudo greenprompt setup
```

**Power readings are all zero on Linux**

Check which mode the sampler chose — `greenprompt log_api` reports it at startup. In `linear_tdp` mode readings are estimates derived from `CPU_TDP_W`, not measurements. RAPL requires read access to `/sys/class/powercap/intel-rapl*/energy_uj`, which some distributions restrict to root. GPU power requires `nvidia-smi` on `PATH`.

**Power readings are all zero on Windows**

Live power sampling is not yet implemented on Windows. The `energy_estimate_tokens` field provides a token-count-based approximation.

**Dashboard shows no data**

The database is either empty or was created in a different working directory. Check:
```bash
greenprompt monitor --count 5
```
If empty, send some prompts first. If the DB file is in a different directory, `cd` there before running GreenPrompt.

**`Warning: Power usage data is incomplete or missing`**

The `PowerMonitor` has not yet collected enough samples. Wait 10–15 seconds after `greenprompt run` before sending the first prompt.

---

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for the full contribution workflow. Quick summary:

```bash
git checkout -b feature/your-feature
poetry install
# make changes
poetry run ruff check .
poetry run ruff fmt .
git commit -m "feat(scope): description"
git push origin feature/your-feature
# open pull request
```

### Roadmap

- Windows support via Intel Power Gadget / WMI, and a `lsof`-free server lifecycle
- Test coverage for the scoring, database, and analytics modules
- Model-aware token energy estimates (the current table predates Ollama model naming)
- VS Code extension
- Browser extension for cloud LLM APIs
- Carbon offset integration
- Team dashboards and enterprise reporting

---

## License

[MIT License](LICENSE) — © Uday & Anirudh

