cd /news/ai-tools/show-hn-higgs-a-local-ai-cli-for-pro… · home topics ai-tools article
[ARTICLE · art-77153] src=github.com ↗ pub= topic=ai-tools verified=true sentiment=· neutral

Show HN: Higgs, a local AI CLI for Proton Mail (no cloud, no telemetry)

Higgs, an unofficial, agent-first CLI for Proton Mail designed to be driven by language models, has been released as a local-only tool with no cloud dependencies or telemetry. The project, built by the community and not affiliated with Proton AG, features a schema manifest, NDJSON streaming, typed error envelopes, and checkpointed state to simplify integration with AI agents. Its first workload is a Proton Mail inbox classifier using a local LLM via Ollama and Proton Mail Bridge.

read10 min views1 publishedJul 28, 2026
Show HN: Higgs, a local AI CLI for Proton Mail (no cloud, no telemetry)
Image: source

An agent-first CLI for Proton Mail. Schema manifest for tool use, NDJSON on stdout, typed error envelopes, and a stable exit-code enum — designed to be driven by a language model, not a human.

Unofficial project.higgs

is an independent, community-built CLI for Proton Mail. It is not affiliated with, endorsed by, or sponsored by Proton AG. "Proton", "Proton Mail", and related marks are trademarks of Proton AG; this project uses them only to describe interoperability.

Wiring a normal CLI into an agent loop is painful. Stdout mixes prose and data, errors are English sentences, exit codes are 0-or-1, and the only tool specification is --help

. higgs

inverts that. Every design decision assumes the primary caller is a model:

Schema manifest.higgs schema

emits a JSON description of every subcommand — flags, args, stdout format, exit codes. An agent loads it once and can drive the tool without prompt-engineered command syntax.NDJSON streaming with a terminator. Every streaming command emits one JSON object per line and ends with{"type":"summary", ...}

. Callers know when a stream is done without heuristics.Typed error envelopes. Every failure emits{"error": {"kind", "code", "reason", "message", "hint"}}

. Agents branch on.error.kind

, not on parsed English.Exit codes as an enum. Exit codes map 1:1 to error kinds, so retry and escalation are deterministic: retry on5 imap

, prompt the user on2 auth

, surface to the caller on4 config

.Sanitized stderr. Human-readable progress on stderr, stripped of ANSI escapes, bidi controls, and zero-width characters — safe to feed back into a model's context.Checkpointed state. SQLite state DB withbackfill

andstate clear

so runs are resumable across crashes and restarts.Secrets out-of-band. Credentials go to the OS keyring (macOS Keychain, Windows Credential Manager, libsecret on Linux) with an AES-256-GCM file fallback, so nothing sensitive flows through an agent's context. The first workload riding this contract is a local-only Proton Mail inbox classifier via Proton Mail Bridge and Ollama. The classifier is useful on its own, but the contract is the point.

higgs classify

connects to a running Proton Mail Bridge over IMAP, streams each message through a local LLM (Ollama by default, or any self-hosted OpenAI-compatible server such as llama.cpp llama-server

— see PM_LLM_BACKEND

), and applies one or more labels from an 11-category taxonomy. Every step runs on localhost

: no API keys, no cloud inference, no telemetry.

The default model is Gemma 4, chosen because it has native function-calling support, a 128K context window on the small variants, and fits comfortably on a laptop.

Install and sign into

Proton Mail Bridge. Note the IMAP username and bridge password it assigns. - Install

Ollamaand pull a model:

ollama pull gemma4

Install

higgs

:

brew tap higgscli/higgs
brew install higgs

go install github.com/higgscli/higgs/cmd/higgs@latest

Or build from source:

git clone https://github.com/higgscli/higgs.git
cd higgs
make build

Export Bridge and Ollama settings. A

.env

at the repo root works:

export PM_IMAP_USERNAME="alice@proton.me"
export PM_IMAP_PASSWORD="bridge-generated-password"
export PM_IMAP_HOST="127.0.0.1"
export PM_IMAP_PORT="1143"
export PM_OLLAMA_MODEL="gemma4"

Dry-run against your inbox:

higgs classify --dry-run --limit 20 INBOX

Review the NDJSON. When the suggestions look right, rerun with

--apply

to write labels back to Proton.

higgs schema

returns a manifest of every subcommand. Load it once, drive the CLI from it.

higgs schema classify
{
  "name": "classify",
  "summary": "Classify messages with Ollama and optionally apply labels",
  "args": [{"name": "mailbox", "required": false, "default": "INBOX"}],
  "flags": [
    {"name": "dry-run", "type": "bool", "description": "Preview suggestions without writing labels"},
    {"name": "apply", "type": "bool", "description": "Apply suggested labels to IMAP"},
    {"name": "limit", "type": "int", "default": 100},
    {"name": "workers", "type": "int", "default": 4},
    {"name": "no-state", "type": "bool"},
    {"name": "reprocess", "type": "bool"}
  ],
  "stdout": "ndjson",
  "exit_codes": [0, 2, 3, 4, 5, 6, 7, 9]
}

stdout: structured JSON. Single-object commands pretty-print; streaming commands emit NDJSON.** stderr**: human-readable progress, sanitized of ANSI escapes, bidi controls, and zero-width characters.** NDJSON terminator**: every streaming command ends with one{"type":"summary", ...}

line. Read until you see it.Error envelope: failures emit a typed envelope withkind

,code

,reason

,message

, andhint

.

{
  "error": {
    "kind": "config",
    "code": 400,
    "reason": "configError",
    "message": "PM_IMAP_USERNAME is required",
    "hint": "export PM_IMAP_USERNAME=<bridge-username>"
  }
}
Code Kind Description
0 success Command completed without error
1 api Upstream API error (generic)
2 auth Authentication or credential failure
3 validation Invalid flags, arguments, or input
4 config Missing or malformed configuration
5 imap IMAP protocol or connection error
6 classify Classification error (Ollama, prompt, parsing)
7 state State DB error (SQLite)
8 discovery Mailbox discovery failure
9 internal Unexpected internal error

The intended flow is: discover mailboxes, classify them, apply labels. Everything else (backfill

, cleanup-labels

, state

, verify

) exists to repair or inspect state along the way.

Commands compose over pipes: every command that accepts an explicit --uid

list also accepts --uid -

, which reads the UID set from stdin. Stdin may be plain UIDs (comma- or whitespace-separated) or NDJSON, from which each row's numeric uid

field is taken (rows without one, like summary

lines, are skipped). So any command's NDJSON output is directly usable as another's input:

higgs search INBOX --before 2025-07-01 | higgs archive INBOX --uid -
higgs state query INBOX --is-mailing-list false | higgs move INBOX Folders/Personal --uid -

Enumerate IMAP mailboxes and return the canonical All Mail

and Labels

roots.

higgs scan-folders
{
  "mailboxes": [
    {"name": "INBOX", "delimiter": "/", "messages": 1204, "unseen": 18, "attributes": []},
    {"name": "Labels/Orders", "delimiter": "/", "attributes": ["\\HasNoChildren"]}
  ],
  "all_mail": "All Mail",
  "labels_root": "Labels"
}

Stream messages through Ollama and emit one NDJSON object per message, followed by a summary

terminator. Add --apply

to write labels back to IMAP in the same pass.

higgs classify --dry-run --limit 20 INBOX
higgs classify --apply --workers 4 "Folders/Accounts"
higgs classify --reprocess --no-state INBOX

Flags: --dry-run

, --apply

, --limit N

, --no-state

, --reprocess

, --workers N

.

{"mailbox":"INBOX","uid":1842,"uid_validity":1,"subject":"Your order has shipped","from":"ship-confirm@amazon.com","date":"2026-04-09T14:22:10Z","suggested_labels":["Orders"],"confidence":0.94,"rationale":"Shipping notification with tracking number","is_mailing_list":false}
{"type":"summary","mailbox":"INBOX","classified":20,"errors":0,"skipped":0}

Apply pending labels recorded in the state DB. Use when classify

ran without --apply

.

higgs apply-labels --limit 100 "Folders/Accounts"
higgs apply-labels --dry-run "Folders/Accounts"

Consolidate legacy or user-created labels into the canonical 11-label taxonomy. Useful after migrating from Proton's built-in filters.

higgs cleanup-labels --dry-run
higgs cleanup-labels

Fetch and parse messages without classifying. Useful for piping into other tools.

higgs fetch-and-parse INBOX | jq 'select(.from | contains("github"))'

Replay a prior classify

NDJSON log into the state DB. Recovers state after a crash or migration.

higgs backfill classify.log

Inspect or reset the SQLite state DB.

higgs state stats
higgs state stats "Folders/Accounts"
higgs state clear "Folders/Accounts"

state query

exposes the per-message classification records classify

persists (labels, confidence, rationale, mailing-list flag), so results stay queryable after the fact — no re-parsing saved NDJSON. It is purely local (no IMAP connection) and streams NDJSON rows whose uid

fields pipe straight into --uid -

consumers.

higgs state query INBOX --is-mailing-list false
higgs state query INBOX --label Personal --min-confidence 0.8
higgs state query --failed

Flags: --is-mailing-list true|false

, --applied true|false

, --min-confidence F

, --max-confidence F

, --label X

(exact element match), --failed

, --limit N

.

Audit a mailbox against an expected UID set without mutating anything. --expect present

(default) requires every given UID to exist, --expect absent

requires none to, and --expect exact

requires the mailbox's UID set to equal the given set. Violations stream as NDJSON rows and make the command exit non-zero.

higgs verify Archive --uid 1842,1843
higgs search INBOX --before 2025-07-01 | higgs verify INBOX --uid - --expect absent

Emit a machine-readable manifest of every subcommand. See The agent contract.

higgs schema
higgs schema classify

All configuration is read from environment variables. Defaults target a standard Proton Mail Bridge + Ollama setup on the same host.

Credentials are stored in the OS keyring (macOS Keychain, Windows Credential Manager, Linux Secret Service via libsecret) so they never live in shell history or a .env

file. When the keyring is unreachable, an encrypted-file fallback (~/.higgs/credentials.enc

, AES-256-GCM with Argon2id-derived keys) is available.

higgs auth login
 
pass show proton/bridge | higgs auth login --username alice@proton.me --password-stdin
 
higgs auth status
 
higgs auth logout

If PM_IMAP_USERNAME

and/or PM_IMAP_PASSWORD

are set in the environment they always win — useful for one-off overrides in CI or shells. To use the encrypted-file backend, export PM_KEYSTORE_PASSPHRASE

(required to read or write the file) and optionally PM_KEYSTORE_PATH

to relocate it.

Variable Default Description
PM_IMAP_HOST
127.0.0.1
Bridge host
PM_IMAP_PORT
1143
Bridge IMAP port
PM_IMAP_USERNAME
(required)
Bridge IMAP username
PM_IMAP_PASSWORD
(required)
Bridge-generated password
PM_IMAP_SECURITY
starttls
One of starttls , tls , insecure
PM_IMAP_TLS_SKIP_VERIFY
auto Skip TLS verification (auto-enabled for loopback)
PM_IMAP_APPLY_TIMEOUT
180
Per-command timeout (seconds) for --apply
Variable Default Description
PM_LLM_BACKEND
ollama
Chat backend: ollama or openai

Everything stays local either way: openai

means the OpenAI-compatible Chat Completions API served by self-hosted engines like llama.cpp llama-server

— not the OpenAI cloud.

Variable Default Description
PM_OLLAMA_BASE_URL
http://localhost:11434
Ollama API base URL
PM_OLLAMA_MODEL
gemma4
Model name passed to Ollama
Variable Default Description
PM_OPENAI_BASE_URL
(required)
Server base URL, e.g. http://localhost:8080 (llama.cpp llama-server )
PM_OPENAI_MODEL
(required)
Model name/alias the server expects
PM_OPENAI_API_KEY
(none)
Optional bearer token; sent only when set

Reasoning models (e.g. Qwen3) are handled automatically: thinking is disabled for structured calls (classify

, extract

, digest

), enabled for ask

and summarize

, and any <think>

preamble is stripped before parsing. Structured output uses response_format: json_schema

. Example:

PM_LLM_BACKEND=openai \
PM_OPENAI_BASE_URL=http://localhost:8080 \
PM_OPENAI_MODEL=qwen3.6-35b-a3b \
higgs classify INBOX --limit 20 --dry-run
Variable Default Description
PM_CLASSIFY_LIMIT
100
Max messages per classify run
PM_CLASSIFY_BATCH_SIZE
25
IMAP fetch batch size
PM_CLASSIFY_WORKERS
4
Parallel LLM requests (llama-server serializes unless started with --parallel N )
Variable Default Description
PM_STATE_DB
~/.higgs/state.db
SQLite state DB path

The classifier is constrained to 11 canonical labels. 612 aliases in internal/labels/data/labels.toml

normalize legacy or model-generated names back to this set.

Label Covers
Orders Purchase confirmations, shipping, returns
Finance Banks, cards, taxes, invoices
Newsletters Editorial digests, blog mailings
Promotions Marketing, discounts, sales
Jobs Recruiters, job boards, offers
Social Social network notifications, friend activity
Services SaaS account activity, product updates
Health Providers, pharmacy, insurance
Travel Flights, hotels, itineraries
Security 2FA, password resets, security alerts
Signups Account creation, email verification

See internal/labels/data/labels.toml for the full alias map.

Every common task is wrapped in the repo Makefile

.

Target Description
make build
Build the ./bin/higgs binary
make test
Run go test ./...
make test-race
Run tests with the race detector
make cover
Coverage profile plus go tool cover -func summary
make cover-html
HTML coverage report at coverage.html
make vet
Run go vet ./...
make vuln
Run govulncheck (installs into ./bin if missing)
make check
vet + test-race + vuln
make clean
Remove the built binary and coverage outputs
make tidy
Run go mod tidy

Run make check

before opening a PR.

Bug reports and pull requests are welcome — see CONTRIBUTING.md for the workflow and code-review expectations.

Please report vulnerabilities privately via the process in SECURITY.md. Do not open public issues for security reports.

Apache License 2.0 — see LICENSE.

Proton Mail Bridge— local IMAP gateway to Proton MailOllama— local LLM runtimeemersion/go-imap— IMAP client libraryspf13/cobra— CLI frameworkBurntSushi/toml— TOML decoder for the label taxonomy

── more in #ai-tools 4 stories · sorted by recency
── more on @higgs 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-higgs-a-loca…] indexed:0 read:10min 2026-07-28 ·