tossinbox 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -7
- package/dist/core/net.js +46 -0
- package/dist/core/providers/guerrillamail.js +5 -1
- package/dist/core/providers/mailtm.js +7 -2
- package/dist/core/state.js +23 -4
- package/dist/version.js +1 -1
- package/llms.txt +17 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,11 +68,11 @@ Requires Node.js 18+.
|
|
|
68
68
|
# Homebrew (macOS, Linux)
|
|
69
69
|
brew install mohamed-khairy-5i/tap/tossinbox
|
|
70
70
|
|
|
71
|
-
# npm
|
|
72
|
-
npm install -g
|
|
71
|
+
# npm (npmjs.com)
|
|
72
|
+
npm install -g tossinbox
|
|
73
73
|
|
|
74
74
|
# Or run without installing
|
|
75
|
-
npx
|
|
75
|
+
npx tossinbox@latest spawn
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
From source:
|
|
@@ -164,13 +164,14 @@ TossInbox ships with an MCP server exposing four tools:
|
|
|
164
164
|
"mcpServers": {
|
|
165
165
|
"tossinbox": {
|
|
166
166
|
"command": "npx",
|
|
167
|
-
"args": ["-y", "
|
|
167
|
+
"args": ["-y", "tossinbox", "mcp"]
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
-
Or after a global install, simply use `tossinbox-mcp` as the command
|
|
173
|
+
Or after a global install, simply use `tossinbox-mcp` as the command —
|
|
174
|
+
equivalent to `npx -y tossinbox mcp`.
|
|
174
175
|
|
|
175
176
|
### Works with any AI agent
|
|
176
177
|
|
|
@@ -229,7 +230,7 @@ No — receive-only by design. TossInbox exists for privacy and testing and
|
|
|
229
230
|
ships no bulk-send or bulk-signup mode.
|
|
230
231
|
|
|
231
232
|
**Does it work on Windows?**
|
|
232
|
-
Yes, anywhere Node.js 18+ runs. `npx
|
|
233
|
+
Yes, anywhere Node.js 18+ runs. `npx tossinbox@latest spawn`
|
|
233
234
|
works in PowerShell exactly the same.
|
|
234
235
|
|
|
235
236
|
**A site blocked my disposable address. What now?**
|
|
@@ -252,7 +253,7 @@ round.
|
|
|
252
253
|
- [x] GitHub Action: `mohamed-khairy-5i/tossinbox@v1`
|
|
253
254
|
- [x] Homebrew tap: `brew install mohamed-khairy-5i/tap/tossinbox`
|
|
254
255
|
- [x] Project website at [tossinbox.pages.dev](https://tossinbox.pages.dev/)
|
|
255
|
-
- [
|
|
256
|
+
- [x] Publish `tossinbox` + `tossinbox-mcp` to the [npm registry](https://www.npmjs.com/package/tossinbox)
|
|
256
257
|
- [ ] `mail.gw` provider (mail.tm-compatible API — small lift)
|
|
257
258
|
- [ ] `tempmail.lol` provider (free API)
|
|
258
259
|
- [ ] Provider failover: auto-switch when a provider is down
|
|
@@ -264,6 +265,9 @@ round.
|
|
|
264
265
|
- [CLI reference](https://tossinbox.pages.dev/cli) — every command, flag, and exit code
|
|
265
266
|
- [Agents & MCP](https://tossinbox.pages.dev/agents) — setup for every MCP client
|
|
266
267
|
- [FAQ](https://tossinbox.pages.dev/faq) — privacy, providers, troubleshooting
|
|
268
|
+
- [Examples](https://tossinbox.pages.dev/examples) — copy-paste recipes: shell, CI, Node.js, MCP
|
|
269
|
+
- [Guide](https://tossinbox.pages.dev/guide) — providers, state, flags, exit codes, troubleshooting
|
|
270
|
+
- [Roadmap](https://tossinbox.pages.dev/roadmap) — what shipped and what is next
|
|
267
271
|
- [Changelog](./CHANGELOG.md) · [Contributing](./CONTRIBUTING.md) · [Security](./SECURITY.md)
|
|
268
272
|
|
|
269
273
|
## License
|
package/dist/core/net.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ProviderError } from "./types.js";
|
|
2
|
+
/** Translate Node's raw fetch failures into a ProviderError that says what
|
|
3
|
+
* actually happened and what to do next. Without this, a DNS blip or a dead
|
|
4
|
+
* upstream surfaces as Node's bare "fetch failed" — useless to humans and
|
|
5
|
+
* unparsable for agents. Messages stay short, actionable, and stable. */
|
|
6
|
+
export function networkError(err, provider, host, timeoutMs) {
|
|
7
|
+
const failure = (err ?? {});
|
|
8
|
+
const cause = failure.cause ?? {};
|
|
9
|
+
const code = cause.code ?? "";
|
|
10
|
+
// AbortSignal.timeout rejects with a DOMException named "TimeoutError"
|
|
11
|
+
// (sometimes wrapped as the fetch cause).
|
|
12
|
+
if (failure.name === "TimeoutError" ||
|
|
13
|
+
cause.name === "TimeoutError" ||
|
|
14
|
+
code === "UND_ERR_HEADERS_TIMEOUT") {
|
|
15
|
+
return new ProviderError(provider, `no response from ${host} within ${Math.round(timeoutMs / 1000)}s — ` +
|
|
16
|
+
"provider is down or unreachable; retry, or switch with --provider");
|
|
17
|
+
}
|
|
18
|
+
switch (code) {
|
|
19
|
+
case "ENOTFOUND":
|
|
20
|
+
case "EAI_AGAIN":
|
|
21
|
+
return new ProviderError(provider, `DNS lookup failed for ${host} — no internet, bad DNS, or a blocked ` +
|
|
22
|
+
"network; check connectivity and retry");
|
|
23
|
+
case "ECONNREFUSED":
|
|
24
|
+
return new ProviderError(provider, `connection refused by ${host} — provider may be down, or a ` +
|
|
25
|
+
"firewall/proxy is blocking it; retry later");
|
|
26
|
+
case "ECONNRESET":
|
|
27
|
+
case "EPIPE":
|
|
28
|
+
return new ProviderError(provider, `connection to ${host} dropped mid-request — usually transient; retry`);
|
|
29
|
+
case "ENETUNREACH":
|
|
30
|
+
case "ENETDOWN":
|
|
31
|
+
case "EHOSTUNREACH":
|
|
32
|
+
return new ProviderError(provider, "network unreachable — you appear to be offline; check your connection and retry");
|
|
33
|
+
case "ECONNABORTED":
|
|
34
|
+
return new ProviderError(provider, `connection to ${host} aborted — usually transient; retry`);
|
|
35
|
+
case "CERT_HAS_EXPIRED":
|
|
36
|
+
case "DEPTH_ZERO_SELF_SIGNED_CERT":
|
|
37
|
+
case "SELF_SIGNED_CERT_IN_CHAIN":
|
|
38
|
+
case "UNABLE_TO_VERIFY_LEAF_SIGNATURE":
|
|
39
|
+
return new ProviderError(provider, `TLS certificate problem with ${host} — if a corporate proxy ` +
|
|
40
|
+
"intercepts TLS, point NODE_EXTRA_CA_CERTS at its CA bundle");
|
|
41
|
+
default: {
|
|
42
|
+
const detail = cause.message || failure.message || String(err);
|
|
43
|
+
return new ProviderError(provider, `cannot reach ${host} (${detail}) — check connectivity and retry`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ProviderError } from "../types.js";
|
|
2
2
|
import { extractCode } from "../otp.js";
|
|
3
|
+
import { networkError } from "../net.js";
|
|
3
4
|
import { VERSION } from "../../version.js";
|
|
4
5
|
const BASE = "https://api.guerrillamail.com/ajax.php";
|
|
5
6
|
const REQUEST_TIMEOUT_MS = 20_000;
|
|
@@ -13,10 +14,13 @@ async function call(params) {
|
|
|
13
14
|
const url = new URL(BASE);
|
|
14
15
|
for (const [k, v] of Object.entries(params))
|
|
15
16
|
url.searchParams.set(k, v);
|
|
16
|
-
// Hard timeout on every request — an agent must never hang forever.
|
|
17
|
+
// Hard timeout on every request — an agent must never hang forever. Raw
|
|
18
|
+
// network failures are translated into a readable, actionable ProviderError.
|
|
17
19
|
const res = await fetch(url, {
|
|
18
20
|
headers: { Accept: "application/json", "User-Agent": `tossinbox/${VERSION}` },
|
|
19
21
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
22
|
+
}).catch((err) => {
|
|
23
|
+
throw networkError(err, "guerrillamail", "api.guerrillamail.com", REQUEST_TIMEOUT_MS);
|
|
20
24
|
});
|
|
21
25
|
if (!res.ok)
|
|
22
26
|
throw new ProviderError("guerrillamail", `HTTP ${res.status}`, res.status);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { ProviderError } from "../types.js";
|
|
3
3
|
import { extractCode } from "../otp.js";
|
|
4
|
+
import { networkError } from "../net.js";
|
|
4
5
|
import { VERSION } from "../../version.js";
|
|
5
6
|
const BASE = "https://api.mail.tm";
|
|
6
7
|
const REQUEST_TIMEOUT_MS = 20_000;
|
|
@@ -27,9 +28,13 @@ function headers(token) {
|
|
|
27
28
|
h.Authorization = `Bearer ${token}`;
|
|
28
29
|
return h;
|
|
29
30
|
}
|
|
30
|
-
/** Hard timeout on every request — an agent must never hang forever.
|
|
31
|
+
/** Hard timeout on every request — an agent must never hang forever. Raw
|
|
32
|
+
* network failures (DNS, refused, timeout) are translated into a readable,
|
|
33
|
+
* actionable ProviderError instead of Node's bare "fetch failed". */
|
|
31
34
|
function fetchJson(url, init) {
|
|
32
|
-
return fetch(url, { ...init, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) })
|
|
35
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) }).catch((err) => {
|
|
36
|
+
throw networkError(err, "mailtm", "api.mail.tm", REQUEST_TIMEOUT_MS);
|
|
37
|
+
});
|
|
33
38
|
}
|
|
34
39
|
/** mail.tm allows ~8 requests per second; retry once on 429. */
|
|
35
40
|
async function request(method, url, options = {}) {
|
package/dist/core/state.js
CHANGED
|
@@ -61,9 +61,28 @@ export async function resolveInbox(address) {
|
|
|
61
61
|
}
|
|
62
62
|
async function writeState(state) {
|
|
63
63
|
const file = statePath();
|
|
64
|
+
const dir = path.dirname(file);
|
|
64
65
|
// The state file contains provider tokens — keep it private.
|
|
65
|
-
await fs.mkdir(
|
|
66
|
-
|
|
67
|
-
//
|
|
68
|
-
|
|
66
|
+
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
67
|
+
// Atomic save: write a sibling temp file, fsync it, then rename it over the
|
|
68
|
+
// real one. A crash mid-write can no longer truncate state.json and destroy
|
|
69
|
+
// saved inboxes — readers always see either the old file or the new one.
|
|
70
|
+
const tmp = path.join(dir, `.${path.basename(file)}.${process.pid}.${Date.now()}.tmp`);
|
|
71
|
+
const handle = await fs.open(tmp, "w", 0o600);
|
|
72
|
+
try {
|
|
73
|
+
await handle.writeFile(JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
74
|
+
// open()'s mode is filtered by umask — enforce 0600 before the rename.
|
|
75
|
+
await handle.chmod(0o600);
|
|
76
|
+
await handle.sync(); // flush to disk before it becomes the real file
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
await handle.close();
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
await fs.rename(tmp, file); // atomic on POSIX; replaces the target on Windows too
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
await fs.rm(tmp, { force: true }).catch(() => { });
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
69
88
|
}
|
package/dist/version.js
CHANGED
package/llms.txt
CHANGED
|
@@ -7,6 +7,21 @@ an MCP server, collects verification codes (OTP) from them, and deletes the
|
|
|
7
7
|
inboxes when done. It is designed agent-first: every CLI command supports
|
|
8
8
|
`--json`, exit codes are documented, and there are no interactive prompts.
|
|
9
9
|
|
|
10
|
+
## Site
|
|
11
|
+
|
|
12
|
+
- [Home](https://tossinbox.pages.dev/): overview, install options, comparison table
|
|
13
|
+
- [Quickstart](https://tossinbox.pages.dev/quickstart): first inbox in four commands
|
|
14
|
+
- [CLI reference](https://tossinbox.pages.dev/cli): all commands, flags, exit codes
|
|
15
|
+
- [Agents & MCP](https://tossinbox.pages.dev/agents): MCP server setup for AI agents
|
|
16
|
+
- [Examples](https://tossinbox.pages.dev/examples): copy-paste recipes (shell, CI, Node.js, MCP)
|
|
17
|
+
- [Guide](https://tossinbox.pages.dev/guide): providers, state, flags, exit codes, troubleshooting
|
|
18
|
+
- [FAQ](https://tossinbox.pages.dev/faq): privacy, providers, troubleshooting
|
|
19
|
+
- [Roadmap](https://tossinbox.pages.dev/roadmap): shipped and planned
|
|
20
|
+
- [Changelog](https://tossinbox.pages.dev/changelog): every change by version
|
|
21
|
+
- [Arabic home](https://tossinbox.pages.dev/ar/): الصفحة الرئيسية بالعربية
|
|
22
|
+
- [Arabic quickstart](https://tossinbox.pages.dev/ar/quickstart): البداية السريعة بالعربية
|
|
23
|
+
- [Source repository](https://github.com/mohamed-khairy-5i/tossinbox): code, issues, releases
|
|
24
|
+
|
|
10
25
|
## CLI (binary: `tossinbox`)
|
|
11
26
|
|
|
12
27
|
- `tossinbox spawn` — create a new disposable inbox (flags: `-p provider`, `-l label`)
|
|
@@ -37,9 +52,9 @@ Typical agent flow: `create_inbox` -> use the address in a signup form ->
|
|
|
37
52
|
## Installation
|
|
38
53
|
|
|
39
54
|
```bash
|
|
40
|
-
npm install -g
|
|
55
|
+
npm install -g tossinbox
|
|
41
56
|
# or run without installing:
|
|
42
|
-
npx
|
|
57
|
+
npx tossinbox@latest spawn
|
|
43
58
|
```
|
|
44
59
|
|
|
45
60
|
## Notes
|