@soffits/dsh-deltachat-bot 0.0.0-stage → 0.1.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/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +182 -2
- package/bin/dsh-deltachat.js +140 -0
- package/cordis.patch.yml +9 -0
- package/icon.svg +14 -0
- package/index.js +4 -0
- package/locale/en.json +6 -0
- package/package.json +67 -4
- package/src/app.js +628 -0
- package/src/commands.js +429 -0
- package/src/config.js +63 -0
- package/src/constants.js +50 -0
- package/src/controls.js +214 -0
- package/src/core.js +236 -0
- package/src/identity.js +134 -0
- package/src/inbound.js +100 -0
- package/src/models.js +191 -0
- package/src/operations.js +174 -0
- package/src/outbound.js +79 -0
- package/src/prompts.js +179 -0
- package/src/sessions.js +380 -0
- package/src/spool.js +172 -0
- package/src/state.js +147 -0
- package/src/text.js +175 -0
- package/src/titles.js +146 -0
- package/src/turns.js +161 -0
- package/src/voice.js +126 -0
- package/tools.js +60 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
First release: a Delta Chat bot for DSH on several chatmail relays, with a key-fingerprint allowlist, one session per chat (groups included), steering, mirrored turns, pictures, files, voice transcription, quotes, reactions, agent questions and tool approvals, Telegram-style commands, `DeltaChat-<person>-<task>` session titles, the `deltachatChats` send service, the `deltachat_send` tool and the `dsh-deltachat` command.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sakina
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,183 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-deltachat-bot
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use [Delta Chat](https://delta.chat) to reach sessions in your [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness). Within DSH, the plugin runs a Delta Chat bot: it creates its own chatmail address (end-to-end encrypted; no phone number or sign-up needed), keeps that address on several relays so it stays reachable when a relay goes down, and ties each Delta Chat chat to a DSH session. Message it just as you would in the Web UI; answers, files and images come back to you as messages.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Use the DSH Plugins page, or install from a shell:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
dsh plugin --profile web add @soffits/dsh-deltachat-bot
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You need Node.js 22 or newer. A prebuilt npm binary (`@deltachat/stdio-rpc-server`) provides the Delta Chat core for Linux, macOS and Windows on x64 and arm64; nothing is compiled.
|
|
14
|
+
|
|
15
|
+
The bundle adds two plugin rows: `deltachat-bot` (the bot itself) and `deltachat-bot-tools` (which gives agents the `deltachat_send` tool).
|
|
16
|
+
|
|
17
|
+
## First start
|
|
18
|
+
|
|
19
|
+
On the first start, the bot creates a Delta Chat profile on the relays named in `relays` (by default [nine.testrun.org](https://nine.testrun.org), the usual relay for Delta Chat apps, and [chtml.ca](https://chtml.ca)). This takes a few seconds. It then produces a QR code plus an invite link:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
dsh-deltachat invite # prints the link; the QR code is $DSH_HOME/deltachat/invite.svg
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Open the link on a phone that has Delta Chat installed, or scan the QR code on the app's "New chat" screen. The app and the bot exchange keys, and the chat appears.
|
|
26
|
+
|
|
27
|
+
## Who may talk to it
|
|
28
|
+
|
|
29
|
+
Nobody can, unless you allow it. Only keys listed in `allowedUsers` get replies from the bot; all others are ignored and receive nothing. People are identified by their key fingerprint, not their address, because an address changes when someone adds a relay or switches to another.
|
|
30
|
+
|
|
31
|
+
To give yourself access, message the bot once, then ask it which contacts it ignored:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
dsh-deltachat status # "pending" lists ignored contacts with their fingerprints
|
|
35
|
+
dsh-deltachat allow "3C1F 9E0A 5B7D 2468 ACE0 1357 9BDF 0246 8ACE 1357" --name Alex
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`allow` stores the entry through DSH's configuration editor, exactly as the settings page would. You can also add it to the profile by hand:
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
- id: deltachat-bot
|
|
42
|
+
config:
|
|
43
|
+
allowedUsers:
|
|
44
|
+
- fingerprint: 3C1F9E0A5B7D2468ACE013579BDF02468ACE1357
|
|
45
|
+
name: Alex
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Your fingerprint is also the part after `#` in the invite link for your own Delta Chat account. If you want the bot to start the chat, supply your invite link after your key is allowed:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
dsh-deltachat join "https://i.delta.chat/#3C1F9E0A...&a=you%40relay.example&n=Alex"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Every message the agent receives starts with a line the bot adds from the sender's verified key, such as `[Delta Chat message from Alex (key 3C1F…, chat 10, message 42)]`. No text in the message can change that line.
|
|
55
|
+
|
|
56
|
+
## Chats and sessions
|
|
57
|
+
|
|
58
|
+
- The first message in a chat starts a new session in `cwd`. Each group you create with the bot gets its own session, much like a separate topic.
|
|
59
|
+
- Session titles use `DeltaChat-<person>-<task>`: the other party in the chat (a group's name, for a group) plus either the conversation title DSH produces or the chat's name until that title exists. A person's name comes from `peopleFile` when configured, else from `allowedUsers`, else from their Delta Chat profile.
|
|
60
|
+
- If a message arrives while a turn is running, it joins that turn, as in the Web UI (`busyMessages: steer`; the message gets a 👀 reaction). With `busyMessages: queue`, it waits for the turn to finish.
|
|
61
|
+
- Turns started elsewhere (scheduled tasks, the Web UI) also show up in the chat unless `mirrorTurns` is off.
|
|
62
|
+
- When the turn ends, replies arrive as plain text: Delta Chat apps do not render Markdown, so the bot turns it into readable text. Files the agent presents, and local images inside an answer, are sent as files. An answer that is exactly `[SILENT]` sends nothing.
|
|
63
|
+
- The model receives images you send as images; other files come through as attachments. Voice messages are transcribed by DSH's speech-to-text service (or by a command you choose; see `transcriber`) and attached.
|
|
64
|
+
- Quoting a message tells the agent what you are replying to. A reaction to the bot's message is saved in the session but does not begin a turn.
|
|
65
|
+
- When the agent asks a question, the options show as a numbered list: answer with a number or in your own words. A tool that needs approval waits for `/allow` or `/deny`.
|
|
66
|
+
|
|
67
|
+
### Commands
|
|
68
|
+
|
|
69
|
+
Send these as messages; Delta Chat apps make them tappable.
|
|
70
|
+
|
|
71
|
+
| Command | Result |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `/help` | List commands |
|
|
74
|
+
| `/new` | Start a fresh session here; the old one stays in DSH |
|
|
75
|
+
| `/stop` | Stop the running turn |
|
|
76
|
+
| `/clear` | Drop any messages waiting in the session queue |
|
|
77
|
+
| `/status` | Bot, relay and session status |
|
|
78
|
+
| `/current` | Show this chat's session |
|
|
79
|
+
| `/last [1-5]` | Show the latest answers |
|
|
80
|
+
| `/sessions` | List workspaces; `/sessions 2` displays workspace 2's sessions; `/sessions 2 3` brings session 3 into this chat (`0` opens a new one) |
|
|
81
|
+
| `/workspace <n>` | Open a fresh session in workspace n |
|
|
82
|
+
| `/fork [name]` | Make a new group with the same people and fork the session there |
|
|
83
|
+
| `/rename <name>` | In a group chat, change the group's name; in a one-to-one chat, change the session title |
|
|
84
|
+
| `/goal …` | Goal actions: `/goal <objective>`, `/goal edit <objective>`, `pause`, `resume`, `clear` |
|
|
85
|
+
| `/menu` | See the model, effort level, access setting and context together |
|
|
86
|
+
| `/model` | List providers; `/model 2` lists models from that provider; `/model 2 5` picks one |
|
|
87
|
+
| `/effort`, `/mode` | Works the same way for effort level and access setting |
|
|
88
|
+
| `/context` | Context use for the session |
|
|
89
|
+
| `/allow`, `/deny` | Answer a tool approval that is waiting |
|
|
90
|
+
| `/invite` | Show the invite link for the bot |
|
|
91
|
+
| `/id` | Show your key, the bot's key and the chat id |
|
|
92
|
+
| `/ping` | Check whether the bot answers |
|
|
93
|
+
| `/restart` | Reconnect to Delta Chat |
|
|
94
|
+
|
|
95
|
+
A message that starts with `/` yet matches no command above (a path, for example) goes to the session as normal text.
|
|
96
|
+
|
|
97
|
+
## Sending from scripts, agents and additional plugins
|
|
98
|
+
|
|
99
|
+
Other plugins send through the `deltachatChats` Host service:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
await ctx.get('deltachatChats').send({
|
|
103
|
+
to: 10, // chat id (/id in the chat shows it)
|
|
104
|
+
text: 'Build finished', // Markdown; made plain
|
|
105
|
+
files: ['/abs/path/log.txt'],
|
|
106
|
+
from: { job: 'nightly build' }, // or { sessionId } or { label }
|
|
107
|
+
replyTo: 42, // optional: quote a message
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Errors a retry cannot solve carry `retryable: false`. The message is also recorded in the session tied to that chat, without starting a turn, so the agent knows it went out; if the person quotes it later, the agent can tell which job or session sent it. `chats()` lists chats bound to sessions, and `invite()` returns the invite link.
|
|
112
|
+
|
|
113
|
+
Agents can use the `deltachat_send` tool, which offers the same options (plus `reaction` to react to a message). From a shell:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
dsh-deltachat send --chat 10 --job backup "Backup done"
|
|
117
|
+
dsh-deltachat send --chat 10 --file /tmp/report.pdf
|
|
118
|
+
dsh-deltachat react --chat 10 --reply-to 42 👍
|
|
119
|
+
dsh-deltachat chats
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The command line talks to the running bot through a spool directory under `$DSH_HOME/deltachat`, so it needs no network access to DSH. A send made while DSH is stopped waits there and is delivered when the bot starts.
|
|
123
|
+
|
|
124
|
+
## Configuration
|
|
125
|
+
|
|
126
|
+
| Field | Default | |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| `enabled` | `true` | Whether the bot is active |
|
|
129
|
+
| `displayName` | `DSH` | Name shown in Delta Chat |
|
|
130
|
+
| `avatar` | | Full path of an image to use as the bot avatar |
|
|
131
|
+
| `relays` | `[nine.testrun.org, chtml.ca]` | Chatmail relays, given by domain. A relay added here joins the bot's profile; one removed here stays in the profile |
|
|
132
|
+
| `allowedUsers` | `[]` | Items shaped like `{ fingerprint, name }` |
|
|
133
|
+
| `peopleFile` | | JSON file with a `deltachat_keys` object mapping fingerprints to names, for session titles |
|
|
134
|
+
| `cwd` | DSH's directory | Directory where new sessions start |
|
|
135
|
+
| `agentPreset` | | Preset for new sessions; blank uses the default |
|
|
136
|
+
| `mirrorTurns` | `true` | Show turns started elsewhere |
|
|
137
|
+
| `busyMessages` | `steer` | `steer` or `queue` |
|
|
138
|
+
| `transcriber` | `dsh` | `dsh`, `command` or `off` |
|
|
139
|
+
| `transcribeLanguage` | | Language hint for DSH speech-to-text |
|
|
140
|
+
| `transcribeCommand` | `[]` | Program and arguments; `{file}` is the recording |
|
|
141
|
+
| `ffmpegPath` | `ffmpeg` | Decodes audio for DSH speech-to-text |
|
|
142
|
+
|
|
143
|
+
Every field applies live: changing one needs no DSH restart. Relays, name and avatar take effect at once.
|
|
144
|
+
|
|
145
|
+
Pick relays you expect to stay up; the [chatmail relay list](https://chatmail.at/relays) has many. Relays remove accounts after about 90 days without a login, which a running bot never allows to happen.
|
|
146
|
+
|
|
147
|
+
## Where files live
|
|
148
|
+
|
|
149
|
+
Everything is stored under `$DSH_HOME/deltachat/`:
|
|
150
|
+
|
|
151
|
+
- `accounts/` holds the Delta Chat profile, with the bot's private key and its message database. Keep it out of shared backups outside your control, and keep it when moving DSH: otherwise the bot gets a fresh address and key, and people have to scan a fresh invite.
|
|
152
|
+
- `state.json`: chat-to-session bindings, ignored contacts, and a log of messages sent by jobs and sessions.
|
|
153
|
+
- `status.json`: the bot's state and latest error, for diagnostics.
|
|
154
|
+
- `invite.txt`, `invite.svg`: the invite link and its QR code.
|
|
155
|
+
- `spool/`: requests that come from the command line.
|
|
156
|
+
|
|
157
|
+
The bot's log lines appear in DSH's output prefixed with `[dsh-deltachat-bot]`.
|
|
158
|
+
|
|
159
|
+
## Updates and removal
|
|
160
|
+
|
|
161
|
+
Update it like any DSH plugin. Every release locks the Delta Chat core at a tested version.
|
|
162
|
+
|
|
163
|
+
To remove it, remove the bundle from DSH, then delete `$DSH_HOME/deltachat/` unless you want to keep the bot's identity. Anyone who chatted with it can remove the chat in their app.
|
|
164
|
+
|
|
165
|
+
## Troubleshooting
|
|
166
|
+
|
|
167
|
+
- **No reply from the bot.** `dsh-deltachat status` shows whether it runs and lists ignored contacts; the common cause is an entry missing from `allowedUsers`. DSH's log contains lines prefixed `[dsh-deltachat-bot]`.
|
|
168
|
+
- **"no relay could create the Delta Chat profile".** None of the relays in `relays` could be reached or would take a new account. Try a different relay from the [relay list](https://chatmail.at/relays).
|
|
169
|
+
- **Core stopped.** The bot restarts it with a growing delay (5 s up to 5 min) and logs why it stopped.
|
|
170
|
+
|
|
171
|
+
## Development
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
npm install
|
|
175
|
+
npm test # unit and bridge tests against a fake core and a fake Host
|
|
176
|
+
npm run e2e # real relays: a bot and two throwaway accounts, all deleted afterwards
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Each `npm run e2e` run creates fresh accounts on public relays; run it when message handling or the core changes, not over and over. Set `E2E_BOT_RELAYS` and `E2E_TESTER_RELAY` to choose other relays.
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT. The core packages from Delta Chat use MPL-2.0.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Talk to the running DSH Delta Chat bot from a shell: send messages, show
|
|
4
|
+
* its invite link, join someone's invite, list chats, allow a contact.
|
|
5
|
+
*
|
|
6
|
+
* Requests go through the plugin's file spool under $DSH_HOME/deltachat, so
|
|
7
|
+
* no network access to DSH is needed, and a send waits there while DSH is
|
|
8
|
+
* not running.
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync } from 'node:fs'
|
|
11
|
+
import { join, resolve } from 'node:path'
|
|
12
|
+
import { parseArgs } from 'node:util'
|
|
13
|
+
import { dataDir } from '../src/constants.js'
|
|
14
|
+
import { enqueue, waitForResult, withdraw } from '../src/spool.js'
|
|
15
|
+
|
|
16
|
+
const usage = `Usage: dsh-deltachat <command> [options]
|
|
17
|
+
|
|
18
|
+
Commands:
|
|
19
|
+
send --chat <id> [--file <path>]... [--reply-to <msg>] [--job <name>] [text...]
|
|
20
|
+
Send text (from the arguments, or stdin when none are given) and files
|
|
21
|
+
into a chat. The message is also recorded in the chat's session.
|
|
22
|
+
react --chat <id> --reply-to <msg> <emoji>
|
|
23
|
+
React to a message (an empty emoji removes the reaction).
|
|
24
|
+
invite
|
|
25
|
+
Print the bot's invite link; its QR code is in ${join('$DSH_HOME', 'deltachat', 'invite.svg')}.
|
|
26
|
+
join <invite-link>
|
|
27
|
+
Join someone's invite link (their key must be in allowedUsers).
|
|
28
|
+
allow <fingerprint> [--name <label>]
|
|
29
|
+
Add a key to allowedUsers (writes the profile's configuration).
|
|
30
|
+
chats
|
|
31
|
+
List the chats bound to sessions.
|
|
32
|
+
status
|
|
33
|
+
Show the bot's state, relays and the contacts it ignored.
|
|
34
|
+
|
|
35
|
+
Options:
|
|
36
|
+
--timeout <seconds> how long to wait for the bot (default 30)
|
|
37
|
+
--no-wait queue a send and return at once
|
|
38
|
+
|
|
39
|
+
Exit codes: 0 done, 1 failed, 2 bad arguments, 3 the bot did not answer in time.`
|
|
40
|
+
|
|
41
|
+
function fail(message, code = 2) {
|
|
42
|
+
process.stderr.write(`${message}\n`)
|
|
43
|
+
process.exit(code)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let parsed
|
|
47
|
+
try {
|
|
48
|
+
parsed = parseArgs({
|
|
49
|
+
allowPositionals: true,
|
|
50
|
+
options: {
|
|
51
|
+
chat: { type: 'string', short: 'c' },
|
|
52
|
+
file: { type: 'string', short: 'f', multiple: true },
|
|
53
|
+
'reply-to': { type: 'string' },
|
|
54
|
+
job: { type: 'string', short: 'j' },
|
|
55
|
+
'from-session': { type: 'string' },
|
|
56
|
+
name: { type: 'string' },
|
|
57
|
+
timeout: { type: 'string' },
|
|
58
|
+
'no-wait': { type: 'boolean' },
|
|
59
|
+
help: { type: 'boolean', short: 'h' },
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
} catch (error) {
|
|
63
|
+
fail(`${error.message}\n\n${usage}`)
|
|
64
|
+
}
|
|
65
|
+
const { values, positionals } = parsed
|
|
66
|
+
const [command, ...rest] = positionals
|
|
67
|
+
if (values.help || command === undefined) {
|
|
68
|
+
process.stdout.write(`${usage}\n`)
|
|
69
|
+
process.exit(values.help ? 0 : 2)
|
|
70
|
+
}
|
|
71
|
+
const timeoutMs = Math.max(1, Number(values.timeout ?? 30)) * 1000
|
|
72
|
+
|
|
73
|
+
function readStdin() {
|
|
74
|
+
try {
|
|
75
|
+
return readFileSync(0, 'utf8')
|
|
76
|
+
} catch {
|
|
77
|
+
return ''
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let request
|
|
82
|
+
if (command === 'send' || command === 'react') {
|
|
83
|
+
if (values.chat === undefined || !/^\d+$/u.test(values.chat.trim())) fail('--chat takes the numeric chat id (/id in the chat shows it).')
|
|
84
|
+
const sessionId = values['from-session'] ?? process.env.DSH_SESSION_ID
|
|
85
|
+
const from = {
|
|
86
|
+
...(values.job ? { job: values.job } : {}),
|
|
87
|
+
...(sessionId ? { sessionId } : {}),
|
|
88
|
+
...(!values.job && !sessionId ? { label: 'dsh-deltachat' } : {}),
|
|
89
|
+
}
|
|
90
|
+
if (command === 'react') {
|
|
91
|
+
if (values['reply-to'] === undefined) fail('react needs --reply-to <message id>.')
|
|
92
|
+
request = { op: 'send', to: values.chat.trim(), replyTo: values['reply-to'], reaction: rest.join(' '), from }
|
|
93
|
+
} else {
|
|
94
|
+
const text = rest.length > 0 ? rest.join(' ') : (values.file?.length ? '' : readStdin())
|
|
95
|
+
request = {
|
|
96
|
+
op: 'send',
|
|
97
|
+
to: values.chat.trim(),
|
|
98
|
+
text,
|
|
99
|
+
files: (values.file ?? []).map((path) => resolve(path)),
|
|
100
|
+
...(values['reply-to'] === undefined ? {} : { replyTo: values['reply-to'] }),
|
|
101
|
+
from,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} else if (command === 'invite' || command === 'chats' || command === 'status') {
|
|
105
|
+
request = { op: command }
|
|
106
|
+
} else if (command === 'join') {
|
|
107
|
+
if (rest.length !== 1) fail('join takes one invite link.')
|
|
108
|
+
request = { op: 'join', link: rest[0] }
|
|
109
|
+
} else if (command === 'allow') {
|
|
110
|
+
if (rest.length !== 1) fail('allow takes one fingerprint (quote it if it has spaces).')
|
|
111
|
+
request = { op: 'allow', fingerprint: rest[0], name: values.name ?? '' }
|
|
112
|
+
} else {
|
|
113
|
+
fail(`Unknown command ${command}.\n\n${usage}`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const id = await enqueue(request)
|
|
117
|
+
if (values['no-wait'] && request.op === 'send') {
|
|
118
|
+
process.stdout.write(`queued ${id}\n`)
|
|
119
|
+
process.exit(0)
|
|
120
|
+
}
|
|
121
|
+
const result = await waitForResult(id, timeoutMs)
|
|
122
|
+
if (result === undefined) {
|
|
123
|
+
// Only sends keep waiting for the bot; other requests are withdrawn.
|
|
124
|
+
if (request.op !== 'send') await withdraw(id)
|
|
125
|
+
let state = ''
|
|
126
|
+
try {
|
|
127
|
+
const status = JSON.parse(readFileSync(join(dataDir(), 'status.json'), 'utf8'))
|
|
128
|
+
state = ` (last state: ${status.state}${status.detail ? `, ${status.detail}` : ''})`
|
|
129
|
+
} catch {
|
|
130
|
+
// No status yet.
|
|
131
|
+
}
|
|
132
|
+
process.stderr.write(`The Delta Chat bot did not answer in time${state}.${request.op === 'send' ? ' The message stays queued and goes out when the bot runs.' : ''}\n`)
|
|
133
|
+
process.exit(3)
|
|
134
|
+
}
|
|
135
|
+
if (!result.ok) {
|
|
136
|
+
process.stderr.write(`${result.error}\n`)
|
|
137
|
+
process.exit(1)
|
|
138
|
+
}
|
|
139
|
+
if (command === 'invite') process.stdout.write(`${result.result.link}\nQR code: ${result.result.svgPath}\n`)
|
|
140
|
+
else process.stdout.write(`${JSON.stringify(result.result, null, 2)}\n`)
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Bundle layer for @soffits/dsh-deltachat-bot. The bot row's id is also its
|
|
2
|
+
# settings namespace. The tools row registers deltachat_send for every agent;
|
|
3
|
+
# disable it, or mount `@soffits/dsh-deltachat-bot/tools` in one agent preset,
|
|
4
|
+
# to limit who gets it.
|
|
5
|
+
- insert:
|
|
6
|
+
- id: deltachat-bot
|
|
7
|
+
name: '@soffits/dsh-deltachat-bot'
|
|
8
|
+
- id: deltachat-bot-tools
|
|
9
|
+
name: '@soffits/dsh-deltachat-bot/tools'
|
package/icon.svg
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Delta Chat bot">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="ddb-bg" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop stop-color="#3D6FD9"/>
|
|
5
|
+
<stop offset="1" stop-color="#2A4FA8"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="64" height="64" rx="15" fill="url(#ddb-bg)"/>
|
|
9
|
+
<path d="M14 18c0-3.3 2.7-6 6-6h24c3.3 0 6 2.7 6 6v18c0 3.3-2.7 6-6 6H28l-9 8v-8h-1c-2.2 0-4-1.8-4-4z" fill="#fff"/>
|
|
10
|
+
<path d="M32 18.5 41.5 35h-19z" fill="none" stroke="#2A4FA8" stroke-width="3.2" stroke-linejoin="round"/>
|
|
11
|
+
<circle cx="48" cy="48" r="9" fill="#fff"/>
|
|
12
|
+
<rect x="44.6" y="46.4" width="6.8" height="5.6" rx="1.2" fill="#2A4FA8"/>
|
|
13
|
+
<path d="M45.9 46.6v-1.9a2.1 2.1 0 0 1 4.2 0v1.9" fill="none" stroke="#2A4FA8" stroke-width="1.5"/>
|
|
14
|
+
</svg>
|
package/index.js
ADDED
package/locale/en.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soffits/dsh-deltachat-bot",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Delta Chat bot for DeepSeek Harness: an end-to-end encrypted chatmail bot on several relays, one DSH session per chat, commands, files, voice messages and a send service for other plugins.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dsh-deltachat": "./bin/dsh-deltachat.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./index.js",
|
|
12
|
+
"./tools": "./tools.js",
|
|
13
|
+
"./locale/*.json": "./locale/*.json",
|
|
14
|
+
"./icon": "./icon.svg",
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin",
|
|
19
|
+
"index.js",
|
|
20
|
+
"tools.js",
|
|
21
|
+
"src/*.js",
|
|
22
|
+
"cordis.patch.yml",
|
|
23
|
+
"locale/*.json",
|
|
24
|
+
"icon.svg",
|
|
25
|
+
"README.md",
|
|
26
|
+
"CHANGELOG.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"dsh": {
|
|
30
|
+
"bundle": {
|
|
31
|
+
"patch": "./cordis.patch.yml"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22",
|
|
36
|
+
"dsh": ">=0.2.1-rc.2 <0.3.0-0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"test": "NODE_ENV=test node --import ./test/home.js --test \"test/*.test.js\"",
|
|
40
|
+
"check": "for f in index.js tools.js bin/*.js src/*.js test/*.js scripts/*.js; do node --check \"$f\" || exit 1; done",
|
|
41
|
+
"e2e": "node scripts/e2e.js"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@deepseek-ai/schemastery": "^3.18.5",
|
|
45
|
+
"@deltachat/jsonrpc-client": "2.69.0",
|
|
46
|
+
"@deltachat/stdio-rpc-server": "2.69.0"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"dsh-plugin",
|
|
50
|
+
"deepseek-harness",
|
|
51
|
+
"dsh",
|
|
52
|
+
"deltachat",
|
|
53
|
+
"chatmail",
|
|
54
|
+
"bot"
|
|
55
|
+
],
|
|
56
|
+
"author": "Sakina",
|
|
57
|
+
"license": "MIT",
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/soffits/dsh-deltachat-bot.git"
|
|
61
|
+
},
|
|
62
|
+
"homepage": "https://github.com/soffits/dsh-deltachat-bot#readme",
|
|
63
|
+
"bugs": {
|
|
64
|
+
"url": "https://github.com/soffits/dsh-deltachat-bot/issues"
|
|
65
|
+
},
|
|
66
|
+
"publishConfig": {
|
|
67
|
+
"access": "public"
|
|
68
|
+
}
|
|
69
|
+
}
|