Three atomics finish phase 3 of kei-buddy contacts integration:
## kei-buddy: contact-sync glue + slash commands (+5 tests)
New src/contacts_sync.rs (146 LOC):
* SyncReport { fetched, added, skipped, errors }
* sync_from_google(access_token, contacts) — builds GooglePeopleClient,
list_connections, dedups by (name+email) via search_contacts,
add_contact in loop
* sync_from_apple(apple_id, app_pw, addressbook_url, contacts) — same
pattern over ICloudCardDavClient.list_contacts
* All errors collected into report.errors; never panics, never propagates
New slash commands in commands.rs / command_exec.rs:
* /sync-google — reads GOOGLE_OAUTH_ACCESS_TOKEN env, calls sync_from_google,
Russian-formatted summary "Google: загружено N, добавлено M, пропущено K"
* /sync-apple — reads APPLE_ID + APPLE_APP_PASSWORD + APPLE_CARDDAV_URL,
calls sync_from_apple
* Missing env → human-readable "не настроено: …" response
* /help text updated
Deps added: kei-contacts-google + kei-contacts-apple as path deps.
## kei-contacts-google: pagination via nextPageToken (+1 test)
Refactor: client.rs 182→56 LOC; pagination logic + deserialization moved
to new src/pagination.rs (188 LOC). list_connections unchanged
(back-compat, returns first page only). New list_all_connections loops
via fetch_page(Some(token)) until token=None; hard cap 50 pages with
tracing::warn on cap.
Test list_all_connections_two_pages: wiremock returns page 1 with
nextPageToken="abc" + page 2 without; assert len = sum AND second
request carries pageToken=abc query.
## kei-contacts-apple: vCard line-folding + CardDAV auto-discovery (+2 tests)
vcard.rs +unfold() helper applied in parse_vcard per RFC 6350 §3.2:
continuation lines starting with space/tab strip the prefix and append
to previous line. Test parse_folded_vcard.
New src/discovery.rs (199 LOC): discover_addressbook() walks
.well-known/carddav → current-user-principal → addressbook-home-set →
first addressbook with C:addressbook resourcetype. Three PROPFIND
requests with canned XML bodies. Regex-based extract_first_href_under +
extract_addressbook_href helpers. Test discover_walks_three_propfinds
against 3-step wiremock fixture.
client.rs adds discover_addressbook_url() method calling discovery.
## Verify-before-commit
* cargo check --workspace: PASS
* cargo test -p kei-buddy --lib: 46/0 (was 41)
* cargo test -p kei-contacts-google: 5/0 (was 4, +1 pagination)
* cargo test -p kei-contacts-apple: 9/0 (was 7, +1 folding +1 discovery)
NOT deployed — user still in live conversation with bot.
Follow-up (deferred, non-blocking):
* Real iCloud smoke test for discover_addressbook_url — regex parser
may need adjustment for deeply-nested namespace prefixes
* Wiremock-backed integration test for sync_from_google glue (HTTP
layer already covered in kei-contacts-google tests)
|
||
|---|---|---|
| .. | ||
| src | ||
| tests | ||
| Cargo.toml | ||
| README.md | ||
kei-buddy
Maturity: concept / scaffold — no business logic yet.
Purpose
kei-buddy is the runtime crate that composes existing KeiSeiKit
primitives (kei-pet, kei-memory-sqlite, kei-cortex,
kei-notify-telegram) into a personal-assistant Telegram bot called
KeiBuddy.
On first contact the bot walks the user through an 11-state onboarding flow: name, tone, interests, hobbies, per-topic decomposition (specifics → now-or-later → research preference → source selection), and digest schedule. After onboarding the bot enters ongoing conversation mode, drawing on the stored persona and memory.
This crate provides the state-machine enum and skeleton driver. The
onboarding FSM is ported from
keisei-marketplace/src/lib/keibuddy/chat-onboard.ts.
Status
Scaffold only. The OnboardState enum and TransitionInput struct are
defined. All transition logic is stubbed (next() returns self.clone()).
The binary entry point prints a placeholder message and exits 0.
Running
Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
yes (serve) | — | Bot token from @BotFather |
TELEGRAM_WEBHOOK_SECRET |
yes (serve) | — | Secret token for webhook verification |
KEI_BUDDY_PORT |
no | 8080 |
HTTP port to bind |
KEI_BUDDY_DB_PATH |
no | ./kei-buddy.db |
SQLite database path |
OPENAI_API_KEY |
no | — | Enables OpenAiExtractor when set (requires extractor-openai feature) |
Subcommands
# Apply schema (idempotent; run once before first serve)
kei-buddy migrate
# Register the webhook URL with Telegram
kei-buddy webhook-set https://your-domain.com/webhook
# Start the HTTP server
kei-buddy serve
# Remove the registered webhook (revert to polling)
kei-buddy webhook-delete
Example systemd unit
[Unit]
Description=KeiBuddy Telegram bot
After=network.target
[Service]
EnvironmentFile=/etc/kei-buddy/env
ExecStart=/usr/local/bin/kei-buddy serve
Restart=on-failure
User=keisei
[Install]
WantedBy=multi-user.target
Roadmap
- OpenAiExtractor wiring — pass real OPENAI_API_KEY to OpenAiExtractor in serve.rs when feature enabled.
- Persona binding — read persona manifest via
kei-pet; apply tone overlay to outgoing replies. - Digest scheduling — wire
kei-cron-schedulerfor morning/evening digest delivery.