iris-relay 1.0.0 → 1.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # iris-relay
2
2
 
3
- Lightweight, zero-dependency Telegram message relay. Tree-shakeable ESM package with built-in `.env` loading — true plug-and-play.
3
+ Lightweight Telegram relay dev tool — crash alerts, deploy notifications, heartbeats, and more. Zero-config, tree-shakeable, built-in `.env` loading.
4
4
 
5
5
  ## Install
6
6
 
@@ -15,17 +15,16 @@ Create a `.env` file in your project root:
15
15
  ```env
16
16
  XERO_BOT_TOKEN=your_bot_token_from_botfather
17
17
  XERO_CHAT_ID=your_telegram_chat_id
18
+ XERO_DRY_RUN=false
18
19
  ```
19
20
 
20
- > **How to get these:**
21
- > - **Bot Token** — message [@BotFather](https://t.me/BotFather) on Telegram to create a bot
22
- > - **Chat ID** — message [@userinfobot](https://t.me/userinfobot) on Telegram to get your chat ID
21
+ > **Bot Token** [@BotFather](https://t.me/BotFather) · **Chat ID** → [@userinfobot](https://t.me/userinfobot)
23
22
 
24
- That's it. No dotenv import needed — `iris-relay` auto-loads your `.env` file.
23
+ No dotenv import needed — `iris-relay` auto-loads your `.env`.
25
24
 
26
- ## Usage
25
+ ---
27
26
 
28
- ### One-off message
27
+ ## Quick Start
29
28
 
30
29
  ```ts
31
30
  import { relay } from "iris-relay";
@@ -33,52 +32,199 @@ import { relay } from "iris-relay";
33
32
  await relay("Hello from my server! 🚀");
34
33
  ```
35
34
 
36
- ### Pre-configured sender
35
+ ---
36
+
37
+ ## Features
38
+
39
+ ### 📨 Core Relay
37
40
 
38
41
  ```ts
39
- import { createRelay } from "iris-relay";
42
+ import { relay, createRelay } from "iris-relay";
40
43
 
41
- const send = createRelay({
42
- botToken: process.env.XERO_BOT_TOKEN,
43
- chatId: process.env.XERO_CHAT_ID,
44
+ // One-off message
45
+ await relay("Server started");
46
+
47
+ // With options
48
+ await relay("Check this out", {
49
+ parseMode: "HTML",
50
+ disablePreview: true,
51
+ silent: true,
52
+ retries: 3, // retry with exponential backoff
53
+ retryDelay: 1000, // base delay in ms
44
54
  });
45
55
 
56
+ // Pre-configured sender
57
+ const send = createRelay({ botToken: "...", chatId: "..." });
46
58
  await send("Deploy successful ✅");
47
- await send("Build failed ❌");
48
59
  ```
49
60
 
50
- ### Options
61
+ ### 🚨 Error Relay
51
62
 
52
63
  ```ts
53
- await relay("Check this out", {
54
- parseMode: "HTML", // HTML | Markdown | MarkdownV2
55
- disablePreview: true, // no link previews
56
- silent: true, // no notification sound
64
+ import { relayError } from "iris-relay";
65
+
66
+ try {
67
+ dangerousOperation();
68
+ } catch (err) {
69
+ await relayError(err); // sends formatted stack trace
70
+ }
71
+ ```
72
+
73
+ ### 📋 JSON Relay
74
+
75
+ ```ts
76
+ import { relayJSON } from "iris-relay";
77
+
78
+ await relayJSON({ users: 42, status: "healthy" }, "Server Stats");
79
+ ```
80
+
81
+ ### 🚀 Deploy Notifications
82
+
83
+ ```ts
84
+ import { relayDeploy } from "iris-relay";
85
+
86
+ // Auto-reads git branch, commit, and message
87
+ await relayDeploy({ app: "my-api", env: "production", version: "1.2.3" });
88
+ ```
89
+
90
+ ### ✍️ Message Builder
91
+
92
+ ```ts
93
+ import { message } from "iris-relay";
94
+
95
+ await message()
96
+ .bold("Deploy")
97
+ .text(" ")
98
+ .code("v1.2.3")
99
+ .text(" to ")
100
+ .italic("production")
101
+ .br()
102
+ .separator()
103
+ .link("View Dashboard", "https://example.com")
104
+ .send();
105
+ ```
106
+
107
+ Builder methods: `.bold()` `.italic()` `.code()` `.codeBlock()` `.strike()` `.underline()` `.link()` `.text()` `.br()` `.separator()`
108
+
109
+ ### 📎 File Relay
110
+
111
+ ```ts
112
+ import { relayFile } from "iris-relay";
113
+
114
+ await relayFile("./logs/error.log", "Latest error log");
115
+ ```
116
+
117
+ ### 💀 Process Crash Watcher
118
+
119
+ ```ts
120
+ import { watchProcess } from "iris-relay";
121
+
122
+ watchProcess(); // done — uncaught exceptions & rejections get reported
123
+ ```
124
+
125
+ ### 💓 Heartbeat
126
+
127
+ ```ts
128
+ import { startHeartbeat } from "iris-relay";
129
+
130
+ const stop = startHeartbeat({
131
+ interval: 60_000, // every minute
132
+ app: "my-api",
57
133
  });
134
+
135
+ // Later: stop();
136
+ ```
137
+
138
+ ### 🔌 Express Middleware
139
+
140
+ ```ts
141
+ import express from "express";
142
+ import { irisMiddleware, irisErrorHandler } from "iris-relay";
143
+
144
+ const app = express();
145
+
146
+ // Reports slow requests (>3s) and 5xx errors
147
+ app.use(irisMiddleware({ slowThreshold: 3000 }));
148
+
149
+ // Your routes...
150
+
151
+ // Catches unhandled errors and sends to Telegram
152
+ app.use(irisErrorHandler());
153
+ ```
154
+
155
+ ### 📡 Multi-Channel
156
+
157
+ ```ts
158
+ import { createChannels } from "iris-relay";
159
+
160
+ const channels = createChannels([
161
+ { name: "alerts", chatId: "-100123456" },
162
+ { name: "deploys", chatId: "-100789012", silent: true },
163
+ { name: "logs", chatId: "-100345678" },
164
+ ]);
165
+
166
+ await channels.send("alerts", "🚨 Server is down!");
167
+ await channels.send("deploys", "🚀 v1.2.3 deployed");
168
+ await channels.broadcast("System maintenance in 5 minutes");
58
169
  ```
59
170
 
60
- ## API
171
+ ### 🧪 Dry Run Mode
61
172
 
62
- ### `relay(message, config?): Promise<RelayResult>`
173
+ Set `XERO_DRY_RUN=true` in `.env` to log messages to console instead of sending to Telegram. Perfect for local development.
63
174
 
64
- Send a single message. Returns `{ success: boolean, messageId?: number, error?: string }`.
175
+ ---
65
176
 
66
- | Param | Type | Description |
67
- |-------|------|-------------|
68
- | `message` | `string` | The message text to send |
69
- | `config.botToken` | `string?` | Bot token (defaults to `XERO_BOT_TOKEN` env var) |
70
- | `config.chatId` | `string?` | Chat ID (defaults to `XERO_CHAT_ID` env var) |
71
- | `config.parseMode` | `"HTML" \| "Markdown" \| "MarkdownV2"` | Message formatting |
72
- | `config.disablePreview` | `boolean?` | Disable link previews |
73
- | `config.silent` | `boolean?` | Send without notification sound |
177
+ ## CLI
178
+
179
+ ```bash
180
+ # Send a message
181
+ npx iris-relay "Deploy complete ✅"
74
182
 
75
- ### `createRelay(config): (message, overrides?) => Promise<RelayResult>`
183
+ # Send with HTML
184
+ npx iris-relay --html "<b>Bold</b> message"
185
+
186
+ # Send a file
187
+ npx iris-relay --file ./logs/error.log "Error log attached"
188
+
189
+ # Silent (no notification sound)
190
+ npx iris-relay --silent "Background update"
191
+ ```
76
192
 
77
- Create a pre-configured sender function. Useful when sending multiple messages with the same config.
193
+ ---
194
+
195
+ ## API Reference
196
+
197
+ | Export | Description |
198
+ |--------|-------------|
199
+ | `relay(msg, config?)` | Send a text message |
200
+ | `createRelay(config)` | Create a pre-configured sender |
201
+ | `relayError(err, config?)` | Send formatted error + stack trace |
202
+ | `relayJSON(obj, label?, config?)` | Send pretty-printed JSON |
203
+ | `relayDeploy(meta?, config?)` | Send deploy notification with git info |
204
+ | `relayFile(path, caption?, config?)` | Send a file |
205
+ | `message()` | Create a fluent message builder |
206
+ | `watchProcess(config?)` | Auto-report crashes |
207
+ | `startHeartbeat(opts?, config?)` | Periodic alive pings |
208
+ | `irisMiddleware(opts?)` | Express middleware for slow/error reporting |
209
+ | `irisErrorHandler(config?)` | Express error handler |
210
+ | `createChannels(channels, config?)` | Multi-channel manager |
211
+ | `isDryRun()` | Check if dry run mode is active |
212
+
213
+ ## Config
214
+
215
+ | Option | Type | Default | Description |
216
+ |--------|------|---------|-------------|
217
+ | `botToken` | `string` | `XERO_BOT_TOKEN` | Bot token |
218
+ | `chatId` | `string` | `XERO_CHAT_ID` | Chat ID |
219
+ | `parseMode` | `"HTML" \| "Markdown" \| "MarkdownV2"` | — | Message format |
220
+ | `disablePreview` | `boolean` | `false` | Disable link previews |
221
+ | `silent` | `boolean` | `false` | No notification sound |
222
+ | `retries` | `number` | `0` | Retry count |
223
+ | `retryDelay` | `number` | `1000` | Base retry delay (ms) |
78
224
 
79
225
  ## Requirements
80
226
 
81
- - Node.js ≥ 18 (uses native `fetch`)
227
+ - Node.js ≥ 18
82
228
 
83
229
  ## License
84
230
 
@@ -0,0 +1,50 @@
1
+ import type { RelayConfig, RelayResult } from "./types.js";
2
+ /**
3
+ * Fluent message builder for composing formatted Telegram messages.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { message } from "iris-relay";
8
+ *
9
+ * await message()
10
+ * .bold("Deploy")
11
+ * .text(" ")
12
+ * .code("v1.2.3")
13
+ * .text(" to ")
14
+ * .italic("production")
15
+ * .send();
16
+ * ```
17
+ */
18
+ export declare class MessageBuilder {
19
+ private parts;
20
+ private config;
21
+ /** Add plain text */
22
+ text(content: string): this;
23
+ /** Add bold text */
24
+ bold(content: string): this;
25
+ /** Add italic text */
26
+ italic(content: string): this;
27
+ /** Add inline code */
28
+ code(content: string): this;
29
+ /** Add a code block with optional language */
30
+ codeBlock(content: string, lang?: string): this;
31
+ /** Add a strikethrough text */
32
+ strike(content: string): this;
33
+ /** Add underlined text */
34
+ underline(content: string): this;
35
+ /** Add a link */
36
+ link(text: string, url: string): this;
37
+ /** Add a newline */
38
+ br(): this;
39
+ /** Add a horizontal separator */
40
+ separator(): this;
41
+ /** Override relay config for this message */
42
+ withConfig(config: RelayConfig): this;
43
+ /** Build the message string without sending */
44
+ build(): string;
45
+ /** Send the composed message */
46
+ send(config?: RelayConfig): Promise<RelayResult>;
47
+ }
48
+ /** Create a new message builder */
49
+ export declare function message(): MessageBuilder;
50
+ //# sourceMappingURL=builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAO3D;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,cAAc;IACvB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,MAAM,CAAsC;IAEpD,qBAAqB;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK3B,oBAAoB;IACpB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK3B,sBAAsB;IACtB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK7B,sBAAsB;IACtB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK3B,8CAA8C;IAC9C,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAM/C,+BAA+B;IAC/B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK7B,0BAA0B;IAC1B,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKhC,iBAAiB;IACjB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAKrC,oBAAoB;IACpB,EAAE,IAAI,IAAI;IAKV,iCAAiC;IACjC,SAAS,IAAI,IAAI;IAKjB,6CAA6C;IAC7C,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAKrC,+CAA+C;IAC/C,KAAK,IAAI,MAAM;IAIf,gCAAgC;IAC1B,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;CAGzD;AAED,mCAAmC;AACnC,wBAAgB,OAAO,IAAI,cAAc,CAExC"}
@@ -0,0 +1,94 @@
1
+ import { relay } from "./relay.js";
2
+ /** Escape HTML special characters */
3
+ function esc(text) {
4
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5
+ }
6
+ /**
7
+ * Fluent message builder for composing formatted Telegram messages.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { message } from "iris-relay";
12
+ *
13
+ * await message()
14
+ * .bold("Deploy")
15
+ * .text(" ")
16
+ * .code("v1.2.3")
17
+ * .text(" to ")
18
+ * .italic("production")
19
+ * .send();
20
+ * ```
21
+ */
22
+ export class MessageBuilder {
23
+ parts = [];
24
+ config = { parseMode: "HTML" };
25
+ /** Add plain text */
26
+ text(content) {
27
+ this.parts.push(esc(content));
28
+ return this;
29
+ }
30
+ /** Add bold text */
31
+ bold(content) {
32
+ this.parts.push(`<b>${esc(content)}</b>`);
33
+ return this;
34
+ }
35
+ /** Add italic text */
36
+ italic(content) {
37
+ this.parts.push(`<i>${esc(content)}</i>`);
38
+ return this;
39
+ }
40
+ /** Add inline code */
41
+ code(content) {
42
+ this.parts.push(`<code>${esc(content)}</code>`);
43
+ return this;
44
+ }
45
+ /** Add a code block with optional language */
46
+ codeBlock(content, lang) {
47
+ const langAttr = lang ? ` class="language-${esc(lang)}"` : "";
48
+ this.parts.push(`<pre${langAttr}>${esc(content)}</pre>`);
49
+ return this;
50
+ }
51
+ /** Add a strikethrough text */
52
+ strike(content) {
53
+ this.parts.push(`<s>${esc(content)}</s>`);
54
+ return this;
55
+ }
56
+ /** Add underlined text */
57
+ underline(content) {
58
+ this.parts.push(`<u>${esc(content)}</u>`);
59
+ return this;
60
+ }
61
+ /** Add a link */
62
+ link(text, url) {
63
+ this.parts.push(`<a href="${esc(url)}">${esc(text)}</a>`);
64
+ return this;
65
+ }
66
+ /** Add a newline */
67
+ br() {
68
+ this.parts.push("\n");
69
+ return this;
70
+ }
71
+ /** Add a horizontal separator */
72
+ separator() {
73
+ this.parts.push("\n———————————\n");
74
+ return this;
75
+ }
76
+ /** Override relay config for this message */
77
+ withConfig(config) {
78
+ this.config = { ...this.config, ...config };
79
+ return this;
80
+ }
81
+ /** Build the message string without sending */
82
+ build() {
83
+ return this.parts.join("");
84
+ }
85
+ /** Send the composed message */
86
+ async send(config) {
87
+ return relay(this.build(), { ...this.config, ...config });
88
+ }
89
+ }
90
+ /** Create a new message builder */
91
+ export function message() {
92
+ return new MessageBuilder();
93
+ }
94
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.js","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { Channel, RelayConfig, RelayResult } from "./types.js";
2
+ /**
3
+ * Multi-channel manager for sending to different Telegram chats.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { createChannels } from "iris-relay";
8
+ *
9
+ * const channels = createChannels([
10
+ * { name: "alerts", chatId: "-100123456" },
11
+ * { name: "deploys", chatId: "-100789012", silent: true },
12
+ * { name: "logs", chatId: "-100345678" },
13
+ * ]);
14
+ *
15
+ * await channels.send("alerts", "🚨 Server is down!");
16
+ * await channels.send("deploys", "🚀 v1.2.3 deployed");
17
+ * await channels.broadcast("System maintenance in 5 minutes");
18
+ * ```
19
+ */
20
+ export declare function createChannels(channels: Channel[], defaultConfig?: RelayConfig): {
21
+ /** Send a message to a specific channel */
22
+ send(channelName: string, message: string, config?: RelayConfig): Promise<RelayResult>;
23
+ /** Broadcast a message to all channels */
24
+ broadcast(message: string, config?: RelayConfig): Promise<Map<string, RelayResult>>;
25
+ /** Get list of registered channel names */
26
+ list(): string[];
27
+ };
28
+ //# sourceMappingURL=channels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channels.d.ts","sourceRoot":"","sources":["../src/channels.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEpE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,aAAa,CAAC,EAAE,WAAW;IASvE,2CAA2C;sBACnB,MAAM,WAAW,MAAM,WAAW,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAgB5F,0CAA0C;uBACjB,MAAM,WAAW,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAUzF,2CAA2C;YACnC,MAAM,EAAE;EAIvB"}
@@ -0,0 +1,56 @@
1
+ import { relay, resolveConfig } from "./relay.js";
2
+ /**
3
+ * Multi-channel manager for sending to different Telegram chats.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { createChannels } from "iris-relay";
8
+ *
9
+ * const channels = createChannels([
10
+ * { name: "alerts", chatId: "-100123456" },
11
+ * { name: "deploys", chatId: "-100789012", silent: true },
12
+ * { name: "logs", chatId: "-100345678" },
13
+ * ]);
14
+ *
15
+ * await channels.send("alerts", "🚨 Server is down!");
16
+ * await channels.send("deploys", "🚀 v1.2.3 deployed");
17
+ * await channels.broadcast("System maintenance in 5 minutes");
18
+ * ```
19
+ */
20
+ export function createChannels(channels, defaultConfig) {
21
+ const resolved = resolveConfig(defaultConfig);
22
+ const channelMap = new Map();
23
+ for (const ch of channels) {
24
+ channelMap.set(ch.name, ch);
25
+ }
26
+ return {
27
+ /** Send a message to a specific channel */
28
+ async send(channelName, message, config) {
29
+ const channel = channelMap.get(channelName);
30
+ if (!channel) {
31
+ return { success: false, error: `Channel "${channelName}" not found` };
32
+ }
33
+ return relay(message, {
34
+ ...resolved,
35
+ botToken: channel.botToken ?? resolved.botToken,
36
+ chatId: channel.chatId,
37
+ parseMode: channel.parseMode ?? resolved.parseMode,
38
+ silent: channel.silent ?? resolved.silent,
39
+ ...config,
40
+ });
41
+ },
42
+ /** Broadcast a message to all channels */
43
+ async broadcast(message, config) {
44
+ const results = new Map();
45
+ for (const [name] of channelMap) {
46
+ results.set(name, await this.send(name, message, config));
47
+ }
48
+ return results;
49
+ },
50
+ /** Get list of registered channel names */
51
+ list() {
52
+ return Array.from(channelMap.keys());
53
+ },
54
+ };
55
+ }
56
+ //# sourceMappingURL=channels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channels.js","sourceRoot":"","sources":["../src/channels.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGlD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,cAAc,CAAC,QAAmB,EAAE,aAA2B;IAC3E,MAAM,QAAQ,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,IAAI,GAAG,EAAmB,CAAC;IAE9C,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QACxB,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAChC,CAAC;IAED,OAAO;QACH,2CAA2C;QAC3C,KAAK,CAAC,IAAI,CAAC,WAAmB,EAAE,OAAe,EAAE,MAAoB;YACjE,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACX,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,WAAW,aAAa,EAAE,CAAC;YAC3E,CAAC;YAED,OAAO,KAAK,CAAC,OAAO,EAAE;gBAClB,GAAG,QAAQ;gBACX,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ;gBAC/C,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS;gBAClD,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM;gBACzC,GAAG,MAAM;aACZ,CAAC,CAAC;QACP,CAAC;QAED,0CAA0C;QAC1C,KAAK,CAAC,SAAS,CAAC,OAAe,EAAE,MAAoB;YACjD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;YAE/C,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,UAAU,EAAE,CAAC;gBAC9B,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;YAC9D,CAAC;YAED,OAAO,OAAO,CAAC;QACnB,CAAC;QAED,2CAA2C;QAC3C,IAAI;YACA,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;KACJ,CAAC;AACN,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import { config } from "dotenv";
3
+ import { relay } from "./relay.js";
4
+ import { relayFile } from "./file.js";
5
+ config();
6
+ const args = process.argv.slice(2);
7
+ function printHelp() {
8
+ console.log(`
9
+ iris-relay — Send messages to Telegram from the CLI
10
+
11
+ Usage:
12
+ iris-relay "Your message here"
13
+ iris-relay --file ./path/to/file.log "Optional caption"
14
+ iris-relay --silent "No notification sound"
15
+ iris-relay --html "<b>Bold</b> message"
16
+
17
+ Options:
18
+ --file <path> Send a file instead of a text message
19
+ --silent Send without notification sound
20
+ --html Parse message as HTML
21
+ --markdown Parse message as Markdown
22
+ --help, -h Show this help message
23
+
24
+ Environment:
25
+ XERO_BOT_TOKEN Your Telegram bot token
26
+ XERO_CHAT_ID Your Telegram chat ID
27
+ `);
28
+ }
29
+ async function main() {
30
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
31
+ printHelp();
32
+ process.exit(0);
33
+ }
34
+ const silent = args.includes("--silent");
35
+ const html = args.includes("--html");
36
+ const markdown = args.includes("--markdown");
37
+ const fileIdx = args.indexOf("--file");
38
+ // Filter out flags to get the message
39
+ const messageArgs = args.filter((a, i) => !a.startsWith("--") && !(i === fileIdx + 1 && fileIdx >= 0));
40
+ const message = messageArgs.join(" ");
41
+ const parseMode = html ? "HTML" : markdown ? "Markdown" : undefined;
42
+ try {
43
+ // File mode
44
+ if (fileIdx >= 0) {
45
+ const filePath = args[fileIdx + 1];
46
+ if (!filePath) {
47
+ console.error("Error: --file requires a path argument");
48
+ process.exit(1);
49
+ }
50
+ const result = await relayFile(filePath, message || undefined, {
51
+ silent,
52
+ parseMode,
53
+ });
54
+ if (result.success) {
55
+ console.log(`✅ File sent: ${filePath}`);
56
+ }
57
+ else {
58
+ console.error(`❌ Failed: ${result.error}`);
59
+ process.exit(1);
60
+ }
61
+ return;
62
+ }
63
+ // Message mode
64
+ if (!message) {
65
+ console.error("Error: No message provided");
66
+ printHelp();
67
+ process.exit(1);
68
+ }
69
+ const result = await relay(message, { silent, parseMode });
70
+ if (result.success) {
71
+ console.log(`✅ Message sent (ID: ${result.messageId})`);
72
+ }
73
+ else {
74
+ console.error(`❌ Failed: ${result.error}`);
75
+ process.exit(1);
76
+ }
77
+ }
78
+ catch (err) {
79
+ console.error(`💥 ${err instanceof Error ? err.message : String(err)}`);
80
+ process.exit(1);
81
+ }
82
+ }
83
+ main();
84
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,EAAE,CAAC;AAET,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,SAAS,SAAS;IACd,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;CAmBf,CAAC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACf,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACtE,SAAS,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEvC,sCAAsC;IACtC,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAC3B,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,GAAG,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC,CACxE,CAAC;IACF,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEtC,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,MAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAmB,CAAC,CAAC,CAAC,SAAS,CAAC;IAEtF,IAAI,CAAC;QACD,YAAY;QACZ,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;YACf,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;YACnC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;gBACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,IAAI,SAAS,EAAE;gBAC3D,MAAM;gBACN,SAAS;aACZ,CAAC,CAAC;YAEH,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACjB,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,EAAE,CAAC,CAAC;YAC5C,CAAC;iBAAM,CAAC;gBACJ,OAAO,CAAC,KAAK,CAAC,aAAa,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,CAAC;YACD,OAAO;QACX,CAAC;QAED,eAAe;QACf,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAC5C,SAAS,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAE3D,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,uBAAuB,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC;QAC5D,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,KAAK,CAAC,aAAa,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC"}
package/dist/file.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { RelayConfig, RelayResult } from "./types.js";
2
+ /**
3
+ * Send a file to your Telegram chat via sendDocument API.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { relayFile } from "iris-relay";
8
+ * await relayFile("./logs/error.log", "Latest error log");
9
+ * ```
10
+ */
11
+ export declare function relayFile(filePath: string, caption?: string, config?: RelayConfig): Promise<RelayResult>;
12
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAoB,MAAM,YAAY,CAAC;AAO7E;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACrB,OAAO,CAAC,WAAW,CAAC,CAgDtB"}