diff --git a/DISCLOSURE.md b/DISCLOSURE.md index 997e356..0c4537c 100644 --- a/DISCLOSURE.md +++ b/DISCLOSURE.md @@ -76,17 +76,20 @@ No direct HTTP/gRPC sovereign API calls. Indirect access flows through the (curr No `cortex/L6-*` or `cortex/PG-*` libraries consumed at runtime. `lib/` scripts (`credbridge.sh`, `validate_access.sh`) are repo-local utility shims, not cortex tools. -## §7 Credentials (3 declared) +## §7 Credentials (6 declared) -Per `disclosure.credentials` allowlist. Names + scopes only — NEVER values. Pre-push check 6.d enforces vault_name exact-match. +Per `disclosure.credentials` allowlist. Names + scopes only — NEVER values. Pre-push check 6.d enforces vault_name exact-match. **Wave 8 (2026-05-24): aligned with vault.** | Vault name | Status | Scope | Used by | Governance | |---|---|---|---|---| -| `google-workspace` | required | read-write | `credbridge.sh` | JP-personal; Gmail+Calendar+Contacts for briefing + inbox triage | -| `proton-bridge-imap` | required | read-write | `credbridge.sh` | JP-personal; local Proton Bridge IMAP/SMTP (himalaya path) | -| `perplexity-api` | optional | read | `credbridge.sh` | JP-personal; WebSearch fallback (MCP path preferred) | +| `proton-bridge-imap-user` | required | read | `credbridge.sh` | JP-personal; local Proton Bridge IMAP/SMTP username (himalaya path) | +| `proton-bridge-imap-pass` | required | read | `credbridge.sh` | JP-personal; local Proton Bridge IMAP/SMTP password (himalaya path) | +| `perplexity` | optional | read | `credbridge.sh` | JP-personal; WebSearch fallback (MCP path preferred) | +| `proton-account-email` | required | read | `credbridge.sh`, `mcp_proton_email` | JP-personal; Proton account email (consumed by proton-email MCP server) | +| `proton-account-password` | required | read | `credbridge.sh`, `mcp_proton_email` | JP-personal; Proton account password (consumed by proton-email MCP server) | +| `proton-mailbox-password` | required | read | `credbridge.sh`, `mcp_proton_email` | JP-personal; Proton mailbox E2E key for mail decryption | -> **PENDING JP REVIEW** — Per Wave-3 recommendations §5a, all three declared names are reported by audit as not exact-matching the vault (`credctl list` shows `proton-bridge-imap-pass`/`-user` split, `perplexity` without `-api`, and `google-workspace` plausibly absent or composite). Cred-rename rows are governance-class W3.4 and require JP decision (manifest-rename vs vault-rename vs bundle-indirection) — surfaced in §12. +> **google-workspace removed Wave 8** — Hermes builtin `google-workspace` skill manages its own OAuth flow via Hermes hub, not credctl vault. credbridge.sh google-workspace case dropped accordingly. ## §8 Cron (1) @@ -118,20 +121,25 @@ Per `disclosure.credentials` allowlist. Names + scopes only — NEVER values. Pr - Standards: `../sot/04-STANDARDS/FRONTMATTER-SPEC.md`, `../sot/04-STANDARDS/SOT-ENFORCEMENT.md`, `../sot/04-STANDARDS/DISCLOSURE-SCHEMA.md` - Brand master ref: omitted (scope: personal) — steev serves JP personally, not a brand/org -## §12 Open issues + next steps (PENDING JP REVIEW) +## §12 Open issues + next steps -Rows below are **PAUSED for JP** per W3.4 governance-class rule. Wave-4 applies auto-approved rows only (REMOVE bte MCP + DROP 17 builtins + scaffold disclosure block). JP must mark each PAUSE row approve/reject/edit before next apply wave. +All 8 Wave-3 PAUSE rows resolved in **Wave 8 (2026-05-24)**. Audit trail retained below. -| # | Topic | Recommended action | Why PAUSED | +| # | Topic | Resolution | Wave | |---|---|---|---| -| 1 | Personal-scope discriminator values (`chat_facing: true`, `delegates_to: [ceo-planb]`, `sovereign_only: false`) | Confirm values | New disclosure surface; JP confirms intent matches CLAUDE.md L7-L8 + CONTRACT delegation chain | -| 2 | Cred `google-workspace` not in vault | (a) add composite OAuth JSON to vault, OR (b) split manifest into per-cred entries matching vault | Cred binding (W3.4) | -| 3 | Cred `proton-bridge-imap` vs vault `proton-bridge-imap-pass` + `proton-bridge-imap-user` | Rename manifest entry to TWO entries matching vault | Cred binding (W3.4) | -| 4 | Cred `perplexity-api` vs vault `perplexity` | Rename manifest declaration `perplexity-api` → `perplexity` (exact-match per schema §4.5) | Cred binding (W3.4) | -| 5 | 5 vault entries plausibly steev-scope but undeclared (`proton-account-email`, `proton-account-password`, `proton-mailbox-password`, `proton-bridge-imap-pass`, `proton-bridge-imap-user`) | ADD to `disclosure.credentials` after MCP install confirms which are consumed | Cred binding (W3.4); also depends on MCP install (row 6) | -| 6 | 4 declared MCP servers absent from `hermes mcp list` (`mcp_proton_calendar`, `mcp_proton_email`, `mcp_proton_contacts`, `mcp_perplexity`) | Confirm install order — Wave-4 install.sh patch, or deferred | Install gap; cred-adjacent | -| 7 | macOS-only externals (`apple-notes`, `apple-reminders`, `imessage`) in `expected_external_skills` | Gate on OS in `install.sh`, or document as macOS-host-only | OS-platform decision | -| 8 | Pre-push hook check 6 not yet wired (curator/lib/pre-push.sh patch belongs to Wave-5+) | Wire check 6 per DISCLOSURE-SCHEMA §6 | Cross-profile rollup (Wave-5) | +| 1 | Personal-scope discriminator values (`chat_facing: true`, `delegates_to: [ceo-planb]`, `sovereign_only: false`) | **CONFIRMED** (Q4). Matches CLAUDE.md L7-L8 + CONTRACT delegation chain. | 8 | +| 2 | Cred `google-workspace` not in vault | **REMOVED** (Q5 + scope-expansion). Builtin manages own OAuth via Hermes hub; no credctl vault entry needed. credbridge.sh google-workspace case dropped. | 8 | +| 3 | Cred `proton-bridge-imap` vs vault `proton-bridge-imap-pass` + `proton-bridge-imap-user` | **SPLIT** (Q6). Manifest split into 2 entries matching vault. credbridge.sh exports both `PROTON_BRIDGE_IMAP_USER` + `PROTON_BRIDGE_IMAP_PASSWORD`. | 8 | +| 4 | Cred `perplexity-api` vs vault `perplexity` | **RENAMED** (Q7). Manifest + credbridge.sh updated to `perplexity` (exact-match per schema §4.5). | 8 | +| 5 | 3 proton vault entries undeclared (`proton-account-email`, `proton-account-password`, `proton-mailbox-password`) | **ADDED** (Q8). Declared in `disclosure.credentials` w/ `used_by: [credbridge.sh, mcp_proton_email]`. The other 2 (`proton-bridge-imap-pass/-user`) covered by row 3. | 8 | +| 6 | 4 declared MCP servers absent from `hermes mcp list` (`mcp_proton_calendar`, `mcp_proton_email`, `mcp_proton_contacts`, `mcp_perplexity`) | **MATERIALIZED 3/4** (Q9). install.sh F6 wires 3 proton MCPs into per-profile config from `optional_tools`. Also removed bte (hard-rule leak discovered Wave 8). mcp_perplexity DEFERRED (server not in global `hermes mcp list`). | 8 | +| 7 | macOS-only externals (`apple-notes`, `apple-reminders`, `imessage`) in `expected_external_skills` | **OS-GATED** (Q10). Annotated `os_constraint: darwin`. install.sh F7 emits INFO on non-Darwin hosts that these are unavailable. | 8 | +| 8 | Pre-push hook check 6 not yet wired (curator/lib/pre-push.sh patch belongs to Wave-5+) | **WIRED** (Wave 7 D6). Subrepo pre-push hook installed via `install.sh F4`; main repo hook covers 6.a-6.f. | 7 | + +### Wave 8 follow-ups (not PAUSE — separate work) + +- **mcp_perplexity install** — server doesn't exist in global `hermes mcp list`. When provisioned, install.sh F6 will materialize automatically (no code change). +- **Per-tool enumeration in `disclosure.mcp_servers`** — currently `[]` w/ install.sh F6 driven from `optional_tools`. Wave 8.5: introspect each MCP server, populate `disclosure.mcp_servers[*].tools[]` per DISCLOSURE-SCHEMA §4.2. ## §13 Related diff --git a/credbridge.sh b/credbridge.sh index 763ebb3..bacd90a 100755 --- a/credbridge.sh +++ b/credbridge.sh @@ -4,7 +4,7 @@ # written to disk. # # Usage: credbridge.sh [args...] -# tools: google-workspace | proton-bridge | perplexity +# tools: proton-bridge | perplexity # # Per PROFILE-DISTRIBUTION-PROTOCOL §3 (shared core, "credbridge" row) and §6 # (Conventions → Secrets), every profile distribution exposes credentials via @@ -13,13 +13,16 @@ # This is the personal-assistant variant of the credbridge pattern. Steev's # cred surface is narrow by design: # -# - google-workspace: Gmail + Calendar + Contacts (OAuth blob from credctl) -# - proton-bridge: IMAP/SMTP password for the local Proton Bridge T6 -# sidecar — gives Steev access to JP's Proton mail via -# himalaya (cleartext on 127.0.0.1 only) +# - proton-bridge: IMAP/SMTP user + password for the local Proton Bridge +# T6 sidecar — gives Steev access to JP's Proton mail +# via himalaya (cleartext on 127.0.0.1 only) # - perplexity: Perplexity API key for WebSearch toolset (lightweight # — most Steev work uses the perplexity MCP instead) # +# Wave 8 (2026-05-24): google-workspace case REMOVED — Hermes builtin +# google-workspace skill manages its own OAuth flow via the Hermes hub, not +# the credctl vault. Vault contains no google-workspace-* entries. +# # Plan B marketing platforms (WooCommerce, Mailchimp, Meta, GA4, etc.) are OUT # OF SCOPE here — that's cmo-planb's credbridge. Steev MUST NEVER resolve a # marketing platform credential. The CLAUDE.md "no access to Plan B marketing @@ -27,8 +30,6 @@ # # Design notes (same as cmo/credbridge.sh — shared core): # - credctl values read into local vars, exported straight to the child env -# - JSON-valued creds (google-workspace OAuth) parsed via `node -e` reading -# from stdin so the value never lands on argv / process list # - No `echo $secret`. set +x stays off. set -euo pipefail @@ -39,7 +40,7 @@ STEEV_LIB="${STEEV_LIB:-/home/svrnty/.hermes/steev}" die() { printf '{"error":"%s"}\n' "$1" >&2; exit 1; } -[ $# -ge 1 ] || die "usage: credbridge.sh [args...]" +[ $# -ge 1 ] || die "usage: credbridge.sh [args...]" TOOL="$1"; shift [ -x "$CREDCTL" ] || die "credctl not found/executable at $CREDCTL" @@ -51,44 +52,30 @@ cred_raw() { | sed -n '/^Value:/,$p' | sed '1s/^Value:[[:space:]]*//' } -# json_field — extract a string field via node; value never on argv. -json_field() { - printf '%s' "$1" | node -e ' - let s="";process.stdin.on("data",d=>s+=d); - process.stdin.on("end",()=>{try{const o=JSON.parse(s); - const v=o[process.argv[1]];process.stdout.write(v==null?"":String(v)); - }catch(e){process.stdout.write("");}});' "$2" -} - case "$TOOL" in - google-workspace) - # Gmail Data API + Calendar API + People API all expect a bearer token - # minted from this service-account / OAuth blob. The blob is JSON; we - # export the whole document so the child CLI can introspect scope. - GW_JSON="$(cred_raw google-workspace)" - [ -n "$GW_JSON" ] || die "credctl: google-workspace not set" - export GOOGLE_WORKSPACE_CREDENTIALS_JSON="$GW_JSON" - exec "$@" - ;; proton-bridge) # Steev reads JP's Proton inbox via the local Proton Bridge IMAP daemon # (T6 sidecar — see PROFILE-DISTRIBUTION-PROTOCOL §4.T6). credctl stores - # the bridge password (rotates when JP rotates the bridge). - PB_PASS="$(cred_raw proton-bridge-imap)" - [ -n "$PB_PASS" ] || die "credctl: proton-bridge-imap not set" + # user + password as separate vault entries (Wave 8 aligned to vault). + PB_USER="$(cred_raw proton-bridge-imap-user)" + [ -n "$PB_USER" ] || die "credctl: proton-bridge-imap-user not set" + PB_PASS="$(cred_raw proton-bridge-imap-pass)" + [ -n "$PB_PASS" ] || die "credctl: proton-bridge-imap-pass not set" + export PROTON_BRIDGE_IMAP_USER="$PB_USER" export PROTON_BRIDGE_IMAP_PASSWORD="$PB_PASS" exec "$@" ;; perplexity) # Lightweight WebSearch path. Most Steev research goes through the # perplexity MCP server (which holds its own key); this credbridge entry - # exists for scripts that need a raw key (rare). - PPL_KEY="$(cred_raw perplexity-api)" - [ -n "$PPL_KEY" ] || die "credctl: perplexity-api not set" + # exists for scripts that need a raw key (rare). Wave 8 renamed + # vault entry `perplexity-api` → `perplexity`. + PPL_KEY="$(cred_raw perplexity)" + [ -n "$PPL_KEY" ] || die "credctl: perplexity not set" export PERPLEXITY_API_KEY="$PPL_KEY" exec "$@" ;; *) - die "unknown tool: $TOOL (allowed: google-workspace|proton-bridge|perplexity)" + die "unknown tool: $TOOL (allowed: proton-bridge|perplexity)" ;; esac diff --git a/install.sh b/install.sh index a64f443..242acec 100755 --- a/install.sh +++ b/install.sh @@ -310,8 +310,72 @@ HOOK_EOF echo " F4 installed: $HOOK_DST" fi +# F6 — MCP server materialization (Wave 8 Q9) +# Reads manifest.optional_tools (mcp_ aliases), +# maps to runtime MCP server names (hyphenated), copies global config block +# into per-profile config.yaml. Removes non-declared MCPs (closes bte leak). +if [ "$DRY" = 1 ]; then + echo "DRY: F6 materialize MCP allowlist → $PROFILE_CFG" +elif command -v yq >/dev/null 2>&1 && [ -f "$HERMES_HOME/config.yaml" ]; then + # Declared MCP set (mcp_proton_calendar → proton-calendar etc). + DECLARED_MCPS=$(yq -r '.optional_tools[]?' "$REPO/manifest.yaml" 2>/dev/null | sed 's/^mcp_//; s/_/-/g') + if [ -z "$DECLARED_MCPS" ]; then + echo " F6: no optional_tools declared — skip" + else + mkdir -p "$(dirname "$PROFILE_CFG")" + [ -f "$PROFILE_CFG" ] || : > "$PROFILE_CFG" + F6_ADDED=0; F6_REMOVED=0; F6_MISSING=0 + # Set the per-profile mcp_servers block from the declared list. Existing + # entries NOT in declared list are dropped (denylist enforcement). + GLOBAL_CFG="$HERMES_HOME/config.yaml" + python3 - "$GLOBAL_CFG" "$PROFILE_CFG" "$DECLARED_MCPS" <<'PY' +import sys, yaml +gcfg, pcfg, declared_str = sys.argv[1], sys.argv[2], sys.argv[3] +declared = [s.strip() for s in declared_str.splitlines() if s.strip()] +g = yaml.safe_load(open(gcfg).read()) or {} +p = yaml.safe_load(open(pcfg).read()) or {} +g_mcps = g.get('mcp_servers', {}) or {} +new_block = {} +missing = [] +for name in declared: + if name in g_mcps: + new_block[name] = g_mcps[name] + else: + missing.append(name) +prev = set((p.get('mcp_servers') or {}).keys()) +new = set(new_block.keys()) +added = sorted(new - prev) +removed = sorted(prev - new) +p['mcp_servers'] = new_block +open(pcfg, 'w').write(yaml.safe_dump(p, sort_keys=False, allow_unicode=True)) +for n in added: print(f" F6 + {n}") +for n in removed: print(f" F6 - {n} (denied)") +for n in missing: print(f" F6 ⚠ {n} (declared but not in global mcp_servers — skipped)") +print(f" F6 wrote mcp_servers: {len(new_block)} entr{'y' if len(new_block)==1 else 'ies'}") +PY + fi +else + echo " WARN: F6 yq/global config missing — skipping MCP materialization" +fi + +# F7 — macOS-only externals OS-gate (Wave 8 Q10) +# Reads expected_external_skills entries with os_constraint: darwin and emits +# an INFO line on non-Darwin hosts. No install action (these are external +# prereqs, not provisioned by this installer); annotation is the audit record. +HOST_OS="$(uname -s 2>/dev/null || echo Unknown)" +if [ "$DRY" = 1 ]; then + echo "DRY: F7 OS-gate check (host=$HOST_OS)" +elif command -v yq >/dev/null 2>&1; then + MACOS_ONLY=$(yq -r '.expected_external_skills[] | select(type == "!!map") | select(.os_constraint == "darwin") | .name' "$REPO/manifest.yaml" 2>/dev/null || true) + if [ -n "$MACOS_ONLY" ] && [ "$HOST_OS" != "Darwin" ]; then + echo " F7 INFO: macOS-only externals declared but host=$HOST_OS — unavailable:" + while IFS= read -r s; do [ -n "$s" ] && echo " - $s"; done <<< "$MACOS_ONLY" + fi +fi + echo "" echo "== done ==" echo " verify skills: hermes -p steev skills list | grep steev-agent" +echo " verify mcp servers: hermes -p steev mcp list" echo " verify assignee registered: hermes kanban assignees | grep steev" echo " start gateway (when ready): hermes profile gateway start steev" diff --git a/manifest.yaml b/manifest.yaml index 074fa06..e35534e 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -44,12 +44,16 @@ lib: # tree (~/.hermes/skills/) or external skill libraries the principal already installed. expected_external_skills: - google-workspace # Gmail + Calendar + Contacts - - apple-notes # macOS-local via osascript - - apple-reminders # macOS-local via osascript - obsidian # ~/vaults/steev PKM - himalaya # IMAP/SMTP via proton-bridge sidecar - - imessage # macOS-local - perplexity # WebSearch toolset (lightweight; MCP preferred) + # macOS-only skills (Wave 8 Q10): install.sh F7 emits info on non-Darwin hosts. + - name: apple-notes + os_constraint: darwin + - name: apple-reminders + os_constraint: darwin + - name: imessage + os_constraint: darwin # MCP servers Steev consumes. Names match runtime-prefixed form (mcp__). optional_tools: @@ -61,15 +65,26 @@ optional_tools: requires_tools: [terminal, memory_tool] credentials: # validated by validate_access.sh - - name: google-workspace - purpose: Gmail + Calendar + Contacts read/write for daily briefing + inbox triage + # Wave 8 (2026-05-24): aligned with vault exact-match per DISCLOSURE-SCHEMA §4.5. + # google-workspace removed — builtin manages its own OAuth via Hermes hub (not credctl vault). + - name: proton-bridge-imap-user + purpose: Proton Bridge IMAP/SMTP username (himalaya path) resolved_via: credbridge.sh - - name: proton-bridge-imap - purpose: local Proton Bridge IMAP/SMTP password (himalaya path) + - name: proton-bridge-imap-pass + purpose: Proton Bridge IMAP/SMTP password (himalaya path) resolved_via: credbridge.sh - - name: perplexity-api + - name: perplexity purpose: Perplexity API key for raw WebSearch (MCP path preferred) resolved_via: credbridge.sh + - name: proton-account-email + purpose: Proton account email (consumed by proton-email MCP server) + resolved_via: credbridge.sh + - name: proton-account-password + purpose: Proton account password (consumed by proton-email MCP server) + resolved_via: credbridge.sh + - name: proton-mailbox-password + purpose: Proton mailbox E2E key for mail decryption (consumed by proton-email MCP server) + resolved_via: credbridge.sh db: file: steev.db # runtime state; created from schema.sql; never committed @@ -140,26 +155,47 @@ disclosure: justification: "CEO delegation transport — steev → ceo-planb (steev-agent SKILL.md L83)" mcp_servers: [] # DENY-BY-DEFAULT. bte REMOVED (hard-rule fix). - # proton-* + perplexity MCP installs PENDING JP review - # (install-gap row in DISCLOSURE.md §12). + # Wave 8 (2026-05-24): install.sh F6 wires the 3 proton MCPs + # (proton-calendar, proton-email, proton-contacts) into per-profile + # runtime config from manifest.optional_tools. mcp_perplexity is + # deferred — server not yet in `hermes mcp list`. + # Per-tool enumeration in disclosure.mcp_servers DEFERRED to + # Wave 8.5 (requires tool introspection per server). sovereign_apis: [] # 0 direct HTTP/gRPC calls (per audit §3) cortex_tools: [] # steev does not consume cortex/L6-* or cortex/PG-* credentials: - - vault_name: google-workspace + # Wave 8 (2026-05-24) — aligned with vault per DISCLOSURE-SCHEMA §4.5 (exact-match). + # google-workspace removed (Hermes builtin self-manages OAuth, not in credctl vault). + - vault_name: proton-bridge-imap-user status: required - scope: read-write + scope: read used_by: [credbridge.sh] - governance: "JP-personal; Gmail+Calendar+Contacts for briefing + inbox triage" - - vault_name: proton-bridge-imap + governance: "JP-personal; local Proton Bridge IMAP/SMTP username (himalaya path)" + - vault_name: proton-bridge-imap-pass status: required - scope: read-write + scope: read used_by: [credbridge.sh] - governance: "JP-personal; local Proton Bridge IMAP/SMTP (himalaya path)" - - vault_name: perplexity-api + governance: "JP-personal; local Proton Bridge IMAP/SMTP password (himalaya path)" + - vault_name: perplexity status: optional scope: read used_by: [credbridge.sh] governance: "JP-personal; WebSearch fallback (MCP path preferred)" + - vault_name: proton-account-email + status: required + scope: read + used_by: [credbridge.sh, mcp_proton_email] + governance: "JP-personal; Proton account email (consumed by proton-email MCP server)" + - vault_name: proton-account-password + status: required + scope: read + used_by: [credbridge.sh, mcp_proton_email] + governance: "JP-personal; Proton account password (consumed by proton-email MCP server)" + - vault_name: proton-mailbox-password + status: required + scope: read + used_by: [credbridge.sh, mcp_proton_email] + governance: "JP-personal; Proton mailbox E2E key for mail decryption" diff --git a/skills/proton-tools/SKILL.md b/skills/proton-tools/SKILL.md new file mode 100644 index 0000000..86865d6 --- /dev/null +++ b/skills/proton-tools/SKILL.md @@ -0,0 +1,91 @@ +--- +name: proton-tools +description: "When Steev needs to access JP's Proton account — Calendar, Mail, or Contacts. Use this skill to discover which tool answers the user's question, and how to call it. Covers all 24 Proton MCP tools across the three cortex MCP servers (proton-calendar, proton-email, proton-contacts). Triggers: any request involving JP's calendar (events, meetings, availability), mail (inbox, send, reply, search, folders), or contacts (lookup, add, search). Drive is NOT in scope — defer Drive requests." +metadata: + version: 1.0.0 + hermes: + requires_mcp_servers: [proton-calendar, proton-email, proton-contacts] +--- + +# Proton Tools — Calendar + Mail + Contacts + +Authoritative reference for the 24 tools exposed by three cortex MCP servers — `proton-calendar` (8 tools), `proton-email` (10 tools), `proton-contacts` (6 tools). Each MCP facade dials a long-running gRPC gate that holds the Proton session. + +## Hard rules + +- **Drive is out of scope.** If the user asks about Proton Drive files/folders, say so and defer — there is no `drive_*` tool. Roadmap: `rclone-module` MCP wrap. +- **Destructive tools require explicit confirmation.** `email_send`, `email_reply`, `email_forward`, `calendar_delete`, `contacts_delete`. Never call these without quoting back the action + target + asking JP to confirm. +- **Date inputs are ISO 8601** (`2026-05-23T14:00:00-04:00`). Convert relative dates ("tomorrow", "next Tuesday") into ISO before tool call. +- **Pagination**: `email_list`, `calendar_events`, `contacts_list` are paginated. Default page size is small (~20). Fetch additional pages only when the user asks for more. + +## When to use which tool + +### Calendar (8 tools) + +| User intent | Tool | +|---|---| +| "What calendars do I have?" | `calendar_list` | +| "What's on my calendar today/this week?" | `calendar_events` with date range | +| "What's coming up?" "Next few meetings?" | `calendar_upcoming` | +| "Find meetings about X" | `calendar_search` | +| "Show me details of [event]" | `calendar_event_get` | +| "Schedule a meeting with…" | `calendar_create` (confirm first) | +| "Move my 3pm to 4pm" | `calendar_update` | +| "Cancel my 3pm" | `calendar_delete` (DESTRUCTIVE — confirm) | + +### Mail (10 tools) + +| User intent | Tool | +|---|---| +| "How many unread?" "What folders?" | `email_folders` | +| "Show me my inbox" "Latest emails" | `email_list` (folder=INBOX) | +| "Open that email" | `email_read` by UID | +| "Search inbox for…" | `email_search` | +| "Send an email to…" | `email_send` (DESTRUCTIVE — draft + confirm) | +| "Reply to that" | `email_reply` (DESTRUCTIVE — draft + confirm) | +| "Forward this to…" | `email_forward` (DESTRUCTIVE — confirm) | +| "Archive that" | `email_archive` | +| "Mark as read/unread" | `email_mark_read` / `email_mark_unread` | + +### Contacts (6 tools) + +| User intent | Tool | +|---|---| +| "Who do I have in contacts?" | `contacts_list` | +| "Look up [person]" | `contacts_search` | +| "Pull up [person]'s details" | `contacts_get` | +| "Add [person] to contacts" | `contacts_create` | +| "Update [person]'s email/phone" | `contacts_update` | +| "Remove [person]" | `contacts_delete` (DESTRUCTIVE — confirm) | + +## Daily briefing — tool order + +When JP asks for the morning briefing, query in this order: + +1. `calendar_upcoming` (hours=24) → events today +2. `email_folders` → unread counts +3. `email_list` (folder=INBOX, limit=10) → recent inbox +4. `email_search` (folder=INBOX, query="from:important-person OR is:flagged") → priorities + +Don't dump raw output. Synthesize. Lead with what's actionable in JP's voice. + +## Search composition + +For broad questions like "anything from [person] this week": +- `email_search` (folder=INBOX, query="from:") +- `calendar_search` (query="") +- `contacts_search` (query="") +Run in parallel. Merge results. Group by source. + +## Error handling + +- **"WaitReady timeout"** → proton connector still booting. Retry once after 2-3s. If still failing, say so + suggest JP check `hermes mcp test proton`. +- **403 / scope error** → proton session expired. Tool handler should re-auth automatically; if not, JP needs to re-run setup. +- **Network / 5xx** → transient. Retry once. If persistent, report and stop. + +## What NOT to do + +- Don't paginate aggressively — fetch one page, summarize, ask if JP wants more. +- Don't auto-send drafts. Even after JP says "send" once, re-quote subject + recipient on the next compose. +- Don't synthesize calendar events from email content unless JP explicitly asks ("add this to my calendar"). +- Don't enumerate every contact when JP asks "who's [person]" — use search, not list.