{"id":50976868,"url":"https://github.com/lduchosal/jeff","last_synced_at":"2026-06-19T09:02:37.227Z","repository":{"id":356602330,"uuid":"1233261807","full_name":"lduchosal/jeff","owner":"lduchosal","description":"Jeff transforme un carnet de contacts en CRM structuré, enrichi et publiable sous forme de site statique.","archived":false,"fork":false,"pushed_at":"2026-06-15T15:20:57.000Z","size":1318,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-15T17:15:31.281Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lduchosal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-08T19:10:26.000Z","updated_at":"2026-06-15T15:21:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/lduchosal/jeff","commit_stats":null,"previous_names":["lduchosal/jeff"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/lduchosal/jeff","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lduchosal%2Fjeff","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lduchosal%2Fjeff/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lduchosal%2Fjeff/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lduchosal%2Fjeff/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lduchosal","download_url":"https://codeload.github.com/lduchosal/jeff/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lduchosal%2Fjeff/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34523991,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-19T02:00:06.005Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-19T09:02:36.247Z","updated_at":"2026-06-19T09:02:37.221Z","avatar_url":"https://github.com/lduchosal.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"logo.svg\" alt=\"jeff\" width=\"480\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003ejeff\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003eYour contacts live in CardDAV. Your CRM lives in Markdown.\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\n[![PyPI version](https://img.shields.io/pypi/v/jeff-contacts.svg)](https://pypi.org/project/jeff-contacts/)\n[![Python versions](https://img.shields.io/pypi/pyversions/jeff-contacts.svg)](https://pypi.org/project/jeff-contacts/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Build](https://github.com/lduchosal/jeff/actions/workflows/python-package.yml/badge.svg)](https://github.com/lduchosal/jeff/actions/workflows/python-package.yml)\n[![Publish](https://github.com/lduchosal/jeff/actions/workflows/publish.yml/badge.svg)](https://github.com/lduchosal/jeff/actions/workflows/publish.yml)\n[![codecov](https://codecov.io/gh/lduchosal/jeff/branch/main/graph/badge.svg)](https://codecov.io/gh/lduchosal/jeff)\n[![Docstring coverage](./interrogate_badge.svg)](./interrogate_badge.svg)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=lduchosal_jeff\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=lduchosal_jeff)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=lduchosal_jeff\u0026metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=lduchosal_jeff)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=lduchosal_jeff\u0026metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=lduchosal_jeff)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=lduchosal_jeff\u0026metric=security_rating)](https://sonarcloud.io/summary/new_code?id=lduchosal_jeff)\n[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=lduchosal_jeff\u0026metric=bugs)](https://sonarcloud.io/summary/new_code?id=lduchosal_jeff)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=lduchosal_jeff\u0026metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=lduchosal_jeff)\n[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=lduchosal_jeff\u0026metric=code_smells)](https://sonarcloud.io/summary/new_code?id=lduchosal_jeff)\n[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=lduchosal_jeff\u0026metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=lduchosal_jeff)\n\n\u003c/p\u003e\n\n**jeff** syncs contacts from a Baikal CardDAV server into clean Markdown files with YAML frontmatter, then publishes a static HTML site. No database, no SaaS, no vendor lock-in — just files, Git, and a fast static site.\n\n## Quick start\n\n```sh\npip install jeff-contacts\njeff init                # creates .jeff config file\n# Edit .jeff with your CardDAV credentials\njeff sync                # fetch contacts\njeff publish             # build HTML site\nopen public/index.html\n```\n\n## Workflow\n\nLe workflow jeff se fait en 5 étapes. Les 4 premières sont à faire une fois lors de la mise en place, la dernière tourne quotidiennement.\n\n### 1. `jeff sync` — Récupérer les contacts\n\n```sh\njeff sync           # sync incrémental (seuls les changements)\njeff sync --full    # force un re-sync complet\n```\n\nConnecte le serveur CardDAV (Baikal), détecte les contacts modifiés via ctag/etag, télécharge les vCards et les transforme en fichiers Markdown avec frontmatter YAML. Chaque contact devient un fichier `.md` dans `content/contacts/`.\n\nLe sync est incrémental : seuls les contacts ajoutés, modifiés ou supprimés depuis le dernier sync sont traités. Un `--full` force la régénération de tous les fichiers.\n\n### 2. `jeff triage` — Trier les contacts\n\n```sh\njeff triage\n```\n\n```\n  [1/559] ──────────────────────────────────────────\n  Jean Dupont\n    tags: ami, ski, photo\n    note: Photographe amateur\n    addr: Avenue des Alpes 109, Geneve, Suisse\n    email: jean@example.com\n    phone: +41791234567\n\n  \u003e a a h H    # actif, ami, haute priorité, homme\n  ✓ Jean Dupont → actif\n```\n\nPasse en revue chaque contact non trié et permet de décider rapidement :\n- **a** = actif (garder dans le CRM) avec relation, priorité et genre\n- **r** = archivé (masqué du dashboard mais conservé)\n- **s** = skip (on décide plus tard)\n- **q** = quitter (reprend où on s'est arrêté la prochaine fois)\n\nRelations : `a`=ami, `c`=collègue, `f`=famille, `k`=connaissance.\nPriorités : `h`=haute, `m`=moyenne, `b`=basse.\nGenre : `H`=homme, `F`=femme.\n\n### 3. `jeff genre` — Assigner le genre\n\n```sh\njeff genre\n```\n\n```\n  [1/204] Jean Dupont: h\n  [2/204] Marie Martin: f\n  [3/204] Acme Corp: n\n```\n\nPasse rapidement sur tous les contacts sans genre. Utile pour :\n- Les liens familiaux (distinguer père/mère automatiquement)\n- L'affichage dans le dashboard (♂️/♀️)\n- Le writeback vers CardDAV (X-GENDER)\n\nCodes : `H`=homme, `F`=femme, `N`=none (entreprise), Enter=skip, `q`=quit.\n\n### 4. `jeff famille` — Liens familiaux\n\n```sh\njeff famille              # tous les contacts famille\njeff famille dupont       # filtrer par nom\njeff famille --check      # vérifier la cohérence\n```\n\n```\n── [1/54] Jacques Dupont (homme) ──\n    ↳ (aucun lien)\n\n    1. Anne Dupont (femme)\n    2. Jean Dupont (homme)\n    3. Luc Dupont (homme)\n\n    f=père m=mère w=conjoint c=enfant b=frère/sœur\n    ?texte=chercher  Enter=skip  q=quit\n\n  \u003e 1w 2c 3c\n  ✓ Jacques Dupont: conjoint=anne-dupont enfants=[jean-dupont, luc-dupont]\n  ↔ Anne Dupont: conjoint=jacques-dupont\n  ↔ Jean Dupont: pere=jacques-dupont\n  ↔ Luc Dupont: pere=jacques-dupont\n```\n\nPour chaque contact famille, affiche les membres du même nom de famille numérotés. On tape les numéros avec le code de relation. Les liens sont écrits **réciproquement** — assigner un père ajoute automatiquement l'enfant chez le père, et le genre détermine si c'est père ou mère.\n\n`jeff famille --check` vérifie que tous les liens sont bidirectionnels et propose de corriger les incohérences.\n\n### 5. `jeff note` — Consigner une interaction\n\n```sh\njeff note antoine                       # note pour aujourd'hui\njeff note antoine --date 2025-05-06     # date passée\n```\n\n```\n  Antoine Martin\n  type (w=whatsapp  t=tel  m=mail  v=visite  n=note): t\n  note: Pris des nouvelles, on planifie une rando pour juin.\n  ✓ 2025-05-06.md\n```\n\nCrée un fichier d'interaction daté dans le dossier du contact. La recherche est partielle — `jeff note ant` trouve \"Antoine Martin\". Chaque interaction est un `.md` avec date et type, visible dans le dossier du contact :\n\n```\ncontent/contacts/antoine-martin/\n  antoine-martin.md     # fiche contact\n  2025-05-06.md         # interaction WhatsApp\n  2025-04-01.md         # interaction téléphone\n```\n\n### 6. `jeff cron` — Automatisation quotidienne\n\n```sh\njeff cron\n```\n\n```\n── Sync ──\nDiscovering addressbooks...\nAddressbook: Contacts\nAlready up to date.\n\n── Birthdays ──\n  🎂 Jean Dupont (recorded)\n\n── Publish ──\nPublished 204 contact(s) to public/\n```\n\nEnchaîne automatiquement : **sync** → **détection des anniversaires** → **publication du site HTML**. Conçu pour tourner dans un crontab :\n\n```\n0 7 * * * cd /path/to/project \u0026\u0026 jeff cron\n```\n\nLe site publié inclut :\n- Dashboard avec contacts groupés par relation et triés par priorité\n- Section anniversaires du jour avec bouton WhatsApp pré-rempli\n- Arbre généalogique (Graphviz SVG)\n- Fiches contact avec coordonnées, liens familiaux et signe astrologique\n\n## Autres commandes\n\n| Commande | Description |\n|----------|-------------|\n| `jeff delete` | Marquer des contacts archivés pour suppression du CardDAV |\n| `jeff check` | Détecter et nettoyer les doublons (même UID) |\n| `jeff export` | Exporter au format SquirrelMail (.abook) |\n| `jeff birthday-mail` | Envoyer un rappel anniversaire par email |\n| `jeff sync --writeback-gender` | Pousser le genre vers CardDAV (X-GENDER) |\n| `jeff sync --writeback-famille` | Pousser les liens familiaux vers CardDAV (RELATED) |\n\n## Architecture\n\n```\nsrc/jeff/\n  cli.py                  CLI commands (click)\n  domain/                 Business objects\n    carddav.py            CardDAV client\n    config.py             Configuration (.jeff file)\n    transform.py          vCard → Markdown\n    urlback.py            vCard injection (URL, gender, RELATED)\n  services/               Business logic\n    sync.py               Sync orchestration\n    publish.py            Static site builder\n    triage.py             Contact triage\n    famille.py            Family link management\n    genealogy.py          Family tree (Graphviz)\n    birthday.py           Birthday detection\n    birthday_mail.py      Email reminders\n    genre.py              Gender assignment\n    duplicates.py         Duplicate detection\n    export.py             Address book export\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flduchosal%2Fjeff","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flduchosal%2Fjeff","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flduchosal%2Fjeff/lists"}