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)
63 lines
2.1 KiB
TOML
63 lines
2.1 KiB
TOML
[package]
|
|
name = "kei-buddy"
|
|
version = "0.1.0"
|
|
edition.workspace = true
|
|
rust-version.workspace = true
|
|
description = "KeiBuddy personal-assistant Telegram bot — onboarding state-machine + skeleton driver. Concept-level scaffold."
|
|
authors.workspace = true
|
|
license.workspace = true
|
|
|
|
[[bin]]
|
|
name = "kei-buddy"
|
|
path = "src/bin/kei-buddy.rs"
|
|
|
|
[[bin]]
|
|
name = "kei-buddy-tick"
|
|
path = "src/bin/kei-buddy-tick.rs"
|
|
|
|
[lib]
|
|
name = "kei_buddy"
|
|
path = "src/lib.rs"
|
|
|
|
[dependencies]
|
|
serde = { workspace = true, features = ["derive"] }
|
|
serde_json = { workspace = true }
|
|
thiserror = { workspace = true }
|
|
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "net"] }
|
|
tracing = "0.1"
|
|
clap = { workspace = true, features = ["derive"] }
|
|
async-trait = { workspace = true }
|
|
rusqlite = { workspace = true }
|
|
reqwest = { workspace = true }
|
|
anyhow = { workspace = true }
|
|
kei-memory-sqlite = { path = "../kei-memory-sqlite" }
|
|
kei-chat-store = { path = "../kei-chat-store" }
|
|
kei-social-store = { path = "../kei-social-store" }
|
|
kei-sage = { path = "../kei-sage" }
|
|
kei-contacts-google = { path = "../kei-contacts-google" }
|
|
kei-contacts-apple = { path = "../kei-contacts-apple" }
|
|
chrono = { workspace = true }
|
|
|
|
# serve feature deps
|
|
axum = { version = "0.7", features = ["json", "http1", "tokio"], optional = true }
|
|
kei-telegram-webhook = { path = "../kei-telegram-webhook", optional = true }
|
|
tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = true }
|
|
|
|
[dev-dependencies]
|
|
wiremock = { workspace = true }
|
|
tokio = { workspace = true }
|
|
|
|
[features]
|
|
default = ["serve"]
|
|
# HTTP server — axum router + webhook handler + Telegram send_message.
|
|
serve = ["axum", "kei-telegram-webhook", "tracing-subscriber"]
|
|
# Enables OpenAiExtractor — real HTTP to LiteLLM proxy using reqwest.
|
|
# Off by default; tests use MockExtractor which has no extra deps.
|
|
extractor-openai = []
|
|
# future: pulls in kei-notify-telegram for real Telegram transport
|
|
telegram = []
|
|
|
|
[package.metadata.keisei]
|
|
maturity = "concept"
|
|
description = "KeiBuddy personal-assistant: onboarding FSM + bot driver scaffold"
|
|
authors = ["Denis Parfionovich <parfionovich@keilab.io>"]
|