navig-contacts
navig contacts: the address book and the alias-to-route table dispatch resolves against, in one store. Imports vCard and Telegram exports, merges the same person out of many records, and keeps only contacts with a phone number that validates.
Works on its own — its own CLI, no navig required · v0.3.0 · PyPI
On its own (no navig needed)
pip install navig-contacts
navig-contacts --help # no navig needed
With navig
pip install navig-contacts
navig contacts --help
Commands
| Command | What it does |
|---|---|
navig contacts add |
Add a contact by hand. |
navig contacts doctor |
Check that the book says the same thing in both of its halves. |
navig contacts export |
Export contacts as CSV. |
navig contacts flag |
Record whether a handle still resolves, so exports can skip dead ones. |
navig contacts import |
Import an address book, merging duplicates into one contact per person. |
navig contacts list |
List contacts. |
navig contacts liveness |
What the book knows about which routes still resolve. |
navig contacts merge |
Fold one contact into another, reversibly. |
navig contacts merges |
Every merge and split applied, and whether it was undone. |
navig contacts migrate |
Bring a pre-merge contact book onto the shared schema. |
navig contacts remove |
Remove a contact. |
navig contacts resolve |
Ask Telegram which of these handles still reach a person. |
navig contacts review |
Record that a flagged merge was looked at and found correct. |
navig contacts route |
Add or remove a route for a contact. |
navig contacts search |
Find someone by anything you remember about them. |
navig contacts show |
Everything known about one contact, and where it came from. |
navig contacts split |
Move one identifier onto a contact of its own. |
navig contacts stats |
How many people, at which tiers, and how much evidence backs them. |
navig contacts undo |
Reverse a merge, restoring the absorbed contact. |
navig contacts — your address book and the routing table messages go out on,
in one store. Import the exports you have accumulated, merge the same person out
of the many records they are scattered across, and keep straight who you can
actually reach.
navig contacts import ~/Backups/AddressBook --dry-run
navig contacts import ~/Backups/AddressBook
navig contacts list --tier verified
navig contacts search Totara
navig contacts add --alias alice --phone +33612345678
One command, two halves
navig contacts used to be two things. Core had an alias→route table for
message dispatch — who is @alice, and where does a message to her go. Nothing
had the other half: the people themselves, merged out of whatever exports had
piled up.
They are the same question at two depths, so they are now one command over one store:
| is | example | |
|---|---|---|
| a route | a transport | sms:+33612345678, whatsapp:…, discord:… |
| an identifier | the address itself | phone:+33612345678 — and it belongs to exactly one person |
That last property is what makes an identifier the key that decides two records
are the same human. And because both live in one store, importing an address
book makes those people messageable: an imported phone becomes an sms: route,
so navig dispatch send can reach anyone the import found.
Two books exist. The default is the global one at ~/.navig/data/contacts.db —
what this command has always meant. --space <name> reads a space's own book,
so a client list and a private circle are not forced into one table.
The rule it turns on
A contact is legit only if it has a phone number — and "has a phone number"
means libphonenumber agrees the number is valid, not that the string contained
digits. Real archives are full of things that look like numbers and are not:
service short codes (3949, 33700, 36102), placeholders (+7**********),
caller-ID-block prefixes (#31#15), doubled country codes
(+7+79603166750 — repaired, then validated), and troll numbers
(+7696969696969696969969999).
Everything else is filed as a lead, at a tier that says exactly what is known:
| Tier | Meaning |
|---|---|
verified |
a phone number validated — the real contacts |
email |
an email address, no phone |
handle |
only a Skype / Telegram / Facebook / VK account |
archive |
no way to reach them at all — reported, never stored |
A lead is promoted automatically: the day a card turns up carrying a phone for
someone known only by a handle, they become verified.
One person, many cards
contacts holds the person. Four tables hold the evidence:
contact_identifiers— every phone, email and handle they are reachable by. A partial unique index on(kind, value_norm)is the merge key: a second card carrying a known number attaches to the existing person instead of creating a twin.contact_aliases— every name variant ever seen.contact_sources— which file, which card, and the original vCard text, so any import decision can be re-derived.merge_log— a JSON snapshot per absorbed record, so every merge is reversible withnavig contacts undo.
Names are never a merge key
Not a preference — a measurement. On the archive this was built against, the
only names shared between an imported vCard set and an existing contact
database were generic first names (anna, alex, diana). Merging on them
fuses strangers. Name similarity is reported for a human to judge and never
applied.
What it tells you about its own work
Every import writes its reasoning to <space>/exports/:
| Report | What it settles |
|---|---|
-verified.txt |
the roster of people with a valid phone |
-merges.md |
every merge, and the identifier that caused it |
-review-suspicious.md |
merges that may have fused two people — read this |
-rejected-phones.csv |
every number thrown away, with the reason |
-assumed-regions.csv |
every number that only validated because a region was guessed |
vcf-interests-*.md |
the non-people found in a follow list |
-dropped.csv |
everything discarded, so nothing vanishes unrecorded |
A bare national number needs a region. The vCard ADR country and
X-SKYPE-COUNTRY are consulted first; only genuinely context-free numbers fall
back to a default, and every one of those assumptions is listed rather than
made silently.
Commands
navig contacts import <file|folder> [--dry-run] [--source LABEL] [--no-reports]
navig contacts list [--tier verified|email|handle|archive] [--limit N] [--plain]
navig contacts search <query> # any alias, name, phone, email, handle or route
navig contacts show <alias>
navig contacts add --alias A [--name N] [--phone P] [--route network:address]
navig contacts route <alias> add|remove <network:address> [--priority N]
navig contacts remove <alias> [--yes]
navig contacts merge <alias> <into> # merges / undo <id>
navig contacts split <alias> KIND:VALUE [--as NAME]
navig contacts export [--tier T] [--output FILE]
navig contacts stats
navig contacts migrate [--apply] # a book from before the two halves merged
Every command takes --space/-s; without it the global book is used.
Install
pip install navig-contacts # on its own: `navig-contacts --help`
pip install "navig-contacts[photos]" # with avatar extraction
pip install -e plugins/navig-contacts # in the monorepo
On its own it is the same address book at the same path (<data dir>/contacts.db), so a
book built with navig-contacts is the one navig contacts and message dispatch read once
navig is installed. Two things need navig and say so: --space (spaces are navig's) and
importing a Telegram export (vCards import without it).
phonenumbers is a hard dependency — the phone gate is the product. Pillow and
ImageHash are optional: without them avatar extraction reports itself as
unavailable instead of failing the import.
Migrating a book from before the merge
A space that ran the older space-local tool has a book keyed on uid, with
handles in a social_profiles table. navig contacts migrate --space <name>
reports what it would change; --apply does it, after a backup. The columns are
renamed in place and the handles become identifiers and routes — nothing is
rebuilt and nothing is discarded. On a real 4,077-contact book that produced
2,038 routes, which is 2,038 people who became messageable.
Status
The engine came from a working tool with 4,077 contacts in it, so the vCard dialects it handles (Google Contacts, Apple/iOS, Skype) are the ones it was actually tested against.
Metadata
Release files for navig-contacts 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| navig_contacts-0.3.0.tar.gz | 111.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| navig_contacts-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 208.6 kB