Documentation
Proselign is at milestone M0: the command-line contract, voice storage and fidelity checks are in place; style transfer quality depends on the local model you connect. Expect changes before 1.0.
Install
Ubuntu (PPA)
Ubuntu 22.04, 24.04 and 26.04 LTS. Packages are built for the architectures the local model tooling supports:
| Architecture | Tier | Local inference |
|---|---|---|
amd64, arm64 | Full | llama.cpp (CPU or GPU), Ollama, vLLM |
ppc64el, s390x | CPU-only | llama.cpp or the vLLM CPU backend. No Ollama and no GPU acceleration. Expect a quantized 7–9B model to be usable but slow on many-core POWER and Z systems. |
sudo add-apt-repository ppa:bceverly/proselign
sudo apt update
sudo apt install proselign
Debian package
Download the .deb for your architecture from the releases page, check it against SHA256SUMS, then:
sudo apt install ./proselign_*_amd64.deb
pip
Any system with Python 3.10 or newer. Proselign has no third-party runtime dependencies.
pip install proselign-*-py3-none-any.whl
From source
git clone https://github.com/bceverly/proselign
cd proselign
sudo make install # into /usr/local; PREFIX=~/.local for a user install
Run a local model
Proselign rewrites through any server that speaks the OpenAI-compatible chat API. By default it expects one at http://127.0.0.1:8080/v1.
llama.cpp
llama-server -m Qwen2.5-7B-Instruct-Q4_K_M.gguf --host 127.0.0.1 --port 8080
Ollama
ollama pull qwen2.5:7b-instruct
export PROSELIGN_ENDPOINT=http://127.0.0.1:11434/v1
export PROSELIGN_MODEL=qwen2.5:7b-instruct
IBM POWER and Z (CPU-only tier)
Build llama.cpp from source (it includes POWER and Z vector optimizations) and run llama-server as above, or use vLLM's CPU backend. On s390x, which is big-endian, use a big-endian GGUF model: convert one with llama.cpp's gguf-py tools or download a published big-endian build.
A 4-bit 7–9B model runs well on a 12–16 GB GPU; larger models are worth evaluating on 24–32 GB cards. Bigger is not automatically better for style fidelity, so test with your own writing. Model weights carry their own licenses, separate from Proselign's.
Quick start
# 1. Learn a voice from your own .txt and .md files
proselign train --voice Bryan ~/Documents/Writing/
# 2. Rewrite: stdout carries the result and nothing else
cat draft.md | proselign --voice Bryan > final.md
# 3. Be conservative with a sensitive document
proselign --voice Bryan --strict --strength 0.3 < contract-summary.md
Try the pipeline without a model: --backend mock echoes text through every stage (segmentation, protection, validation), which is useful in scripts and CI.
Command reference
| Command | What it does |
|---|---|
proselign --voice NAME [FILE...] | Rewrite stdin (or files; - is stdin) in a voice. Same as proselign rewrite. |
proselign train --voice NAME PATH... | Learn a voice from .txt/.md files. --append re-learns with additional documents. |
proselign voices | List voices: id, name, document count (tab-separated). |
proselign inspect NAME | Print a voice's profile as JSON. |
proselign export NAME [-o FILE] | Write a portable .proselign archive. Corpus paths are omitted unless --include-sources. |
proselign import FILE [--force] | Install a voice from an archive. Nothing in an archive is ever executed. |
proselign delete NAME | Permanently remove a voice. |
Rewrite options
| Option | Meaning |
|---|---|
--strength 0.0–1.0 | Degree of stylistic change. Default 0.5. |
--format markdown|text | How input is segmented. Default markdown. |
--strict | Fail (exit 4) instead of keeping the original text when a chunk fails fidelity checks. |
--backend openai-compat|mock | Inference backend. |
--endpoint URL, --model NAME | Where and what to call. |
--offline | Refuse any endpoint that is not loopback. |
--seed N | Sampling seed for reproducible runs (0–4294967295). |
--max-chunk-tokens N | Upper bound on each rewritten chunk. Default 1500. |
--json-diagnostics | Write a JSON run report to stderr. |
Exit status
| Code | Meaning |
|---|---|
0 | Success. |
1 | Unexpected error. |
2 | Usage error: bad arguments or unreadable input. |
3 | Voice error: unknown, invalid or conflicting voice. |
4 | Validation error: a rewrite failed fidelity checks under --strict. |
5 | Backend error: the endpoint is unreachable or replied badly. |
Fidelity checks
Before a chunk is rewritten, Proselign records its protected tokens: numbers and currency, percentages, URLs, e-mail addresses, quotations, citations like [3] and inline code. After the rewrite, every token must still be present as many times as before. A failure is retried with the specific problems as feedback; if retries fail, the original text is kept and a warning goes to stderr, or with --strict the run fails with exit status 4.
Fenced code blocks, indented code, pipe tables, HTML blocks and YAML front matter are copied byte for byte and never sent to the model.
Voice format
A voice is a JSON manifest stored in $XDG_DATA_HOME/proselign/voices/<id>/voice.json (by default ~/.local/share/proselign/), readable only by you. It keeps objective measurements (metrics) apart from interpretive hints (style):
{
"schema_version": 1,
"voice_id": "bryan",
"display_name": "Bryan",
"style": { "tone": ["conversational"], "sentence_length": "short", "paragraphing": "compact" },
"metrics": { "sentence_length_words": { "mean": 13.4, "median": 12.0, "stdev": 6.1 }, "...": "..." },
"corpus_summary": { "documents": 214, "words": 186402 },
"license_and_consent": { "raw_examples_exportable": false }
}
Exported .proselign archives are gzip'd tar files containing only that manifest. Import accepts nothing else: no other members, no links, no oversized files, no executable formats.
Configuration
| Variable | Meaning |
|---|---|
PROSELIGN_BACKEND | Default for --backend. |
PROSELIGN_ENDPOINT | Default for --endpoint. |
PROSELIGN_MODEL | Default for --model. |
PROSELIGN_HOME | Override the data directory entirely. |
XDG_DATA_HOME | Base of the default data directory. |
Security & privacy
- No telemetry, no network by default. The only connection Proselign makes is to the endpoint you configure;
--offlinerestricts that to loopback. - Documents are data, never instructions. Ingested text and text being rewritten are delimited with per-request random markers, so embedded "instructions" cannot escape into the prompt.
- Private storage. The voice store is created 0700 and manifests 0600 regardless of your umask; writes never follow symbolic links.
- Symlinks are not followed when walking a corpus directory.
- Exports disclose little. Corpus paths and hashes are stripped unless you ask for them.
- Tested against MITRE's "Lucky 13" unforgivable vulnerability classes on every change, alongside Bandit, Semgrep, CodeQL, dependency audits and secret scanning.
- Attribution. Rewriting text in someone else's voice without their consent is impersonation. Use voices you own or are authorized to use, and disclose assisted writing where it matters.
Report vulnerabilities privately as described in SECURITY.md.