Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## [0.3.0] - 2026-05-26

### Changed
- Project renamed from **promptvero** to **graver** and transferred to the Practical Mind organization
- Originally created by Muhammed Şen as [promptvero](https://github.com/MuhammedSenn/promptvero)
- Python package renamed: `promptvero` → `graver`
- CLI command renamed: `pv` → `gr`
- Default storage directory renamed: `.promptvero/` → `.graver/`
- Base exception class renamed: `PromptVeroError` → `GraverError`

## [0.2.0] - 2026-04-14

### Added
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2026 Muhammed Şen
Copyright (c) 2026 Practical Mind

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
42 changes: 21 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# promptvero
# Graver

Git-like version control for your LLM prompts.

Save prompt versions, view history, compare diffs, and roll back — all from Python, with zero external dependencies.

## Why promptvero?
## Why graver?

LLM prompts change constantly. Tracking what changed, when, and which version performed better quickly becomes a mess.

promptvero solves this:
graver solves this:
- Every change is automatically versioned — v1, v2, v3...
- Roll back to any version instantly
- Pin a specific version as your main (canonical) prompt
Expand All @@ -18,13 +18,13 @@ promptvero solves this:
## Installation

```bash
pip install promptvero
pip install graver
```

## Quick Start

```python
from promptvero import Prompt
from graver import Prompt

p = Prompt("system")
p.save("You are a helpful assistant")
Expand All @@ -45,34 +45,34 @@ print(p.changes("v1", "v2"))

## CLI

The `pv` command lets you use promptvero directly from the terminal — no Python script needed. Useful for quickly saving files, inspecting version history, or integrating into shell scripts and CI/CD pipelines.
The `gr` command lets you use graver directly from the terminal — no Python script needed. Useful for quickly saving files, inspecting version history, or integrating into shell scripts and CI/CD pipelines.

```bash
pv list # list all saved prompts
pv save <name> <file> # save a file as a new version
pv log <name> # show version history
pv show <name> [version] # show prompt content
pv changes <name> [v1] [v2] # show what changed between versions
pv set-main <name> <version> # mark a version as main
pv get-main <name> # print the main version content
pv delete <name> <version> # delete a specific version
pv delete-prompt <name> # delete a prompt and all its versions
gr list # list all saved prompts
gr save <name> <file> # save a file as a new version
gr log <name> # show version history
gr show <name> [version] # show prompt content
gr changes <name> [v1] [v2] # show what changed between versions
gr set-main <name> <version> # mark a version as main
gr get-main <name> # print the main version content
gr delete <name> <version> # delete a specific version
gr delete-prompt <name> # delete a prompt and all its versions
```

Use `--base-dir` to specify a custom storage directory:

```bash
pv --base-dir ./prompts log my-prompt
gr --base-dir ./prompts log my-prompt
```

## API Reference

### `Prompt(name, base_dir=".promptvero")`
### `Prompt(name, base_dir=".graver")`

| Parameter | Type | Description |
|-----------|------|-------------|
| `name` | `str` | Unique identifier for this prompt. |
| `base_dir` | `str` | Root directory for storage. Defaults to `.promptvero`. |
| `base_dir` | `str` | Root directory for storage. Defaults to `.graver`. |

---

Expand Down Expand Up @@ -194,7 +194,7 @@ Returns the full content of a version with a formatted header. Displays a `[main

---

### `Prompt.list_all(base_dir=".promptvero") -> list[str]`
### `Prompt.list_all(base_dir=".graver") -> list[str]`

Returns the names of all saved prompts in the given base directory.

Expand All @@ -204,10 +204,10 @@ Returns the names of all saved prompts in the given base directory.

## How It Works

Every `Prompt` writes to a `.promptvero/` folder in your working directory.
Every `Prompt` writes to a `.graver/` folder in your working directory.

```
.promptvero/
.graver/
system/
v1.txt
v2.txt
Expand Down
18 changes: 9 additions & 9 deletions promptvero/__init__.py → graver/__init__.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
"""promptvero — Git-like version control for LLM prompts.
"""graver — Git-like version control for LLM prompts.

Save, diff, and roll back prompt versions with a simple Python API.
All data is stored as plain text files under a ``.promptvero/`` directory.
All data is stored as plain text files under a ``.graver/`` directory.

Example::

from promptvero import Prompt
from graver import Prompt

p = Prompt("system")
p.save("You are a helpful assistant")
print(p.get())
"""

from promptvero.core import Prompt
from promptvero.exceptions import (
from graver.core import Prompt
from graver.exceptions import (
GraverError,
PromptNotFoundError,
PromptVeroError,
StorageError,
VersionNotFoundError,
)

__version__ = "0.2.0"
__author__ = "Muhammed Sen"
__version__ = "0.3.0"
__author__ = "Practical Mind"

__all__ = [
"Prompt",
"PromptVeroError",
"GraverError",
"PromptNotFoundError",
"VersionNotFoundError",
"StorageError",
Expand Down
14 changes: 7 additions & 7 deletions promptvero/cli.py → graver/cli.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Command-line interface for promptvero."""
"""Command-line interface for graver."""

import argparse
import sys

from promptvero.core import Prompt
from promptvero.exceptions import PromptVeroError
from graver.core import Prompt
from graver.exceptions import GraverError


def cmd_list(args: argparse.Namespace) -> None:
Expand Down Expand Up @@ -68,14 +68,14 @@ def cmd_delete_prompt(args: argparse.Namespace) -> None:

def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="pv",
prog="gr",
description="Git-like version control for LLM prompts.",
)
parser.add_argument(
"--base-dir",
default=".promptvero",
default=".graver",
metavar="DIR",
help="Root directory for prompt storage (default: .promptvero)",
help="Root directory for prompt storage (default: .graver)",
)

sub = parser.add_subparsers(dest="command", metavar="COMMAND")
Expand Down Expand Up @@ -144,6 +144,6 @@ def main() -> None:
args = parser.parse_args()
try:
COMMANDS[args.command](args)
except PromptVeroError as exc:
except GraverError as exc:
print(f"Error: {exc}", file=sys.stderr)
sys.exit(1)
12 changes: 6 additions & 6 deletions promptvero/core.py → graver/core.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
"""Public-facing API for promptvero."""
"""Public-facing API for graver."""

from pathlib import Path

from promptvero.exceptions import PromptNotFoundError
from promptvero.storage import Storage
from graver.exceptions import PromptNotFoundError
from graver.storage import Storage


class Prompt:
"""Git-like version control for a single named prompt.

Args:
name: Unique identifier for this prompt.
base_dir: Root directory for all prompt storage. Defaults to ".promptvero".
base_dir: Root directory for all prompt storage. Defaults to ".graver".
"""

def __init__(self, name: str, base_dir: str = ".promptvero") -> None:
def __init__(self, name: str, base_dir: str = ".graver") -> None:
self.name = name
self._storage = Storage(base_dir=base_dir)

@staticmethod
def list_all(base_dir: str = ".promptvero") -> list[str]:
def list_all(base_dir: str = ".graver") -> list[str]:
"""Return the names of all saved prompts in the given base directory.

Args:
Expand Down
17 changes: 17 additions & 0 deletions graver/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Custom exceptions for the graver library."""


class GraverError(Exception):
"""Base class for all graver errors."""


class PromptNotFoundError(GraverError):
"""Raised when a prompt name does not exist in storage."""


class VersionNotFoundError(GraverError):
"""Raised when a requested version string does not exist."""


class StorageError(GraverError):
"""Raised when a file system operation fails."""
8 changes: 4 additions & 4 deletions promptvero/storage.py → graver/storage.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
"""File-system storage backend for promptvero."""
"""File-system storage backend for graver."""

import difflib
import json
from datetime import datetime
from pathlib import Path

from promptvero.exceptions import (
from graver.exceptions import (
PromptNotFoundError,
StorageError,
VersionNotFoundError,
)


class Storage:
"""Handles all file system operations for promptvero.
"""Handles all file system operations for graver.

Args:
base_dir: Root directory for all prompt storage.
"""

def __init__(self, base_dir: str = ".promptvero") -> None:
def __init__(self, base_dir: str = ".graver") -> None:
self._base = Path(base_dir).resolve()
self._base.mkdir(parents=True, exist_ok=True)

Expand Down
17 changes: 0 additions & 17 deletions promptvero/exceptions.py

This file was deleted.

16 changes: 8 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "promptvero"
version = "0.2.0"
name = "graver"
version = "0.3.0"
description = "Git-like version control for LLM prompts"
readme = "README.md"
license = {file = "LICENSE"}
requires-python = ">=3.10"
authors = [
{name = "Muhammed Sen"}
{name = "Practical Mind"}
]
keywords = ["prompt", "versioning", "llm", "ai", "agents", "version-control"]
classifiers = [
Expand All @@ -28,7 +28,7 @@ classifiers = [
dependencies = []

[project.scripts]
pv = "promptvero.cli:main"
gr = "graver.cli:main"

[project.optional-dependencies]
dev = [
Expand All @@ -38,9 +38,9 @@ dev = [
]

[project.urls]
Homepage = "https://github.com/MuhammedSenn/promptvero"
Repository = "https://github.com/MuhammedSenn/promptvero"
Issues = "https://github.com/MuhammedSenn/promptvero/issues"
Homepage = "https://github.com/PracticalMind/graver"
Repository = "https://github.com/PracticalMind/graver"
Issues = "https://github.com/PracticalMind/graver/issues"

[tool.black]
line-length = 88
Expand All @@ -53,7 +53,7 @@ line-length = 88
select = ["E", "F", "I"]

[tool.hatch.build.targets.wheel]
packages = ["promptvero"]
packages = ["graver"]
exclude = ["tests/"]

[tool.hatch.build.targets.sdist]
Expand Down
8 changes: 4 additions & 4 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""Tests for promptvero.cli."""
"""Tests for graver.cli."""

import sys

import pytest

from promptvero.cli import main
from promptvero.core import Prompt
from graver.cli import main
from graver.core import Prompt


def run_cli(args: list[str], base_dir: str) -> tuple[str, str, int]:
"""Run the CLI with given args and return (stdout, stderr, exit_code)."""
argv = ["pv", "--base-dir", base_dir] + args
argv = ["gr", "--base-dir", base_dir] + args
with pytest.MonkeyPatch().context() as mp:
mp.setattr(sys, "argv", argv)
try:
Expand Down
6 changes: 3 additions & 3 deletions tests/test_core.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Tests for promptvero.core.Prompt."""
"""Tests for graver.core.Prompt."""

import pytest

from promptvero.core import Prompt
from promptvero.exceptions import PromptNotFoundError
from graver.core import Prompt
from graver.exceptions import PromptNotFoundError


def test_save_and_get(tmp_path):
Expand Down
Loading