Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand Down
2 changes: 1 addition & 1 deletion .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand Down
2 changes: 1 addition & 1 deletion plugins/e2a/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion plugins/e2a/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 1 addition & 1 deletion plugins/e2a/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
5 changes: 4 additions & 1 deletion plugins/e2a/skills/tether/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ Let `T="${CLAUDE_PLUGIN_ROOT}/skills/tether/tether.sh"`.
2. **Start**: `"$T" start <email>` — sends the intro email, opens the thread, arms.
3. **Work**, and **send updates as you see fit**: `"$T" update "<what changed / what you need>"`.
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 <file>` — a plain-text fallback is auto-derived (or pass
`--text "<fallback>"`).
4. **Need a decision from the user? Ask by email — never the terminal.** Run
`"$T" ask "<question>"` (in the background); it emails the question and blocks
until the user replies, then prints the answer. **Do not** use AskUserQuestion
Expand Down
18 changes: 16 additions & 2 deletions plugins/e2a/skills/tether/lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,33 @@ try:print(json.load(sys.stdin).get("message_id",""))
except Exception:print("")'
}

# t_api_reply <in_reply_to_id> <body> → prints new message_id
# t_api_reply <in_reply_to_id> <body> [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).*?</\1>"," ",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 <conversation_id> <since_iso> → TSV lines: id<TAB>from<TAB>created_at (inbound, oldest first)
t_api_poll() {
local email resp
Expand Down
25 changes: 22 additions & 3 deletions plugins/e2a/skills/tether/tether.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#
# tether.sh start <your-email> send the intro email, open the thread, arm
# tether.sh update "<message>" send a threaded update ("as you see fit")
# tether.sh update --html <file> send an HTML update (+ auto text fallback)
# tether.sh ask "<question>" 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
Expand Down Expand Up @@ -43,13 +44,31 @@ minutes). Reply any time with a question or instruction; reply \"stop\" to end.
;;

update)
# update "<text>" plain-text update
# update --html <file> [--text "<t>"] HTML update (+ optional text fallback)
need_config; need_armed
msg="${1:-}"; [ -n "$msg" ] || { echo "usage: tether.sh update \"<message>\""; 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 \"<text>\" | update --html <file> [--text \"<fallback>\"]"; 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)
Expand Down
Loading