clawleash 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawleash",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Approve or deny Claude Code permission prompts from your phone — so long autonomous runs never stall while you're away from the desk. Self-hosted, token-gated, over Tailscale or LAN.",
5
5
  "keywords": [
6
6
  "claude-code",
package/src/config.js CHANGED
@@ -5,6 +5,7 @@ const os = require("os");
5
5
  const fs = require("fs");
6
6
  const path = require("path");
7
7
  const crypto = require("crypto");
8
+ const { sanitizeNtfyTopic } = require("./notify");
8
9
 
9
10
  const DEFAULT_PORT = 4271;
10
11
 
@@ -41,6 +42,8 @@ function ensure() {
41
42
  if (!cfg.port) { cfg.port = Number(process.env.CLAWLEASH_PORT) || DEFAULT_PORT; changed = true; }
42
43
  if (typeof cfg.approvals !== "boolean") { cfg.approvals = true; changed = true; }
43
44
  if (typeof cfg.ntfyTopic !== "string") { cfg.ntfyTopic = ""; changed = true; }
45
+ const cleanTopic = sanitizeNtfyTopic(cfg.ntfyTopic);
46
+ if (cleanTopic !== cfg.ntfyTopic) { cfg.ntfyTopic = cleanTopic; changed = true; }
44
47
  if (changed) save(cfg);
45
48
  return cfg;
46
49
  }
package/src/notify.js CHANGED
@@ -3,8 +3,19 @@
3
3
  // The user installs the ntfy app and subscribes to their topic; we POST to it.
4
4
  const https = require("https");
5
5
 
6
+ // ntfy topics must be URL-safe ([-_A-Za-z0-9]{1,64}). Clean anything else so a
7
+ // topic with spaces/punctuation doesn't silently fail to deliver.
8
+ function sanitizeNtfyTopic(value) {
9
+ return String(value == null ? "" : value)
10
+ .replace(/[^A-Za-z0-9_-]+/g, "-")
11
+ .replace(/-{2,}/g, "-")
12
+ .replace(/^[-_]+|[-_]+$/g, "")
13
+ .slice(0, 64);
14
+ }
15
+
6
16
  function pushNtfy(topic, title, message, opts = {}) {
7
- if (!topic || typeof topic !== "string") return;
17
+ const t = sanitizeNtfyTopic(topic);
18
+ if (!t) return;
8
19
  try {
9
20
  const body = Buffer.from(String(message == null ? "" : message), "utf8");
10
21
  // Title header must be ASCII; keep details in the (UTF-8) body.
@@ -17,7 +28,7 @@ function pushNtfy(topic, title, message, opts = {}) {
17
28
  if (opts.tags) headers.Tags = String(opts.tags);
18
29
  if (opts.priority) headers.Priority = String(opts.priority);
19
30
  const req = https.request(
20
- { hostname: "ntfy.sh", path: "/" + encodeURIComponent(topic), method: "POST", headers, timeout: 4000 },
31
+ { hostname: "ntfy.sh", path: "/" + t, method: "POST", headers, timeout: 4000 },
21
32
  (res) => { res.resume(); }
22
33
  );
23
34
  req.on("error", () => {});
@@ -27,4 +38,4 @@ function pushNtfy(topic, title, message, opts = {}) {
27
38
  } catch { /* best-effort */ }
28
39
  }
29
40
 
30
- module.exports = { pushNtfy };
41
+ module.exports = { pushNtfy, sanitizeNtfyTopic };