aimailsdk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aimail/__init__.py +175 -0
- aimail/_aimail_bootstrap.py +87 -0
- aimail/_resources_release.py +91 -0
- aimail/aimail_base.py +1248 -0
- aimail/aimail_board.py +193 -0
- aimail/aimail_tools.py +1316 -0
- aimail/amail_mcp_server.py +302 -0
- aimail/deer-flow/aimail_inbound.py +171 -0
- aimail/deer-flow/amail_base.py +186 -0
- aimail/deer-flow/manage.py +710 -0
- aimail/gateway_api.py +177 -0
- aimail/hermes/aimail_hermes.py +1023 -0
- aimail/hermes/ensure_config.py +148 -0
- aimail/hermes/patch_profiles.py +350 -0
- aimail/hermes/patch_webhook.py +624 -0
- aimail/hermes/register_profiles.py +116 -0
- aimail/hermes/toolsets.py +132 -0
- aimail/install.py +433 -0
- aimail/openclaw/amail_base.py +353 -0
- aimail/resources/board/role_prompt_en/common.md +33 -0
- aimail/resources/board/role_prompt_en/orchestrator.md +43 -0
- aimail/resources/board/role_prompt_en/role_calibrator.md +27 -0
- aimail/resources/board/role_prompt_en/verifier.md +47 -0
- aimail/resources/board/role_prompt_en/whoami.md +24 -0
- aimail/resources/board/role_prompt_en/worker.md +48 -0
- aimail/resources/board/role_prompt_zh/common.md +33 -0
- aimail/resources/board/role_prompt_zh/orchestrator.md +43 -0
- aimail/resources/board/role_prompt_zh/role_calibrator.md +27 -0
- aimail/resources/board/role_prompt_zh/verifier.md +47 -0
- aimail/resources/board/role_prompt_zh/whoami.md +24 -0
- aimail/resources/board/role_prompt_zh/worker.md +48 -0
- aimail/resources/board/role_soul_en/Orchestrator.md +24 -0
- aimail/resources/board/role_soul_en/Owner.md +23 -0
- aimail/resources/board/role_soul_en/Verifier.md +23 -0
- aimail/resources/board/role_soul_en/Worker.md +23 -0
- aimail/resources/board/role_soul_zh/Orchestrator.md +24 -0
- aimail/resources/board/role_soul_zh/Owner.md +25 -0
- aimail/resources/board/role_soul_zh/Verifier.md +23 -0
- aimail/resources/board/role_soul_zh/Worker.md +23 -0
- aimail/resources/skills/DESCRIPTION.md +3 -0
- aimail/resources/skills/SKILL.md +231 -0
- aimailsdk-0.1.0.dist-info/METADATA +125 -0
- aimailsdk-0.1.0.dist-info/RECORD +45 -0
- aimailsdk-0.1.0.dist-info/WHEEL +4 -0
- aimailsdk-0.1.0.dist-info/licenses/COPYING +674 -0
aimail/__init__.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""aimail — AIMail runtime SDK (Python). Unified public entry point.
|
|
3
|
+
|
|
4
|
+
This package is the single public surface for integrating aimail: both the
|
|
5
|
+
platform hosts (Hermes / OpenClaw / DeerFlow) and third-party agents should
|
|
6
|
+
``import aimail`` and use the re-exported API, rather than each doing its own
|
|
7
|
+
``sys.path`` bootstrap + flat-script imports.
|
|
8
|
+
|
|
9
|
+
Unified usage (recommended — hosts AND third parties):
|
|
10
|
+
|
|
11
|
+
import aimail
|
|
12
|
+
aimail.send_mail(to="x@example.com", subject="hi", body="hello")
|
|
13
|
+
client = aimail.GatewayClient(aimail.agent_email(), api_key="...")
|
|
14
|
+
aimail.manage_contacts(action="list")
|
|
15
|
+
|
|
16
|
+
The runtime core is implemented as flat scripts (``aimail_base.py``,
|
|
17
|
+
``aimail_tools.py``, ...) that import each other by top-level name, so they
|
|
18
|
+
must sit on ``sys.path``. ``import aimail`` does that bootstrap for you and
|
|
19
|
+
re-exports the curated public API below. The legacy flat path (insert
|
|
20
|
+
``aimail.core_dir()`` on ``sys.path`` then ``import aimail_tools``) still
|
|
21
|
+
works and is what the host adapters' bootstrap relies on — it is preserved,
|
|
22
|
+
not replaced.
|
|
23
|
+
|
|
24
|
+
Module layout inside the wheel mirrors the repository ``pysdk/`` directory
|
|
25
|
+
(single source of truth), so the runtime's flat imports keep working:
|
|
26
|
+
|
|
27
|
+
aimail/
|
|
28
|
+
aimail_base.py shared core (platform-agnostic)
|
|
29
|
+
aimail_tools.py shared core (GatewayClient / send_mail)
|
|
30
|
+
aimail_board.py shared core (A2A board)
|
|
31
|
+
gateway_api.py standard amail API client
|
|
32
|
+
amail_mcp_server.py platform-agnostic MCP server (stdio JSON-RPC)
|
|
33
|
+
_aimail_bootstrap.py location-agnostic sys.path bootstrap (runtime glue)
|
|
34
|
+
install.py self-contained install/uninstall entry
|
|
35
|
+
_resources_release.py board/skills resource release (install-time)
|
|
36
|
+
hermes/ Hermes adapter (6 modules, host-injected registry)
|
|
37
|
+
openclaw/ OpenClaw adapter (amail_base)
|
|
38
|
+
deer-flow/ DeerFlow adapter (inbound router / manage / base)
|
|
39
|
+
resources/
|
|
40
|
+
skills/ aimail SKILL.md + DESCRIPTION.md
|
|
41
|
+
board/role_prompt_en|zh, role_soul_en|zh board templates
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
import os as _os
|
|
45
|
+
import sys as _sys
|
|
46
|
+
|
|
47
|
+
__version__ = "0.1.0"
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"__version__",
|
|
51
|
+
# path helpers
|
|
52
|
+
"root",
|
|
53
|
+
"core_dir",
|
|
54
|
+
"skills_dir",
|
|
55
|
+
"board_role_prompt_dir",
|
|
56
|
+
"mcp_server_path",
|
|
57
|
+
# identity
|
|
58
|
+
"agent_email",
|
|
59
|
+
# outbound
|
|
60
|
+
"send_mail",
|
|
61
|
+
"GatewayClient",
|
|
62
|
+
# contacts
|
|
63
|
+
"manage_contacts",
|
|
64
|
+
"contact_profile",
|
|
65
|
+
"set_contact_profile",
|
|
66
|
+
# email
|
|
67
|
+
"email_summary",
|
|
68
|
+
"set_email_summary",
|
|
69
|
+
"store_inbound_message",
|
|
70
|
+
"render_message",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ── Path helpers ──────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
def root() -> str:
|
|
77
|
+
"""Absolute path of this package directory."""
|
|
78
|
+
return _os.path.dirname(_os.path.abspath(__file__))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def core_dir() -> str:
|
|
82
|
+
"""Directory holding the flat-script core modules (aimail_base.py ...).
|
|
83
|
+
|
|
84
|
+
Insert this on ``sys.path`` to enable the legacy flat imports::
|
|
85
|
+
|
|
86
|
+
import sys, aimail
|
|
87
|
+
sys.path.insert(0, aimail.core_dir())
|
|
88
|
+
import aimail_base
|
|
89
|
+
|
|
90
|
+
In an installed wheel the flat modules are force-included into the package
|
|
91
|
+
dir, so this equals ``root()``. In a raw repo checkout pysdk/ is the
|
|
92
|
+
single source of truth and mirrors the wheel layout 1:1, so this equals
|
|
93
|
+
``root()`` there as well. Either way, the returned dir is guaranteed to
|
|
94
|
+
contain ``aimail_base.py``.
|
|
95
|
+
"""
|
|
96
|
+
return _resolve_core_dir()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def skills_dir() -> str:
|
|
100
|
+
"""aimail SKILL.md / DESCRIPTION.md directory."""
|
|
101
|
+
return _os.path.join(root(), "resources", "skills")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def board_role_prompt_dir() -> str:
|
|
105
|
+
"""Board role prompt templates (English, default) directory."""
|
|
106
|
+
return _os.path.join(root(), "resources", "board", "role_prompt_en")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def mcp_server_path() -> str:
|
|
110
|
+
"""Path of the platform-agnostic MCP server entry script."""
|
|
111
|
+
return _os.path.join(root(), "amail_mcp_server.py")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ── Unified core bootstrap + re-export ────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
def _resolve_core_dir() -> str:
|
|
117
|
+
"""Locate the directory containing the flat core modules.
|
|
118
|
+
|
|
119
|
+
Resolution order (most specific first):
|
|
120
|
+
1. installed layout / repo pysdk — flat modules sit directly in the
|
|
121
|
+
package dir (pysdk/ is the single source of truth; the wheel
|
|
122
|
+
mirrors it 1:1 inside site-packages/aimail/)
|
|
123
|
+
2. AIMAIL_RUNTIME_DIR env override (explicit)
|
|
124
|
+
|
|
125
|
+
Returns the first candidate that contains ``aimail_base.py``, falling
|
|
126
|
+
back to ``root()`` (best effort) if none match.
|
|
127
|
+
"""
|
|
128
|
+
here = root()
|
|
129
|
+
# 1. installed / repo pysdk layout: core modules are flat siblings here.
|
|
130
|
+
if _os.path.isfile(_os.path.join(here, "aimail_base.py")):
|
|
131
|
+
return here
|
|
132
|
+
# 2. explicit override.
|
|
133
|
+
env = _os.environ.get("AIMAIL_RUNTIME_DIR", "").strip()
|
|
134
|
+
if env:
|
|
135
|
+
d = _os.path.expanduser(env)
|
|
136
|
+
if _os.path.isfile(_os.path.join(d, "aimail_base.py")):
|
|
137
|
+
return d
|
|
138
|
+
return here
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _import_core():
|
|
142
|
+
"""Idempotently put the core dir on sys.path and import the flat core
|
|
143
|
+
modules as top-level modules (the same module objects the host adapters
|
|
144
|
+
use — no dual-module identity problem). Returns (base, tools, board, api).
|
|
145
|
+
"""
|
|
146
|
+
core = _resolve_core_dir()
|
|
147
|
+
if core not in _sys.path:
|
|
148
|
+
_sys.path.insert(0, core)
|
|
149
|
+
# Reuse the single-source runtime bootstrap so any entry-point layout
|
|
150
|
+
# (adapter subdirs, env override) is handled consistently.
|
|
151
|
+
try:
|
|
152
|
+
import _aimail_bootstrap as _b
|
|
153
|
+
_b.ensure_core(core)
|
|
154
|
+
except Exception:
|
|
155
|
+
pass # core already on path from the insert above; bootstrap is glue.
|
|
156
|
+
import aimail_base as _base
|
|
157
|
+
import aimail_tools as _tools
|
|
158
|
+
import aimail_board as _board
|
|
159
|
+
import gateway_api as _api
|
|
160
|
+
return _base, _tools, _board, _api
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
_base, _tools, _board, _api = _import_core()
|
|
164
|
+
|
|
165
|
+
# Re-export the curated public API (single public entry point).
|
|
166
|
+
send_mail = _tools.send_mail
|
|
167
|
+
GatewayClient = _tools._GatewayClient
|
|
168
|
+
agent_email = _tools._resolve_agent_email
|
|
169
|
+
manage_contacts = _tools.manage_contacts
|
|
170
|
+
contact_profile = _tools.contact_profile
|
|
171
|
+
set_contact_profile = _tools.set_contact_profile
|
|
172
|
+
email_summary = _tools.email_summary
|
|
173
|
+
set_email_summary = _tools.set_email_summary
|
|
174
|
+
store_inbound_message = _tools.store_inbound_message
|
|
175
|
+
render_message = _base.render_message
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""_aimail_bootstrap — runtime core location resolution (single source).
|
|
3
|
+
|
|
4
|
+
Runtime modules are flat-script style (``import aimail_base``) and must
|
|
5
|
+
run from any location: the source repository (tools/), an installed bundle
|
|
6
|
+
(~/.aimail/... provisioner copies), or site-packages (pip aimail).
|
|
7
|
+
This module resolves the directory containing the shared core modules
|
|
8
|
+
(aimail_base.py / aimail_tools.py / aimail_board.py /
|
|
9
|
+
gateway_api.py) and puts it — plus the calling entry point's own
|
|
10
|
+
directory, for sibling imports — on sys.path.
|
|
11
|
+
|
|
12
|
+
Resolution order (most specific first):
|
|
13
|
+
1. AIMAIL_RUNTIME_DIR env var (explicit override)
|
|
14
|
+
2. the directory above the entry point (bundle / site-packages layout:
|
|
15
|
+
adapter dirs openclaw|deer-flow sit directly under the core dir)
|
|
16
|
+
3. the entry point's own directory (flat layout: entry lives in core)
|
|
17
|
+
|
|
18
|
+
The repo checkout is deliberately NOT a path fallback: installed runtimes
|
|
19
|
+
must be self-contained (bundles) or installed (pip). Development from the
|
|
20
|
+
repo works via case 3, which is local by definition.
|
|
21
|
+
|
|
22
|
+
Entry points use it like:
|
|
23
|
+
|
|
24
|
+
def _amail_bootstrap():
|
|
25
|
+
import importlib.util as _ilu
|
|
26
|
+
_here = os.path.dirname(os.path.abspath(__file__))
|
|
27
|
+
for _d in (_here, os.path.dirname(_here)):
|
|
28
|
+
_p = os.path.join(_d, "_aimail_bootstrap.py")
|
|
29
|
+
if os.path.isfile(_p):
|
|
30
|
+
_spec = _ilu.spec_from_file_location("_aimail_bootstrap", _p)
|
|
31
|
+
_m = _ilu.module_from_spec(_spec)
|
|
32
|
+
sys.modules["_aimail_bootstrap"] = _m
|
|
33
|
+
_spec.loader.exec_module(_m)
|
|
34
|
+
_m.ensure_core(_here)
|
|
35
|
+
return
|
|
36
|
+
raise ImportError("aimail runtime core not found — set AIMAIL_RUNTIME_DIR")
|
|
37
|
+
|
|
38
|
+
_amail_bootstrap()
|
|
39
|
+
|
|
40
|
+
import aimail_base as _base # noqa: E402
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
import os
|
|
44
|
+
import sys
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _core_ok(d):
|
|
48
|
+
return bool(d) and os.path.isfile(os.path.join(d, "aimail_base.py"))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def ensure_core(self_dir=None):
|
|
52
|
+
"""Put the core module dir (and the entry point's own dir) on sys.path.
|
|
53
|
+
|
|
54
|
+
Returns the resolved core dir, or None if no candidate exists.
|
|
55
|
+
Idempotent: repeated calls do not duplicate entries.
|
|
56
|
+
"""
|
|
57
|
+
self_dir = os.path.abspath(self_dir or os.path.dirname(os.path.abspath(__file__)))
|
|
58
|
+
here = self_dir
|
|
59
|
+
|
|
60
|
+
core = None
|
|
61
|
+
# 1. explicit override
|
|
62
|
+
env = os.environ.get("AIMAIL_RUNTIME_DIR", "").strip()
|
|
63
|
+
if env:
|
|
64
|
+
d = os.path.expanduser(env)
|
|
65
|
+
if _core_ok(d):
|
|
66
|
+
core = d
|
|
67
|
+
# 2. parent dir (adapter layout: core sits above openclaw|deer-flow)
|
|
68
|
+
if core is None and os.path.basename(here) in ("openclaw", "deer-flow", "hermes"):
|
|
69
|
+
d = os.path.dirname(here)
|
|
70
|
+
if _core_ok(d):
|
|
71
|
+
core = d
|
|
72
|
+
# 3. own dir (flat layout: entry point lives in the core dir)
|
|
73
|
+
if core is None and _core_ok(here):
|
|
74
|
+
core = here
|
|
75
|
+
|
|
76
|
+
if core is None:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
# adapter dir first (sibling imports), then core; dedupe both
|
|
80
|
+
for d in (here, core):
|
|
81
|
+
if d not in sys.path:
|
|
82
|
+
sys.path.insert(0, d)
|
|
83
|
+
return core
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
print(ensure_core())
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""_resources_release — SDK 资源的本地配置目录展开(python 版)。
|
|
3
|
+
|
|
4
|
+
架构:资源(role_prompt/role_soul × en/zh + skills)是公共种子,随 SDK
|
|
5
|
+
分发;安装/启动时释放到 ~/.aimail/systems/{sid}/board/ 供运行时读取
|
|
6
|
+
(pysdk 与 tssdk 运行时同路径)。只补缺失/更新的文件,绝不覆盖用户已在
|
|
7
|
+
配置目录个性化过的内容。
|
|
8
|
+
|
|
9
|
+
与 tssdk mail-core release-resources.ts、cli 旧 release-board-resources.sh
|
|
10
|
+
同语义;本模块为 pip/repo 双形态的 python 实现。
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import shutil
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
# 双形态自举:core 目录(含本模块与 aimail_base.py)即 _CORE
|
|
19
|
+
_CORE = os.path.dirname(os.path.abspath(__file__))
|
|
20
|
+
|
|
21
|
+
# 源子目录(包内 resources/board) → 配置目录目标子目录
|
|
22
|
+
_DIR_MAP = (
|
|
23
|
+
("role_prompt_en", "role_prompt"),
|
|
24
|
+
("role_prompt_zh", "role_prompt_zh"),
|
|
25
|
+
("role_soul_en", "role_soul"),
|
|
26
|
+
("role_soul_zh", "role_soul_zh"),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
_AIMAIL_HOME = os.path.join(os.path.expanduser("~"), ".aimail")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def agentmail_home() -> str:
|
|
33
|
+
return os.environ.get("AIMAIL_HOME", "") or _AIMAIL_HOME
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def resources_board_dir() -> str:
|
|
37
|
+
"""包内 resources/board 目录(repo:pysdk/resources/board;pip:aimail/resources/board)。"""
|
|
38
|
+
return os.path.join(_CORE, "resources", "board")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def release_resources(system_id: str, board_root: str | None = None) -> dict:
|
|
42
|
+
"""释放 board 资源到 ~/.aimail/systems/{sid}/board/(幂等)。"""
|
|
43
|
+
src_root = board_root or resources_board_dir()
|
|
44
|
+
board_dir = os.path.join(agentmail_home(), "systems", system_id, "board")
|
|
45
|
+
copied = 0
|
|
46
|
+
skipped = 0
|
|
47
|
+
for src_name, dst_name in _DIR_MAP:
|
|
48
|
+
src_dir = os.path.join(src_root, src_name)
|
|
49
|
+
if not os.path.isdir(src_dir):
|
|
50
|
+
continue
|
|
51
|
+
dst_dir = os.path.join(board_dir, dst_name)
|
|
52
|
+
os.makedirs(dst_dir, exist_ok=True)
|
|
53
|
+
for fname in sorted(os.listdir(src_dir)):
|
|
54
|
+
if not fname.endswith(".md"):
|
|
55
|
+
continue
|
|
56
|
+
src = os.path.join(src_dir, fname)
|
|
57
|
+
dst = os.path.join(dst_dir, fname)
|
|
58
|
+
if os.path.exists(dst):
|
|
59
|
+
if os.path.getmtime(dst) >= os.path.getmtime(src):
|
|
60
|
+
skipped += 1
|
|
61
|
+
continue
|
|
62
|
+
shutil.copy2(src, dst)
|
|
63
|
+
copied += 1
|
|
64
|
+
return {"board_dir": board_dir, "copied": copied, "skipped": skipped}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def release_all_systems(board_root: str | None = None) -> list:
|
|
68
|
+
"""对 ~/.aimail/systems/ 下全部已有系统展开(单系统机器亦覆盖)。"""
|
|
69
|
+
systems_root = os.path.join(agentmail_home(), "systems")
|
|
70
|
+
if not os.path.isdir(systems_root):
|
|
71
|
+
return []
|
|
72
|
+
out = []
|
|
73
|
+
for ent in sorted(os.listdir(systems_root)):
|
|
74
|
+
p = os.path.join(systems_root, ent)
|
|
75
|
+
if os.path.isdir(p):
|
|
76
|
+
try:
|
|
77
|
+
out.append(release_resources(ent, board_root))
|
|
78
|
+
except Exception: # noqa: BLE001
|
|
79
|
+
pass
|
|
80
|
+
return out
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
# 便捷:python _resources_release.py [system_id...]
|
|
85
|
+
sids = sys.argv[1:] or sorted(
|
|
86
|
+
d for d in os.listdir(os.path.join(agentmail_home(), "systems"))
|
|
87
|
+
if os.path.isdir(os.path.join(agentmail_home(), "systems", d))
|
|
88
|
+
) if os.path.isdir(os.path.join(agentmail_home(), "systems")) else []
|
|
89
|
+
for sid in sids:
|
|
90
|
+
r = release_resources(sid)
|
|
91
|
+
print(f"{sid}: {r['board_dir']} (copied {r['copied']}, kept {r['skipped']})")
|