← 返回 Skills 市场
Iris Lite — Inbox Intelligence (Free)
作者
OccupyTheMilkyWay
· GitHub ↗
· v1.0.0
· MIT-0
25
总下载
0
收藏
0
当前安装
1
版本数
在 OpenClaw 中安装
/install iris-lite
功能描述
Iris Lite â Inbox Intelligence (Free). Scans your last 25 Gmail emails, shows urgency scores and a priority list, and drafts 2 quick replies. A free taste...
使用说明 (SKILL.md)
Iris Lite â Free Inbox Triage
Scan your last 25 emails and get a quick priority list with 2 draft replies.
Free vs Pro
| Feature | Iris Lite (Free) | Iris Pro |
|---|---|---|
| Emails scanned | 25 | Up to 200 |
| Draft replies | 2 | Every actionable email |
| Email categories | â | â Sales, HR, Legal, Finance |
| Reply tones | â | Professional / Friendly / Brief |
| Weekly analytics | â | â |
| JSON export | â | â |
ð Upgrade: openclaw skills install iris-pro â get your key at ko-fi.com/occupythemilkyway
Step 1 â Install
pip3 install rich --break-system-packages --quiet
Step 2 â Quick inbox scan (Lite)
import os, imaplib, email, re
from email.header import decode_header
from email.utils import parsedate_to_datetime
from datetime import datetime, timezone
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich import box
console = Console()
GMAIL_ADDR = os.environ.get("GMAIL_ADDRESS", "").strip()
GMAIL_PASS = os.environ.get("GMAIL_APP_PASSWORD", "").strip()
YOUR_NAME = os.environ.get("YOUR_NAME", "").strip()
# Lite hard limits
SCAN_LIMIT = 25
DRAFT_LIMIT = 2
if not GMAIL_ADDR or not GMAIL_PASS:
console.print(Panel(
"[red]GMAIL_ADDRESS and GMAIL_APP_PASSWORD are required.[/red]\
\
"
"App password: myaccount.google.com/apppasswords",
title="Setup Required", border_style="red"))
raise SystemExit(1)
URGENT_KEYWORDS = ["urgent","asap","deadline","immediately","overdue","payment","invoice","emergency"]
NOISE_PATTERNS = [r"unsubscribe",r"newsletter",r"no-reply@",r"noreply@",r"marketing@"]
def decode_str(s):
if not s: return ""
parts = decode_header(s)
result = []
for part, enc in parts:
if isinstance(part, bytes):
result.append(part.decode(enc or "utf-8", errors="replace"))
else:
result.append(str(part))
return " ".join(result)
def is_noise(sender, subject):
return any(re.search(p, (sender+subject).lower()) for p in NOISE_PATTERNS)
def score_email(subject, snippet, sender, age_hours, has_replied):
score = 50
text = (subject + " " + snippet).lower()
if any(k in text for k in URGENT_KEYWORDS): score += 20
if "?" in text: score += 10
if age_hours > 48: score -= 10
if has_replied: score -= 15
return max(0, min(score, 100))
console.print(Panel.fit(
f"[bold cyan]ð Iris Lite â Quick Inbox Scan[/bold cyan]\
"
f"Scanning last [yellow]{SCAN_LIMIT}[/yellow] emails from [green]{GMAIL_ADDR}[/green]\
"
f"[dim]Lite: 25 emails, 2 drafts â upgrade to Pro for full inbox coverage[/dim]",
border_style="cyan"
))
try:
mail = imaplib.IMAP4_SSL("imap.gmail.com", 993)
mail.login(GMAIL_ADDR, GMAIL_PASS)
mail.select("INBOX", readonly=True)
except Exception as e:
console.print(f"[red]â Login failed: {e}\
Check GMAIL_ADDRESS and GMAIL_APP_PASSWORD.[/red]")
raise SystemExit(1)
_, msg_ids = mail.search(None, "ALL")
all_ids = msg_ids[0].split() if msg_ids and msg_ids[0] else []
recent_ids = list(reversed(all_ids[-SCAN_LIMIT:])) if len(all_ids) > SCAN_LIMIT else list(reversed(all_ids))
emails_data = []
now = datetime.now(timezone.utc)
for uid in recent_ids:
try:
_, raw = mail.fetch(uid, "(RFC822.HEADER FLAGS)")
if not raw or not raw[0]: continue
raw_header = raw[0][1] if isinstance(raw[0], tuple) else raw[0]
msg = email.message_from_bytes(raw_header)
subject = decode_str(msg.get("Subject","(no subject)"))
sender = decode_str(msg.get("From",""))
flags_raw = raw[0][0] if isinstance(raw[0], tuple) else b""
has_replied = b"\\Answered" in flags_raw
try:
sent_dt = parsedate_to_datetime(msg.get("Date",""))
if sent_dt.tzinfo is None: sent_dt = sent_dt.replace(tzinfo=timezone.utc)
age_hours = (now - sent_dt).total_seconds() / 3600
except Exception:
age_hours = 0
body_snippet = ""
try:
_, rb = mail.fetch(uid, "(BODY[TEXT]\x3C0.150>)")
if rb and rb[0] and isinstance(rb[0], tuple) and rb[0][1]:
body_snippet = rb[0][1].decode("utf-8", errors="replace").strip()[:100]
except Exception:
pass
m = re.search(r'"?([^"\x3C]+)"?\s*\x3C([^>]+)>', sender)
sender_name = m.group(1).strip() if m else sender
noise = is_noise(sender, subject)
urgency = score_email(subject, body_snippet, sender, age_hours, has_replied)
emails_data.append({
"subject": subject, "sender": sender_name, "age_hours": age_hours,
"urgency": urgency, "is_noise": noise, "replied": has_replied, "snippet": body_snippet,
})
except Exception:
continue
mail.logout()
actionable = sorted([e for e in emails_data if not e["is_noise"]], key=lambda e: -e["urgency"])
noise_count = sum(1 for e in emails_data if e["is_noise"])
console.print()
tbl = Table(title=f"ð¬ Priority Inbox â Top {min(len(actionable), 15)} of {len(actionable)}",
box=box.ROUNDED, border_style="cyan")
tbl.add_column("Score", width=7, justify="right")
tbl.add_column("From", width=22, style="yellow")
tbl.add_column("Subject", width=42)
tbl.add_column("Age", width=6, style="dim")
for e in actionable[:15]:
sc = "red" if e["urgency"] >= 70 else "yellow" if e["urgency"] >= 50 else "dim"
age = f"{int(e['age_hours'])}h" if e["age_hours"] \x3C 48 else f"{int(e['age_hours']//24)}d"
tbl.add_row(f"[{sc}]{e['urgency']}[/{sc}]", e["sender"][:20], e["subject"][:40], age)
console.print(tbl)
# 2 draft replies (Lite limit)
unreplied = [e for e in actionable if not e["replied"]]
console.print()
for e in unreplied[:DRAFT_LIMIT]:
name = e["sender"].split()[0]
text = (e["subject"] + " " + e["snippet"]).lower()
if any(k in text for k in ["urgent","deadline","asap"]):
body = "Thank you for flagging this â I'll look into it and get back to you shortly."
elif "?" in text:
body = "Thanks for your question. To answer: [your answer here]\
\
Let me know if you need more."
else:
body = "Thanks for reaching out. [Your response here]."
sig = f"\
\
â {YOUR_NAME}" if YOUR_NAME else ""
console.print(Panel(
f"Hi {name},\
\
{body}{sig}",
title=f"[bold]ð Draft â Re: {e['subject'][:45]}[/bold]",
border_style="yellow"
))
console.print()
console.print(Panel(
f"Scanned: [yellow]{len(emails_data)}[/yellow] "
f"Actionable: [cyan]{len(actionable)}[/cyan] "
f"Noise: [dim]{noise_count}[/dim] "
f"Drafts: [yellow]{min(len(unreplied), DRAFT_LIMIT)}/{DRAFT_LIMIT}[/yellow] (Lite limit)\
\
"
f"[bold yellow]ð Want more?[/bold yellow]\
"
f"Iris Pro scans [bold]200 emails[/bold], drafts [bold]every reply[/bold], classifies by category, "
f"and gives you weekly analytics.\
\
"
f"[bold cyan]openclaw skills install iris-pro[/bold cyan]\
"
f"Get your key â [bold]ko-fi.com/occupythemilkyway[/bold]",
title="Summary + Upgrade",
border_style="cyan"
))
安全使用建议
Before installing, make sure you are comfortable giving the skill a Gmail app password and allowing it to read recent inbox subjects and snippets. Consider using a revocable app password and installing Python dependencies in a virtual environment.
功能分析
Type: OpenClaw Skill
Name: iris-lite
Version: 1.0.0
The iris-lite skill is a legitimate email triage tool that scans the last 25 Gmail messages via IMAP to generate urgency scores and draft replies. The code in SKILL.md uses standard Python libraries (imaplib, email) and follows its stated purpose without any evidence of data exfiltration, hidden network calls, or malicious instructions. It correctly uses a read-only IMAP connection and handles sensitive credentials via environment variables.
能力标签
能力评估
Purpose & Capability
The stated purpose is to scan recent Gmail messages and create a priority list, and the provided code matches that purpose, but it necessarily handles sensitive email account access and email snippets.
Instruction Scope
The visible instructions bound the scan to the last 25 inbox messages, use readonly IMAP selection, and do not show sending, deleting, or modifying Gmail messages.
Install Mechanism
The skill is instruction-only but asks the user to install the unpinned PyPI package 'rich' with '--break-system-packages', which can affect the local Python environment.
Credentials
Requiring GMAIL_ADDRESS and GMAIL_APP_PASSWORD is proportionate for a Gmail IMAP skill, but a Gmail app password is still a sensitive credential.
Persistence & Privilege
The provided artifacts show no background persistence, no stored files, no protected-path writes, and the visible code logs out of IMAP after scanning.
如何使用
- 确保已安装 OpenClaw(本地或 Docker 部署)
- 在对话框中输入安装命令:
/install iris-lite - 安装完成后,直接呼叫该 Skill 的名称或使用
/iris-lite触发 - 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
版本历史
v1.0.0
Initial Lite release: 25 email scan, 2 drafts, priority list, Pro upsell
元数据
常见问题
Iris Lite — Inbox Intelligence (Free) 是什么?
Iris Lite â Inbox Intelligence (Free). Scans your last 25 Gmail emails, shows urgency scores and a priority list, and drafts 2 quick replies. A free taste... 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 25 次。
如何安装 Iris Lite — Inbox Intelligence (Free)?
在 OpenClaw 或 Claude Code 对话框中运行命令「/install iris-lite」即可一键安装,无需额外配置。
Iris Lite — Inbox Intelligence (Free) 是免费的吗?
是的,Iris Lite — Inbox Intelligence (Free) 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。
Iris Lite — Inbox Intelligence (Free) 支持哪些平台?
Iris Lite — Inbox Intelligence (Free) 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。
谁开发了 Iris Lite — Inbox Intelligence (Free)?
由 OccupyTheMilkyWay(@occupythemilkyway)开发并维护,当前版本 v1.0.0。
推荐 Skills