diff --git a/Cargo.lock b/Cargo.lock index 6e20797..985802e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,6 +136,15 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -225,6 +234,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "croner" version = "2.2.0" @@ -234,6 +252,16 @@ dependencies = [ "chrono", ] +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "csv" version = "1.4.0" @@ -355,6 +383,16 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -571,6 +609,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getopts" version = "0.2.24" @@ -1365,6 +1413,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -1463,6 +1520,36 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + [[package]] name = "rc-box" version = "1.3.0" @@ -1653,15 +1740,18 @@ dependencies = [ "anyhow", "async-trait", "axum", + "base64", "chrono", "futures", "futures-util", "pulldown-cmark", + "rand", "reqwest", "rmcp", "rusqlite", "serde", "serde_json", + "sha2", "sqlite-vec", "teloxide", "tempfile", @@ -1930,6 +2020,17 @@ dependencies = [ "syn", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2489,6 +2590,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicase" version = "2.9.0" @@ -2561,6 +2668,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "want" version = "0.3.1" @@ -3122,6 +3235,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index 35f4b93..42e3250 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,5 +58,10 @@ sqlite-vec = "0.1" # Setup wizard web server (used only by src/bin/setup.rs) axum = "0.8" +# OAuth 2.0 / PKCE helpers (used only by src/bin/setup.rs) +rand = "0.8" +sha2 = "0.10" +base64 = "0.22" + [dev-dependencies] tempfile = "3" diff --git a/README.md b/README.md index 5b617eb..b086364 100644 --- a/README.md +++ b/README.md @@ -261,11 +261,11 @@ skills/ - [x] RAG query rewriting (disambiguates follow-up questions before vector search) - [x] Nightly conversation summarization (LLM-based cron job) - [x] Verbose tool UI (`/verbose` command — live tool call progress in Telegram) +- [x] Google integration tools (Calendar, Email, Drive) ### Planned - [ ] Image upload support -- [ ] Google integration tools (Calendar, Email, Drive) - [ ] Event trigger framework (e.g., on email receive) - [ ] WhatsApp support - [ ] Webhook mode (in addition to polling) diff --git a/config.example.toml b/config.example.toml index 8a0c605..7efcb30 100644 --- a/config.example.toml +++ b/config.example.toml @@ -148,6 +148,30 @@ directory = "skills" # These servers are reached over HTTPS and do not require a local command. # Use `url` instead of `command`; optionally set `auth_token` for Bearer auth. +# Example: Notion MCP server (official HTTP MCP — no Node.js required) +# Docs: https://developers.notion.com/guides/mcp/mcp +# +# Recommended: use the setup wizard (cargo run --bin setup) to obtain an OAuth +# access token via the built-in OAuth 2.0 flow. The wizard also stores the +# refresh token and expiry so the bot can automatically renew the connection. +# +# The bot refreshes the access token automatically when it is within 5 minutes +# of expiry and writes the new credentials back to this file. Manual setup: +# 1. Create a Notion integration at https://www.notion.so/my-integrations +# and note your client_id and client_secret. +# 2. Complete the OAuth flow to obtain an access_token and refresh_token. +# 3. Fill in the fields below. +# +# [[mcp_servers]] +# name = "notion" +# url = "https://mcp.notion.com/mcp" +# auth_token = "your-notion-oauth-access-token" +# refresh_token = "your-notion-oauth-refresh-token" +# token_expires_at = 1234567890 # Unix timestamp; set by the wizard +# token_endpoint = "https://api.notion.com/v1/oauth/token" +# oauth_client_id = "your-notion-client-id" +# oauth_client_secret = "your-notion-client-secret" # omit for public clients + # Example: Exa AI web search (https://mcp.exa.ai) # Get your API key at https://dashboard.exa.ai/api-keys # @@ -161,3 +185,11 @@ directory = "skills" # [[mcp_servers]] # name = "exa" # url = "https://mcp.exa.ai/mcp?exaApiKey=your-exa-api-key" + +# ── Self-Learning (optional; defaults apply if section omitted) ───────────── +# [learning] +# user_model_path = "memory/USER.md" # Honcho-style user model file +# skill_extraction_enabled = true # Auto-generate skills from tool-heavy tasks +# skill_extraction_threshold = 5 # Min tool calls to trigger extraction +# user_model_update_interval = 10 # Update user model every N user messages +# user_model_cron = "0 0 3 * * SUN" # Weekly user model refresh (6-field cron) diff --git a/setup/index.html b/setup/index.html index 2f2b829..aae4f2e 100644 --- a/setup/index.html +++ b/setup/index.html @@ -164,6 +164,36 @@ transition: background 0.15s, color 0.15s; } .btn-guide:hover { background: #f6851b; color: #fff; opacity: 1; } + /* OAuth connect button + row */ + .oauth-connect-row { + display: flex; + align-items: center; + gap: 0.75rem; + margin-left: 1.75rem; + margin-top: 0.55rem; + flex-wrap: wrap; + } + .btn-oauth-connect { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.4rem 1rem; + border-radius: 6px; + border: none; + background: #2b6cb0; + color: #fff; + font-size: 0.85rem; + font-weight: 600; + cursor: pointer; + transition: background 0.15s, opacity 0.15s; + } + .btn-oauth-connect:hover { background: #2c5282; } + .btn-oauth-connect:disabled { opacity: 0.55; cursor: not-allowed; } + .oauth-connect-status { + font-size: 0.82rem; + font-weight: 500; + color: #718096; + } /* Modal overlay */ .modal-overlay { display: none; @@ -486,6 +516,58 @@