ntfy-bridge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.gitattributes ADDED
@@ -0,0 +1,3 @@
1
+ # Linguist overrides
2
+ *.ts linguist-language=TypeScript
3
+ package-lock.json linguist-generated=true
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # ntfy-bridge
2
+
3
+ Local bridge from [ntfy.sh](https://ntfy.sh) to localhost.
4
+
5
+ Subscribe to ntfy.sh topics and forward messages to your local endpoint. Perfect for AI agents that need real-time notifications without exposing public webhooks.
6
+
7
+ ```
8
+ ntfy.sh → ntfy-bridge (local) → localhost:8080
9
+ ```
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install -g ntfy-bridge
15
+ ```
16
+
17
+ Or run directly with npx:
18
+
19
+ ```bash
20
+ npx ntfy-bridge --topic alerts --forward http://localhost:8080/hooks
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```bash
26
+ ntfy-bridge --topic <topic> --forward <url>
27
+ ```
28
+
29
+ **Options:**
30
+ - `-t, --topic <topic>` — ntfy.sh topic to subscribe (can repeat)
31
+ - `-f, --forward <url>` — Local endpoint to forward messages to
32
+
33
+ **Examples:**
34
+
35
+ ```bash
36
+ # Single topic
37
+ ntfy-bridge -t alerts -f http://localhost:8080/hooks
38
+
39
+ # Multiple topics
40
+ ntfy-bridge -t alerts -t news -f http://localhost:18789/hooks/ntfy
41
+
42
+ # Full URL
43
+ ntfy-bridge -t ntfy.sh/my-secret-topic -f http://localhost:8080/hooks
44
+
45
+ # Self-hosted ntfy
46
+ ntfy-bridge -t ntfy.example.com/alerts -f http://localhost:8080/hooks
47
+ ```
48
+
49
+ ## Payload Format
50
+
51
+ Messages are normalized and forwarded as JSON:
52
+
53
+ ```json
54
+ {
55
+ "source": "ntfy",
56
+ "topic": "alerts",
57
+ "id": "abc123",
58
+ "time": 1707379800,
59
+ "title": "Alert",
60
+ "message": "Something happened",
61
+ "tags": ["warning"],
62
+ "priority": 3,
63
+ "raw": { ... }
64
+ }
65
+ ```
66
+
67
+ ## Use Cases
68
+
69
+ - **AI Agents** — Feed real-time signals to OpenClaw, AutoGPT, etc.
70
+ - **Automation** — Trigger local scripts from ntfy notifications
71
+ - **Development** — Test webhook integrations locally
72
+
73
+ ## How It Works
74
+
75
+ 1. ntfy-bridge connects to ntfy.sh topics via SSE (outbound only)
76
+ 2. When a message arrives, it forwards to your local endpoint
77
+ 3. Your app receives the webhook on localhost
78
+
79
+ No public endpoint needed. No firewall config. Just run locally.
80
+
81
+ ## Related
82
+
83
+ For a managed signal marketplace with routing, billing, and delivery guarantees, see [Herald](https://github.com/starksama/herald).
84
+
85
+ ## License
86
+
87
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ import EventSource from "eventsource";
3
+ import { normalizeTopicUrl, createPayload } from "./lib.js";
4
+ function parseArgs() {
5
+ const args = process.argv.slice(2);
6
+ const topics = [];
7
+ let forward = "";
8
+ for (let i = 0; i < args.length; i++) {
9
+ const arg = args[i];
10
+ if (arg === "--topic" || arg === "-t") {
11
+ topics.push(args[++i]);
12
+ }
13
+ else if (arg === "--forward" || arg === "-f") {
14
+ forward = args[++i];
15
+ }
16
+ else if (arg === "--help" || arg === "-h") {
17
+ console.log(`
18
+ ntfy-bridge - Local bridge from ntfy.sh to localhost
19
+
20
+ Usage:
21
+ ntfy-bridge --topic <topic> --forward <url>
22
+
23
+ Options:
24
+ -t, --topic <topic> ntfy.sh topic to subscribe to (can be repeated)
25
+ -f, --forward <url> Local endpoint to forward messages to
26
+ -h, --help Show this help
27
+
28
+ Examples:
29
+ ntfy-bridge -t alerts -f http://localhost:8080/hooks
30
+ ntfy-bridge -t ntfy.sh/my-topic -t ntfy.sh/other -f http://localhost:18789/hooks/ntfy
31
+ `);
32
+ process.exit(0);
33
+ }
34
+ }
35
+ if (topics.length === 0) {
36
+ console.error("Error: No topics specified. Use --topic <topic>");
37
+ process.exit(1);
38
+ }
39
+ if (!forward) {
40
+ console.error("Error: No forward URL specified. Use --forward <url>");
41
+ process.exit(1);
42
+ }
43
+ return { topics, forward };
44
+ }
45
+ async function forwardMessage(msg, forwardUrl) {
46
+ const payload = createPayload(msg);
47
+ try {
48
+ const response = await fetch(forwardUrl, {
49
+ method: "POST",
50
+ headers: { "Content-Type": "application/json" },
51
+ body: JSON.stringify(payload),
52
+ });
53
+ if (response.ok) {
54
+ console.log(`[${msg.topic}] Forwarded: ${msg.title || msg.message || msg.id}`);
55
+ }
56
+ else {
57
+ console.warn(`[${msg.topic}] Forward returned ${response.status}`);
58
+ }
59
+ }
60
+ catch (error) {
61
+ console.error(`[${msg.topic}] Forward failed:`, error);
62
+ }
63
+ }
64
+ function subscribeToTopic(topic, forwardUrl) {
65
+ const url = normalizeTopicUrl(topic);
66
+ console.log(`Subscribing to: ${url}`);
67
+ const es = new EventSource(url);
68
+ es.onopen = () => {
69
+ console.log(`Connected to ${topic}`);
70
+ };
71
+ es.onmessage = async (event) => {
72
+ try {
73
+ const msg = JSON.parse(event.data);
74
+ if (msg.event === "message" || !msg.event) {
75
+ await forwardMessage(msg, forwardUrl);
76
+ }
77
+ }
78
+ catch (error) {
79
+ console.warn(`Failed to parse message:`, error);
80
+ }
81
+ };
82
+ es.onerror = (error) => {
83
+ console.error(`SSE error on ${topic}:`, error);
84
+ // EventSource auto-reconnects
85
+ };
86
+ }
87
+ function main() {
88
+ const { topics, forward } = parseArgs();
89
+ console.log("ntfy-bridge starting");
90
+ console.log(`Forwarding to: ${forward}`);
91
+ console.log(`Subscribing to ${topics.length} topic(s)`);
92
+ for (const topic of topics) {
93
+ subscribeToTopic(topic, forward);
94
+ }
95
+ // Keep alive
96
+ process.on("SIGINT", () => {
97
+ console.log("\nShutting down...");
98
+ process.exit(0);
99
+ });
100
+ }
101
+ main();
package/dist/lib.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ export interface NtfyMessage {
2
+ id: string;
3
+ time: number;
4
+ event?: string;
5
+ topic: string;
6
+ title?: string;
7
+ message?: string;
8
+ tags?: string[];
9
+ priority?: number;
10
+ }
11
+ export interface BridgePayload {
12
+ source: "ntfy";
13
+ topic: string;
14
+ id: string;
15
+ time: number;
16
+ title?: string;
17
+ message?: string;
18
+ tags: string[];
19
+ priority: number;
20
+ raw: NtfyMessage;
21
+ }
22
+ export declare function normalizeTopicUrl(topic: string): string;
23
+ export declare function createPayload(msg: NtfyMessage): BridgePayload;
24
+ export declare function parseArgs(argv: string[]): {
25
+ topics: string[];
26
+ forward: string;
27
+ } | null;
package/dist/lib.js ADDED
@@ -0,0 +1,45 @@
1
+ export function normalizeTopicUrl(topic) {
2
+ if (topic.startsWith("http")) {
3
+ return `${topic.replace(/\/$/, "")}/sse`;
4
+ }
5
+ else if (topic.includes("/")) {
6
+ return `https://${topic.replace(/\/$/, "")}/sse`;
7
+ }
8
+ else {
9
+ return `https://ntfy.sh/${topic}/sse`;
10
+ }
11
+ }
12
+ export function createPayload(msg) {
13
+ return {
14
+ source: "ntfy",
15
+ topic: msg.topic,
16
+ id: msg.id,
17
+ time: msg.time,
18
+ title: msg.title,
19
+ message: msg.message,
20
+ tags: msg.tags || [],
21
+ priority: msg.priority || 3,
22
+ raw: msg,
23
+ };
24
+ }
25
+ export function parseArgs(argv) {
26
+ const args = argv.slice(2);
27
+ const topics = [];
28
+ let forward = "";
29
+ for (let i = 0; i < args.length; i++) {
30
+ const arg = args[i];
31
+ if (arg === "--topic" || arg === "-t") {
32
+ topics.push(args[++i]);
33
+ }
34
+ else if (arg === "--forward" || arg === "-f") {
35
+ forward = args[++i];
36
+ }
37
+ else if (arg === "--help" || arg === "-h") {
38
+ return null; // Signal help requested
39
+ }
40
+ }
41
+ if (topics.length === 0 || !forward) {
42
+ return null;
43
+ }
44
+ return { topics, forward };
45
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { normalizeTopicUrl, createPayload, parseArgs } from "./lib.js";
3
+ describe("normalizeTopicUrl", () => {
4
+ it("handles full https URL", () => {
5
+ expect(normalizeTopicUrl("https://ntfy.sh/alerts")).toBe("https://ntfy.sh/alerts/sse");
6
+ });
7
+ it("handles full https URL with trailing slash", () => {
8
+ expect(normalizeTopicUrl("https://ntfy.sh/alerts/")).toBe("https://ntfy.sh/alerts/sse");
9
+ });
10
+ it("handles domain/topic format", () => {
11
+ expect(normalizeTopicUrl("ntfy.example.com/alerts")).toBe("https://ntfy.example.com/alerts/sse");
12
+ });
13
+ it("handles simple topic name (defaults to ntfy.sh)", () => {
14
+ expect(normalizeTopicUrl("alerts")).toBe("https://ntfy.sh/alerts/sse");
15
+ });
16
+ it("handles http URL", () => {
17
+ expect(normalizeTopicUrl("http://localhost:8080/test")).toBe("http://localhost:8080/test/sse");
18
+ });
19
+ });
20
+ describe("createPayload", () => {
21
+ it("creates payload with all fields", () => {
22
+ const msg = {
23
+ id: "abc123",
24
+ time: 1700000000,
25
+ event: "message",
26
+ topic: "alerts",
27
+ title: "Test Title",
28
+ message: "Test message",
29
+ tags: ["warning", "test"],
30
+ priority: 4,
31
+ };
32
+ const payload = createPayload(msg);
33
+ expect(payload.source).toBe("ntfy");
34
+ expect(payload.topic).toBe("alerts");
35
+ expect(payload.id).toBe("abc123");
36
+ expect(payload.time).toBe(1700000000);
37
+ expect(payload.title).toBe("Test Title");
38
+ expect(payload.message).toBe("Test message");
39
+ expect(payload.tags).toEqual(["warning", "test"]);
40
+ expect(payload.priority).toBe(4);
41
+ expect(payload.raw).toEqual(msg);
42
+ });
43
+ it("uses defaults for missing optional fields", () => {
44
+ const msg = {
45
+ id: "xyz",
46
+ time: 1700000000,
47
+ topic: "test",
48
+ };
49
+ const payload = createPayload(msg);
50
+ expect(payload.tags).toEqual([]);
51
+ expect(payload.priority).toBe(3);
52
+ expect(payload.title).toBeUndefined();
53
+ expect(payload.message).toBeUndefined();
54
+ });
55
+ });
56
+ describe("parseArgs", () => {
57
+ it("parses topic and forward", () => {
58
+ const result = parseArgs(["node", "script", "-t", "alerts", "-f", "http://localhost:8080"]);
59
+ expect(result).toEqual({
60
+ topics: ["alerts"],
61
+ forward: "http://localhost:8080",
62
+ });
63
+ });
64
+ it("parses multiple topics", () => {
65
+ const result = parseArgs([
66
+ "node", "script",
67
+ "--topic", "alerts",
68
+ "--topic", "news",
69
+ "--forward", "http://localhost:8080"
70
+ ]);
71
+ expect(result).toEqual({
72
+ topics: ["alerts", "news"],
73
+ forward: "http://localhost:8080",
74
+ });
75
+ });
76
+ it("returns null for help flag", () => {
77
+ const result = parseArgs(["node", "script", "--help"]);
78
+ expect(result).toBeNull();
79
+ });
80
+ it("returns null if missing topics", () => {
81
+ const result = parseArgs(["node", "script", "-f", "http://localhost"]);
82
+ expect(result).toBeNull();
83
+ });
84
+ it("returns null if missing forward", () => {
85
+ const result = parseArgs(["node", "script", "-t", "alerts"]);
86
+ expect(result).toBeNull();
87
+ });
88
+ });
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "ntfy-bridge",
3
+ "version": "0.1.0",
4
+ "description": "Local bridge from ntfy.sh to localhost",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "ntfy-bridge": "dist/index.js"
9
+ },
10
+ "keywords": [
11
+ "ntfy",
12
+ "notifications",
13
+ "webhook",
14
+ "bridge",
15
+ "localhost",
16
+ "ai-agents"
17
+ ],
18
+ "author": "starksama",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/starksama/ntfy-bridge.git"
23
+ },
24
+ "dependencies": {
25
+ "eventsource": "^2.0.2"
26
+ },
27
+ "devDependencies": {
28
+ "@types/eventsource": "^1.1.15",
29
+ "@types/node": "^22.0.0",
30
+ "tsx": "^4.0.0",
31
+ "typescript": "^5.0.0",
32
+ "vitest": "^4.0.18"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc",
36
+ "start": "node dist/index.js",
37
+ "dev": "tsx src/index.ts",
38
+ "test": "vitest run",
39
+ "test:watch": "vitest"
40
+ }
41
+ }
package/src/index.ts ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+
3
+ import EventSource from "eventsource";
4
+ import { normalizeTopicUrl, createPayload, NtfyMessage } from "./lib.js";
5
+
6
+ function parseArgs(): { topics: string[]; forward: string } {
7
+ const args = process.argv.slice(2);
8
+ const topics: string[] = [];
9
+ let forward = "";
10
+
11
+ for (let i = 0; i < args.length; i++) {
12
+ const arg = args[i];
13
+ if (arg === "--topic" || arg === "-t") {
14
+ topics.push(args[++i]);
15
+ } else if (arg === "--forward" || arg === "-f") {
16
+ forward = args[++i];
17
+ } else if (arg === "--help" || arg === "-h") {
18
+ console.log(`
19
+ ntfy-bridge - Local bridge from ntfy.sh to localhost
20
+
21
+ Usage:
22
+ ntfy-bridge --topic <topic> --forward <url>
23
+
24
+ Options:
25
+ -t, --topic <topic> ntfy.sh topic to subscribe to (can be repeated)
26
+ -f, --forward <url> Local endpoint to forward messages to
27
+ -h, --help Show this help
28
+
29
+ Examples:
30
+ ntfy-bridge -t alerts -f http://localhost:8080/hooks
31
+ ntfy-bridge -t ntfy.sh/my-topic -t ntfy.sh/other -f http://localhost:18789/hooks/ntfy
32
+ `);
33
+ process.exit(0);
34
+ }
35
+ }
36
+
37
+ if (topics.length === 0) {
38
+ console.error("Error: No topics specified. Use --topic <topic>");
39
+ process.exit(1);
40
+ }
41
+ if (!forward) {
42
+ console.error("Error: No forward URL specified. Use --forward <url>");
43
+ process.exit(1);
44
+ }
45
+
46
+ return { topics, forward };
47
+ }
48
+
49
+ async function forwardMessage(
50
+ msg: NtfyMessage,
51
+ forwardUrl: string
52
+ ): Promise<void> {
53
+ const payload = createPayload(msg);
54
+
55
+ try {
56
+ const response = await fetch(forwardUrl, {
57
+ method: "POST",
58
+ headers: { "Content-Type": "application/json" },
59
+ body: JSON.stringify(payload),
60
+ });
61
+
62
+ if (response.ok) {
63
+ console.log(`[${msg.topic}] Forwarded: ${msg.title || msg.message || msg.id}`);
64
+ } else {
65
+ console.warn(`[${msg.topic}] Forward returned ${response.status}`);
66
+ }
67
+ } catch (error) {
68
+ console.error(`[${msg.topic}] Forward failed:`, error);
69
+ }
70
+ }
71
+
72
+ function subscribeToTopic(topic: string, forwardUrl: string): void {
73
+ const url = normalizeTopicUrl(topic);
74
+ console.log(`Subscribing to: ${url}`);
75
+
76
+ const es = new EventSource(url);
77
+
78
+ es.onopen = () => {
79
+ console.log(`Connected to ${topic}`);
80
+ };
81
+
82
+ es.onmessage = async (event) => {
83
+ try {
84
+ const msg: NtfyMessage = JSON.parse(event.data);
85
+ if (msg.event === "message" || !msg.event) {
86
+ await forwardMessage(msg, forwardUrl);
87
+ }
88
+ } catch (error) {
89
+ console.warn(`Failed to parse message:`, error);
90
+ }
91
+ };
92
+
93
+ es.onerror = (error) => {
94
+ console.error(`SSE error on ${topic}:`, error);
95
+ // EventSource auto-reconnects
96
+ };
97
+ }
98
+
99
+ function main(): void {
100
+ const { topics, forward } = parseArgs();
101
+
102
+ console.log("ntfy-bridge starting");
103
+ console.log(`Forwarding to: ${forward}`);
104
+ console.log(`Subscribing to ${topics.length} topic(s)`);
105
+
106
+ for (const topic of topics) {
107
+ subscribeToTopic(topic, forward);
108
+ }
109
+
110
+ // Keep alive
111
+ process.on("SIGINT", () => {
112
+ console.log("\nShutting down...");
113
+ process.exit(0);
114
+ });
115
+ }
116
+
117
+ main();
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { normalizeTopicUrl, createPayload, parseArgs, NtfyMessage } from "./lib.js";
3
+
4
+ describe("normalizeTopicUrl", () => {
5
+ it("handles full https URL", () => {
6
+ expect(normalizeTopicUrl("https://ntfy.sh/alerts")).toBe(
7
+ "https://ntfy.sh/alerts/sse"
8
+ );
9
+ });
10
+
11
+ it("handles full https URL with trailing slash", () => {
12
+ expect(normalizeTopicUrl("https://ntfy.sh/alerts/")).toBe(
13
+ "https://ntfy.sh/alerts/sse"
14
+ );
15
+ });
16
+
17
+ it("handles domain/topic format", () => {
18
+ expect(normalizeTopicUrl("ntfy.example.com/alerts")).toBe(
19
+ "https://ntfy.example.com/alerts/sse"
20
+ );
21
+ });
22
+
23
+ it("handles simple topic name (defaults to ntfy.sh)", () => {
24
+ expect(normalizeTopicUrl("alerts")).toBe("https://ntfy.sh/alerts/sse");
25
+ });
26
+
27
+ it("handles http URL", () => {
28
+ expect(normalizeTopicUrl("http://localhost:8080/test")).toBe(
29
+ "http://localhost:8080/test/sse"
30
+ );
31
+ });
32
+ });
33
+
34
+ describe("createPayload", () => {
35
+ it("creates payload with all fields", () => {
36
+ const msg: NtfyMessage = {
37
+ id: "abc123",
38
+ time: 1700000000,
39
+ event: "message",
40
+ topic: "alerts",
41
+ title: "Test Title",
42
+ message: "Test message",
43
+ tags: ["warning", "test"],
44
+ priority: 4,
45
+ };
46
+
47
+ const payload = createPayload(msg);
48
+
49
+ expect(payload.source).toBe("ntfy");
50
+ expect(payload.topic).toBe("alerts");
51
+ expect(payload.id).toBe("abc123");
52
+ expect(payload.time).toBe(1700000000);
53
+ expect(payload.title).toBe("Test Title");
54
+ expect(payload.message).toBe("Test message");
55
+ expect(payload.tags).toEqual(["warning", "test"]);
56
+ expect(payload.priority).toBe(4);
57
+ expect(payload.raw).toEqual(msg);
58
+ });
59
+
60
+ it("uses defaults for missing optional fields", () => {
61
+ const msg: NtfyMessage = {
62
+ id: "xyz",
63
+ time: 1700000000,
64
+ topic: "test",
65
+ };
66
+
67
+ const payload = createPayload(msg);
68
+
69
+ expect(payload.tags).toEqual([]);
70
+ expect(payload.priority).toBe(3);
71
+ expect(payload.title).toBeUndefined();
72
+ expect(payload.message).toBeUndefined();
73
+ });
74
+ });
75
+
76
+ describe("parseArgs", () => {
77
+ it("parses topic and forward", () => {
78
+ const result = parseArgs(["node", "script", "-t", "alerts", "-f", "http://localhost:8080"]);
79
+ expect(result).toEqual({
80
+ topics: ["alerts"],
81
+ forward: "http://localhost:8080",
82
+ });
83
+ });
84
+
85
+ it("parses multiple topics", () => {
86
+ const result = parseArgs([
87
+ "node", "script",
88
+ "--topic", "alerts",
89
+ "--topic", "news",
90
+ "--forward", "http://localhost:8080"
91
+ ]);
92
+ expect(result).toEqual({
93
+ topics: ["alerts", "news"],
94
+ forward: "http://localhost:8080",
95
+ });
96
+ });
97
+
98
+ it("returns null for help flag", () => {
99
+ const result = parseArgs(["node", "script", "--help"]);
100
+ expect(result).toBeNull();
101
+ });
102
+
103
+ it("returns null if missing topics", () => {
104
+ const result = parseArgs(["node", "script", "-f", "http://localhost"]);
105
+ expect(result).toBeNull();
106
+ });
107
+
108
+ it("returns null if missing forward", () => {
109
+ const result = parseArgs(["node", "script", "-t", "alerts"]);
110
+ expect(result).toBeNull();
111
+ });
112
+ });
package/src/lib.ts ADDED
@@ -0,0 +1,69 @@
1
+ export interface NtfyMessage {
2
+ id: string;
3
+ time: number;
4
+ event?: string;
5
+ topic: string;
6
+ title?: string;
7
+ message?: string;
8
+ tags?: string[];
9
+ priority?: number;
10
+ }
11
+
12
+ export interface BridgePayload {
13
+ source: "ntfy";
14
+ topic: string;
15
+ id: string;
16
+ time: number;
17
+ title?: string;
18
+ message?: string;
19
+ tags: string[];
20
+ priority: number;
21
+ raw: NtfyMessage;
22
+ }
23
+
24
+ export function normalizeTopicUrl(topic: string): string {
25
+ if (topic.startsWith("http")) {
26
+ return `${topic.replace(/\/$/, "")}/sse`;
27
+ } else if (topic.includes("/")) {
28
+ return `https://${topic.replace(/\/$/, "")}/sse`;
29
+ } else {
30
+ return `https://ntfy.sh/${topic}/sse`;
31
+ }
32
+ }
33
+
34
+ export function createPayload(msg: NtfyMessage): BridgePayload {
35
+ return {
36
+ source: "ntfy",
37
+ topic: msg.topic,
38
+ id: msg.id,
39
+ time: msg.time,
40
+ title: msg.title,
41
+ message: msg.message,
42
+ tags: msg.tags || [],
43
+ priority: msg.priority || 3,
44
+ raw: msg,
45
+ };
46
+ }
47
+
48
+ export function parseArgs(argv: string[]): { topics: string[]; forward: string } | null {
49
+ const args = argv.slice(2);
50
+ const topics: string[] = [];
51
+ let forward = "";
52
+
53
+ for (let i = 0; i < args.length; i++) {
54
+ const arg = args[i];
55
+ if (arg === "--topic" || arg === "-t") {
56
+ topics.push(args[++i]);
57
+ } else if (arg === "--forward" || arg === "-f") {
58
+ forward = args[++i];
59
+ } else if (arg === "--help" || arg === "-h") {
60
+ return null; // Signal help requested
61
+ }
62
+ }
63
+
64
+ if (topics.length === 0 || !forward) {
65
+ return null;
66
+ }
67
+
68
+ return { topics, forward };
69
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }