diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 292035cf..db505544 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -7,7 +7,7 @@ }, "metadata": { "description": "e2a plugins for Claude Code — authenticated email for AI agents", - "version": "0.4.3" + "version": "0.4.4" }, "plugins": [ { diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index 1a46c3bf..c0205dc4 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "e2a — authenticated email gateway for AI agents (MCP server + operate-well skill).", - "version": "0.4.3" + "version": "0.4.4" }, "plugins": [ { diff --git a/plugins/e2a/.claude-plugin/plugin.json b/plugins/e2a/.claude-plugin/plugin.json index 6b7ba780..3492ccb7 100644 --- a/plugins/e2a/.claude-plugin/plugin.json +++ b/plugins/e2a/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "e2a", "displayName": "e2a", - "version": "0.4.3", + "version": "0.4.4", "description": "Authenticated email gateway for AI agents — per-agent inboxes, HITL approval, SPF/DKIM verification, and a queryable, replayable event log. 37 MCP tools over hosted streamable HTTP with OAuth.", "author": { "name": "Mnexa AI", diff --git a/plugins/e2a/.codex-plugin/plugin.json b/plugins/e2a/.codex-plugin/plugin.json index c48b6ec8..70d85206 100644 --- a/plugins/e2a/.codex-plugin/plugin.json +++ b/plugins/e2a/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "e2a", "displayName": "e2a", - "version": "0.4.3", + "version": "0.4.4", "description": "Authenticated email gateway for AI agents — per-agent inboxes, HITL approval, SPF/DKIM verification, and a queryable, replayable event log. 37 MCP tools over hosted streamable HTTP with OAuth.", "author": { "name": "Mnexa AI" diff --git a/plugins/e2a/.cursor-plugin/plugin.json b/plugins/e2a/.cursor-plugin/plugin.json index 68b0fad0..2cd4eeb2 100644 --- a/plugins/e2a/.cursor-plugin/plugin.json +++ b/plugins/e2a/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "e2a", "displayName": "e2a", - "version": "0.4.3", + "version": "0.4.4", "description": "Authenticated email gateway for AI agents — per-agent inboxes, HITL approval, SPF/DKIM verification, and a queryable, replayable event log. 37 MCP tools over hosted streamable HTTP with OAuth.", "author": { "name": "Mnexa AI", diff --git a/plugins/e2a/skills/tether/SKILL.md b/plugins/e2a/skills/tether/SKILL.md index 7eb8da64..79e681bf 100644 --- a/plugins/e2a/skills/tether/SKILL.md +++ b/plugins/e2a/skills/tether/SKILL.md @@ -60,7 +60,10 @@ Let `T="${CLAUDE_PLUGIN_ROOT}/skills/tether/tether.sh"`. 2. **Start**: `"$T" start ` — sends the intro email, opens the thread, arms. 3. **Work**, and **send updates as you see fit**: `"$T" update ""`. Good moments: finished a slice, made a decision that's worth surfacing, hit a - blocker, or before a long unattended stretch. Skip trivial turns. + blocker, or before a long unattended stretch. Skip trivial turns. For a rich + update (diagram, table, formatting), write the HTML to a file and run + `"$T" update --html ` — a plain-text fallback is auto-derived (or pass + `--text ""`). 4. **Need a decision from the user? Ask by email — never the terminal.** Run `"$T" ask ""` (in the background); it emails the question and blocks until the user replies, then prints the answer. **Do not** use AskUserQuestion diff --git a/plugins/e2a/skills/tether/lib.sh b/plugins/e2a/skills/tether/lib.sh index af89138e..da090258 100755 --- a/plugins/e2a/skills/tether/lib.sh +++ b/plugins/e2a/skills/tether/lib.sh @@ -75,19 +75,33 @@ try:print(json.load(sys.stdin).get("message_id","")) except Exception:print("")' } -# t_api_reply → prints new message_id +# t_api_reply [html_body] → prints new message_id t_api_reply() { local email resp email="$(t_urlencode "$E2A_AGENT_EMAIL")" resp="$(curl -sS -m 30 -X POST \ -H "Authorization: Bearer ${E2A_API_KEY}" -H "Content-Type: application/json" \ - -d "$(python3 -c 'import json,sys;print(json.dumps({"body":sys.argv[1]}))' "$2")" \ + -d "$(python3 -c 'import json,sys +p={"body":sys.argv[1]} +if len(sys.argv)>2 and sys.argv[2]:p["html_body"]=sys.argv[2] +print(json.dumps(p))' "$2" "${3:-}")" \ "${E2A_BASE_URL}/v1/agents/${email}/messages/${1}/reply" 2>/dev/null)" || return 1 printf '%s' "$resp" | python3 -c 'import json,sys try:print(json.load(sys.stdin).get("message_id","")) except Exception:print("")' } +# strip HTML tags → a plain-text fallback (crude but fine for email body) +t_html_to_text() { + python3 -c 'import sys,re,html +t=sys.stdin.read() +t=re.sub(r"(?is)<(script|style).*?"," ",t) +t=re.sub(r"(?i)<(br|/p|/div|/li|/tr|/h[1-6])\s*/?>","\n",t) +t=re.sub(r"<[^>]+>"," ",t) +t=html.unescape(t) +print(re.sub(r"[ \t]+"," ",re.sub(r"\n\s*\n\s*","\n\n",t)).strip())' +} + # t_api_poll → TSV lines: idfromcreated_at (inbound, oldest first) t_api_poll() { local email resp diff --git a/plugins/e2a/skills/tether/tether.sh b/plugins/e2a/skills/tether/tether.sh index 4ed240ea..e63f520b 100755 --- a/plugins/e2a/skills/tether/tether.sh +++ b/plugins/e2a/skills/tether/tether.sh @@ -3,6 +3,7 @@ # # tether.sh start send the intro email, open the thread, arm # tether.sh update "" send a threaded update ("as you see fit") +# tether.sh update --html send an HTML update (+ auto text fallback) # tether.sh ask "" email a question and BLOCK until the reply # tether.sh poll print any new replies since last poll (exit 0) # tether.sh status show tether state @@ -43,13 +44,31 @@ minutes). Reply any time with a question or instruction; reply \"stop\" to end. ;; update) + # update "" plain-text update + # update --html [--text ""] HTML update (+ optional text fallback) need_config; need_armed - msg="${1:-}"; [ -n "$msg" ] || { echo "usage: tether.sh update \"\""; exit 2; } + htmlfile=""; textarg=""; msg="" + while [ $# -gt 0 ]; do + case "$1" in + --html) htmlfile="${2:-}"; shift 2;; + --text) textarg="${2:-}"; shift 2;; + *) msg="$1"; shift;; + esac + done + html="" + if [ -n "$htmlfile" ]; then + [ -f "$htmlfile" ] || { echo "tether: --html file not found: $htmlfile"; exit 2; } + html="$(cat "$htmlfile")" + # plain-text fallback: explicit --text/positional, else derived from the HTML + [ -n "$msg" ] || msg="$textarg" + [ -n "$msg" ] || msg="$(printf '%s' "$html" | t_html_to_text)" + fi + [ -n "$msg" ] || { echo "usage: tether.sh update \"\" | update --html [--text \"\"]"; exit 2; } rid="$(t_state_get last_message_id)" - mid="$(t_api_reply "$rid" "$msg")" + mid="$(t_api_reply "$rid" "$msg" "$html")" if [ -z "$mid" ]; then echo "tether: update send failed"; exit 1; fi t_state_set last_message_id "$mid" - echo "tether: update sent (${mid})" + echo "tether: update sent (${mid})$([ -n "$html" ] && echo ' [html]')" ;; poll)