dsh-email 0.6.2 → 0.8.0
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.
- package/README.en.md +31 -11
- package/README.md +185 -166
- package/lib/index.d.ts +5 -0
- package/lib/index.js +70 -3
- package/lib/mail-client.d.ts +2 -2
- package/lib/mail-client.js +23 -10
- package/lib/parse.js +2 -2
- package/lib/types.d.ts +4 -0
- package/package.json +73 -71
package/README.en.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# dsh-email
|
|
2
2
|
|
|
3
|
+
[](https://awesome-dsh-plugin.com)
|
|
4
|
+
|
|
3
5
|

|
|
4
6
|
|
|
5
7
|
Email tools for DeepSeek Harness: let the agent **check the inbox, read mail, search mail, send mail on your behalf, and send/receive attachments** through standard IMAP/SMTP — with one-line presets for QQ / 163 / 126 / Sina / Aliyun / Gmail / Outlook / iCloud. Pure plugin implementation, zero core changes, works out of the box.
|
|
@@ -12,7 +14,7 @@ Pure Node, **cross-platform** (one codebase for Windows / macOS / Linux), no she
|
|
|
12
14
|
|---|---|
|
|
13
15
|
| `email_list` | List the newest mail in a folder (unread filter, pagination, summaries only, no body) |
|
|
14
16
|
| `email_read` | Read one message's full text by uid (HTML auto-converted to plain text, oversized bodies truncated) |
|
|
15
|
-
| `email_search` | Search sender/recipient/
|
|
17
|
+
| `email_search` | Search subject/sender/recipient/CC by keyword (server-side subject/from/to/cc); with no results, falls back to a body scan of the most recent 30 messages by default (including to/cc) |
|
|
16
18
|
| `email_send` | Send mail on your behalf (attachments supported). **Prompts for confirmation before sending by default**, showing recipients, subject and attachment count; only sends after you approve |
|
|
17
19
|
| `email_folders` | List the mailbox folders (INBOX/Sent/Junk/custom…); feed the `path` to other tools |
|
|
18
20
|
| `email_attachment` | Download an attachment by index (saved to the session workspace by default so the model can read it directly; size capped by `maxAttachmentBytes`) |
|
|
@@ -21,6 +23,18 @@ Example:
|
|
|
21
23
|
|
|
22
24
|
> Check the 10 newest unread messages in my QQ mailbox and list the ones that need a reply.
|
|
23
25
|
|
|
26
|
+
### v0.6.2 improvements
|
|
27
|
+
|
|
28
|
+
- Server-side search now covers `cc`, so the search truly spans subject / sender / recipients / CC.
|
|
29
|
+
- The client-side body fallback also matches `to` and `cc`; one malformed message no longer aborts the whole batch scan.
|
|
30
|
+
- Mail lists are sorted by UID descending to guarantee newest-first results.
|
|
31
|
+
- `email_send` validates `attachments` as a string array of file paths and trims each path.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Compatibility
|
|
35
|
+
|
|
36
|
+
Verified against `@deepseek-ai/dsh@0.1.1-rc.2` on 2026-08-26. Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
|
|
37
|
+
|
|
24
38
|
## Installation
|
|
25
39
|
|
|
26
40
|
```sh
|
|
@@ -38,6 +52,15 @@ After installing, restart `dsh web`. The plugin ships with an empty config and *
|
|
|
38
52
|
|
|
39
53
|
Values saved in the settings page live in the `dsh-email` namespace of `settings.yaml` and override the YAML default-account config; password fields are marked secret (they never appear in exports or diagnostics).
|
|
40
54
|
|
|
55
|
+
## Uninstall
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
dsh plugin --profile web remove dsh-email
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then restart the web service. To clean up fully, also remove the plugin entry from your profile `cordis.patch.yml` if you overrode it.
|
|
62
|
+
|
|
63
|
+
|
|
41
64
|
## Configuration
|
|
42
65
|
|
|
43
66
|
In your profile's `cordis.patch.yml` (under `$DSH_HOME/profiles/<name>/`), override the `tool-email` line, then restart:
|
|
@@ -105,24 +128,21 @@ Every provider requires an authorization code / app-specific password instead of
|
|
|
105
128
|
- When the session is in **Full Access** mode, the harness approval policy is never (no confirmation dialogs) — `email_send` is **blocked with a clear hint**. Two ways out: ① switch the access mode back to Read Only / Write; ② turn off `sendApproval` (uncheck "Confirm before sending" in the settings page), explicitly declaring you accept the risk.
|
|
106
129
|
- This plugin performs no outbound telemetry; credentials are used in memory only to connect to your mail servers.
|
|
107
130
|
|
|
108
|
-
## Known limitations
|
|
131
|
+
## Known limitations
|
|
109
132
|
|
|
110
|
-
- **
|
|
111
|
-
- **
|
|
112
|
-
- **
|
|
113
|
-
- **
|
|
114
|
-
- **Body search uses client-side fallback**: most servers (e.g. QQ) have unreliable IMAP `TEXT`/`HEADER` search, so the server side only searches subject/sender/recipient; with no results it falls back to a body scan of the most recent `bodySearchLimit` messages (slower; disable with `bodySearchFallback`).
|
|
115
|
-
- **Password storage form**: the authorization code saved in the settings page is written in plaintext to the local `settings.yaml` (the schema's secret mark only keeps it out of logs/exports/diagnostics; no disk encryption). Don't hand settings.yaml to untrusted people.
|
|
116
|
-
- **Settings page vs. plugin-set changes**: saving in the settings page takes effect **immediately** (live), no restart needed; but upgrading or adding/removing plugins (composition-tree changes) still requires restarting `dsh web`.
|
|
133
|
+
- **No OAuth2**: enterprise environments that force OAuth (some M365 / Google Workspace) aren't usable yet; use an app-specific password / authorization code instead.
|
|
134
|
+
- **Body search**: the server side only searches subject / from / to / cc. Most servers (e.g. QQ) have unreliable IMAP `TEXT` / `HEADER` search, so with no results it falls back to a body scan of the most recent `bodySearchLimit` messages (slower; disable with `bodySearchFallback`).
|
|
135
|
+
- **Attachments**: inline images aren't downloadable separately yet; a failed attachment match errors instead of downloading the wrong file (safe default).
|
|
136
|
+
- **Password storage**: the authorization code saved in the settings page is written in plaintext to the local `settings.yaml` (the secret mark only keeps it out of logs / exports / diagnostics; no disk encryption). Don't hand `settings.yaml` to untrusted people.
|
|
117
137
|
|
|
118
138
|
## Development
|
|
119
139
|
|
|
120
140
|
```sh
|
|
121
141
|
pnpm install
|
|
122
142
|
pnpm run build # tsc → lib/
|
|
123
|
-
pnpm test # 构建 + node --test(配置/解析/注册与审批门,
|
|
143
|
+
pnpm test # 构建 + node --test(配置/解析/注册与审批门,44 个用例,无需真实邮箱)
|
|
124
144
|
```
|
|
125
145
|
|
|
126
146
|
## License
|
|
127
147
|
|
|
128
|
-
MIT. This is a community plugin, not affiliated with DeepSeek; `@deepseek-ai/*` is an officially reserved namespace.
|
|
148
|
+
MIT. This is a community plugin, not affiliated with DeepSeek; `@deepseek-ai/*` is an officially reserved namespace.
|
package/README.md
CHANGED
|
@@ -1,166 +1,185 @@
|
|
|
1
|
-
[English](README.en.md)
|
|
2
|
-
|
|
3
|
-
# dsh-email
|
|
4
|
-
|
|
5
|
-
> **给 agent 一个邮箱**:八个国内邮箱预设开箱即用,收发/搜索/附件下载全搞定。
|
|
6
|
-
|
|
7
|
-
   
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
|
24
|
-
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
1.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
|
115
|
-
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
|
|
1
|
+
[English](README.en.md)
|
|
2
|
+
|
|
3
|
+
# dsh-email
|
|
4
|
+
|
|
5
|
+
> **给 agent 一个邮箱**:八个国内邮箱预设开箱即用,收发/搜索/附件下载全搞定。
|
|
6
|
+
|
|
7
|
+
   
|
|
8
|
+
|
|
9
|
+
[](https://awesome-dsh-plugin.com)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
DeepSeek Harness 邮件工具插件:让 agent 能**查收件箱、读邮件、搜邮件、代发邮件、收发附件**。纯插件实现,零核心改动,安装即可用。
|
|
16
|
+
|
|
17
|
+
Email tools for DeepSeek Harness: list, read, search and send mail through standard IMAP/SMTP — with one-line presets for QQ / 163 / 126 / Sina / Aliyun / Gmail / Outlook / iCloud.
|
|
18
|
+
|
|
19
|
+
纯 Node 实现,**全平台通用**(Windows / macOS / Linux 同一份代码),不依赖 shell、无原生二进制。
|
|
20
|
+
|
|
21
|
+
## 工具一览
|
|
22
|
+
|
|
23
|
+
| 工具 | 作用 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `email_list` | 列出文件夹里最新的邮件(未读过滤、分页、只看摘要不带正文) |
|
|
26
|
+
| `email_read` | 按 uid 读取一封邮件的全文(HTML 邮件自动转纯文本,超长截断) |
|
|
27
|
+
| `email_search` | 按关键词搜索主题/发件人/收件人/抄送(服务器端 subject/from/to/cc);无结果时默认回退到最近 30 封的正文扫描(含 to/cc) |
|
|
28
|
+
| `email_send` | 代发邮件(支持带附件)。**默认发信前会弹确认**,显示收件人、主题和附件数,由你批准后才发出 |
|
|
29
|
+
| `email_folders` | 列出邮箱的文件夹(INBOX/已发送/垃圾邮件/自定义…),拿 path 喂给其他工具 |
|
|
30
|
+
| `email_attachment` | 按序号下载邮件附件(默认存到会话工作区,模型可直接读取;大小受 maxAttachmentBytes 限制) |
|
|
31
|
+
|
|
32
|
+
示例对话:
|
|
33
|
+
|
|
34
|
+
> 帮我看下 QQ 邮箱最新的 10 封未读,把要回复的列出来。
|
|
35
|
+
|
|
36
|
+
### v0.6.2 优化
|
|
37
|
+
|
|
38
|
+
- 服务器端搜索补齐 `cc`,搜索范围真正覆盖主题 / 发件人 / 收件人 / 抄送。
|
|
39
|
+
- 正文回退扫描的匹配范围也加入 `to` / `cc`;单封邮件解析失败不会中断整批扫描。
|
|
40
|
+
- 邮件列表结果强制按 UID 降序,保证「最新在前」。
|
|
41
|
+
- `email_send` 的附件参数严格校验为路径字符串数组,并做 trim。
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## 兼容性
|
|
45
|
+
|
|
46
|
+
在 `@deepseek-ai/dsh@0.1.1-rc.2` 上验证(2026-08-26)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
|
|
47
|
+
|
|
48
|
+
## 安装
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
dsh plugin --profile web add dsh-email
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
(或从 GitHub 安装:`dsh plugin --profile web add github:你的账号/dsh-email#<commit>`,随后按提示在 profile 的 `pnpm-workspace.yaml` 里授权 `prepare` 构建。)
|
|
55
|
+
|
|
56
|
+
装好后重启 `dsh web`。插件自带空配置,**不会弄崩启动**;配置前调用任何 email 工具都会返回明确的配置提示。
|
|
57
|
+
|
|
58
|
+
**配置方式有两种(任选其一):**
|
|
59
|
+
|
|
60
|
+
1. **网页设置(推荐)**:重启后打开 **设置 → 邮件 (dsh-email)**,表单里填邮箱地址和授权码,点「保存并应用」,还带「测试连接」按钮。零 YAML、零重启。
|
|
61
|
+
2. **YAML**:按下面的 cordis.patch.yml 模板手写;设置页的「多账号(高级,YAML)」文本框也能填账号映射(覆盖 YAML 里的 accounts)。
|
|
62
|
+
|
|
63
|
+
设置页保存的值存在 `settings.yaml` 的 `dsh-email` 命名空间里,覆盖 YAML 的默认账号配置;密码字段标记为 secret(不会出现在任何导出/诊断里)。
|
|
64
|
+
|
|
65
|
+
## 卸载
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
dsh plugin --profile web remove dsh-email
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
卸载后重启 Web 服务。如需彻底清理,可再手动删除自己 profile `cordis.patch.yml` 中覆盖的插件行。
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## 配置
|
|
75
|
+
|
|
76
|
+
在你 profile 的 `cordis.patch.yml` 里覆盖 `tool-email` 行(在 `$DSH_HOME/profiles/<name>/` 下),然后重启:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
- id: tool-email
|
|
80
|
+
config:
|
|
81
|
+
provider: qq # qq | 163 | 126 | sina | aliyun | gmail | outlook | icloud
|
|
82
|
+
user: you@qq.com
|
|
83
|
+
password: 你的授权码 # 强烈建议改用环境变量 DSH_EMAIL_PASSWORD,见下
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
不需要预设?手填任意 IMAP/SMTP 服务器即可:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
- id: tool-email
|
|
90
|
+
config:
|
|
91
|
+
user: you@corp.example
|
|
92
|
+
password: 你的授权码
|
|
93
|
+
imap: { host: imap.corp.example, port: 993, secure: true }
|
|
94
|
+
smtp: { host: smtp.corp.example, port: 465, secure: true }
|
|
95
|
+
inboxFolder: INBOX
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
多账号:一个 `tool-email` 行可以配多个邮箱,工具调用时用 `account` 参数选择:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
- id: tool-email
|
|
102
|
+
config:
|
|
103
|
+
accounts:
|
|
104
|
+
work: { provider: qq, user: work@qq.com, password: 授权码1 }
|
|
105
|
+
home: { provider: '163', user: home@163.com, password: 授权码2 }
|
|
106
|
+
defaultAccount: work # 省略 account 参数时用这个
|
|
107
|
+
downloadDir: E:/attachments # 可选,默认 $DSH_HOME/email-downloads
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
顶层的 `provider`/`user`/`password`/`imap`/`smtp`/`inboxFolder` 仍然可用,作为各账号的共享默认值(v0.1 单账号写法完全兼容)。
|
|
111
|
+
|
|
112
|
+
### 常用邮箱预设
|
|
113
|
+
|
|
114
|
+
| provider | IMAP | SMTP |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `qq` | imap.qq.com:993 (SSL) | smtp.qq.com:465 (SSL) |
|
|
117
|
+
| `163` | imap.163.com:993 | smtp.163.com:465 |
|
|
118
|
+
| `126` | imap.126.com:993 | smtp.126.com:465 |
|
|
119
|
+
| `sina` | imap.sina.com:993 | smtp.sina.com:465 |
|
|
120
|
+
| `aliyun` | imap.aliyun.com:993 | smtp.aliyun.com:465 |
|
|
121
|
+
| `gmail` | imap.gmail.com:993 | smtp.gmail.com:465 |
|
|
122
|
+
| `outlook` | outlook.office365.com:993 | smtp.office365.com:587 (STARTTLS) |
|
|
123
|
+
| `icloud` | imap.mail.me.com:993 | smtp.mail.me.com:587 (STARTTLS) |
|
|
124
|
+
|
|
125
|
+
### 完整配置项
|
|
126
|
+
|
|
127
|
+
| 字段 | 默认 | 说明 |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `provider` | 无 | 预设名,自动填 imap/smtp 地址;显式写的 host/port/secure 优先 |
|
|
130
|
+
| `user` | 必填 | 登录邮箱地址 |
|
|
131
|
+
| `password` | 必填* | 授权码/应用专用密码;*也可用环境变量 `DSH_EMAIL_PASSWORD` |
|
|
132
|
+
| `imap.host/port/secure` | 按预设 | 收信服务器(另有 connectionTimeoutMs/socketTimeoutMs 可调超时) |
|
|
133
|
+
| `smtp.host/port/secure` | 按预设 | 发信服务器 |
|
|
134
|
+
| `inboxFolder` | `INBOX` | 收发工具默认使用的文件夹 |
|
|
135
|
+
| `sendApproval` | `true` | 发信前弹确认(强烈建议保留) |
|
|
136
|
+
| `maxBodyChars` | `20000` | email_read 正文截断上限(1000–200000) |
|
|
137
|
+
| `accounts` | 无 | 具名账号表;账号级字段覆盖顶层简写 |
|
|
138
|
+
| `accountsYaml` | 无 | 设置页「多账号(高级)」文本框的 YAML 文本;非空时覆盖 accounts |
|
|
139
|
+
| `defaultAccount` | 单账号时自动 | 工具省略 account 参数时使用的账号(多账号必填) |
|
|
140
|
+
| `downloadDir` | 会话工作区下 .dsh-email-downloads(回退 $DSH_HOME/email-downloads) | email_attachment 的落盘目录;显式设置后固定 |
|
|
141
|
+
| `maxAttachmentBytes` | 20 MiB | 单个附件与附件总大小上限(1024–512 MiB) |
|
|
142
|
+
| `idleTimeoutMs` | `60000` | IMAP 空闲连接回收时间(连接复用,连续操作更快) |
|
|
143
|
+
| `bodySearchFallback` | `true` | 服务器搜索无结果时,回退到客户端扫描最近邮件的正文 |
|
|
144
|
+
| `bodySearchLimit` | `30` | 正文回退扫描的邮件数量(5-200) |
|
|
145
|
+
|
|
146
|
+
## 第一步:拿到授权码
|
|
147
|
+
|
|
148
|
+
各邮箱都要求用「授权码/应用专用密码」而不是登录密码:
|
|
149
|
+
|
|
150
|
+
- **QQ 邮箱**:设置 → 账户 → 开启 IMAP/SMTP 服务 → 生成授权码
|
|
151
|
+
- **163/126**:设置 → POP3/SMTP/IMAP → 开启 → 新增授权码
|
|
152
|
+
- **Gmail**:开启两步验证 → 安全 → 应用专用密码
|
|
153
|
+
- **Outlook**:Microsoft 账户安全 → 应用密码(部分账号需先开两步验证)
|
|
154
|
+
|
|
155
|
+
## 安全须知
|
|
156
|
+
|
|
157
|
+
- **授权码就是你的邮箱钥匙**。它写在本机(profile 的 `cordis.patch.yml` 或 `settings.yaml`),请勿提交到任何 Git 仓库;更推荐用环境变量 `DSH_EMAIL_PASSWORD`。
|
|
158
|
+
- `email_send` 默认走 DSH 审批通道:每次发信都显示「发送邮件给 xx,主题「xx」」,你批准才发出。没有审批通道的环境(如无 UI 的 headless)会**直接拒绝发信**,这是安全默认。
|
|
159
|
+
- 会话处于 **Full Access(完全访问)** 模式时,harness 的审批策略是 never(不弹任何确认框)——`email_send` 会**被拦截并给出明确提示**。两条出路:① 把访问模式切回 Read Only / Write;② 关闭 `sendApproval`(设置页勾掉「发信前确认」),即显式声明自行承担风险。
|
|
160
|
+
- 本插件不做任何联网上报,凭证只在内存中用于连接你的邮箱服务器。
|
|
161
|
+
|
|
162
|
+
## 已知限制
|
|
163
|
+
|
|
164
|
+
- **不支持 OAuth2**:强制 OAuth 的企业环境(部分 M365 / Google Workspace)暂不可用,只能使用邮箱服务商的应用专用密码 / 授权码。
|
|
165
|
+
- **正文搜索**:服务器端只搜 subject / from / to / cc;多数服务器(如 QQ)的 IMAP `TEXT` / `HEADER` 搜索不可靠,无结果时回退到最近 `bodySearchLimit` 封的正文扫描(较慢,可用 `bodySearchFallback` 关闭)。
|
|
166
|
+
- **附件**:内嵌图片暂不支持单独下载;附件定位失败会直接报错而不是下载错误文件(安全默认)。
|
|
167
|
+
- **密码落盘**:设置页保存的授权码以明文写在本机 `settings.yaml`(secret 标记只保证它不进日志 / 导出 / 诊断,不做磁盘加密)。请勿把 `settings.yaml` 交给不信任的人。
|
|
168
|
+
|
|
169
|
+
## 开发
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
pnpm install
|
|
173
|
+
pnpm run build # tsc → lib/
|
|
174
|
+
pnpm test # 构建 + node --test(配置/解析/注册与审批门,44 个用例,无需真实邮箱)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 协议
|
|
178
|
+
|
|
179
|
+
MIT。这是一个社区插件,与 DeepSeek 官方无关;`@deepseek-ai/*` 为官方保留命名空间。
|
|
180
|
+
|
|
181
|
+
## 相关插件
|
|
182
|
+
|
|
183
|
+
- [dsh-slack](https://github.com/STARDUSTLC666/dsh-slack) — Slack 通知/收件箱
|
|
184
|
+
- [dsh-dingtalk](https://github.com/STARDUSTLC666/dsh-dingtalk) — 钉钉群通知(零依赖)
|
|
185
|
+
- [dsh-email](https://github.com/STARDUSTLC666/dsh-email) — 邮件六件套 + Web 设置页
|
package/lib/index.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { type EmailConfig } from './config.js';
|
|
|
2
2
|
export declare const name = "tool-email";
|
|
3
3
|
export declare const inject: string[];
|
|
4
4
|
export type Config = EmailConfig;
|
|
5
|
+
/**
|
|
6
|
+
* 解析天级日期参数为 Date。接受 YYYY-MM-DD 或完整 ISO 时间。
|
|
7
|
+
* endInclusive=true 时返回“该日结束”(次日零点),用于 until 语义(IMAP BEFORE 是不含当天的)。
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseEmailDay(input: string, label: string, endInclusive?: boolean): Date;
|
|
5
10
|
export declare function apply(ctx: any, config?: Config): void;
|
|
6
11
|
export { PROVIDER_NAMES, EMAIL_PASSWORD_ENV } from './config.js';
|
|
7
12
|
export { resolveEmailConfig, resolveEmailSettings, parseAccountsYaml, clampInt, defaultDownloadDir } from './config.js';
|
package/lib/index.js
CHANGED
|
@@ -5,6 +5,28 @@ import { EmailSettingsBackend, installEmailSettingsWeb } from './web.js';
|
|
|
5
5
|
export const name = 'tool-email';
|
|
6
6
|
export const inject = ['settings', 'tools'];
|
|
7
7
|
const MAX_LIMIT = 100;
|
|
8
|
+
/**
|
|
9
|
+
* 解析天级日期参数为 Date。接受 YYYY-MM-DD 或完整 ISO 时间。
|
|
10
|
+
* endInclusive=true 时返回“该日结束”(次日零点),用于 until 语义(IMAP BEFORE 是不含当天的)。
|
|
11
|
+
*/
|
|
12
|
+
export function parseEmailDay(input, label, endInclusive = false) {
|
|
13
|
+
const text = input.trim();
|
|
14
|
+
let date = null;
|
|
15
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(text)) {
|
|
16
|
+
date = new Date(text + 'T00:00:00Z');
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const parsed = new Date(text);
|
|
20
|
+
if (!Number.isNaN(parsed.getTime()))
|
|
21
|
+
date = parsed;
|
|
22
|
+
}
|
|
23
|
+
if (date === null || Number.isNaN(date.getTime())) {
|
|
24
|
+
throw new Error(label + ' 不是有效日期,请给形如 2026-08-01 或 2026-08-01T00:00:00Z 的值');
|
|
25
|
+
}
|
|
26
|
+
if (endInclusive)
|
|
27
|
+
return new Date(date.getTime() + 24 * 3600 * 1000);
|
|
28
|
+
return date;
|
|
29
|
+
}
|
|
8
30
|
const ACCOUNT_HINT = '账号名(配置了 accounts 多个账号时选择),省略时用 defaultAccount。可用账号见 email_folders 的报错或插件 README';
|
|
9
31
|
/**
|
|
10
32
|
* Compile the author DSL map into a raw JSON Schema object, exactly what
|
|
@@ -227,12 +249,14 @@ export function apply(ctx, config = {}) {
|
|
|
227
249
|
installEmailSettingsWeb(ctx, new EmailSettingsBackend(ctx, settingsScope, config));
|
|
228
250
|
ctx.tools.register({
|
|
229
251
|
name: 'email_list',
|
|
230
|
-
description: 'List recent emails in a mailbox folder (newest first). Returns uid, date, sender, subject and flags without message bodies; use email_read with a uid to fetch the full text.',
|
|
252
|
+
description: 'List recent emails in a mailbox folder (newest first). Returns uid, date, sender, subject and flags without message bodies; use email_read with a uid to fetch the full text. Optional since/until (dates like 2026-08-01) filter by received date.',
|
|
231
253
|
parameters: compileParameters({
|
|
232
254
|
folder: { type: 'string', description: 'IMAP folder path (see email_folders); defaults to the account inboxFolder' },
|
|
233
255
|
limit: { type: 'integer', description: 'How many messages to return, 1-100, default 20' },
|
|
234
256
|
offset: { type: 'integer', description: 'Skip this many newest messages first, default 0' },
|
|
235
257
|
unreadOnly: { type: 'boolean', description: 'Only list unread messages, default false' },
|
|
258
|
+
since: { type: 'string', description: '只列出该日期(含)之后收到的邮件,如 2026-08-01(可选)' },
|
|
259
|
+
until: { type: 'string', description: '只列出该日期(含)之前收到的邮件,如 2026-08-26(可选)' },
|
|
236
260
|
account: { type: 'string', description: ACCOUNT_HINT },
|
|
237
261
|
}),
|
|
238
262
|
output: {
|
|
@@ -243,7 +267,9 @@ export function apply(ctx, config = {}) {
|
|
|
243
267
|
const args = rawArgs;
|
|
244
268
|
const limit = clampInt(args.limit, 20, 1, MAX_LIMIT);
|
|
245
269
|
const offset = clampInt(args.offset, 0, 0, 10000);
|
|
246
|
-
|
|
270
|
+
const since = args.since?.trim() ? parseEmailDay(args.since, 'since') : undefined;
|
|
271
|
+
const until = args.until?.trim() ? parseEmailDay(args.until, 'until', true) : undefined;
|
|
272
|
+
return await getPool().list(args.account, args.folder?.trim() || '', limit, offset, args.unreadOnly === true, since, until);
|
|
247
273
|
},
|
|
248
274
|
});
|
|
249
275
|
ctx.tools.register({
|
|
@@ -273,6 +299,8 @@ export function apply(ctx, config = {}) {
|
|
|
273
299
|
query: { type: 'string', required: true, description: 'Keyword to search for' },
|
|
274
300
|
folder: { type: 'string', description: 'IMAP folder to search in; defaults to the account inboxFolder' },
|
|
275
301
|
limit: { type: 'integer', description: 'How many matches to return, 1-100, default 10' },
|
|
302
|
+
since: { type: 'string', description: '只搜索该日期(含)之后的邮件,如 2026-08-01(可选)' },
|
|
303
|
+
until: { type: 'string', description: '只搜索该日期(含)之前的邮件,如 2026-08-26(可选)' },
|
|
276
304
|
account: { type: 'string', description: ACCOUNT_HINT },
|
|
277
305
|
}),
|
|
278
306
|
output: {
|
|
@@ -284,7 +312,9 @@ export function apply(ctx, config = {}) {
|
|
|
284
312
|
if (typeof args.query !== 'string' || args.query.trim() === '')
|
|
285
313
|
throw new Error('query 不能为空');
|
|
286
314
|
const limit = clampInt(args.limit, 10, 1, MAX_LIMIT);
|
|
287
|
-
|
|
315
|
+
const since = args.since?.trim() ? parseEmailDay(args.since, 'since') : undefined;
|
|
316
|
+
const until = args.until?.trim() ? parseEmailDay(args.until, 'until', true) : undefined;
|
|
317
|
+
return await getPool().search(args.account, args.query.trim(), args.folder?.trim() || '', limit, since, until);
|
|
288
318
|
},
|
|
289
319
|
});
|
|
290
320
|
ctx.tools.register({
|
|
@@ -327,6 +357,43 @@ export function apply(ctx, config = {}) {
|
|
|
327
357
|
return await getPool().folders(args.account, args.subscribedOnly === true);
|
|
328
358
|
},
|
|
329
359
|
});
|
|
360
|
+
ctx.tools.register({
|
|
361
|
+
name: 'email_health',
|
|
362
|
+
description: 'Self-check for dsh-email: summarizes configured accounts (provider / IMAP / SMTP hosts) without any network connection and never shows passwords. Run this first when troubleshooting.',
|
|
363
|
+
parameters: compileParameters({}),
|
|
364
|
+
output: {
|
|
365
|
+
schema: { type: 'object', additionalProperties: true },
|
|
366
|
+
render: (_args, value) => {
|
|
367
|
+
const rec = (value ?? {});
|
|
368
|
+
const rawChecks = Array.isArray(rec.checks) ? rec.checks : [];
|
|
369
|
+
const lines = ['dsh-email 自检' + (rec.ok === true ? ':正常。' : ':发现问题。')];
|
|
370
|
+
for (const item of rawChecks) {
|
|
371
|
+
const c = (item ?? {});
|
|
372
|
+
lines.push('- ' + String(c.name) + ':' + (c.ok === true ? '✅ ' + String(c.detail ?? '') : '❌ ' + String(c.detail ?? '')));
|
|
373
|
+
}
|
|
374
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
async execute() {
|
|
378
|
+
const accounts = (config.accounts ?? {});
|
|
379
|
+
const names = Object.keys(accounts);
|
|
380
|
+
const checks = [];
|
|
381
|
+
if (names.length === 0) {
|
|
382
|
+
const topLevelUser = typeof config.user === 'string' && config.user.trim() !== '';
|
|
383
|
+
checks.push({ name: '默认账号', ok: topLevelUser, detail: topLevelUser ? '顶层账号已配置' : '未配置账号:请在 cordis.patch.yml 配置 accounts(或顶层 user/password)' });
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
for (const accountName of names.slice(0, 8)) {
|
|
387
|
+
const account = accounts[accountName] ?? {};
|
|
388
|
+
const hasUser = typeof account.user === 'string' && account.user.trim() !== '';
|
|
389
|
+
const provider = typeof account.provider === 'string' && account.provider !== '' ? account.provider : 'custom';
|
|
390
|
+
checks.push({ name: '账号 ' + accountName, ok: hasUser, detail: hasUser ? provider + ' / ' + account.user : '未配置 user' });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const ok = checks.every((c) => c.ok === true);
|
|
394
|
+
return { ok, plugin: 'dsh-email', accountCount: names.length, checks };
|
|
395
|
+
},
|
|
396
|
+
});
|
|
330
397
|
ctx.tools.register({
|
|
331
398
|
name: 'email_attachment',
|
|
332
399
|
description: 'Download one attachment of a message to a local file (size capped by maxAttachmentBytes). The index matches the attachments array of email_read. Returns the absolute path of the written file.',
|
package/lib/mail-client.d.ts
CHANGED
|
@@ -44,8 +44,8 @@ export declare class EmailPool {
|
|
|
44
44
|
startIdleSweep(): void;
|
|
45
45
|
dispose(): void;
|
|
46
46
|
private transporter;
|
|
47
|
-
list(accountName: string | undefined, folder: string, limit: number, offset: number, unreadOnly: boolean): Promise<EmailListResult>;
|
|
48
|
-
search(accountName: string | undefined, query: string, folder: string, limit: number): Promise<EmailSearchResult>;
|
|
47
|
+
list(accountName: string | undefined, folder: string, limit: number, offset: number, unreadOnly: boolean, since?: Date, until?: Date): Promise<EmailListResult>;
|
|
48
|
+
search(accountName: string | undefined, query: string, folder: string, limit: number, since?: Date, until?: Date): Promise<EmailSearchResult>;
|
|
49
49
|
/** Client-side scan of the tail of the mailbox, newest first. */
|
|
50
50
|
private searchBodies;
|
|
51
51
|
private fetchListed;
|
package/lib/mail-client.js
CHANGED
|
@@ -226,7 +226,7 @@ export class EmailPool {
|
|
|
226
226
|
}
|
|
227
227
|
return t;
|
|
228
228
|
}
|
|
229
|
-
async list(accountName, folder, limit, offset, unreadOnly) {
|
|
229
|
+
async list(accountName, folder, limit, offset, unreadOnly, since, until) {
|
|
230
230
|
const name = this.resolveName(accountName);
|
|
231
231
|
const cfg = this.account(name);
|
|
232
232
|
const folderName = folder || cfg.inboxFolder;
|
|
@@ -235,14 +235,22 @@ export class EmailPool {
|
|
|
235
235
|
const total = mailbox === false ? 0 : mailbox.exists;
|
|
236
236
|
let scopeCount = total;
|
|
237
237
|
let uids = [];
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
const hasDateFilter = since !== undefined || until !== undefined;
|
|
239
|
+
if (unreadOnly || hasDateFilter) {
|
|
240
|
+
const query = {};
|
|
241
|
+
if (unreadOnly)
|
|
242
|
+
query.seen = false;
|
|
243
|
+
if (since !== undefined)
|
|
244
|
+
query.since = since;
|
|
245
|
+
if (until !== undefined)
|
|
246
|
+
query.before = until;
|
|
247
|
+
const found = await client.search(query, { uid: true });
|
|
240
248
|
uids = found === false ? [] : found;
|
|
241
249
|
scopeCount = uids.length;
|
|
242
250
|
}
|
|
243
251
|
else if (total > 0) {
|
|
244
252
|
const start = Math.max(1, total - (limit + offset) + 1);
|
|
245
|
-
const fetched = await client.fetchAll(start + ':*', { uid: true }
|
|
253
|
+
const fetched = await client.fetchAll(start + ':*', { uid: true });
|
|
246
254
|
uids = fetched.map(message => message.uid);
|
|
247
255
|
}
|
|
248
256
|
uids.reverse();
|
|
@@ -251,7 +259,7 @@ export class EmailPool {
|
|
|
251
259
|
return { account: name, count: scopeCount, folder: folderName, messages };
|
|
252
260
|
});
|
|
253
261
|
}
|
|
254
|
-
async search(accountName, query, folder, limit) {
|
|
262
|
+
async search(accountName, query, folder, limit, since, until) {
|
|
255
263
|
const name = this.resolveName(accountName);
|
|
256
264
|
const cfg = this.account(name);
|
|
257
265
|
const folderName = folder || cfg.inboxFolder;
|
|
@@ -259,11 +267,16 @@ export class EmailPool {
|
|
|
259
267
|
// No nested OR and no TEXT search: several servers (QQ among them)
|
|
260
268
|
// silently answer those with empty or match-everything results.
|
|
261
269
|
// subject/from/to/cc searches unioned client-side behave well everywhere.
|
|
270
|
+
const dateRange = {};
|
|
271
|
+
if (since !== undefined)
|
|
272
|
+
dateRange.since = since;
|
|
273
|
+
if (until !== undefined)
|
|
274
|
+
dateRange.before = until;
|
|
262
275
|
const found = await Promise.all([
|
|
263
|
-
client.search({ subject: query }, { uid: true }),
|
|
264
|
-
client.search({ from: query }, { uid: true }),
|
|
265
|
-
client.search({ to: query }, { uid: true }),
|
|
266
|
-
client.search({ cc: query }, { uid: true }),
|
|
276
|
+
client.search({ subject: query, ...dateRange }, { uid: true }),
|
|
277
|
+
client.search({ from: query, ...dateRange }, { uid: true }),
|
|
278
|
+
client.search({ to: query, ...dateRange }, { uid: true }),
|
|
279
|
+
client.search({ cc: query, ...dateRange }, { uid: true }),
|
|
267
280
|
]);
|
|
268
281
|
const uids = [...new Set(found.flatMap(result => result === false ? [] : result))].sort((a, b) => a - b);
|
|
269
282
|
uids.reverse();
|
|
@@ -284,7 +297,7 @@ export class EmailPool {
|
|
|
284
297
|
if (total === 0)
|
|
285
298
|
return [];
|
|
286
299
|
const start = Math.max(1, total - this.settings.bodySearchLimit + 1);
|
|
287
|
-
const fetched = await client.fetchAll(start + ':*', { uid: true, envelope: true, flags: true, size: true, bodyStructure: true, source: true }
|
|
300
|
+
const fetched = await client.fetchAll(start + ':*', { uid: true, envelope: true, flags: true, size: true, bodyStructure: true, source: true });
|
|
288
301
|
const out = [];
|
|
289
302
|
for (const message of [...fetched].reverse()) {
|
|
290
303
|
if (out.length >= limit)
|
package/lib/parse.js
CHANGED
|
@@ -47,8 +47,8 @@ export function truncateText(text, maxChars) {
|
|
|
47
47
|
*/
|
|
48
48
|
export function sanitizeFilename(raw, fallback = 'attachment.bin') {
|
|
49
49
|
let name = String(raw ?? '').replace(/\\/g, '/').split('/').pop() ?? '';
|
|
50
|
-
name = name.replace(/[\u0000-\u001f\u007f]/g, '').trim();
|
|
51
|
-
if (name === '' || name === '.' || name === '..')
|
|
50
|
+
name = name.replace(/[\u0000-\u001f\u007f]/g, '').replace(/[<>:"|?*]/g, '_').trim().replace(/[. ]+$/g, '');
|
|
51
|
+
if (name === '' || name === '.' || name === '..' || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(name))
|
|
52
52
|
name = fallback;
|
|
53
53
|
if (name.length > 120) {
|
|
54
54
|
const dot = name.lastIndexOf('.');
|
package/lib/types.d.ts
CHANGED
|
@@ -88,6 +88,8 @@ export interface EmailListArgs extends AccountArg {
|
|
|
88
88
|
limit?: number;
|
|
89
89
|
offset?: number;
|
|
90
90
|
unreadOnly?: boolean;
|
|
91
|
+
since?: string;
|
|
92
|
+
until?: string;
|
|
91
93
|
}
|
|
92
94
|
export interface EmailReadArgs extends AccountArg {
|
|
93
95
|
uid: number;
|
|
@@ -97,6 +99,8 @@ export interface EmailSearchArgs extends AccountArg {
|
|
|
97
99
|
query: string;
|
|
98
100
|
folder?: string;
|
|
99
101
|
limit?: number;
|
|
102
|
+
since?: string;
|
|
103
|
+
until?: string;
|
|
100
104
|
}
|
|
101
105
|
export interface EmailSendArgs extends AccountArg {
|
|
102
106
|
to: string;
|
package/package.json
CHANGED
|
@@ -1,71 +1,73 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-email",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "IMAP/SMTP email tools for DeepSeek Harness: list, read, search and send mail, with QQ/163/126/Sina/Aliyun/Gmail/Outlook/iCloud presets.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"types": "lib/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./lib/index.d.ts",
|
|
11
|
-
"default": "./lib/index.js"
|
|
12
|
-
},
|
|
13
|
-
"./client": "./lib/client.js",
|
|
14
|
-
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
15
|
-
"./package.json": "./package.json"
|
|
16
|
-
},
|
|
17
|
-
"files": [
|
|
18
|
-
"lib",
|
|
19
|
-
"cordis.patch.yml",
|
|
20
|
-
"README.md"
|
|
21
|
-
],
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
},
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-email",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "IMAP/SMTP email tools for DeepSeek Harness: list, read, search and send mail, with QQ/163/126/Sina/Aliyun/Gmail/Outlook/iCloud presets.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": "./lib/client.js",
|
|
14
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"lib",
|
|
19
|
+
"cordis.patch.yml",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"prepublishOnly": "pnpm run build",
|
|
25
|
+
"test": "pnpm run build && node --test \"test/*.test.mjs\""
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"dsh-plugin",
|
|
29
|
+
"deepseek-harness",
|
|
30
|
+
"dsh",
|
|
31
|
+
"email",
|
|
32
|
+
"imap",
|
|
33
|
+
"smtp"
|
|
34
|
+
],
|
|
35
|
+
"author": "stardustlc",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/STARDUSTLC666/dsh-email.git"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/STARDUSTLC666/dsh-email/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/STARDUSTLC666/dsh-email#readme",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"packageManager": "pnpm@11.7.0",
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22"
|
|
48
|
+
},
|
|
49
|
+
"dsh": {
|
|
50
|
+
"bundle": {
|
|
51
|
+
"patch": "./cordis.patch.yml"
|
|
52
|
+
},
|
|
53
|
+
"client": {
|
|
54
|
+
"inject": [
|
|
55
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
57
|
+
],
|
|
58
|
+
"platform": "web"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"imapflow": "^1.7.0",
|
|
63
|
+
"mailparser": "^3.9.15",
|
|
64
|
+
"nodemailer": "^9.0.5",
|
|
65
|
+
"schemastery": "^3.18.0",
|
|
66
|
+
"yaml": "^2.9.0"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@types/node": "^26.2.0",
|
|
70
|
+
"@types/nodemailer": "^8.0.1",
|
|
71
|
+
"typescript": "^5.7.3"
|
|
72
|
+
}
|
|
73
|
+
}
|