nudj 1.0.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -0
  3. package/dist/cli/cli.test.d.ts +2 -0
  4. package/dist/cli/cli.test.d.ts.map +1 -0
  5. package/dist/cli/cli.test.js +129 -0
  6. package/dist/cli/cli.test.js.map +1 -0
  7. package/dist/cli/commands/config.d.ts +8 -0
  8. package/dist/cli/commands/config.d.ts.map +1 -0
  9. package/dist/cli/commands/config.js +26 -0
  10. package/dist/cli/commands/config.js.map +1 -0
  11. package/dist/cli/commands/pair.d.ts +2 -0
  12. package/dist/cli/commands/pair.d.ts.map +1 -0
  13. package/dist/cli/commands/pair.js +66 -0
  14. package/dist/cli/commands/pair.js.map +1 -0
  15. package/dist/cli/commands/push.d.ts +24 -0
  16. package/dist/cli/commands/push.d.ts.map +1 -0
  17. package/dist/cli/commands/push.js +88 -0
  18. package/dist/cli/commands/push.js.map +1 -0
  19. package/dist/cli/commands/receivers.d.ts +8 -0
  20. package/dist/cli/commands/receivers.d.ts.map +1 -0
  21. package/dist/cli/commands/receivers.js +155 -0
  22. package/dist/cli/commands/receivers.js.map +1 -0
  23. package/dist/cli/index.d.ts +3 -0
  24. package/dist/cli/index.d.ts.map +1 -0
  25. package/dist/cli/index.js +22 -0
  26. package/dist/cli/index.js.map +1 -0
  27. package/dist/cli/lib/config.d.ts +43 -0
  28. package/dist/cli/lib/config.d.ts.map +1 -0
  29. package/dist/cli/lib/config.js +120 -0
  30. package/dist/cli/lib/config.js.map +1 -0
  31. package/dist/cli/lib/pairing.d.ts +7 -0
  32. package/dist/cli/lib/pairing.d.ts.map +1 -0
  33. package/dist/cli/lib/pairing.js +67 -0
  34. package/dist/cli/lib/pairing.js.map +1 -0
  35. package/dist/cli/lib/push.d.ts +13 -0
  36. package/dist/cli/lib/push.d.ts.map +1 -0
  37. package/dist/cli/lib/push.js +103 -0
  38. package/dist/cli/lib/push.js.map +1 -0
  39. package/dist/cli/lib/types.d.ts +20 -0
  40. package/dist/cli/lib/types.d.ts.map +1 -0
  41. package/dist/cli/lib/types.js +2 -0
  42. package/dist/cli/lib/types.js.map +1 -0
  43. package/dist/cli/version.d.ts +2 -0
  44. package/dist/cli/version.d.ts.map +1 -0
  45. package/dist/cli/version.js +17 -0
  46. package/dist/cli/version.js.map +1 -0
  47. package/package.json +69 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 nudj contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,190 @@
1
+ <p align="center">
2
+ <img src="assets/logo.svg" alt="nudj logo" width="120" height="120">
3
+ </p>
4
+
5
+ # nudj
6
+
7
+ **Send push notifications from your CLI to your phone**
8
+
9
+ [![CI](https://github.com/redneb/nudj/actions/workflows/ci.yml/badge.svg)](https://github.com/redneb/nudj/actions/workflows/ci.yml)
10
+ [![npm version](https://badge.fury.io/js/nudj.svg)](https://www.npmjs.com/package/nudj)
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
12
+
13
+ ---
14
+
15
+ ## What is nudj?
16
+
17
+ nudj lets you send push notifications from your computer to your phone using a simple command-line tool. No account required, no servers — just Web Push and end-to-end encryption.
18
+
19
+ ### Features
20
+
21
+ - 🔒 **End-to-end encrypted** — Only you can read your notifications
22
+ - 🚫 **No account required** — No sign-up, no login, no tracking
23
+ - 🌐 **Works everywhere** — Uses standard Web Push (works on iOS, Android, and desktop)
24
+ - ⚡ **Lightweight** — Single-file CLI, ~18KB PWA
25
+ - 🔓 **Open source** — MIT licensed, fully auditable
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ ### CLI
32
+
33
+ **Option 1: npm (recommended)**
34
+
35
+ ```bash
36
+ npm install -g nudj
37
+ ```
38
+
39
+ **Option 2: Single-file download**
40
+
41
+ ```bash
42
+ # Download the latest release
43
+ curl -L https://github.com/redneb/nudj/releases/latest/download/nudj.js -o ~/.local/bin/nudj
44
+ chmod +x ~/.local/bin/nudj
45
+ ```
46
+
47
+ Requires Node.js 22 or later.
48
+
49
+ ---
50
+
51
+ ## Quick Start
52
+
53
+ ### 1. Install the PWA on your phone
54
+
55
+ Visit **[https://nudj.atopon.net](https://nudj.atopon.net)** on your phone.
56
+
57
+ **iOS users must install it as an app** (required for push notifications):
58
+
59
+ - **iOS**: Safari → Share → "Add to Home Screen" (required)
60
+ - **Android**: Chrome → Menu → "Install app" (optional — or just use the website)
61
+
62
+ Then tap **Enable Notifications** in the app.
63
+
64
+ ### 2. Pair your phone with your computer
65
+
66
+ Copy the pairing code shown in the app, then on your computer:
67
+
68
+ ```bash
69
+ nudj pair
70
+ # Paste the pairing code when prompted
71
+ # Give your phone a name (e.g., 'iPhone')
72
+ ```
73
+
74
+ ### 3. Send your first notification
75
+
76
+ ```bash
77
+ nudj push 'Hello from my computer!'
78
+ ```
79
+
80
+ That's it! You should see a notification on your phone.
81
+
82
+ ---
83
+
84
+ ## CLI Reference
85
+
86
+ ### Send a notification
87
+
88
+ ```bash
89
+ nudj push 'Build completed'
90
+ nudj push --title 'CI' 'Build #1234 passed'
91
+ nudj push --to iPhone 'Your coffee is ready'
92
+ ```
93
+
94
+ ### Manage receivers
95
+
96
+ ```bash
97
+ nudj receivers # List all paired devices
98
+ nudj receivers rename 'Old' 'New' # Rename a device
99
+ nudj receivers remove 'Phone' # Remove a device
100
+ ```
101
+
102
+ ### Configuration
103
+
104
+ ```bash
105
+ nudj config # Show config file location
106
+ ```
107
+
108
+ Configuration is stored at:
109
+ - Linux/macOS: `~/.config/nudj/config.json`
110
+ - Windows: `%APPDATA%\nudj\config.json`
111
+
112
+ ---
113
+
114
+ ## Privacy & Security
115
+
116
+ nudj is designed with privacy as a core principle:
117
+
118
+ - **No accounts** — You don't need to sign up for anything
119
+ - **No cloud storage** — Your credentials stay on your devices
120
+ - **No server** — nudj operates no servers and stores none of your data
121
+ - **End-to-end encryption** — Messages are encrypted using Web Push standards (RFC 8291)
122
+ - **No tracking** — The app creator has no access to your data
123
+
124
+ ### How it works
125
+
126
+ 1. Your phone generates encryption keys
127
+ 2. You transfer a pairing code to your computer (containing the keys)
128
+ 3. Your computer encrypts notifications using those keys
129
+ 4. Only your phone can decrypt them
130
+
131
+ The push service (Google FCM, Apple APNs, Mozilla) only sees encrypted blobs — they cannot read your notification content.
132
+
133
+ ### Security considerations
134
+
135
+ - Treat the pairing code like a password — anyone with it can send you notifications
136
+ - You can reset your subscription at any time to revoke all access
137
+ - Store your CLI config file securely (it contains the encryption keys)
138
+
139
+ ---
140
+
141
+ ## Development
142
+
143
+ ### Prerequisites
144
+
145
+ - Node.js 22+
146
+ - npm
147
+
148
+ ### Setup
149
+
150
+ ```bash
151
+ git clone https://github.com/redneb/nudj.git
152
+ cd nudj
153
+ npm install
154
+ ```
155
+
156
+ ### Commands
157
+
158
+ ```bash
159
+ npm run dev:cli # Run CLI in development mode
160
+ npm run dev:web # Start Vite dev server for PWA
161
+ npm run build # Build everything
162
+ npm run build:cli # Build CLI only
163
+ npm run build:web # Build PWA only
164
+ npm run test # Run all tests
165
+ npm run check # Type check and lint
166
+ ```
167
+
168
+ ### Project Structure
169
+
170
+ ```
171
+ src/
172
+ ├── cli/ # CLI implementation (Node.js)
173
+ ├── web/ # PWA implementation (Solid.js)
174
+ └── common/ # Shared type definitions
175
+ ```
176
+
177
+ ---
178
+
179
+ ## License
180
+
181
+ MIT — see [LICENSE](LICENSE)
182
+
183
+ ---
184
+
185
+ ## Acknowledgments
186
+
187
+ Built with:
188
+ - [citty](https://github.com/unjs/citty) — CLI framework
189
+ - [Solid.js](https://www.solidjs.com/) — UI framework
190
+ - [@block65/webcrypto-web-push](https://github.com/block65/webcrypto-web-push) — Web Push library
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.test.d.ts","sourceRoot":"","sources":["../../src/cli/cli.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,129 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import * as os from "node:os";
5
+ import { decodePairingCode } from "./lib/pairing.js";
6
+ import { readConfig, writeConfig, addReceiver, removeReceiver, renameReceiver, getConfigPath, } from "./lib/config.js";
7
+ describe("pairing", () => {
8
+ it("should decode a valid pairing code", () => {
9
+ const pairingData = {
10
+ endpoint: "https://fcm.googleapis.com/fcm/send/abc123",
11
+ keys: {
12
+ p256dh: "BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA",
13
+ auth: "tBHItJI5svbpez7KI4CCXg",
14
+ },
15
+ vapid: {
16
+ privateKey: "dGhpcyBpcyBhIHRlc3Qga2V5Li4u",
17
+ },
18
+ };
19
+ // Encode to base64url
20
+ const json = JSON.stringify(pairingData);
21
+ const base64 = btoa(json);
22
+ const base64url = base64
23
+ .replace(/\+/g, "-")
24
+ .replace(/\//g, "_")
25
+ .replace(/=+$/, "");
26
+ // Decode and verify
27
+ const result = decodePairingCode(base64url);
28
+ expect(result).not.toBeNull();
29
+ expect(result?.endpoint).toBe(pairingData.endpoint);
30
+ expect(result?.keys.p256dh).toBe(pairingData.keys.p256dh);
31
+ expect(result?.keys.auth).toBe(pairingData.keys.auth);
32
+ expect(result?.vapid.privateKey).toBe(pairingData.vapid.privateKey);
33
+ });
34
+ it("should return null for invalid pairing code", () => {
35
+ expect(decodePairingCode("invalid")).toBeNull();
36
+ expect(decodePairingCode("")).toBeNull();
37
+ expect(decodePairingCode("{}")).toBeNull();
38
+ });
39
+ it("should return null for missing required fields", () => {
40
+ const incomplete = { endpoint: "https://example.com" };
41
+ const json = JSON.stringify(incomplete);
42
+ const base64url = btoa(json).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
43
+ expect(decodePairingCode(base64url)).toBeNull();
44
+ });
45
+ });
46
+ describe("config", () => {
47
+ let tempDir;
48
+ const originalEnv = process.env["NUDJ_CONFIG"];
49
+ beforeEach(() => {
50
+ // Create temp directory for test config
51
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "nudj-test-"));
52
+ process.env["NUDJ_CONFIG"] = path.join(tempDir, "config.json");
53
+ });
54
+ afterEach(() => {
55
+ // Restore original env and clean up temp dir
56
+ if (originalEnv)
57
+ process.env["NUDJ_CONFIG"] = originalEnv;
58
+ else
59
+ delete process.env["NUDJ_CONFIG"];
60
+ fs.rmSync(tempDir, { recursive: true, force: true });
61
+ });
62
+ it("should return empty config when file does not exist", () => {
63
+ const config = readConfig();
64
+ expect(config.receivers).toEqual([]);
65
+ });
66
+ it("should write and read config", () => {
67
+ const config = { receivers: [] };
68
+ writeConfig(config);
69
+ const read = readConfig();
70
+ expect(read.receivers).toEqual([]);
71
+ });
72
+ it("should add a receiver", () => {
73
+ const receiver = {
74
+ name: "Test Phone",
75
+ endpoint: "https://example.com/push",
76
+ keys: { p256dh: "abc", auth: "def" },
77
+ vapid: { privateKey: "ghi" },
78
+ addedAt: new Date().toISOString(),
79
+ lastUsedAt: null,
80
+ };
81
+ addReceiver(receiver);
82
+ const config = readConfig();
83
+ expect(config.receivers).toHaveLength(1);
84
+ expect(config.receivers[0]?.name).toBe("Test Phone");
85
+ });
86
+ it("should remove a receiver", () => {
87
+ const receiver = {
88
+ name: "Test Phone",
89
+ endpoint: "https://example.com/push",
90
+ keys: { p256dh: "abc", auth: "def" },
91
+ vapid: { privateKey: "ghi" },
92
+ addedAt: new Date().toISOString(),
93
+ lastUsedAt: null,
94
+ };
95
+ addReceiver(receiver);
96
+ expect(readConfig().receivers).toHaveLength(1);
97
+ const removed = removeReceiver("Test Phone");
98
+ expect(removed).toBe(true);
99
+ expect(readConfig().receivers).toHaveLength(0);
100
+ });
101
+ it("should return false when removing non-existent receiver", () => {
102
+ const removed = removeReceiver("Non-existent");
103
+ expect(removed).toBe(false);
104
+ });
105
+ it("should rename a receiver", () => {
106
+ const receiver = {
107
+ name: "Old Name",
108
+ endpoint: "https://example.com/push",
109
+ keys: { p256dh: "abc", auth: "def" },
110
+ vapid: { privateKey: "ghi" },
111
+ addedAt: new Date().toISOString(),
112
+ lastUsedAt: null,
113
+ };
114
+ addReceiver(receiver);
115
+ const renamed = renameReceiver("Old Name", "New Name");
116
+ expect(renamed).toBe(true);
117
+ const config = readConfig();
118
+ expect(config.receivers[0]?.name).toBe("New Name");
119
+ });
120
+ it("should return false when renaming non-existent receiver", () => {
121
+ const renamed = renameReceiver("Non-existent", "New Name");
122
+ expect(renamed).toBe(false);
123
+ });
124
+ it("should use NUDJ_CONFIG env var for config path", () => {
125
+ const configPath = getConfigPath();
126
+ expect(configPath).toBe(path.join(tempDir, "config.json"));
127
+ });
128
+ });
129
+ //# sourceMappingURL=cli.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.test.js","sourceRoot":"","sources":["../../src/cli/cli.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare const configCommand: import("citty").CommandDef<{
2
+ readonly path: {
3
+ readonly type: "boolean";
4
+ readonly description: "Print only the path (for scripting)";
5
+ readonly default: false;
6
+ };
7
+ }>;
8
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/config.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa;;;;;;EAwBxB,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { defineCommand } from "citty";
2
+ import { getConfigPath, getReceivers } from "../lib/config.js";
3
+ export const configCommand = defineCommand({
4
+ meta: {
5
+ name: "config",
6
+ description: "Show configuration file location",
7
+ },
8
+ args: {
9
+ path: {
10
+ type: "boolean",
11
+ description: "Print only the path (for scripting)",
12
+ default: false,
13
+ },
14
+ },
15
+ run({ args }) {
16
+ const configPath = getConfigPath();
17
+ if (args.path) {
18
+ console.log(configPath);
19
+ return;
20
+ }
21
+ const receivers = getReceivers();
22
+ console.log(`Configuration file: ${configPath}`);
23
+ console.log(`Receivers configured: ${receivers.length}`);
24
+ },
25
+ });
26
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/cli/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,OAAO,CAAC;AACpC,OAAO,EAAC,aAAa,EAAE,YAAY,EAAC,MAAM,kBAAkB,CAAC;AAE7D,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;IAC1C,IAAI,EAAE;QACL,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,kCAAkC;KAC/C;IACD,IAAI,EAAE;QACL,IAAI,EAAE;YACL,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,qCAAqC;YAClD,OAAO,EAAE,KAAK;SACd;KACD;IACD,GAAG,CAAC,EAAC,IAAI,EAAC;QACT,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;QAEnC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACxB,OAAO;QACR,CAAC;QAED,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,uBAAuB,UAAU,EAAE,CAAC,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,yBAAyB,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1D,CAAC;CACD,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const pairCommand: import("citty").CommandDef<{}>;
2
+ //# sourceMappingURL=pair.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pair.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/pair.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,WAAW,gCAuEtB,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { defineCommand } from "citty";
2
+ import * as readline from "node:readline";
3
+ import { decodePairingCode } from "../lib/pairing.js";
4
+ import { addReceiver, getReceivers } from "../lib/config.js";
5
+ export const pairCommand = defineCommand({
6
+ meta: {
7
+ name: "pair",
8
+ description: "Add a new receiver by entering a pairing code",
9
+ },
10
+ args: {},
11
+ async run() {
12
+ const rl = readline.createInterface({
13
+ input: process.stdin,
14
+ output: process.stdout,
15
+ });
16
+ const question = (prompt) => {
17
+ return new Promise((resolve) => {
18
+ rl.question(prompt, resolve);
19
+ });
20
+ };
21
+ try {
22
+ // Get pairing code
23
+ const code = await question("Paste pairing code: ");
24
+ const pairingData = decodePairingCode(code);
25
+ if (!pairingData) {
26
+ console.error("\n✗ Invalid pairing code. Make sure you copied the entire code from the nudj app.");
27
+ process.exit(1);
28
+ }
29
+ // Get receiver name
30
+ let name = "";
31
+ const existingReceivers = getReceivers();
32
+ while (!name) {
33
+ const inputName = await question("Name for this receiver: ");
34
+ const trimmedName = inputName.trim();
35
+ if (!trimmedName) {
36
+ console.error("Name cannot be empty.");
37
+ continue;
38
+ }
39
+ // Check for duplicate name
40
+ if (existingReceivers.some(r => r.name === trimmedName)) {
41
+ console.error(`✗ A receiver named '${trimmedName}' already exists. Choose a different name.`);
42
+ continue;
43
+ }
44
+ name = trimmedName;
45
+ }
46
+ // Create receiver config
47
+ const receiver = {
48
+ name,
49
+ endpoint: pairingData.endpoint,
50
+ keys: pairingData.keys,
51
+ vapid: pairingData.vapid,
52
+ addedAt: new Date().toISOString(),
53
+ lastUsedAt: null,
54
+ };
55
+ // Save receiver
56
+ addReceiver(receiver);
57
+ const totalReceivers = existingReceivers.length + 1;
58
+ console.log(`\n✓ Receiver '${name}' added successfully`);
59
+ console.log(`\nYou now have ${totalReceivers} receiver(s) configured.`);
60
+ }
61
+ finally {
62
+ rl.close();
63
+ }
64
+ },
65
+ });
66
+ //# sourceMappingURL=pair.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pair.js","sourceRoot":"","sources":["../../../src/cli/commands/pair.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAC,iBAAiB,EAAC,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAC,WAAW,EAAE,YAAY,EAAC,MAAM,kBAAkB,CAAC;AAG3D,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;IACxC,IAAI,EAAE;QACL,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,+CAA+C;KAC5D;IACD,IAAI,EAAE,EAAE;IACR,KAAK,CAAC,GAAG;QACR,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;YACnC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;SACtB,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,CAAC,MAAc,EAAmB,EAAE;YACpD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC9B,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC9B,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC;QAEF,IAAI,CAAC;YACJ,mBAAmB;YACnB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,sBAAsB,CAAC,CAAC;YACpD,MAAM,WAAW,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;YAE5C,IAAI,CAAC,WAAW,EAAE,CAAC;gBAClB,OAAO,CAAC,KAAK,CAAC,mFAAmF,CAAC,CAAC;gBACnG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACjB,CAAC;YAED,oBAAoB;YACpB,IAAI,IAAI,GAAG,EAAE,CAAC;YACd,MAAM,iBAAiB,GAAG,YAAY,EAAE,CAAC;YAEzC,OAAO,CAAC,IAAI,EAAE,CAAC;gBACd,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,0BAA0B,CAAC,CAAC;gBAC7D,MAAM,WAAW,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;gBAErC,IAAI,CAAC,WAAW,EAAE,CAAC;oBAClB,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;oBACvC,SAAS;gBACV,CAAC;gBAED,2BAA2B;gBAC3B,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,EAAE,CAAC;oBACzD,OAAO,CAAC,KAAK,CAAC,uBAAuB,WAAW,4CAA4C,CAAC,CAAC;oBAC9F,SAAS;gBACV,CAAC;gBAED,IAAI,GAAG,WAAW,CAAC;YACpB,CAAC;YAED,yBAAyB;YACzB,MAAM,QAAQ,GAAmB;gBAChC,IAAI;gBACJ,QAAQ,EAAE,WAAW,CAAC,QAAQ;gBAC9B,IAAI,EAAE,WAAW,CAAC,IAAI;gBACtB,KAAK,EAAE,WAAW,CAAC,KAAK;gBACxB,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACjC,UAAU,EAAE,IAAI;aAChB,CAAC;YAEF,gBAAgB;YAChB,WAAW,CAAC,QAAQ,CAAC,CAAC;YAEtB,MAAM,cAAc,GAAG,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC;YACpD,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,sBAAsB,CAAC,CAAC;YACzD,OAAO,CAAC,GAAG,CAAC,kBAAkB,cAAc,0BAA0B,CAAC,CAAC;QACzE,CAAC;gBACO,CAAC;YACR,EAAE,CAAC,KAAK,EAAE,CAAC;QACZ,CAAC;IACF,CAAC;CACD,CAAC,CAAC"}
@@ -0,0 +1,24 @@
1
+ export declare const pushCommand: import("citty").CommandDef<{
2
+ readonly message: {
3
+ readonly type: "positional";
4
+ readonly description: "The notification body text (use '-' to read from stdin)";
5
+ readonly required: true;
6
+ };
7
+ readonly title: {
8
+ readonly type: "string";
9
+ readonly alias: "t";
10
+ readonly description: "Notification title";
11
+ readonly default: "nudj";
12
+ };
13
+ readonly to: {
14
+ readonly type: "string";
15
+ readonly description: "Send only to this receiver (repeat for multiple)";
16
+ };
17
+ readonly quiet: {
18
+ readonly type: "boolean";
19
+ readonly alias: "q";
20
+ readonly description: "Suppress output on success";
21
+ readonly default: false;
22
+ };
23
+ }>;
24
+ //# sourceMappingURL=push.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/push.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;EAwFtB,CAAC"}
@@ -0,0 +1,88 @@
1
+ import { defineCommand } from "citty";
2
+ import { text } from "node:stream/consumers";
3
+ import { getReceivers, updateReceiverLastUsed, removeReceiver } from "../lib/config.js";
4
+ import { sendPush } from "../lib/push.js";
5
+ export const pushCommand = defineCommand({
6
+ meta: {
7
+ name: "push",
8
+ description: "Send a push notification to receivers",
9
+ },
10
+ args: {
11
+ message: {
12
+ type: "positional",
13
+ description: "The notification body text (use '-' to read from stdin)",
14
+ required: true,
15
+ },
16
+ title: {
17
+ type: "string",
18
+ alias: "t",
19
+ description: "Notification title",
20
+ default: "nudj",
21
+ },
22
+ to: {
23
+ type: "string",
24
+ // Can be repeated to send to multiple specific receivers, e.g.:
25
+ // nudj push --to iPhone --to iPad 'Meeting starting'
26
+ description: "Send only to this receiver (repeat for multiple)",
27
+ },
28
+ quiet: {
29
+ type: "boolean",
30
+ alias: "q",
31
+ description: "Suppress output on success",
32
+ default: false,
33
+ },
34
+ },
35
+ async run({ args }) {
36
+ // Get message (handle stdin)
37
+ let message = args.message;
38
+ if (message === "-")
39
+ message = (await text(process.stdin)).trimEnd();
40
+ // Get receivers
41
+ const allReceivers = getReceivers();
42
+ if (allReceivers.length === 0) {
43
+ console.error("No receivers configured. Run 'nudj pair' to add one.");
44
+ process.exit(2);
45
+ }
46
+ // Filter receivers if --to is specified
47
+ let targetReceivers = allReceivers;
48
+ if (args.to) {
49
+ // Handle both single and multiple --to values
50
+ const targetNames = Array.isArray(args.to) ? args.to : [args.to];
51
+ targetReceivers = allReceivers.filter(r => targetNames.includes(r.name));
52
+ if (targetReceivers.length === 0) {
53
+ const names = targetNames.join(", ");
54
+ console.error(`No receivers found matching: ${names}`);
55
+ console.error("Run 'nudj receivers' to see available receivers.");
56
+ process.exit(2);
57
+ }
58
+ }
59
+ // Create payload
60
+ const payload = {
61
+ title: args.title,
62
+ body: message,
63
+ timestamp: Date.now(),
64
+ };
65
+ // Send notifications, processing each result as it completes
66
+ let hasFailure = false;
67
+ await Promise.all(targetReceivers.map(async (receiver) => {
68
+ const result = await sendPush(receiver, payload);
69
+ if (result.success) {
70
+ updateReceiverLastUsed(result.name);
71
+ if (!args.quiet)
72
+ console.log(`✓ ${result.name}: delivered`);
73
+ }
74
+ else {
75
+ hasFailure = true;
76
+ if (result.expired) {
77
+ removeReceiver(result.name);
78
+ console.error(`✗ ${result.name}: subscription expired (removed)`);
79
+ }
80
+ else
81
+ console.error(`✗ ${result.name}: ${result.error}`);
82
+ }
83
+ }));
84
+ if (hasFailure)
85
+ process.exit(1);
86
+ },
87
+ });
88
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../../../src/cli/commands/push.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,OAAO,CAAC;AACpC,OAAO,EAAC,IAAI,EAAC,MAAM,uBAAuB,CAAC;AAC3C,OAAO,EAAC,YAAY,EAAE,sBAAsB,EAAE,cAAc,EAAC,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AAGxC,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;IACxC,IAAI,EAAE;QACL,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,uCAAuC;KACpD;IACD,IAAI,EAAE;QACL,OAAO,EAAE;YACR,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,yDAAyD;YACtE,QAAQ,EAAE,IAAI;SACd;QACD,KAAK,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,GAAG;YACV,WAAW,EAAE,oBAAoB;YACjC,OAAO,EAAE,MAAM;SACf;QACD,EAAE,EAAE;YACH,IAAI,EAAE,QAAQ;YACd,gEAAgE;YAChE,qDAAqD;YACrD,WAAW,EAAE,kDAAkD;SAC/D;QACD,KAAK,EAAE;YACN,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,GAAG;YACV,WAAW,EAAE,4BAA4B;YACzC,OAAO,EAAE,KAAK;SACd;KACD;IACD,KAAK,CAAC,GAAG,CAAC,EAAC,IAAI,EAAC;QACf,6BAA6B;QAC7B,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC3B,IAAI,OAAO,KAAK,GAAG;YAClB,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QAEjD,gBAAgB;QAChB,MAAM,YAAY,GAAG,YAAY,EAAE,CAAC;QACpC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;YACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;QAED,wCAAwC;QACxC,IAAI,eAAe,GAAG,YAAY,CAAC;QACnC,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,8CAA8C;YAC9C,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACjE,eAAe,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAEzE,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACrC,OAAO,CAAC,KAAK,CAAC,gCAAgC,KAAK,EAAE,CAAC,CAAC;gBACvD,OAAO,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;gBAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACjB,CAAC;QACF,CAAC;QAED,iBAAiB;QACjB,MAAM,OAAO,GAAwB;YACpC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,IAAI,EAAE,OAAO;YACb,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACrB,CAAC;QAEF,6DAA6D;QAC7D,IAAI,UAAU,GAAG,KAAK,CAAC;QACvB,MAAM,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;YACxD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACjD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,sBAAsB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACpC,IAAI,CAAC,IAAI,CAAC,KAAK;oBACd,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,IAAI,aAAa,CAAC,CAAC;YAC7C,CAAC;iBACI,CAAC;gBACL,UAAU,GAAG,IAAI,CAAC;gBAClB,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;oBACpB,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;oBAC5B,OAAO,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,IAAI,kCAAkC,CAAC,CAAC;gBACnE,CAAC;;oBAEA,OAAO,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;YACrD,CAAC;QACF,CAAC,CAAC,CAAC,CAAC;QAEJ,IAAI,UAAU;YACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;CACD,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare const receiversCommand: import("citty").CommandDef<{
2
+ readonly json: {
3
+ readonly type: "boolean";
4
+ readonly description: "Output as JSON";
5
+ readonly default: false;
6
+ };
7
+ }>;
8
+ //# sourceMappingURL=receivers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receivers.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/receivers.ts"],"names":[],"mappings":"AA8FA,eAAO,MAAM,gBAAgB;;;;;;EAgD3B,CAAC"}