A CLI and OCaml library for managing contacts
1# Sortal - Contact Metadata Management Library
2
3Sortal is an OCaml library that provides a comprehensive system for managing
4contact metadata with temporal validity tracking. It stores data in
5XDG-compliant locations using the YAML format and optionally versions all changes
6with git.
7
8## Features
9
10- **Temporal Support**: Track how contact information changes over time (emails, organizations, URLs)
11- **XDG-compliant storage**: Contact metadata stored in standard XDG data directories
12- **YAML format**: Human-readable YAML files with type-safe encoding/decoding using yamlt
13- **Rich metadata**: Support for multiple names, emails (typed), organizations, services (GitHub, social media), ORCID, URLs, and Atom feeds
14- **Git Versioning**: Optional automatic git commits for all changes with descriptive messages
15- **CLI Interface**: Full command-line interface for CRUD operations on contacts
16- **Simple API**: Easy-to-use functions for saving, loading, searching, and deleting contacts
17
18## Metadata Fields
19
20Each contact can include:
21
22- `handle`: Unique identifier/username (required)
23- `names`: List of full names with primary name first (required)
24- `email`: Email address
25- `icon`: Avatar/icon URL
26- `thumbnail`: Path to a local thumbnail image file
27- `github`: GitHub username
28- `twitter`: Twitter/X username
29- `bluesky`: Bluesky handle
30- `mastodon`: Mastodon handle (with instance)
31- `orcid`: ORCID identifier
32- `url`: Personal/professional website
33- `atom_feeds`: List of Atom/RSS feed URLs
34
35## Storage
36
37Contact data is stored as individual YAML files in the XDG data directory:
38
39- Default location: `$HOME/.local/share/sortal/`
40- Override with: `SORTAL_DATA_DIR` or `XDG_DATA_HOME`
41- Each contact stored as: `{handle}.yaml`
42- Format: Human-readable YAML with temporal data support
43
44## Usage Example
45
46### Basic Usage
47
48```ocaml
49(* Create a contact store from filesystem *)
50let store = Sortal.create env#fs "myapp" in
51
52(* Or create from an existing XDG context (recommended when using eiocmd) *)
53let store = Sortal.create_from_xdg xdg in
54
55(* Create a new contact *)
56let contact = Sortal.Contact.make
57 ~handle:"avsm"
58 ~names:["Anil Madhavapeddy"]
59 ~email:"anil@recoil.org"
60 ~github:"avsm"
61 ~orcid:"0000-0002-7890-1234"
62 () in
63
64(* Save the contact *)
65Sortal.save store contact;
66
67(* Lookup by handle *)
68match Sortal.lookup store "avsm" with
69| Some c -> Printf.printf "Found: %s\n" (Sortal.Contact.name c)
70| None -> Printf.printf "Not found\n"
71
72(* Search for contacts by name *)
73let matches = Sortal.search_all store "Anil" in
74List.iter (fun c ->
75 Printf.printf "%s: %s\n"
76 (Sortal.Contact.handle c)
77 (Sortal.Contact.name c)
78) matches
79
80(* List all contacts *)
81let all_contacts = Sortal.list store in
82List.iter (fun c ->
83 Printf.printf "%s: %s\n"
84 (Sortal.Contact.handle c)
85 (Sortal.Contact.name c)
86) all_contacts
87```
88
89## CLI Tool
90
91The library includes a standalone `sortal` CLI tool with full CRUD functionality:
92
93```bash
94# Initialize git versioning (optional)
95sortal git-init
96
97# List all contacts
98sortal list
99
100# Show details for a specific contact
101sortal show avsm
102
103# Search for contacts
104sortal search "Anil"
105
106# Show database statistics
107sortal stats
108
109# Add a new contact
110sortal add jsmith --name "John Smith" --email "john@example.com" --kind person
111
112# Add metadata to contacts
113sortal add-org jsmith "Acme Corp" --title "Software Engineer" --from 2020-01
114sortal add-service jsmith "https://github.com/jsmith" --kind github --handle jsmith
115sortal add-email jsmith "john.work@example.com" --type work --from 2020-01
116sortal add-url jsmith "https://jsmith.example.com" --label "Personal website"
117
118# Remove metadata
119sortal remove-email jsmith "old@example.com"
120sortal remove-service jsmith "https://old-service.com"
121sortal remove-org jsmith "Old Company"
122sortal remove-url jsmith "https://old-url.com"
123
124# Delete a contact
125sortal delete jsmith
126
127# Synchronize data (convert thumbnails to PNG)
128sortal sync
129```
130
131## Git Versioning
132
133Sortal includes a `Sortal_git_store` module that provides automatic git commits
134for all contact modifications:
135
136```ocaml
137open Sortal
138
139(* Create a git-backed store *)
140let git_store = Git_store.create store env in
141
142(* Initialize git repository *)
143let () = match Git_store.init git_store with
144 | Ok () -> Logs.app (fun m -> m "Git initialized")
145 | Error msg -> Logs.err (fun m -> m "Error: %s" msg)
146in
147
148(* Save a contact - automatically commits with descriptive message *)
149let contact = Contact.make ~handle:"jsmith" ~names:["John Smith"] () in
150match Git_store.save git_store contact with
151| Ok () -> Logs.app (fun m -> m "Contact saved and committed")
152| Error msg -> Logs.err (fun m -> m "Error: %s" msg)
153```
154
155**Commit Messages**: All git store operations create descriptive commit messages:
156- `save`: "Add contact @handle (Name)" or "Update contact @handle (Name)"
157- `delete`: "Delete contact @handle (Name)"
158- `add_email`: "Update @handle: add email address@example.com"
159- `remove_email`: "Update @handle: remove email address@example.com"
160- `add_service`: "Update @handle: add service Kind (url)"
161- `add_organization`: "Update @handle: add organization Org Name"
162- And similar for all other operations
163
164## Project Status
165
166Still very much just used by Anil Madhavapeddy. You're welcome to try it, but let me know...
167
168## License
169
170ISC License - see [LICENSE.md](LICENSE.md) for details.