@northpeak/swarmai 0.0.1
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/LICENSE +122 -0
- package/README.md +260 -0
- package/bee.png +0 -0
- package/package.json +55 -0
- package/plugins/channel-telegram-client.js +10 -0
- package/plugins/channel-whatsapp-personal.js +10 -0
- package/server.js +891 -0
- package/swarmai.js +1166 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
PolyForm Noncommercial License 1.0.0
|
|
2
|
+
|
|
3
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
In order to get any license under these terms, you must agree to them as
|
|
8
|
+
both strict obligations and conditions to all your licenses.
|
|
9
|
+
|
|
10
|
+
## Copyright License
|
|
11
|
+
|
|
12
|
+
The licensor grants you a copyright license for the software to do
|
|
13
|
+
everything you might do with the software that would otherwise infringe
|
|
14
|
+
the licensor's copyright in it for any permitted purpose. However, you
|
|
15
|
+
may only distribute the software according to [Distribution
|
|
16
|
+
License](#distribution-license) and make changes or new works based on
|
|
17
|
+
the software according to [Changes and New Works
|
|
18
|
+
License](#changes-and-new-works-license).
|
|
19
|
+
|
|
20
|
+
## Distribution License
|
|
21
|
+
|
|
22
|
+
The licensor grants you an additional copyright license to distribute
|
|
23
|
+
copies of the software. Your license to distribute covers distributing
|
|
24
|
+
the software with changes and new works permitted by [Changes and New
|
|
25
|
+
Works License](#changes-and-new-works-license).
|
|
26
|
+
|
|
27
|
+
## Changes and New Works License
|
|
28
|
+
|
|
29
|
+
The licensor grants you an additional copyright license to make changes
|
|
30
|
+
and new works based on the software for any permitted purpose.
|
|
31
|
+
|
|
32
|
+
## Patent License
|
|
33
|
+
|
|
34
|
+
The licensor grants you a patent license for the software that covers
|
|
35
|
+
patent claims the licensor can license, or becomes able to license, that
|
|
36
|
+
you would infringe by using the software.
|
|
37
|
+
|
|
38
|
+
## Noncommercial Purposes
|
|
39
|
+
|
|
40
|
+
Any noncommercial purpose is a permitted purpose.
|
|
41
|
+
|
|
42
|
+
## Personal Uses
|
|
43
|
+
|
|
44
|
+
Personal use for research, experiment, and testing for the benefit of
|
|
45
|
+
public knowledge, personal study, private entertainment, hobby projects,
|
|
46
|
+
amateur pursuits, or religious observance, without any anticipated
|
|
47
|
+
commercial application, is use for a permitted purpose.
|
|
48
|
+
|
|
49
|
+
## Noncommercial Organizations
|
|
50
|
+
|
|
51
|
+
Use by any charitable organization, educational institution, public
|
|
52
|
+
research organization, public safety or health organization, environmental
|
|
53
|
+
protection organization, or government institution is use for a permitted
|
|
54
|
+
purpose regardless of the source of funding or obligations resulting from
|
|
55
|
+
the funding.
|
|
56
|
+
|
|
57
|
+
## Fair Use
|
|
58
|
+
|
|
59
|
+
You may have "fair use" rights for the software under the law. These
|
|
60
|
+
terms do not limit them.
|
|
61
|
+
|
|
62
|
+
## No Other Rights
|
|
63
|
+
|
|
64
|
+
These terms do not allow you to sublicense or transfer any of your
|
|
65
|
+
licenses to anyone else, or prevent the licensor from granting licenses
|
|
66
|
+
to anyone else. These terms do not imply any other licenses.
|
|
67
|
+
|
|
68
|
+
## Patent Defense
|
|
69
|
+
|
|
70
|
+
If you make any written claim that the software infringes or contributes
|
|
71
|
+
to infringement of any patent, your patent license for the software
|
|
72
|
+
granted under these terms ends immediately. If your company makes such a
|
|
73
|
+
claim, your patent license ends immediately for work on behalf of your
|
|
74
|
+
company.
|
|
75
|
+
|
|
76
|
+
## Violations
|
|
77
|
+
|
|
78
|
+
The first time you are notified in writing that you have violated any of
|
|
79
|
+
these terms, or done anything with the software not covered by your
|
|
80
|
+
licenses, your licenses can nonetheless continue if you come into full
|
|
81
|
+
compliance with these terms, and take practical steps to correct past
|
|
82
|
+
violations, within 32 days of receiving notice. Otherwise, all your
|
|
83
|
+
licenses end immediately.
|
|
84
|
+
|
|
85
|
+
## No Liability
|
|
86
|
+
|
|
87
|
+
***As far as the law allows, the software comes as is, without any
|
|
88
|
+
warranty or condition, and the licensor will not be liable to you for any
|
|
89
|
+
damages arising out of these terms or the use or nature of the software,
|
|
90
|
+
under any kind of legal claim.***
|
|
91
|
+
|
|
92
|
+
## Definitions
|
|
93
|
+
|
|
94
|
+
The **licensor** is the individual or entity offering these terms, and
|
|
95
|
+
the **software** is the software the licensor makes available under these
|
|
96
|
+
terms.
|
|
97
|
+
|
|
98
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
99
|
+
|
|
100
|
+
**Your company** is any legal entity, sole proprietorship, or other kind
|
|
101
|
+
of organization that you work for, plus all organizations that have
|
|
102
|
+
control over, are under the control of, or are under common control with
|
|
103
|
+
that organization. **Control** means ownership of substantially all the
|
|
104
|
+
assets of an entity, or the power to direct its management and policies
|
|
105
|
+
by vote, contract, or otherwise. Control can be direct or indirect.
|
|
106
|
+
|
|
107
|
+
**Your licenses** are all the licenses granted to you for the software
|
|
108
|
+
under these terms.
|
|
109
|
+
|
|
110
|
+
**Use** means anything you do with the software requiring one of your
|
|
111
|
+
licenses.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Vendor
|
|
116
|
+
|
|
117
|
+
This software is licensed by **NorthPeak Malaysia** under the terms above.
|
|
118
|
+
|
|
119
|
+
For commercial licensing inquiries (using SwarmAI as part of a for-profit
|
|
120
|
+
product, deploying it inside a commercial organisation's operations, or
|
|
121
|
+
selling it as a service), please contact NorthPeak Malaysia at
|
|
122
|
+
<https://hub.northpeak.app>.
|
package/README.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<img src="bee.png" align="right" width="140" alt="SwarmAI" />
|
|
2
|
+
|
|
3
|
+
# SwarmAI
|
|
4
|
+
|
|
5
|
+
> Your personal multi-channel AI assistant — one autonomous agent that talks to you across WhatsApp, Telegram, email, and the dashboard, with secure local-first storage and operator-controlled autonomy.
|
|
6
|
+
|
|
7
|
+
SwarmAI runs on your own machine. It coordinates a swarm of specialised agents to handle messaging, scheduling, knowledge retrieval, and document workflows, talking to you through whatever channel you happen to be on.
|
|
8
|
+
|
|
9
|
+
This repository is the **bundled binary distribution**. The SwarmAI source is proprietary and not published here. Runtime dependencies (`nodemailer`, `imapflow`, `better-sqlite3`, etc.) are pulled from public npm at install time.
|
|
10
|
+
|
|
11
|
+
- 🐝 **Vendor:** [NorthPeak Malaysia](https://northpeak.app)
|
|
12
|
+
- 🐛 **Issues / support:** https://github.com/northpeakmalaysia/SwarmAI/issues
|
|
13
|
+
- 📜 **License:** [PolyForm Noncommercial 1.0.0](LICENSE) — **free for personal use, not for commercial use**
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Table of contents
|
|
18
|
+
|
|
19
|
+
- [Prerequisites](#prerequisites)
|
|
20
|
+
- [Install](#install)
|
|
21
|
+
- [Quick start](#quick-start)
|
|
22
|
+
- [What you can do](#what-you-can-do)
|
|
23
|
+
- [Multi-account email](#multi-account-email)
|
|
24
|
+
- [Pair the dashboard](#pair-the-dashboard)
|
|
25
|
+
- [Common operations](#common-operations)
|
|
26
|
+
- [Where your data lives](#where-your-data-lives)
|
|
27
|
+
- [Updating](#updating)
|
|
28
|
+
- [Troubleshooting](#troubleshooting)
|
|
29
|
+
- [License](#license)
|
|
30
|
+
- [Support](#support)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Prerequisites
|
|
35
|
+
|
|
36
|
+
| Requirement | Notes |
|
|
37
|
+
|-------------|-------|
|
|
38
|
+
| **Node.js ≥ 22** | [Download from nodejs.org](https://nodejs.org). LTS or current both work. |
|
|
39
|
+
| **Git** | For cloning this repo. |
|
|
40
|
+
| **C/C++ toolchain** | Needed by `better-sqlite3` + `sharp` during `npm install`. See per-OS below. |
|
|
41
|
+
|
|
42
|
+
**Per-OS toolchain:**
|
|
43
|
+
- **Windows:** Install [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) with the *"Desktop development with C++"* workload.
|
|
44
|
+
- **macOS:** `xcode-select --install` (Xcode Command Line Tools).
|
|
45
|
+
- **Linux (Debian/Ubuntu):** `sudo apt install build-essential`. RHEL/Fedora: `sudo dnf groupinstall "Development Tools"`.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone https://github.com/northpeakmalaysia/SwarmAI.git
|
|
53
|
+
cd SwarmAI
|
|
54
|
+
npm install
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`npm install` will compile `better-sqlite3` and `sharp` against your local Node version + OS. First-run takes ~30–60 seconds depending on machine.
|
|
58
|
+
|
|
59
|
+
Verify the install:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
node swarmai.js --version
|
|
63
|
+
node swarmai.js --help
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Quick start
|
|
69
|
+
|
|
70
|
+
Three commands and you're running:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
node swarmai.js setup # 1. Interactive bootstrap — picks providers, creates vault, sets your master password
|
|
74
|
+
node swarmai.js start # 2. Launch the gateway (server + dashboard)
|
|
75
|
+
node swarmai.js status # 3. Check it's alive
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The dashboard becomes available at **http://localhost:18789**. The gateway API at **http://localhost:7910**.
|
|
79
|
+
|
|
80
|
+
To stop:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
node swarmai.js stop
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## What you can do
|
|
89
|
+
|
|
90
|
+
Once running, SwarmAI gives you:
|
|
91
|
+
|
|
92
|
+
- **A single AI assistant across all your channels** — message it via WhatsApp, Telegram, email, or the dashboard chat. Same context, same memory.
|
|
93
|
+
- **Self-hosted, local-first storage** — your conversations, vault, and credentials stay on your machine. Nothing leaves unless you configure a remote provider.
|
|
94
|
+
- **Multi-channel email** with reply-to-thread, structured replies, and per-account isolation (see below).
|
|
95
|
+
- **Operator-gated autonomy** — you decide what your agent can do unattended via standing approvals.
|
|
96
|
+
- **Dashboard for management** — channels, approvals, settings, logs, real-time status.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Multi-account email
|
|
101
|
+
|
|
102
|
+
SwarmAI supports running multiple email accounts simultaneously — `email:primary`, `email:support`, `email:sales`, etc. Pairings are isolated per account: a sender paired with `support@` cannot reach the agent via `ceo@`.
|
|
103
|
+
|
|
104
|
+
**List accounts:**
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
node swarmai.js email list
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Add an account** (Gmail / Outlook / Yahoo / iCloud presets autofill SMTP/IMAP):
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
node swarmai.js email add-account support \
|
|
114
|
+
--provider gmail \
|
|
115
|
+
--address support@example.com \
|
|
116
|
+
--app-password "xxxx xxxx xxxx xxxx"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
For Gmail/Yahoo/iCloud you need an **App Password** (not your account password). Generate one at:
|
|
120
|
+
- [Gmail App Passwords](https://myaccount.google.com/apppasswords)
|
|
121
|
+
- [Outlook App Passwords](https://account.microsoft.com/security)
|
|
122
|
+
- [Yahoo App Passwords](https://login.yahoo.com/account/security)
|
|
123
|
+
- [iCloud App-Specific Passwords](https://support.apple.com/en-us/102654)
|
|
124
|
+
|
|
125
|
+
For other providers use `--provider custom` and supply `--smtp-host`, `--smtp-port`, `--imap-host`, `--imap-port`.
|
|
126
|
+
|
|
127
|
+
**Remove an account:**
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
node swarmai.js email remove-account support
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Changes take effect on the next server restart (`stop` then `start`).
|
|
134
|
+
|
|
135
|
+
You can also manage accounts from the dashboard's **Settings → Channels → Email** tab — the multi-account UI lists each account with add/remove inline.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Pair the dashboard
|
|
140
|
+
|
|
141
|
+
The first time you open `http://localhost:18789` the dashboard asks you to pair. Generate a code from the CLI:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
node swarmai.js pair dashboard --master
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Type the 6-digit code into the dashboard. It mints a token bound to your master scope.
|
|
148
|
+
|
|
149
|
+
To revoke all dashboard tokens:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
node swarmai.js logout dashboard
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Common operations
|
|
158
|
+
|
|
159
|
+
| Command | Purpose |
|
|
160
|
+
|---------|---------|
|
|
161
|
+
| `node swarmai.js status` | Health snapshot of the gateway |
|
|
162
|
+
| `node swarmai.js doctor` | Diagnose config + provider + network issues |
|
|
163
|
+
| `node swarmai.js logs --tail 50` | Stream the event bus (like `docker logs -f`) |
|
|
164
|
+
| `node swarmai.js whoami` | Show your effective master scopes |
|
|
165
|
+
| `node swarmai.js master-unlock` | Push your master passphrase to the running server |
|
|
166
|
+
| `node swarmai.js mfa enable` | Enable TOTP / recovery codes |
|
|
167
|
+
| `node swarmai.js channel list` | List configured channel adapters |
|
|
168
|
+
| `node swarmai.js task list` | Show background tasks via the running server |
|
|
169
|
+
|
|
170
|
+
Run `node swarmai.js <command> --help` for any subcommand to see all flags.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Where your data lives
|
|
175
|
+
|
|
176
|
+
| Path | Contents |
|
|
177
|
+
|------|----------|
|
|
178
|
+
| `~/.swarmai/` (default) | Workspace root — vault, ledgers, sessions, peer state, replays |
|
|
179
|
+
| `~/.swarmai/vault.json` | AES-256-GCM encrypted secret store (channel credentials, etc.) |
|
|
180
|
+
| `~/.swarmai/masters.yaml` | Identity registry — master + paired guests |
|
|
181
|
+
| `~/.swarmai/agents/` | Per-agent persona files (`CHARTER.md`, `MANDATE.md`, `LEDGER.md`, …) |
|
|
182
|
+
| `~/.swarmai/sessions.db` | SQLite — conversation transcripts |
|
|
183
|
+
| `~/.swarmai/journal/` | Audit trail of approvals, channel events, autonomy decisions |
|
|
184
|
+
|
|
185
|
+
To move the workspace, set the `SWARMAI_WORKSPACE` environment variable before running:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
SWARMAI_WORKSPACE=/path/to/workspace node swarmai.js start
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Backups:** copy the workspace directory while the server is stopped.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Updating
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
git pull
|
|
199
|
+
npm install # picks up dependency updates and rebuilds native modules
|
|
200
|
+
node swarmai.js stop
|
|
201
|
+
node swarmai.js start
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
If `npm install` fails after a Node upgrade, force a native rebuild:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
npm rebuild better-sqlite3 sharp
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Troubleshooting
|
|
213
|
+
|
|
214
|
+
**"Cannot find module 'better-sqlite3'" or similar after Node upgrade.**
|
|
215
|
+
Native modules are compiled against a specific Node major version. Run `npm rebuild` to recompile.
|
|
216
|
+
|
|
217
|
+
**Mac: "swarmai is damaged and can't be opened" (or similar Gatekeeper message).**
|
|
218
|
+
This distribution isn't yet code-signed for Mac. You can right-click the JS file and choose "Open" once to accept it, or run via `node swarmai.js` directly.
|
|
219
|
+
|
|
220
|
+
**Windows: SmartScreen blocks execution.**
|
|
221
|
+
Same root cause as above — distribution isn't yet Authenticode-signed. Click "More info → Run anyway" once.
|
|
222
|
+
|
|
223
|
+
**`npm install` fails on Windows with `node-gyp` errors.**
|
|
224
|
+
Make sure Visual Studio Build Tools is installed with the *Desktop development with C++* workload. Run `npm install` from a fresh terminal so PATH picks up the new tools.
|
|
225
|
+
|
|
226
|
+
**Gateway doesn't start — port already in use.**
|
|
227
|
+
Default ports are **7910** (server) and **18789** (dashboard). Set `SWARMAI_PORT` and `SWARMAI_DASHBOARD_PORT` to override, or `node swarmai.js stop` to clean up a stale process.
|
|
228
|
+
|
|
229
|
+
**Forgot the master password.**
|
|
230
|
+
```bash
|
|
231
|
+
node swarmai.js reset masterpass --forgot
|
|
232
|
+
```
|
|
233
|
+
This wipes the vault and requires re-pairing every channel.
|
|
234
|
+
|
|
235
|
+
**Email channel keeps disconnecting.**
|
|
236
|
+
Check `node swarmai.js doctor` — most common causes are a wrong app password (not the account password) or IMAP being disabled in your provider's account settings.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
This software is licensed under the **[PolyForm Noncommercial License 1.0.0](LICENSE)**.
|
|
243
|
+
|
|
244
|
+
> **In plain English:**
|
|
245
|
+
> - ✅ **Free for personal use** — run it on your own machine, for your own correspondence, learning, hobby projects.
|
|
246
|
+
> - ✅ **Free for noncommercial research / education / charity / nonprofit work**.
|
|
247
|
+
> - ❌ **NOT free for commercial use** — selling SwarmAI as a service, deploying it inside a for-profit organisation's operations, integrating it into a commercial product, or using it to operate a business is **not permitted** under this license.
|
|
248
|
+
> - 📩 **Need a commercial license?** Contact NorthPeak Malaysia.
|
|
249
|
+
|
|
250
|
+
The full license text is in [`LICENSE`](LICENSE). Cloning this repository does **not** grant you any rights beyond what the license states.
|
|
251
|
+
|
|
252
|
+
Third-party dependencies installed via `npm install` ship under their own licenses (mostly MIT / Apache-2.0 / ISC) — see `node_modules/<pkg>/LICENSE` for each.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Support
|
|
257
|
+
|
|
258
|
+
- **Issues:** https://github.com/northpeakmalaysia/SwarmAI/issues
|
|
259
|
+
- **Vendor / commercial inquiries:** https://northpeak.app
|
|
260
|
+
- **Maintainer:** NorthPeak Malaysia 🇲🇾
|
package/bee.png
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@northpeak/swarmai",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "SwarmAI — your personal multi-channel AI assistant.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"swarmai": "swarmai.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "swarmai.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"swarmai.js",
|
|
12
|
+
"server.js",
|
|
13
|
+
"plugins/",
|
|
14
|
+
"bee.png",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.0.0"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@inquirer/prompts": "^8.4.2",
|
|
23
|
+
"@napi-rs/keyring": "^1.3.0",
|
|
24
|
+
"@noble/ed25519": "^3.1.0",
|
|
25
|
+
"@noble/hashes": "^1.5.0",
|
|
26
|
+
"@whiskeysockets/baileys": "^6.7.21",
|
|
27
|
+
"better-sqlite3": "^11.7.0",
|
|
28
|
+
"docx": "^9.0.2",
|
|
29
|
+
"exceljs": "^4.4.0",
|
|
30
|
+
"imapflow": "^1.0.171",
|
|
31
|
+
"mailparser": "^3.7.2",
|
|
32
|
+
"mammoth": "^1.8.0",
|
|
33
|
+
"marked": "^18.0.3",
|
|
34
|
+
"nodemailer": "^8.0.7",
|
|
35
|
+
"otplib": "^12.0.1",
|
|
36
|
+
"papaparse": "^5.4.1",
|
|
37
|
+
"pdf-lib": "^1.17.1",
|
|
38
|
+
"pdf-parse": "^1.1.1",
|
|
39
|
+
"picocolors": "^1.1.1",
|
|
40
|
+
"pino": "^9.5.0",
|
|
41
|
+
"pino-pretty": "^13.0.0",
|
|
42
|
+
"playwright": "^1.59.1",
|
|
43
|
+
"qrcode-terminal": "^0.12.0",
|
|
44
|
+
"sharp": "^0.33.5",
|
|
45
|
+
"telegram": "^2.26.22",
|
|
46
|
+
"ws": "^8.18.0",
|
|
47
|
+
"yaml": "^2.5.1",
|
|
48
|
+
"zod": "^3.23.8",
|
|
49
|
+
"zod-to-json-schema": "^3.24.1"
|
|
50
|
+
},
|
|
51
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SwarmAI bundled distribution. Source code is proprietary.
|
|
3
|
+
|
|
4
|
+
import S from"pino";import{existsSync as _,mkdirSync as q,statSync as L,renameSync as W,readdirSync as j,unlinkSync as z,createWriteStream as A,chmodSync as Q}from"node:fs";import{join as O}from"node:path";var H=!1,N=S({level:"info"});function K(e={}){let t=e.level??process.env.SWARMAI_LOG_LEVEL??"info",o=e.pretty??!1,n=[],i=o?S.transport({target:"pino-pretty",options:{colorize:!0,translateTime:"SYS:HH:MM:ss",ignore:"pid,hostname"}}):S.destination(1);n.push({stream:i}),e.file&&e.file.dir&&n.push({stream:V(e.file)});let l={level:t};if(e.redactor){let a=e.redactor;l.formatters={log:u=>a(u)}}return N=n.length>1?S(l,S.multistream(n)):S(l,n[0].stream),H=!0,N}function V(e){let t=e.dir;_(t)||q(t,{recursive:!0});let o=e.stem??"swarmai",n=e.rotateAtBytes??10*1024*1024,i=e.retentionDays??14,l=m=>{try{Q(m,384)}catch{}},a=D(t,o),u=A(a,{flags:"a"});l(a);let d=R(),s=m=>{let r=R();if(r!==d){u.end(),d=r,a=D(t,o),u=A(a,{flags:"a"}),l(a),G(t,o,i);return}if(n>0){let c=0;try{c=L(a).size}catch{}if(c+m>n){u.end();let g=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19),w=O(t,`${o}-${d}-${g}.log`);try{W(a,w),l(w)}catch{}u=A(a,{flags:"a"}),l(a)}}};return{write(m){let r=typeof m=="string"?Buffer.byteLength(m):m.byteLength;s(r),u.write(m)}}}function R(e=new Date){return e.toISOString().slice(0,10)}function D(e,t){return O(e,`${t}-${R()}.log`)}function G(e,t,o){if(o<=0)return;let n=Date.now()-o*864e5;try{for(let i of j(e))if(i.startsWith(t+"-")&&i.endsWith(".log"))try{let l=O(e,i);L(l).mtimeMs<n&&z(l)}catch{}}catch{}}var y=new Proxy({},{get(e,t){return H||K({}),N[t]}});import{z as p}from"zod";var Y=p.enum(["system","user","assistant","tool"]),J=p.object({id:p.string(),name:p.string(),arguments:p.string()}),be=p.object({role:Y,content:p.string().optional(),name:p.string().optional(),toolCallId:p.string().optional(),toolCalls:p.array(J).optional(),reasoning:p.string().optional()}),Ce=p.enum(["heavy","average","simple","vision","embedding","speech-tts","speech-stt"]),Me=p.enum(["cli","gateway","monitor","cron","peer-bus","plan-step","bootstrap"]),Ee=p.object({agentId:p.string(),nodeId:p.string().optional()}),Ie=p.object({inputTokens:p.number().int().nonnegative(),outputTokens:p.number().int().nonnegative(),cachedInputTokens:p.number().int().nonnegative().default(0),costUsd:p.number().nonnegative().optional()});import{z as T}from"zod";var k=T.object({apiId:T.number().int().positive().optional(),apiHash:T.string().min(1).optional(),session:T.string().optional(),useTestDc:T.boolean().default(!1),reconnectBaseBackoffMs:T.number().int().positive().default(1e3),reconnectMaxBackoffMs:T.number().int().positive().default(3e4),reconnectMaxAttempts:T.number().int().positive().default(5),markRead:T.boolean().default(!0),typingIndicator:T.boolean().default(!0),respondToMentions:T.boolean().default(!0),selfDisplayName:T.string().optional()}),M=T.object({}).passthrough();import{EventEmitter as Z}from"node:events";var v=class extends Z{config;adapter;socket=null;status="idle";consecutiveFailures=0;reconnectTimer=null;stopRequested=!1;self=null;offMessage=null;constructor(t){super(),this.config=t.config,this.adapter=t.adapter??te()}getStatus(){return this.status}getSelf(){return this.self}getSocket(){return this.socket}async start(){if(this.stopRequested=!1,!this.config.apiId||!this.config.apiHash){this.setStatus("not-paired"),this.emit("not-paired");return}let t=this.config.session??"";if(this.socket=this.adapter.makeSocket({apiId:this.config.apiId,apiHash:this.config.apiHash,stringSession:t,useTestDc:this.config.useTestDc}),!t){this.setStatus("not-paired"),this.emit("not-paired");return}this.setStatus("connecting"),this.emit("connecting");try{await this.socket.connect(),this.self=await this.socket.getSelfEntity(),this.consecutiveFailures=0,this.setStatus("connected"),this.emit("connected",{self:this.self})}catch(o){let n=o instanceof Error?o.message:String(o);if(X(n)){this.setStatus("session-expired"),this.emit("session-expired",{detail:n}),this.emit("disconnected",{reason:"logged-out",detail:n});return}this.emit("disconnected",{reason:"transient",detail:n}),this.stopRequested||this.scheduleReconnect();return}this.offMessage=this.socket.onNewMessage(o=>{try{if(o.out)return;this.emit("message",o)}catch(n){y.warn({err:n instanceof Error?n.message:String(n)},"telegram-client: inbound dispatch threw")}})}async sendText(t,o){if(!this.socket)throw new Error("telegram-client: socket not started");return this.socket.sendMessage(t,o)}async sendFile(t,o){if(!this.socket)throw new Error("telegram-client: socket not started");return this.socket.sendFile(t,o)}async markRead(t,o){if(this.socket)try{await this.socket.markRead(t,o)}catch(n){y.debug({err:n instanceof Error?n.message:String(n)},"telegram-client: markRead failed (non-fatal)")}}async setTyping(t,o){if(this.socket)try{await this.socket.setTyping(t,o)}catch(n){y.debug({err:n instanceof Error?n.message:String(n)},"telegram-client: setTyping failed (non-fatal)")}}async resolveEntity(t){if(!this.socket)return null;try{return await this.socket.resolveEntity(t)}catch{return null}}async invalidateSession(){if(this.socket)try{await this.socket.invalidateSession()}catch(t){y.warn({err:t instanceof Error?t.message:String(t)},"telegram-client: invalidateSession failed")}}async stop(){if(this.stopRequested=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.offMessage){try{this.offMessage()}catch{}this.offMessage=null}if(this.socket){try{await this.socket.disconnect()}catch(t){y.debug({err:t instanceof Error?t.message:String(t)},"telegram-client: socket disconnect threw (ignored)")}this.socket=null}this.setStatus("idle")}setStatus(t){this.status=t}scheduleReconnect(){if(this.consecutiveFailures+=1,this.consecutiveFailures>this.config.reconnectMaxAttempts){this.setStatus("session-down"),this.emit("session-down",{attempts:this.consecutiveFailures});return}let o=Math.min(this.config.reconnectBaseBackoffMs*Math.pow(2,this.consecutiveFailures-1),this.config.reconnectMaxBackoffMs)*(.8+Math.random()*.4),n=Math.round(o);this.setStatus("reconnecting"),this.emit("reconnecting",{attempt:this.consecutiveFailures,delayMs:n}),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,!this.stopRequested&&this.start().catch(i=>this.emit("error",i instanceof Error?i:new Error(String(i))))},n)}};function X(e){if(!e)return!1;let t=e.toUpperCase();return t.includes("AUTH_KEY_UNREGISTERED")||t.includes("AUTH_KEY_INVALID")||t.includes("SESSION_REVOKED")||t.includes("SESSION_EXPIRED")||t.includes("USER_DEACTIVATED")||t.includes("USER_BANNED")}var E=null;async function ee(){if(E)return E;try{let e=await import("telegram"),t=await import("telegram/sessions"),o=await import("telegram/events");return E={TelegramClient:e.TelegramClient??e.default?.TelegramClient,sessions:{StringSession:t.StringSession??t.default?.StringSession},events:{NewMessage:o.NewMessage??o.default?.NewMessage},Api:e.Api??e.default?.Api},E}catch(e){throw new Error(`telegram-client: \`telegram\` package is not installed. Run \`pnpm add telegram qrcode-terminal\` in the consuming app, or pick a different channel mode in vault.json.
|
|
5
|
+
Original error: ${e instanceof Error?e.message:String(e)}`)}}function te(){return{makeSocket(e){let t=null,o=async()=>t||(t=await ne(e),t);return{connect:()=>o().then(n=>n.connect()),disconnect:()=>t?t.disconnect():Promise.resolve(),invalidateSession:()=>t?t.invalidateSession():Promise.resolve(),onNewMessage:n=>{let i=null,l=!0;return o().then(a=>{l&&(i=a.onNewMessage(n))}).catch(()=>{}),()=>{l=!1,i&&i()}},sendMessage:(n,i,l)=>o().then(a=>a.sendMessage(n,i,l)),sendFile:(n,i)=>o().then(l=>l.sendFile(n,i)),markRead:(n,i)=>o().then(l=>l.markRead(n,i)),setTyping:(n,i)=>o().then(l=>l.setTyping(n,i)),getSelfEntity:()=>o().then(n=>n.getSelfEntity()),resolveEntity:n=>o().then(i=>i.resolveEntity(n)),pairWithQr:n=>o().then(i=>i.pairWithQr(n)),pairWithPhone:n=>o().then(i=>i.pairWithPhone(n))}}}}async function ne(e){let t=await ee(),{TelegramClient:o,sessions:n,events:i,Api:l}=t,a=new n.StringSession(e.stringSession),u=new o(a,e.apiId,e.apiHash,{connectionRetries:5,useWSS:!0,testServers:e.useTestDc??!1});return{async connect(){if(await u.connect(),!await u.isUserAuthorized())throw new Error("AUTH_KEY_UNREGISTERED")},async disconnect(){await u.disconnect()},async invalidateSession(){try{await u.invoke(new l.auth.LogOut)}catch(d){y.debug({err:d instanceof Error?d.message:String(d)},"telegram-client: LogOut threw (likely already invalid)")}},onNewMessage(d){let s=m=>{try{let r=m?.message??m;d({id:r.id,message:r.message??r.text??"",chatId:r.chatId??m.chatId,senderId:r.senderId,date:r.date,out:!!r.out,media:r.media?{className:r.media.className,photo:r.media.photo?{mimeType:r.media.photo.mimeType}:void 0,document:r.media.document?{mimeType:r.media.document.mimeType,attributes:r.media.document.attributes}:void 0}:void 0,entities:Array.isArray(r.entities)?r.entities.map(c=>({className:c.className,offset:c.offset,length:c.length,userId:c.userId})):void 0,chatTitle:r.chat?.title,peerKind:re(r.peer??r.toId,r.chat)})}catch(r){y.debug({err:r instanceof Error?r.message:String(r)},"telegram-client: NewMessage normalise threw")}};return u.addEventHandler(s,new i.NewMessage({})),()=>{try{u.removeEventHandler(s,new i.NewMessage({}))}catch{}}},async sendMessage(d,s,m){return(await u.sendMessage(d,{message:s,...m?.replyToMsgId!==void 0?{replyTo:m.replyToMsgId}:{},...m?.linkPreview===!1?{linkPreview:!1}:{}}))?.id},async sendFile(d,s){if(!s.file)throw new Error("sendFile requires `file`");return(await u.sendFile(d,{file:s.file,...s.caption?{caption:s.caption}:{},...s.voiceNote?{voiceNote:!0}:{}}))?.id},async markRead(d,s){await u.markAsRead(d,s)},async setTyping(d,s){await u.invoke(new l.messages.SetTyping({peer:d,action:s?new l.SendMessageTypingAction:new l.SendMessageCancelAction}))},async getSelfEntity(){let d=await u.getMe();if(!d)return null;let s=String(d.id??"");return{id:s,username:d.username??void 0,displayName:[d.firstName,d.lastName].filter(Boolean).join(" ")||d.username||s,kind:"user"}},async resolveEntity(d){try{let s=await u.getEntity(d);return s?{id:String(s.id??""),username:s.username??void 0,displayName:s.title??s.firstName??s.username??String(s.id),kind:ie(s)}:null}catch{return null}},async pairWithQr(d){await u.connect();let s=await u.signInUserWithQrCode({apiId:d.apiId,apiHash:d.apiHash},{qrCode:async c=>{let g=oe(c.token);await d.onToken(`tg://login?token=${g}`)},password:d.askPassword?async()=>{if(!d.askPassword)throw new Error("2FA prompt requested but no askPassword handler");return d.askPassword()}:void 0,onError:c=>{throw c}}),m=a.save(),r=s?{id:String(s.id??""),username:s.username??void 0,displayName:[s.firstName,s.lastName].filter(Boolean).join(" ")||s.username||String(s.id),kind:"user"}:null;return{stringSession:m,self:r}},async pairWithPhone(d){await u.connect();let s=await u.start({phoneNumber:async()=>d.phone,phoneCode:async()=>d.askCode(),password:d.askPassword?async()=>{if(!d.askPassword)throw new Error("2FA prompt requested but no askPassword handler");return d.askPassword()}:void 0,onError:c=>{throw c}}),m=a.save(),r=s?{id:String(s.id??""),username:s.username??void 0,displayName:[s.firstName,s.lastName].filter(Boolean).join(" ")||s.username||String(s.id),kind:"user"}:null;return{stringSession:m,self:r}}}}function re(e,t){let o=e?.className??t?.className??"";return o==="PeerUser"||o==="User"?"private":o==="PeerChat"||o==="Chat"?"group":o==="PeerChannel"||o==="Channel"?t?.megagroup?"supergroup":t?.broadcast?"channel":"supergroup":"private"}function ie(e){let t=e?.className??"";return t==="User"?"user":t==="Chat"?"chat":t==="Channel"?e?.broadcast?"channel-broadcast":"channel-supergroup":"user"}function oe(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function B(e,t,o={}){if(t.out||typeof t.id!="number")return null;let n=t.peerKind??"private",i=n!=="private",l=t.message??"",a=se(t.media),u=t.senderId!==void 0?String(t.senderId):void 0,d=t.chatId!==void 0?String(t.chatId):void 0,s=ae(t,l,o),m={chatType:n};i&&(m.groupChat=!0,d&&(m.groupId=d),t.chatTitle&&(m.groupName=t.chatTitle)),s&&(m.mentioned=!0);let r=u??d??"";if(!r)return null;let c=l;return!c&&!a.length&&t.media&&(c=`[telegram-client:${t.media.className??"unknown-media"}]`),{channelId:e,from:r,body:c,attachments:a.length?a:void 0,raw:t,receivedAt:t.date?new Date(t.date*1e3):new Date,...Object.keys(m).length>0?{flags:m}:{}}}function se(e){if(!e)return[];if(e.photo||e.className==="MessageMediaPhoto")return[{kind:"image",mimeType:e.photo?.mimeType??"image/jpeg"}];if(e.document||e.className==="MessageMediaDocument"){let t=e.document,o=t?.mimeType??"",n=t?.attributes??[];if(o.startsWith("video/")||n.some(a=>a.className==="DocumentAttributeVideo"))return[{kind:"video",mimeType:o||"video/mp4"}];let i=n.find(a=>a.className==="DocumentAttributeAudio");if(i||o.startsWith("audio/"))return[{kind:"audio",mimeType:o||(i?.voice?"audio/ogg":"audio/mpeg")}];let l=n.find(a=>a.className==="DocumentAttributeFilename");return[{kind:"file",mimeType:o||"application/octet-stream",...l?.fileName?{filename:l.fileName}:{}}]}return[]}function ae(e,t,o){let{selfUserId:n,selfUsername:i,selfDisplayName:l}=o;if(!n&&!i&&!l)return!1;if(n&&Array.isArray(e.entities)){for(let a of e.entities)if(a.className==="MessageEntityMentionName"&&a.userId!==void 0&&String(a.userId)===n)return!0}if(i&&Array.isArray(e.entities)&&t){let a=i.toLowerCase();for(let u of e.entities)if(u.className==="MessageEntityMention"&&typeof u.offset=="number"&&typeof u.length=="number"&&t.slice(u.offset,u.offset+u.length).replace(/^@/,"").toLowerCase()===a)return!0}if(i&&t){let a=`@${i}`.toLowerCase();if(t.toLowerCase().includes(a))return!0}if(l&&t){let a=`@${l}`.toLowerCase();if(t.toLowerCase().includes(a))return!0}return!1}function $e(e){return e??"private"}var le={image:10485760,video:2147483648,audio:2147483648,file:2147483648};async function $(e,t){if(t.channelId!==e.channelId)return{ok:!1,detail:`channelId mismatch: got ${t.channelId}, expected ${e.channelId}`};let o=(t.attachments?.length??0)>0;if((!t.body||t.body.length===0)&&!o)return{ok:!1,detail:"empty body \u2014 refusing to send"};let n=ce(t.to);if(n===null)return{ok:!1,detail:`cannot resolve recipient from "${t.to}"`};if(e.onBeforeSend)try{await e.onBeforeSend(n)}catch{}let i,l=!1;try{if(o){for(let a of t.attachments??[]){let u=de(a);if(!u.ok)return{ok:!1,detail:u.detail};let d;try{d=await me(a,e.fetchImpl)}catch(w){return{ok:!1,detail:`media fetch failed: ${w instanceof Error?w.message:String(w)}`}}let s=le[a.kind];if(d.byteLength>s)return{ok:!1,detail:`attachment exceeds size cap (${F(d.byteLength)} > ${F(s)} for ${a.kind})`};let m=a.kind==="image"||a.kind==="video"||a.kind==="file",r=!l&&m?t.body:void 0;r!==void 0&&(l=!0);let c={file:d,...r?{caption:r}:{},...a.kind==="audio"&&ue(a.mimeType)?{voiceNote:!0}:{}},g=await e.client.sendFile(n,c);g!==void 0&&(i=g)}if(!l&&t.body&&t.body.length>0){let a=await e.client.sendText(n,t.body);a!==void 0&&(i=a)}}else{let a=await e.client.sendText(n,t.body);a!==void 0&&(i=a)}if(e.onAfterSend)try{await e.onAfterSend(n)}catch{}return{ok:!0,...i!==void 0?{messageId:i}:{}}}catch(a){return{ok:!1,detail:a instanceof Error?a.message:String(a)}}}function ce(e){let t=e.trim();if(!t)return null;if(/^-?\d+$/.test(t)){let o=Number(t);return Number.isSafeInteger(o)?o:t}return t.startsWith("+")&&/^\+\d+$/.test(t)||t.startsWith("@")?t:/^[A-Za-z0-9_]+$/.test(t)?`@${t}`:null}function de(e){if(!e.mimeType||typeof e.mimeType!="string")return{ok:!1,detail:"attachment.mimeType is required"};let t=e.mimeType.toLowerCase();switch(e.kind){case"image":if(!t.startsWith("image/"))return{ok:!1,detail:`image kind expects image/*, got ${e.mimeType}`};break;case"video":if(!t.startsWith("video/"))return{ok:!1,detail:`video kind expects video/*, got ${e.mimeType}`};break;case"audio":if(!t.startsWith("audio/"))return{ok:!1,detail:`audio kind expects audio/*, got ${e.mimeType}`};break;case"file":break;default:return{ok:!1,detail:`unsupported attachment kind: ${e.kind??"unset"}`}}return!e.data&&!e.url?{ok:!1,detail:"attachment must supply either data or url"}:{ok:!0}}function ue(e){let t=e.toLowerCase();return t.includes("ogg")&&t.includes("opus")}async function me(e,t){if(e.data)return Buffer.isBuffer(e.data)?e.data:Buffer.from(e.data);if(!e.url)throw new Error("attachment has neither data nor url");let n=await(t??(l=>globalThis.fetch(l)))(e.url);if(!n.ok)throw new Error(`fetch ${e.url} returned ${n.status}`);let i=await n.arrayBuffer();return Buffer.from(i)}function F(e){return`${(e/(1024*1024)).toFixed(1)}MB`}async function U(e){if(!e.apiId||!e.apiHash)throw new Error("telegram-client: apiId + apiHash are required to pair");let t=e.mode??"qr";if(t==="phone"&&!e.phone)throw new Error("telegram-client: phone mode requires --phone <e164>");let o=k.parse({apiId:e.apiId,apiHash:e.apiHash,useTestDc:!!e.useTestDc}),n=e.onInfo??he,i=e.timeoutMs??12e4,l=new v({config:o,...e.adapter?{adapter:e.adapter}:{}});await l.start();let a=l.getSocket();if(!a)throw new Error("telegram-client: socket failed to initialise");let u=e.onQr??(async c=>{await ge(c)}),d=e.askCode??fe,s=e.askPassword??pe,m=null,r=new Promise((c,g)=>{m=setTimeout(()=>{g(new Error(`telegram-client: pair timed out after ${Math.round(i/1e3)}s \u2014 the operator did not complete pairing in time. Re-run to try again.`))},i)});try{let c;return t==="qr"?(n("Waiting for you to scan the QR code in Telegram..."),c=await Promise.race([a.pairWithQr({apiId:e.apiId,apiHash:e.apiHash,onToken:async g=>{try{await u(g)}catch(w){n(`(QR render warning: ${w instanceof Error?w.message:String(w)})`)}},askPassword:s,timeoutMs:i}),r])):(n(`Sending login code to ${e.phone}...`),c=await Promise.race([a.pairWithPhone({apiId:e.apiId,apiHash:e.apiHash,phone:e.phone,askCode:d,askPassword:s}),r])),m&&clearTimeout(m),n(c.self?.username?`Connected as @${c.self.username} (${c.self.displayName??c.self.id}).`:`Connected as ${c.self?.displayName??c.self?.id??"unknown"}.`),{stringSession:c.stringSession,self:c.self,mode:t}}finally{m&&clearTimeout(m);try{await l.stop()}catch{}}}async function ge(e){let t=null;try{t=await import("qrcode-terminal")}catch{t=null}if(!t){let o=`[QR pairing \u2014 install qrcode-terminal to render ASCII art]
|
|
6
|
+
Raw token URL: ${e}
|
|
7
|
+
Or generate a QR via https://qr-code-generator.com using this URL.
|
|
8
|
+
`;process.stderr.write(o);return}await new Promise(o=>{t.generate(e,{small:!0},n=>{process.stderr.write(n+`
|
|
9
|
+
`),o()})})}async function fe(){let t=(await import("node:readline/promises")).createInterface({input:process.stdin,output:process.stderr});try{return(await t.question("Login code from Telegram (5 digits): ")).trim()}finally{t.close()}}async function pe(){let t=(await import("node:readline/promises")).createInterface({input:process.stdin,output:process.stderr});try{return(await t.question("2FA cloud password: ")).trim()}finally{t.close()}}function he(e){process.stderr.write(e+`
|
|
10
|
+
`)}function Qe(e){if(!e.apiId||!e.apiHash)throw new Error("telegram-client: apiId + apiHash are required to pair");let t=e.qrTtlMs??3e4,o=e.timeoutMs??3e5,n=!1,i=!1,l=null,a=null,u=null,d,s,m=new Promise((f,h)=>{d=f,s=h}),r=f=>{try{e.emitter.onEvent(f)}catch{}},c=f=>{n||(n=!0,r({kind:"success",username:f.username,sessionString:f.sessionString}),d(f))},g=(f,h)=>{n||(n=!0,r(i?{kind:"cancelled"}:{kind:"error",code:f,message:h}),a&&(a(new Error(`pair flow ${i?"cancelled":"failed"}`)),a=null,l=null),s(new Error(`${f}: ${h}`)))},w=async()=>(r({kind:"need-2fa"}),u||(u=new Promise((f,h)=>{l=f,a=h})),u),C=!1,I=()=>{C||(C=!0,r({kind:"scanned"}))};e.signal&&(e.signal.aborted?(i=!0,queueMicrotask(()=>g("cancelled","aborted before start"))):e.signal.addEventListener("abort",()=>{i=!0,g("cancelled","aborted by caller")},{once:!0}));let x={apiId:e.apiId,apiHash:e.apiHash,mode:"qr",askPassword:async()=>(I(),w()),onQr:async f=>{r({kind:"qr-ready",qrPayload:f,expiresAt:new Date(Date.now()+t).toISOString()})},onInfo:()=>{},timeoutMs:o};return e.useTestDc&&(x.useTestDc=!0),e.adapter&&(x.adapter=e.adapter),U(x).then(f=>{I();let h=f.self;c({username:h?.username?"@"+h.username:h?.displayName??h?.id??"unknown",sessionString:f.stringSession,selfId:h?.id??null,displayName:h?.displayName??null})},f=>{if(n)return;let h=f instanceof Error?f.message:String(f),b="pair-failed";/timed? out/i.test(h)?b="timeout":/SESSION_PASSWORD_NEEDED/i.test(h)?b="two-fa-required":/PASSWORD_HASH_INVALID/i.test(h)?b="two-fa-wrong":/AUTH_KEY/i.test(h)&&(b="auth-failed"),g(b,h)}),{promise:m,submit2fa(f){n||(l?(l(f),l=null,a=null):u=Promise.resolve(f))},cancel(){n||(i=!0,g("cancelled","cancelled by user"))}}}var ye={dm:!0,group:!0,thread:!0,reaction:!0,edit:!0,delete:!0,mediaImage:!0,mediaVideo:!0,mediaAudio:!0,voiceMemo:!0,voiceCall:!1,typing:!0,readReceipt:!0,formatting:"platform",maxMessageBytes:4096,maxAttachmentBytes:2*1024*1024*1024,rateLimit:{perMinute:30,perHour:500}},P="telegram-client";function we(e={}){let t=!1,o=null,n=null,i=null,l=null,a={id:P,displayName:"Telegram (Personal)",description:"Telegram MTProto / user-account \u2014 pair via QR or phone+SMS. Real account, full DM + group + history surface the Bot API can't reach.",version:"0.0.1",kind:"both",defaultDmPolicy:"pairing",features:ye,authSchema:M,configSchema:k,async start(s,m){o=k.parse(s.config??{}),n=m,M.parse(s.secrets??{}),i=new v({config:o,...e.adapter?{adapter:e.adapter}:{}}),i.on("connecting",()=>{let r={kind:"connecting"};l=r,e.onConnectionEvent?.(r)}),i.on("not-paired",()=>{let r={kind:"not-paired"};l=r,e.onConnectionEvent?.(r),y.warn("telegram-client: no session configured \u2014 run `swarmai telegram-client pair`")}),i.on("connected",({self:r})=>{let c={kind:"connected",self:r};l=c,e.onConnectionEvent?.(c),y.info({username:r?.username,id:r?.id},"telegram-client: connected")}),i.on("reconnecting",({attempt:r,delayMs:c})=>{let g={kind:"reconnecting",attempt:r,delayMs:c};l=g,e.onConnectionEvent?.(g)}),i.on("disconnected",r=>{let c={kind:"disconnected",reason:r.reason,...r.detail?{detail:r.detail}:{}};l=c,e.onConnectionEvent?.(c)}),i.on("session-expired",r=>{let c={kind:"session-expired",...r.detail?{detail:r.detail}:{}};l=c,e.onConnectionEvent?.(c),y.warn({detail:r.detail},"telegram-client: session expired \u2014 run `swarmai telegram-client pair` to re-pair")}),i.on("session-down",({attempts:r})=>{let c={kind:"session-down",attempts:r};l=c,e.onConnectionEvent?.(c),y.warn({attempts:r},"telegram-client: gave up reconnecting")}),i.on("error",r=>{y.warn({err:r instanceof Error?r.message:String(r)},"telegram-client: client error")}),i.on("message",async r=>{try{if(!n||!o)return;let c=i?.getSelf(),g=B(P,r,{...c?.id?{selfUserId:c.id}:{},...c?.username?{selfUsername:c.username}:{},...e.selfDisplayName?{selfDisplayName:e.selfDisplayName}:{}});if(!g)return;let w=g.flags?.groupChat===!0,C=g.flags?.mentioned===!0;(!w||!o.respondToMentions||C)&&await n(g),e.onEvent&&await e.onEvent(g),o.markRead&&r.chatId!==void 0&&typeof r.id=="number"&&await i?.markRead(String(r.chatId),r.id)}catch(c){y.warn({err:c instanceof Error?c.message:String(c)},"telegram-client: inbound handler threw")}}),await i.start(),t=!0},async stop(){i&&(await i.stop(),i=null),t=!1,n=null},async healthCheck(){if(!t||!i)return{status:"down",detail:"not started"};let s=i.getStatus();switch(s){case"connected":return{status:"ok"};case"connecting":case"reconnecting":case"qr":case"awaiting-2fa":return{status:"degraded",detail:s};case"not-paired":return{status:"down",detail:"not-paired \u2014 run `swarmai telegram-client pair`"};default:return{status:"down",detail:s}}},async send(s){if(!t||!i||!o)throw new Error("telegram-client channel not started");if(s.channelId!==P)throw new Error(`channelId mismatch: got ${s.channelId}, expected ${P}`);let m=await $({client:i,channelId:P,...o.typingIndicator?{onBeforeSend:r=>i.setTyping(r,!0),onAfterSend:r=>i.setTyping(r,!1)}:{}},s);if(!m.ok)throw new Error(`telegram-client send failed: ${m.detail??"unknown"}`)}},u={id:P,kind:"push",authSchema:M,configSchema:k,async healthCheck(){if(!t||!i)return"down";let s=i.getStatus();return s==="connected"?"ok":s==="session-down"||s==="session-expired"||s==="not-paired"?"down":"degraded"},async webhook(s){return[]}};function d(s){return Promise.resolve({status:405,body:'{"error":"telegram-client has no webhook"}',inbound:[]})}return{channel:a,source:u,handleWebhook:d,getClient:()=>i,_lastEvent:()=>l}}function Xe(e={}){return we(e).source}export{le as MEDIA_SIZE_CAPS,ye as TELEGRAM_CLIENT_FEATURES,M as TelegramClientAuthSchema,k as TelegramClientConfigSchema,v as TelegramMtprotoClient,$e as chatTypeFromPeerKind,Xe as createTelegramClientMonitorSource,we as createTelegramClientPlugin,se as decodeMedia,te as defaultMTProtoAdapter,ge as defaultRenderQr,ae as detectMention,X as isSessionInvalid,ue as isVoiceMime,B as normaliseInbound,me as resolveMediaBuffer,ce as resolveTarget,U as runPairFlow,Qe as runTelegramClientPairForUi,$ as sendOutbound,de as validateAttachment};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SwarmAI bundled distribution. Source code is proprietary.
|
|
3
|
+
|
|
4
|
+
var ee=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,n)=>(typeof require<"u"?require:t)[n]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});import A from"pino";import{existsSync as te,mkdirSync as ne,statSync as O,renameSync as se,readdirSync as re,unlinkSync as oe,createWriteStream as T,chmodSync as ie}from"node:fs";import{join as P}from"node:path";var W=!1,R=A({level:"info"});function ae(e={}){let t=e.level??process.env.SWARMAI_LOG_LEVEL??"info",n=e.pretty??!1,s=[],o=n?A.transport({target:"pino-pretty",options:{colorize:!0,translateTime:"SYS:HH:MM:ss",ignore:"pid,hostname"}}):A.destination(1);s.push({stream:o}),e.file&&e.file.dir&&s.push({stream:le(e.file)});let r={level:t};if(e.redactor){let a=e.redactor;r.formatters={log:c=>a(c)}}return R=s.length>1?A(r,A.multistream(s)):A(r,s[0].stream),W=!0,R}function le(e){let t=e.dir;te(t)||ne(t,{recursive:!0});let n=e.stem??"swarmai",s=e.rotateAtBytes??10*1024*1024,o=e.retentionDays??14,r=d=>{try{ie(d,384)}catch{}},a=N(t,n),c=T(a,{flags:"a"});r(a);let f=x(),y=d=>{let l=x();if(l!==f){c.end(),f=l,a=N(t,n),c=T(a,{flags:"a"}),r(a),de(t,n,o);return}if(s>0){let i=0;try{i=O(a).size}catch{}if(i+d>s){c.end();let u=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19),b=P(t,`${n}-${f}-${u}.log`);try{se(a,b),r(b)}catch{}c=T(a,{flags:"a"}),r(a)}}};return{write(d){let l=typeof d=="string"?Buffer.byteLength(d):d.byteLength;y(l),c.write(d)}}}function x(e=new Date){return e.toISOString().slice(0,10)}function N(e,t){return P(e,`${t}-${x()}.log`)}function de(e,t,n){if(n<=0)return;let s=Date.now()-n*864e5;try{for(let o of re(e))if(o.startsWith(t+"-")&&o.endsWith(".log"))try{let r=P(e,o);O(r).mtimeMs<s&&oe(r)}catch{}}catch{}}var g=new Proxy({},{get(e,t){return W||ae({}),R[t]}});import{z as m}from"zod";var ce=m.enum(["system","user","assistant","tool"]),ue=m.object({id:m.string(),name:m.string(),arguments:m.string()}),nt=m.object({role:ce,content:m.string().optional(),name:m.string().optional(),toolCallId:m.string().optional(),toolCalls:m.array(ue).optional(),reasoning:m.string().optional()}),st=m.enum(["heavy","average","simple","vision","embedding","speech-tts","speech-stt"]),rt=m.enum(["cli","gateway","monitor","cron","peer-bus","plan-step","bootstrap"]),ot=m.object({agentId:m.string(),nodeId:m.string().optional()}),it=m.object({inputTokens:m.number().int().nonnegative(),outputTokens:m.number().int().nonnegative(),cachedInputTokens:m.number().int().nonnegative().default(0),costUsd:m.number().nonnegative().optional()});import{z as h}from"zod";var w=h.object({sessionId:h.string().min(1).default("default"),sessionDir:h.string().optional(),logLevel:h.enum(["silent","fatal","error","warn","info","debug","trace"]).default("silent"),markRead:h.boolean().default(!0),typingIndicator:h.boolean().default(!0),reconnectBaseBackoffMs:h.number().int().positive().default(1e3),reconnectMaxBackoffMs:h.number().int().positive().default(3e4),reconnectMaxAttempts:h.number().int().positive().default(5),respondToMentions:h.boolean().default(!0),useRealPino:h.union([h.boolean(),h.object({level:h.enum(["silent","fatal","error","warn","info","debug","trace"]).default("info")})]).optional(),lockHeartbeatMs:h.number().int().positive().default(3e4),lockStaleMs:h.number().int().positive().default(9e4)}),M=h.object({}).passthrough();import{mkdirSync as $,existsSync as v,chmodSync as me,writeFileSync as fe,readFileSync as he,unlinkSync as ye}from"node:fs";import{hostname as be}from"node:os";import{join as _}from"node:path";import{mkdirSync as ft,existsSync as ht}from"node:fs";import{homedir as pe}from"node:os";import{join as ge,sep as St}from"node:path";function B(e){return e?.root??process.env.SWARMAI_WORKSPACE??ge(pe(),".swarmai")}import{readFileSync as vt,writeFileSync as Et,existsSync as _t,copyFileSync as Mt,mkdirSync as Tt,readdirSync as Rt}from"node:fs";import{join as Pt}from"node:path";import{parse as Ct,stringify as It}from"yaml";import Wt from"better-sqlite3";import{appendFileSync as Ft,existsSync as Ht,writeFileSync as jt,readFileSync as qt}from"node:fs";import{createHmac as Yt}from"node:crypto";function Se(e){let t=e.baseDir??_(B(),"whatsapp-personal"),n=_(t,we(e.sessionId));return{baseDir:t,sessionDir:n}}function F(e){let t=Se(e);return v(t.baseDir)||($(t.baseDir,{recursive:!0}),D(t.baseDir,448)),v(t.sessionDir)||$(t.sessionDir,{recursive:!0}),D(t.sessionDir,448),t}function mn(e){try{return v(_(e,"creds.json"))}catch{return!1}}function we(e){let t=e.trim();if(!t)return"default";let n=t.replace(/[^A-Za-z0-9+._-]/g,"_");return n.includes("..")?n.replace(/\./g,"_"):n||"default"}function D(e,t){try{me(e,t)}catch(n){g.debug({path:e,err:n instanceof Error?n.message:String(n)},"whatsapp-personal: chmod skipped (filesystem unsupported)")}}var H=".swarmai-lock",C=class extends Error{constructor(n,s){super(`whatsapp-personal: session is locked by another process (pid=${s.pid}, host=${s.hostname}, last heartbeat ${new Date(s.heartbeatAt).toISOString()}). If that process is gone, delete ${n} or run \`swarmai whatsapp repair\`.`);this.lockPath=n;this.holder=s;this.name="SessionLockedError"}lockPath;holder};function j(e){let t=e.heartbeatMs??3e4,n=e.staleMs??9e4,s=e.now??(()=>Date.now()),o=e.pid??process.pid,r=e.hostname??ke(),a=_(e.sessionDir,H);if(v(a)){let l=q(a);if(l){let i=s()-l.heartbeatAt;if(l.pid===o&&l.hostname===r)g.debug({lockPath:a,pid:o},"whatsapp-personal: re-acquiring lock from same pid");else{if(i<=n)throw new C(a,l);g.warn({lockPath:a,staleAgeMs:i,previousPid:l.pid,previousHost:l.hostname},"whatsapp-personal: taking over stale lock")}}else g.warn({lockPath:a},"whatsapp-personal: lockfile present but unreadable \u2014 overwriting")}let c={pid:o,hostname:r,startedAt:s(),heartbeatAt:s()};U(a,c);let f=setInterval(()=>{let l={...c,heartbeatAt:s()};try{U(a,l),c.heartbeatAt=l.heartbeatAt}catch(i){g.debug({lockPath:a,err:i instanceof Error?i.message:String(i)},"whatsapp-personal: heartbeat write failed (non-fatal)")}},t);typeof f.unref=="function"&&f.unref();let y=!1;return{path:a,info:c,release:()=>{if(!y){y=!0,clearInterval(f);try{v(a)&&ye(a)}catch(l){g.debug({lockPath:a,err:l instanceof Error?l.message:String(l)},"whatsapp-personal: unlinkSync(lock) failed (non-fatal)")}}}}}function fn(e){return q(_(e,H))}function q(e){try{if(!v(e))return null;let t=he(e,"utf8"),n=JSON.parse(t);return typeof n.pid!="number"||typeof n.hostname!="string"||typeof n.startedAt!="number"||typeof n.heartbeatAt!="number"?null:{pid:n.pid,hostname:n.hostname,startedAt:n.startedAt,heartbeatAt:n.heartbeatAt}}catch{return null}}function U(e,t){fe(e,JSON.stringify(t),{encoding:"utf8",mode:384})}function ke(){try{return be()||"unknown"}catch{return"unknown"}}async function J(e,t={}){let n=t.stream??process.stderr,s=t.small??!0,o=null;try{o=await import("qrcode-terminal")}catch{o=null}if(!o){let r=`[QR pairing \u2014 install qrcode-terminal to render ASCII art]
|
|
5
|
+
Raw QR: ${e}
|
|
6
|
+
Or scan via https://qr-code-generator.com with this string.
|
|
7
|
+
`;t.onRender?t.onRender(e,r):n.write(r);return}await new Promise(r=>{o.generate(e,{small:s},a=>{t.onRender?t.onRender(e,a):n.write(a+`
|
|
8
|
+
`),r()})})}import{EventEmitter as Ae}from"node:events";import{existsSync as ve,mkdirSync as Ee}from"node:fs";var k=class extends Ae{config;adapter;loggerArg;socket=null;status="idle";consecutiveFailures=0;reconnectTimer=null;stopRequested=!1;saveCreds=null;sessionDir=null;phoneNumber=null;lockHandle=null;constructor(t){super(),this.config=t.config,this.adapter=t.adapter??Re(),this.loggerArg=t.loggerOverride??De(t.config)}getStatus(){return this.status}getPhoneNumber(){return this.phoneNumber}getSessionDir(){return this.sessionDir}async start(){this.stopRequested=!1;let t=F({sessionId:this.config.sessionId,...this.config.sessionDir?{baseDir:void 0}:{}});this.sessionDir=this.config.sessionDir??t.sessionDir,this.config.sessionDir&&!ve(this.config.sessionDir)&&Ee(this.config.sessionDir,{recursive:!0}),this.lockHandle||(this.lockHandle=j({sessionDir:this.sessionDir,heartbeatMs:this.config.lockHeartbeatMs,staleMs:this.config.lockStaleMs}));let{state:n,saveCreds:s}=await this.adapter.loadAuthState(this.sessionDir);this.saveCreds=s,this.setStatus("connecting"),this.emit("connecting");let o=this.adapter.makeSocket({auth:n,logger:this.loggerArg,printQRInTerminal:!1});this.socket=o,o.ev.on("connection.update",r=>this.handleConnectionUpdate(r)),o.ev.on("creds.update",()=>{this.saveCreds&&this.saveCreds().catch(r=>{g.warn({err:r instanceof Error?r.message:String(r)},"whatsapp-personal: saveCreds failed")})}),o.ev.on("messages.upsert",({messages:r})=>{for(let a of r)this.handleMessage(a)})}async sendText(t,n){if(!this.socket)throw new Error("whatsapp-personal: socket not started");return(await this.socket.sendMessage(t,{text:n}))?.key?.id}async sendMessage(t,n){if(!this.socket)throw new Error("whatsapp-personal: socket not started");return(await this.socket.sendMessage(t,n))?.key?.id}getOwnJid(){return this.socket?.user?.id??null}async markRead(t){if(this.socket?.readMessages)try{await this.socket.readMessages(t)}catch(n){g.debug({err:n instanceof Error?n.message:String(n)},"whatsapp-personal: markRead failed (non-fatal)")}}async setTyping(t,n){if(this.socket?.sendPresenceUpdate)try{await this.socket.sendPresenceUpdate(n?"composing":"paused",t)}catch(s){g.debug({err:s instanceof Error?s.message:String(s)},"whatsapp-personal: setTyping failed (non-fatal)")}}async stop(){if(this.stopRequested=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.socket){try{this.socket.end?await this.socket.end(void 0):this.socket.ws?.close?.()}catch(t){g.debug({err:t instanceof Error?t.message:String(t)},"whatsapp-personal: socket end threw (ignored)")}this.socket=null}if(this.lockHandle){try{this.lockHandle.release()}catch(t){g.debug({err:t instanceof Error?t.message:String(t)},"whatsapp-personal: lock release threw (ignored)")}this.lockHandle=null}this.setStatus("idle")}handleConnectionUpdate(t){if(t.qr&&(this.setStatus("qr"),this.emit("qr",t.qr)),t.connection==="connecting"&&(this.setStatus("connecting"),this.emit("connecting")),t.connection==="open"){this.consecutiveFailures=0;let n=this.socket?.user?.id??"";this.phoneNumber=_e(n),this.setStatus("connected"),this.emit("connected",{phoneNumber:this.phoneNumber??n})}if(t.connection==="close"){let n=t.lastDisconnect?.error,s=Me(n),o=Te(n);if(s===401||s===403){this.setStatus("session-expired"),this.emit("session-expired",{statusCode:s,detail:o}),this.emit("disconnected",{reason:"logged-out",statusCode:s,detail:o});return}if(this.emit("disconnected",{reason:"transient",statusCode:s,detail:o}),this.stopRequested){this.setStatus("idle");return}this.scheduleReconnect()}}scheduleReconnect(){if(this.consecutiveFailures+=1,this.consecutiveFailures>this.config.reconnectMaxAttempts){this.setStatus("session-down"),this.emit("session-down",{attempts:this.consecutiveFailures});return}let n=Math.min(this.config.reconnectBaseBackoffMs*Math.pow(2,this.consecutiveFailures-1),this.config.reconnectMaxBackoffMs)*(.8+Math.random()*.4),s=Math.round(n);this.setStatus("reconnecting"),this.emit("reconnecting",{attempt:this.consecutiveFailures,delayMs:s}),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,!this.stopRequested&&this.start().catch(o=>this.emit("error",o instanceof Error?o:new Error(String(o))))},s)}handleMessage(t){t?.key&&(t.key.fromMe||t.message&&this.emit("message",t))}setStatus(t){this.status=t}};function _e(e){if(!e)return null;let n=(e.split("@")[0]??"").split(":")[0]??"";return n?n.startsWith("+")?n:"+"+n:null}function Me(e){return!e||typeof e!="object"?void 0:e.output?.statusCode}function Te(e){if(e&&(e instanceof Error||typeof e=="object"))return e.message}function Re(){return{async loadAuthState(e){let t=await xe(),{state:n,saveCreds:s}=await t.useMultiFileAuthState(e);return{state:n,saveCreds:s}},makeSocket(e){return Pe().makeWASocket({auth:e.auth,logger:e.logger,printQRInTerminal:e.printQRInTerminal??!1})}}}var E=null;async function xe(){if(E)return E;try{let e=await import("@whiskeysockets/baileys");return E={makeWASocket:e.default??e.makeWASocket,useMultiFileAuthState:e.useMultiFileAuthState},E}catch(e){throw new Error(`whatsapp-personal: @whiskeysockets/baileys is not installed. Run \`pnpm add @whiskeysockets/baileys qrcode-terminal pino\` in the consuming app, or set the channel mode to \`cloud\` in vault.json.
|
|
9
|
+
Original error: ${e instanceof Error?e.message:String(e)}`)}}function Pe(){if(!E)throw new Error("whatsapp-personal: Baileys not preloaded \u2014 call loadBaileys() first");return E}function De(e){let t=e.useRealPino;if(t){let n=typeof t=="object"&&t&&"level"in t&&t.level?t.level:"info";try{let s=Ie();if(s)return s({level:n});g.warn("whatsapp-personal: useRealPino requested but `pino` is not installed; falling back to noop logger")}catch(s){g.warn({err:s instanceof Error?s.message:String(s)},"whatsapp-personal: pino load failed; falling back to noop logger")}}return Ce(e.logLevel)}function Ce(e){let t=()=>{},n={level:e,fatal:t,error:t,warn:t,info:t,debug:t,trace:t,child:()=>n};return n}function Ie(){try{let{createRequire:e}=ee("node:module"),n=e(import.meta.url)("pino");return n.default??n}catch{return null}}function Y(e,t,n={}){if(!t?.message||!t.key||t.key.fromMe)return null;let s=Ne(t);if(!s)return null;let{body:o,attachments:r,type:a,decoded:c}=Le(t.message),f=Oe(t.messageTimestamp),y=We(t.key.remoteJid),d=Be(t,o,n),l={chatType:y?"group":"private"};return y&&(l.groupChat=!0,t.key.remoteJid&&(l.groupId=t.key.remoteJid)),d&&(l.mentioned=!0),{channelId:e,from:s,body:c?o:o||`[whatsapp-personal:${a}]`,attachments:r.length?r:void 0,raw:t,receivedAt:new Date(f),...Object.keys(l).length>0?{flags:l}:{}}}function Le(e){if(typeof e.conversation=="string"&&e.conversation.length>0)return{body:e.conversation,attachments:[],type:"text",decoded:!0};if(e.extendedTextMessage?.text)return{body:e.extendedTextMessage.text,attachments:[],type:"text",decoded:!0};if(e.imageMessage)return{body:e.imageMessage.caption??"",attachments:[{kind:"image",mimeType:e.imageMessage.mimetype??"image/jpeg"}],type:"image",decoded:!0};if(e.videoMessage)return{body:e.videoMessage.caption??"",attachments:[{kind:"video",mimeType:e.videoMessage.mimetype??"video/mp4"}],type:"video",decoded:!0};if(e.audioMessage)return{body:"",attachments:[{kind:"audio",mimeType:e.audioMessage.mimetype??"audio/ogg"}],type:e.audioMessage.ptt?"voice":"audio",decoded:!0};if(e.documentMessage){let t=e.documentMessage.fileName??void 0;return{body:e.documentMessage.caption??"",attachments:[{kind:"file",mimeType:e.documentMessage.mimetype??"application/octet-stream",...t?{filename:t}:{}}],type:"document",decoded:!0}}return e.stickerMessage?{body:"",attachments:[{kind:"image",mimeType:e.stickerMessage.mimetype??"image/webp"}],type:"sticker",decoded:!0}:e.reactionMessage?{body:e.reactionMessage.text??"",attachments:[],type:"reaction",decoded:!0}:{body:"",attachments:[],type:"unknown",decoded:!1}}function Ne(e){let t=e.key.participant,n=e.key.remoteJid,s=t??n;return s?I(s):null}function I(e){return(e.split("@")[0]??"").split(":")[0]??""}function Oe(e){return typeof e=="number"?e*1e3:e&&typeof e=="object"&&"low"in e?e.low*1e3:Date.now()}function We(e){return typeof e=="string"&&e.endsWith("@g.us")}function Be(e,t,n){let s=n.selfJid?I(n.selfJid):null,o=n.selfDisplayName?.trim();if(!s&&!o)return!1;let r=e.message?.extendedTextMessage?.contextInfo?.mentionedJid??[];if(s&&Array.isArray(r)){for(let a of r)if(typeof a=="string"&&I(a)===s)return!0}if(s&&t&&t.includes(`@${s}`))return!0;if(o&&t){let a=`@${o}`.toLowerCase();if(t.toLowerCase().includes(a))return!0}return!1}var $e=/\[([^\]]*)\]\(([^)\s]+)\)/g,Ue=/^\s*#{1,6}\s+(.*)$/gm;function X(e){if(!e)return e;let t=e;return t=t.replace($e,(n,s,o)=>{let r=(s??"").trim();return!r||r===o?o:`${r}: ${o}`}),t=t.replace(Ue,(n,s)=>s.trim()),t}var Fe={image:16*1024*1024,video:16*1024*1024,audio:16*1024*1024,file:100*1024*1024};async function z(e,t){if(t.channelId!==e.channelId)return{ok:!1,detail:`channelId mismatch: got ${t.channelId}, expected ${e.channelId}`};let n=(t.attachments?.length??0)>0;if((!t.body||t.body.length===0)&&!n)return{ok:!1,detail:"empty body \u2014 refusing to send"};let s=t.body?X(t.body):t.body,o=He(t.to);if(!o)return{ok:!1,detail:`cannot resolve recipient JID from "${t.to}"`};if(e.onBeforeSend)try{await e.onBeforeSend(o)}catch{}let r,a=!1;try{if(n){for(let c of t.attachments??[]){let f=je(c);if(!f.ok)return{ok:!1,detail:f.detail};let y;try{y=await Je(c,e.fetchImpl)}catch(p){return{ok:!1,detail:`media fetch failed: ${p instanceof Error?p.message:String(p)}`}}let d=Fe[c.kind];if(y.byteLength>d)return{ok:!1,detail:`attachment exceeds size cap (${G(y.byteLength)} > ${G(d)} for ${c.kind})`};let l=c.kind==="image"||c.kind==="video"||c.kind==="file",i=!a&&l?s:void 0;i!==void 0&&(a=!0);let u=Ye(c,y,i),b=await e.client.sendMessage(o,u);b&&(r=b)}if(!a&&s&&s.length>0){let c=await e.client.sendText(o,s);c&&(r=c)}}else{let c=await e.client.sendText(o,s);c&&(r=c)}if(e.onAfterSend)try{await e.onAfterSend(o)}catch{}return{ok:!0,...r?{messageId:r}:{}}}catch(c){return{ok:!1,detail:c instanceof Error?c.message:String(c)}}}function He(e){let t=e.trim();if(!t)return null;if(t.includes("@"))return t;let n=t.replace(/[^\d]/g,"");return n?`${n}@s.whatsapp.net`:null}function Mn(e){return e.endsWith("@g.us")}function je(e){if(!e.mimeType||typeof e.mimeType!="string")return{ok:!1,detail:"attachment.mimeType is required"};let t=e.mimeType.toLowerCase();switch(e.kind){case"image":if(!t.startsWith("image/"))return{ok:!1,detail:`image kind expects image/*, got ${e.mimeType}`};break;case"video":if(!t.startsWith("video/"))return{ok:!1,detail:`video kind expects video/*, got ${e.mimeType}`};break;case"audio":if(!t.startsWith("audio/"))return{ok:!1,detail:`audio kind expects audio/*, got ${e.mimeType}`};break;case"file":break;default:return{ok:!1,detail:`unsupported attachment kind: ${e.kind??"unset"}`}}return!e.data&&!e.url?{ok:!1,detail:"attachment must supply either data or url"}:{ok:!0}}function qe(e){return e.toLowerCase().includes("ogg")&&e.toLowerCase().includes("opus")}async function Je(e,t){if(e.data)return Buffer.isBuffer(e.data)?e.data:Buffer.from(e.data);if(!e.url)throw new Error("attachment has neither data nor url");let s=await(t??(r=>globalThis.fetch(r)))(e.url);if(!s.ok)throw new Error(`fetch ${e.url} returned ${s.status}`);let o=await s.arrayBuffer();return Buffer.from(o)}function Ye(e,t,n){let s=e.mimeType;switch(e.kind){case"image":return{image:t,mimetype:s,...n?{caption:n}:{}};case"video":return{video:t,mimetype:s,...n?{caption:n}:{}};case"audio":return{audio:t,mimetype:s,...qe(s)?{ptt:!0}:{}};case"file":return{document:t,mimetype:s,...e.filename?{fileName:e.filename}:{},...n?{caption:n}:{}};default:return{text:n??""}}}function G(e){return`${(e/(1024*1024)).toFixed(1)}MB`}async function Dn(e){let t=w.parse({sessionId:e.config.sessionId??"pending-pair",...e.config}),n=e.onInfo??Xe,s=e.timeoutMs??6e4,o=new k({config:t,...e.adapter?{adapter:e.adapter}:{}}),r=null,a=!1;return new Promise((c,f)=>{let y=()=>{r&&(clearTimeout(r),r=null),o.removeAllListeners("qr"),o.removeAllListeners("connected"),o.removeAllListeners("session-expired"),o.removeAllListeners("error")},d=(l,i)=>{a||(a=!0,y(),o.stop(),l?f(l):i?c(i):f(new Error("pair flow finished without result")))};r=setTimeout(()=>{d(new Error(`whatsapp-personal: QR pairing timed out after ${s}ms \u2014 the QR code expired before scanning. Re-run setup to try again.`),null)},s),o.on("qr",async l=>{try{e.onQr?await e.onQr(l):await J(l),n("Waiting for you to scan the QR code in WhatsApp...")}catch(i){n(`(QR render warning: ${i instanceof Error?i.message:String(i)})`)}}),o.on("connected",({phoneNumber:l})=>{let i=l??"unknown",u=o.getSessionDir()??"";n(`Connected as ${i}.`),d(null,{phoneNumber:i,sessionDir:u,sessionId:t.sessionId})}),o.on("session-expired",({statusCode:l,detail:i})=>{d(new Error(`whatsapp-personal: pairing rejected (status ${l??"?"}). `+(i??"The phone may have logged this device out, or 2FA may be enabled and the verification step failed.")),null)}),o.on("error",l=>{d(l instanceof Error?l:new Error(String(l)),null)}),o.start().catch(l=>{d(l instanceof Error?l:new Error(String(l)),null)})})}function Xe(e){process.stderr.write(e+`
|
|
10
|
+
`)}function Nn(e){let t=w.parse({sessionId:e.config?.sessionId??"pending-pair",...e.config}),n=e.qrTtlMs??6e4,s=e.timeoutMs??3e5,o=new k({config:t,...e.adapter?{adapter:e.adapter}:{}}),r=!1,a=!1,c,f,y=new Promise((p,S)=>{c=p,f=S}),d=null,l=p=>{try{e.emitter.onEvent(p)}catch{}},i=()=>{d&&(clearTimeout(d),d=null),o.removeAllListeners("qr"),o.removeAllListeners("connected"),o.removeAllListeners("session-expired"),o.removeAllListeners("error"),o.stop().catch(()=>{})},u=p=>{r||(r=!0,i(),l({kind:"success",username:p.username,sessionString:p.sessionString}),c(p))},b=(p,S)=>{r||(r=!0,i(),l(a?{kind:"cancelled"}:{kind:"error",code:p,message:S}),f(new Error(`${p}: ${S}`)))};return o.on("qr",p=>{l({kind:"qr-ready",qrPayload:p,expiresAt:new Date(Date.now()+n).toISOString()})}),o.on("connected",({phoneNumber:p})=>{l({kind:"scanned"});let S=p??"unknown",Z=o.getSessionDir()??"";u({username:S,sessionString:S,sessionDir:Z})}),o.on("session-expired",({statusCode:p,detail:S})=>{b("session-expired",`pairing rejected (status ${p??"?"}): ${S??"phone may have logged this device out"}`)}),o.on("error",p=>{b("client-error",p instanceof Error?p.message:String(p))}),d=setTimeout(()=>{b("timeout",`pairing timed out after ${Math.round(s/1e3)}s \u2014 re-open the modal to retry`)},s),e.signal&&(e.signal.aborted?(a=!0,queueMicrotask(()=>b("cancelled","aborted before start"))):e.signal.addEventListener("abort",()=>{a=!0,b("cancelled","aborted by caller")},{once:!0})),o.start().catch(p=>{b("start-failed",p instanceof Error?p.message:String(p))}),{promise:y,submit2fa(p){},cancel(){r||(a=!0,b("cancelled","cancelled by user"))}}}var Ge={dm:!0,group:!0,thread:!1,reaction:!0,edit:!1,delete:!1,mediaImage:!0,mediaVideo:!0,mediaAudio:!0,voiceMemo:!0,voiceCall:!1,typing:!0,readReceipt:!0,formatting:"platform",maxMessageBytes:4096,maxAttachmentBytes:16*1024*1024,rateLimit:{perMinute:30,perHour:500}},K="whatsapp-personal",Q=/^[a-z0-9][a-z0-9._:-]*$/;function ze(e){if(!Q.test(e))throw new Error(`whatsapp-personal: invalid channelId "${e}" \u2014 must match ${Q}`)}function V(e={}){let t=e.channelId??K;ze(t);let n=!1,s=null,o=null,r=null,a=null,c={id:t,displayName:"WhatsApp (Personal)",description:"WhatsApp Web (Baileys) \u2014 pair via QR with your phone.",version:"0.0.1",kind:"both",defaultDmPolicy:"pairing",features:Ge,authSchema:M,configSchema:w,async start(d,l){s=w.parse(d.config??{}),o=l,M.parse(d.secrets??{}),r=new k({config:s,...e.adapter?{adapter:e.adapter}:{}}),r.on("connecting",()=>{let i={kind:"connecting"};a=i,e.onConnectionEvent?.(i)}),r.on("qr",i=>{let u={kind:"qr",qr:i};a=u,e.onConnectionEvent?.(u),g.warn("whatsapp-personal: QR pairing required \u2014 re-run `swarmai setup` to scan")}),r.on("connected",({phoneNumber:i})=>{let u={kind:"connected",phoneNumber:i};a=u,e.onConnectionEvent?.(u),g.info({phoneNumber:i},"whatsapp-personal: connected")}),r.on("reconnecting",({attempt:i,delayMs:u})=>{let b={kind:"reconnecting",attempt:i,delayMs:u};a=b,e.onConnectionEvent?.(b)}),r.on("disconnected",i=>{let u={kind:"disconnected",reason:i.reason,...i.statusCode!==void 0?{statusCode:i.statusCode}:{},...i.detail?{detail:i.detail}:{}};a=u,e.onConnectionEvent?.(u)}),r.on("session-expired",i=>{let u={kind:"session-expired",...i.statusCode!==void 0?{statusCode:i.statusCode}:{},...i.detail?{detail:i.detail}:{}};a=u,e.onConnectionEvent?.(u),g.warn({statusCode:i.statusCode,detail:i.detail},"whatsapp-personal: session expired \u2014 re-run `swarmai setup` to re-pair")}),r.on("session-down",({attempts:i})=>{let u={kind:"session-down",attempts:i};a=u,e.onConnectionEvent?.(u),g.warn({attempts:i},"whatsapp-personal: gave up reconnecting")}),r.on("error",i=>{g.warn({err:i instanceof Error?i.message:String(i)},"whatsapp-personal: client error")}),r.on("message",async i=>{try{let u=Y(t,i,{...r?.getOwnJid()?{selfJid:r.getOwnJid()}:{},...e.selfDisplayName?{selfDisplayName:e.selfDisplayName}:{}});if(!u)return;let b=u.flags?.groupChat===!0,p=u.flags?.mentioned===!0;(!b||!s.respondToMentions||p)&&o&&await o(u),e.onEvent&&await e.onEvent(u),s?.markRead&&await r?.markRead([i.key])}catch(u){g.warn({err:u instanceof Error?u.message:String(u)},"whatsapp-personal: inbound handler threw")}}),await r.start(),n=!0},async stop(){r&&(await r.stop(),r=null),n=!1,o=null},async healthCheck(){if(!n||!r)return{status:"down",detail:"not started"};let d=r.getStatus();switch(d){case"connected":return{status:"ok"};case"connecting":case"qr":case"reconnecting":return{status:"degraded",detail:d};default:return{status:"down",detail:d}}},async send(d){if(!n||!r||!s)throw new Error("whatsapp-personal channel not started");if(d.channelId!==t)throw new Error(`channelId mismatch: got ${d.channelId}, expected ${t}`);let l=await z({client:r,channelId:t,...s.typingIndicator?{onBeforeSend:i=>r.setTyping(i,!0),onAfterSend:i=>r.setTyping(i,!1)}:{}},d);if(!l.ok)throw new Error(`whatsapp-personal send failed: ${l.detail??"unknown"}`)}},f={id:t,kind:"push",authSchema:M,configSchema:w,async healthCheck(){if(!n||!r)return"down";let d=r.getStatus();return d==="connected"?"ok":d==="session-down"||d==="session-expired"?"down":"degraded"},async webhook(d){return[]}};function y(d){return Promise.resolve({status:405,body:'{"error":"whatsapp-personal has no webhook"}',inbound:[]})}return{channel:c,source:f,handleWebhook:y,getClient:()=>r,_lastEvent:()=>a}}function Hn(e={}){return V(e).source}var L=class extends Error{constructor(t){super(`whatsapp-personal: slot "${t}" is monitor-only \u2014 outbound not allowed. Use the primary "whatsapp-personal" channel slot for replies.`),this.name="MonitorOnlySlotError"}};function jn(e={}){let t=V(e),n=e.channelId??K,s={...t.channel,kind:"monitor-source",features:{...t.channel.features,dm:!1,group:!1},async send(){throw new L(n)}};return{...t,channel:s}}export{k as BaileysClient,K as DEFAULT_WHATSAPP_PERSONAL_ID,H as LOCK_FILENAME,Fe as MEDIA_SIZE_CAPS,L as MonitorOnlySlotError,C as SessionLockedError,Ge as WHATSAPP_PERSONAL_FEATURES,M as WhatsAppPersonalAuthSchema,w as WhatsAppPersonalConfigSchema,j as acquireSessionLock,Ye as buildMediaContent,jn as createWhatsAppPersonalMonitorOnlyBundle,Hn as createWhatsAppPersonalMonitorSource,V as createWhatsAppPersonalPlugin,Re as defaultBaileysAdapter,Be as detectMention,F as ensureSessionDir,_e as extractPhoneFromJid,Mn as isGroupJid,We as isGroupRemoteJid,mn as isSessionPaired,qe as isVoiceMemo,Y as normaliseBaileysMessage,X as normaliseForWhatsApp,I as phoneFromJid,Ne as readSenderId,fn as readSessionLock,J as renderQr,Je as resolveMediaBuffer,Se as resolveSessionPaths,Dn as runPairFlow,Nn as runWhatsAppPersonalPairForUi,we as sanitiseSessionId,z as sendOutboundText,He as toJid,je as validateAttachment};
|