ntfy-bridge 0.1.0 → 0.1.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/dist/index.js CHANGED
@@ -5,6 +5,8 @@ function parseArgs() {
5
5
  const args = process.argv.slice(2);
6
6
  const topics = [];
7
7
  let forward = "";
8
+ const headers = {};
9
+ let openclawMode = false;
8
10
  for (let i = 0; i < args.length; i++) {
9
11
  const arg = args[i];
10
12
  if (arg === "--topic" || arg === "-t") {
@@ -13,21 +15,35 @@ function parseArgs() {
13
15
  else if (arg === "--forward" || arg === "-f") {
14
16
  forward = args[++i];
15
17
  }
18
+ else if (arg === "--header" || arg === "-H") {
19
+ const header = args[++i];
20
+ const colonIdx = header.indexOf(":");
21
+ if (colonIdx > 0) {
22
+ const key = header.slice(0, colonIdx).trim();
23
+ const value = header.slice(colonIdx + 1).trim();
24
+ headers[key] = value;
25
+ }
26
+ }
27
+ else if (arg === "--openclaw") {
28
+ openclawMode = true;
29
+ }
16
30
  else if (arg === "--help" || arg === "-h") {
17
31
  console.log(`
18
32
  ntfy-bridge - Local bridge from ntfy.sh to localhost
19
33
 
20
34
  Usage:
21
- ntfy-bridge --topic <topic> --forward <url>
35
+ ntfy-bridge --topic <topic> --forward <url> [options]
22
36
 
23
37
  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
38
+ -t, --topic <topic> ntfy.sh topic to subscribe to (can be repeated)
39
+ -f, --forward <url> Local endpoint to forward messages to
40
+ -H, --header <k:v> Custom header to include in forward requests (can be repeated)
41
+ --openclaw Format payload for OpenClaw hooks (sends {text: "..."})
42
+ -h, --help Show this help
27
43
 
28
44
  Examples:
29
45
  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
46
+ ntfy-bridge -t alerts -f http://localhost:18789/hooks/wake --openclaw -H "Authorization: Bearer token"
31
47
  `);
32
48
  process.exit(0);
33
49
  }
@@ -40,15 +56,25 @@ Examples:
40
56
  console.error("Error: No forward URL specified. Use --forward <url>");
41
57
  process.exit(1);
42
58
  }
43
- return { topics, forward };
59
+ return { topics, forward, headers, openclawMode };
44
60
  }
45
- async function forwardMessage(msg, forwardUrl) {
46
- const payload = createPayload(msg);
61
+ async function forwardMessage(msg, forwardUrl, customHeaders = {}, openclawMode = false) {
62
+ let body;
63
+ if (openclawMode) {
64
+ // OpenClaw expects {text: "message"} format
65
+ const text = msg.title
66
+ ? `[${msg.topic}] ${msg.title}: ${msg.message || ""}`
67
+ : `[${msg.topic}] ${msg.message || msg.id}`;
68
+ body = JSON.stringify({ text });
69
+ }
70
+ else {
71
+ body = JSON.stringify(createPayload(msg));
72
+ }
47
73
  try {
48
74
  const response = await fetch(forwardUrl, {
49
75
  method: "POST",
50
- headers: { "Content-Type": "application/json" },
51
- body: JSON.stringify(payload),
76
+ headers: { "Content-Type": "application/json", ...customHeaders },
77
+ body,
52
78
  });
53
79
  if (response.ok) {
54
80
  console.log(`[${msg.topic}] Forwarded: ${msg.title || msg.message || msg.id}`);
@@ -61,7 +87,7 @@ async function forwardMessage(msg, forwardUrl) {
61
87
  console.error(`[${msg.topic}] Forward failed:`, error);
62
88
  }
63
89
  }
64
- function subscribeToTopic(topic, forwardUrl) {
90
+ function subscribeToTopic(topic, forwardUrl, headers, openclawMode) {
65
91
  const url = normalizeTopicUrl(topic);
66
92
  console.log(`Subscribing to: ${url}`);
67
93
  const es = new EventSource(url);
@@ -72,7 +98,7 @@ function subscribeToTopic(topic, forwardUrl) {
72
98
  try {
73
99
  const msg = JSON.parse(event.data);
74
100
  if (msg.event === "message" || !msg.event) {
75
- await forwardMessage(msg, forwardUrl);
101
+ await forwardMessage(msg, forwardUrl, headers, openclawMode);
76
102
  }
77
103
  }
78
104
  catch (error) {
@@ -85,12 +111,14 @@ function subscribeToTopic(topic, forwardUrl) {
85
111
  };
86
112
  }
87
113
  function main() {
88
- const { topics, forward } = parseArgs();
114
+ const { topics, forward, headers, openclawMode } = parseArgs();
89
115
  console.log("ntfy-bridge starting");
90
116
  console.log(`Forwarding to: ${forward}`);
117
+ if (openclawMode)
118
+ console.log("OpenClaw mode enabled");
91
119
  console.log(`Subscribing to ${topics.length} topic(s)`);
92
120
  for (const topic of topics) {
93
- subscribeToTopic(topic, forward);
121
+ subscribeToTopic(topic, forward, headers, openclawMode);
94
122
  }
95
123
  // Keep alive
96
124
  process.on("SIGINT", () => {
package/dist/lib.d.ts CHANGED
@@ -21,7 +21,10 @@ export interface BridgePayload {
21
21
  }
22
22
  export declare function normalizeTopicUrl(topic: string): string;
23
23
  export declare function createPayload(msg: NtfyMessage): BridgePayload;
24
- export declare function parseArgs(argv: string[]): {
24
+ export interface ParsedArgs {
25
25
  topics: string[];
26
26
  forward: string;
27
- } | null;
27
+ headers: Record<string, string>;
28
+ openclawMode: boolean;
29
+ }
30
+ export declare function parseArgs(argv: string[]): ParsedArgs | null;
package/dist/lib.js CHANGED
@@ -26,6 +26,8 @@ export function parseArgs(argv) {
26
26
  const args = argv.slice(2);
27
27
  const topics = [];
28
28
  let forward = "";
29
+ const headers = {};
30
+ let openclawMode = false;
29
31
  for (let i = 0; i < args.length; i++) {
30
32
  const arg = args[i];
31
33
  if (arg === "--topic" || arg === "-t") {
@@ -34,6 +36,18 @@ export function parseArgs(argv) {
34
36
  else if (arg === "--forward" || arg === "-f") {
35
37
  forward = args[++i];
36
38
  }
39
+ else if (arg === "--header" || arg === "-H") {
40
+ const header = args[++i];
41
+ const colonIdx = header.indexOf(":");
42
+ if (colonIdx > 0) {
43
+ const key = header.slice(0, colonIdx).trim();
44
+ const value = header.slice(colonIdx + 1).trim();
45
+ headers[key] = value;
46
+ }
47
+ }
48
+ else if (arg === "--openclaw") {
49
+ openclawMode = true;
50
+ }
37
51
  else if (arg === "--help" || arg === "-h") {
38
52
  return null; // Signal help requested
39
53
  }
@@ -41,5 +55,5 @@ export function parseArgs(argv) {
41
55
  if (topics.length === 0 || !forward) {
42
56
  return null;
43
57
  }
44
- return { topics, forward };
58
+ return { topics, forward, headers, openclawMode };
45
59
  }
package/dist/lib.test.js CHANGED
@@ -59,6 +59,8 @@ describe("parseArgs", () => {
59
59
  expect(result).toEqual({
60
60
  topics: ["alerts"],
61
61
  forward: "http://localhost:8080",
62
+ headers: {},
63
+ openclawMode: false,
62
64
  });
63
65
  });
64
66
  it("parses multiple topics", () => {
@@ -71,6 +73,40 @@ describe("parseArgs", () => {
71
73
  expect(result).toEqual({
72
74
  topics: ["alerts", "news"],
73
75
  forward: "http://localhost:8080",
76
+ headers: {},
77
+ openclawMode: false,
78
+ });
79
+ });
80
+ it("parses custom headers", () => {
81
+ const result = parseArgs([
82
+ "node", "script",
83
+ "-t", "alerts",
84
+ "-f", "http://localhost:8080",
85
+ "-H", "Authorization: Bearer token123",
86
+ "-H", "X-Custom: value"
87
+ ]);
88
+ expect(result).toEqual({
89
+ topics: ["alerts"],
90
+ forward: "http://localhost:8080",
91
+ headers: {
92
+ "Authorization": "Bearer token123",
93
+ "X-Custom": "value",
94
+ },
95
+ openclawMode: false,
96
+ });
97
+ });
98
+ it("parses openclaw mode", () => {
99
+ const result = parseArgs([
100
+ "node", "script",
101
+ "-t", "alerts",
102
+ "-f", "http://localhost:8080",
103
+ "--openclaw"
104
+ ]);
105
+ expect(result).toEqual({
106
+ topics: ["alerts"],
107
+ forward: "http://localhost:8080",
108
+ headers: {},
109
+ openclawMode: true,
74
110
  });
75
111
  });
76
112
  it("returns null for help flag", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ntfy-bridge",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Local bridge from ntfy.sh to localhost",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import EventSource from "eventsource";
4
- import { normalizeTopicUrl, createPayload, NtfyMessage } from "./lib.js";
4
+ import { normalizeTopicUrl, createPayload, NtfyMessage, ParsedArgs } from "./lib.js";
5
5
 
6
- function parseArgs(): { topics: string[]; forward: string } {
6
+ function parseArgs(): ParsedArgs {
7
7
  const args = process.argv.slice(2);
8
8
  const topics: string[] = [];
9
9
  let forward = "";
10
+ const headers: Record<string, string> = {};
11
+ let openclawMode = false;
10
12
 
11
13
  for (let i = 0; i < args.length; i++) {
12
14
  const arg = args[i];
@@ -14,21 +16,33 @@ function parseArgs(): { topics: string[]; forward: string } {
14
16
  topics.push(args[++i]);
15
17
  } else if (arg === "--forward" || arg === "-f") {
16
18
  forward = args[++i];
19
+ } else if (arg === "--header" || arg === "-H") {
20
+ const header = args[++i];
21
+ const colonIdx = header.indexOf(":");
22
+ if (colonIdx > 0) {
23
+ const key = header.slice(0, colonIdx).trim();
24
+ const value = header.slice(colonIdx + 1).trim();
25
+ headers[key] = value;
26
+ }
27
+ } else if (arg === "--openclaw") {
28
+ openclawMode = true;
17
29
  } else if (arg === "--help" || arg === "-h") {
18
30
  console.log(`
19
31
  ntfy-bridge - Local bridge from ntfy.sh to localhost
20
32
 
21
33
  Usage:
22
- ntfy-bridge --topic <topic> --forward <url>
34
+ ntfy-bridge --topic <topic> --forward <url> [options]
23
35
 
24
36
  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
37
+ -t, --topic <topic> ntfy.sh topic to subscribe to (can be repeated)
38
+ -f, --forward <url> Local endpoint to forward messages to
39
+ -H, --header <k:v> Custom header to include in forward requests (can be repeated)
40
+ --openclaw Format payload for OpenClaw hooks (sends {text: "..."})
41
+ -h, --help Show this help
28
42
 
29
43
  Examples:
30
44
  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
45
+ ntfy-bridge -t alerts -f http://localhost:18789/hooks/wake --openclaw -H "Authorization: Bearer token"
32
46
  `);
33
47
  process.exit(0);
34
48
  }
@@ -43,20 +57,32 @@ Examples:
43
57
  process.exit(1);
44
58
  }
45
59
 
46
- return { topics, forward };
60
+ return { topics, forward, headers, openclawMode };
47
61
  }
48
62
 
49
63
  async function forwardMessage(
50
64
  msg: NtfyMessage,
51
- forwardUrl: string
65
+ forwardUrl: string,
66
+ customHeaders: Record<string, string> = {},
67
+ openclawMode: boolean = false
52
68
  ): Promise<void> {
53
- const payload = createPayload(msg);
69
+ let body: string;
70
+
71
+ if (openclawMode) {
72
+ // OpenClaw expects {text: "message"} format
73
+ const text = msg.title
74
+ ? `[${msg.topic}] ${msg.title}: ${msg.message || ""}`
75
+ : `[${msg.topic}] ${msg.message || msg.id}`;
76
+ body = JSON.stringify({ text });
77
+ } else {
78
+ body = JSON.stringify(createPayload(msg));
79
+ }
54
80
 
55
81
  try {
56
82
  const response = await fetch(forwardUrl, {
57
83
  method: "POST",
58
- headers: { "Content-Type": "application/json" },
59
- body: JSON.stringify(payload),
84
+ headers: { "Content-Type": "application/json", ...customHeaders },
85
+ body,
60
86
  });
61
87
 
62
88
  if (response.ok) {
@@ -69,7 +95,7 @@ async function forwardMessage(
69
95
  }
70
96
  }
71
97
 
72
- function subscribeToTopic(topic: string, forwardUrl: string): void {
98
+ function subscribeToTopic(topic: string, forwardUrl: string, headers: Record<string, string>, openclawMode: boolean): void {
73
99
  const url = normalizeTopicUrl(topic);
74
100
  console.log(`Subscribing to: ${url}`);
75
101
 
@@ -83,7 +109,7 @@ function subscribeToTopic(topic: string, forwardUrl: string): void {
83
109
  try {
84
110
  const msg: NtfyMessage = JSON.parse(event.data);
85
111
  if (msg.event === "message" || !msg.event) {
86
- await forwardMessage(msg, forwardUrl);
112
+ await forwardMessage(msg, forwardUrl, headers, openclawMode);
87
113
  }
88
114
  } catch (error) {
89
115
  console.warn(`Failed to parse message:`, error);
@@ -97,14 +123,15 @@ function subscribeToTopic(topic: string, forwardUrl: string): void {
97
123
  }
98
124
 
99
125
  function main(): void {
100
- const { topics, forward } = parseArgs();
126
+ const { topics, forward, headers, openclawMode } = parseArgs();
101
127
 
102
128
  console.log("ntfy-bridge starting");
103
129
  console.log(`Forwarding to: ${forward}`);
130
+ if (openclawMode) console.log("OpenClaw mode enabled");
104
131
  console.log(`Subscribing to ${topics.length} topic(s)`);
105
132
 
106
133
  for (const topic of topics) {
107
- subscribeToTopic(topic, forward);
134
+ subscribeToTopic(topic, forward, headers, openclawMode);
108
135
  }
109
136
 
110
137
  // Keep alive
package/src/lib.test.ts CHANGED
@@ -79,6 +79,8 @@ describe("parseArgs", () => {
79
79
  expect(result).toEqual({
80
80
  topics: ["alerts"],
81
81
  forward: "http://localhost:8080",
82
+ headers: {},
83
+ openclawMode: false,
82
84
  });
83
85
  });
84
86
 
@@ -92,6 +94,42 @@ describe("parseArgs", () => {
92
94
  expect(result).toEqual({
93
95
  topics: ["alerts", "news"],
94
96
  forward: "http://localhost:8080",
97
+ headers: {},
98
+ openclawMode: false,
99
+ });
100
+ });
101
+
102
+ it("parses custom headers", () => {
103
+ const result = parseArgs([
104
+ "node", "script",
105
+ "-t", "alerts",
106
+ "-f", "http://localhost:8080",
107
+ "-H", "Authorization: Bearer token123",
108
+ "-H", "X-Custom: value"
109
+ ]);
110
+ expect(result).toEqual({
111
+ topics: ["alerts"],
112
+ forward: "http://localhost:8080",
113
+ headers: {
114
+ "Authorization": "Bearer token123",
115
+ "X-Custom": "value",
116
+ },
117
+ openclawMode: false,
118
+ });
119
+ });
120
+
121
+ it("parses openclaw mode", () => {
122
+ const result = parseArgs([
123
+ "node", "script",
124
+ "-t", "alerts",
125
+ "-f", "http://localhost:8080",
126
+ "--openclaw"
127
+ ]);
128
+ expect(result).toEqual({
129
+ topics: ["alerts"],
130
+ forward: "http://localhost:8080",
131
+ headers: {},
132
+ openclawMode: true,
95
133
  });
96
134
  });
97
135
 
package/src/lib.ts CHANGED
@@ -45,10 +45,19 @@ export function createPayload(msg: NtfyMessage): BridgePayload {
45
45
  };
46
46
  }
47
47
 
48
- export function parseArgs(argv: string[]): { topics: string[]; forward: string } | null {
48
+ export interface ParsedArgs {
49
+ topics: string[];
50
+ forward: string;
51
+ headers: Record<string, string>;
52
+ openclawMode: boolean;
53
+ }
54
+
55
+ export function parseArgs(argv: string[]): ParsedArgs | null {
49
56
  const args = argv.slice(2);
50
57
  const topics: string[] = [];
51
58
  let forward = "";
59
+ const headers: Record<string, string> = {};
60
+ let openclawMode = false;
52
61
 
53
62
  for (let i = 0; i < args.length; i++) {
54
63
  const arg = args[i];
@@ -56,6 +65,16 @@ export function parseArgs(argv: string[]): { topics: string[]; forward: string }
56
65
  topics.push(args[++i]);
57
66
  } else if (arg === "--forward" || arg === "-f") {
58
67
  forward = args[++i];
68
+ } else if (arg === "--header" || arg === "-H") {
69
+ const header = args[++i];
70
+ const colonIdx = header.indexOf(":");
71
+ if (colonIdx > 0) {
72
+ const key = header.slice(0, colonIdx).trim();
73
+ const value = header.slice(colonIdx + 1).trim();
74
+ headers[key] = value;
75
+ }
76
+ } else if (arg === "--openclaw") {
77
+ openclawMode = true;
59
78
  } else if (arg === "--help" || arg === "-h") {
60
79
  return null; // Signal help requested
61
80
  }
@@ -65,5 +84,5 @@ export function parseArgs(argv: string[]): { topics: string[]; forward: string }
65
84
  return null;
66
85
  }
67
86
 
68
- return { topics, forward };
87
+ return { topics, forward, headers, openclawMode };
69
88
  }