aboutsummaryrefslogtreecommitdiff
path: root/claude-templates/bin
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-29 14:51:53 -0500
committerCraig Jennings <c@cjennings.net>2026-05-29 14:51:53 -0500
commit664bf01ceaccf730cb636463cc8587cd1d966192 (patch)
treee964f6c88d986454c5a2acfc99dfb55964fbba2b /claude-templates/bin
parentc3cf9a592ea6779ad59f0d79577e29777fce49f6 (diff)
downloadrulesets-664bf01ceaccf730cb636463cc8587cd1d966192.tar.gz
rulesets-664bf01ceaccf730cb636463cc8587cd1d966192.zip
feat(signal): page-signal CLI wrapper + workflows + cross-project broadcast helper
Three coupled additions ship together. claude-templates/bin/page-signal is a bash wrapper around signal-cli send. It defaults to --note-to-self for safety. The wrapper supports --file for attachments, --to <+number> for outbound (explicit per call, no defaults, no batch), --quiet, and --json. Exit codes: 0 sent, 1 signal-cli failure, 2 usage error, 3 signal-cli not installed. claude-templates/.ai/workflows/page-signal.org carries the discrimination rules and safety rails. When desktop notify covers it, don't reach for Signal. Long-running task completion is the canonical case. Outbound to other contacts requires explicit Craig instruction per send. A known-limitation note covers the current notification gap. signal-cli registered on Craig's primary number means messages don't fire notifications until the pending Google Voice registration lands. claude-templates/.ai/workflows/cross-project-broadcast.org and its helper cross-project-broadcast.py fan out a single message file to every AI project's inbox in one operation. Discovery is fingerprint-based: any directory under ~/code, ~/projects, ~/.emacs.d with both .ai/protocols.org and a top-level inbox/ is broadcastable. Senders are auto-excluded. Verified discovery against 23 broadcastable targets. Makefile's install target gains a general bin/ loop. The previous version hardcoded bin/ai. The new version iterates over every executable under claude-templates/bin/ and symlinks each into ~/.local/bin/. install-hooks (existing Claude hook installer) is unchanged. install-githooks (sync-check pre-commit hook setup, added earlier today) is unchanged. The bin/ loop now picks up bin/page-signal automatically. INDEX entries for both new workflows landed under Tools and meta. No bats tests on the new scripts. page-signal was smoke-tested with a live send. The send succeeded. The notification gap is covered by the workflow's known-limitation note. cross-project-broadcast.py was smoke-tested via --list against the live project set. Tests can be added when the broadcast pattern proves out across multiple use cases.
Diffstat (limited to 'claude-templates/bin')
-rwxr-xr-xclaude-templates/bin/page-signal126
1 files changed, 126 insertions, 0 deletions
diff --git a/claude-templates/bin/page-signal b/claude-templates/bin/page-signal
new file mode 100755
index 0000000..5a87c67
--- /dev/null
+++ b/claude-templates/bin/page-signal
@@ -0,0 +1,126 @@
+#!/usr/bin/env bash
+#
+# page-signal — wrap signal-cli send for paging Craig via Signal.
+#
+# Defaults to --note-to-self for safety. Outbound (to another contact)
+# requires an explicit --to <+number> on every call. No defaults, no batch.
+#
+# Usage:
+# page-signal "message" # note-to-self
+# page-signal --file path "message" # with attachment
+# page-signal --to +15551234567 "msg" # outbound (explicit recipient)
+# page-signal --quiet "message" # suppress success output
+# page-signal --json "message" # structured output (timestamp + status)
+# echo "msg" | page-signal # message from stdin (no positional)
+#
+# Requires signal-cli installed and registered with an account.
+#
+# Use this when desktop notifications won't reach the user — page-signal
+# fires cross-device. For routine completions and periodic status pings,
+# stick with `notify`; Signal is for things that need attention while away
+# from the desk.
+
+set -euo pipefail
+
+quiet=0
+json=0
+file=
+recipient_args=(--note-to-self)
+message=
+
+usage() {
+ sed -n '3,21p' "$0" | sed 's/^# \{0,1\}//'
+}
+
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ --quiet)
+ quiet=1
+ shift
+ ;;
+ --json)
+ json=1
+ shift
+ ;;
+ --file)
+ [ "$#" -ge 2 ] || { echo "page-signal: --file needs a path" >&2; exit 2; }
+ file="$2"
+ [ -f "$file" ] || { echo "page-signal: file not found: $file" >&2; exit 2; }
+ shift 2
+ ;;
+ --to)
+ [ "$#" -ge 2 ] || { echo "page-signal: --to needs a +E.164 number" >&2; exit 2; }
+ case "$2" in
+ +[0-9]*) ;;
+ *) echo "page-signal: --to expects +E.164 (e.g. +15551234567)" >&2; exit 2 ;;
+ esac
+ recipient_args=("$2")
+ shift 2
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ echo "page-signal: unknown flag: $1 (use --help)" >&2
+ exit 2
+ ;;
+ *)
+ message="$1"
+ shift
+ ;;
+ esac
+done
+
+if ! command -v signal-cli >/dev/null 2>&1; then
+ echo "page-signal: signal-cli not found in PATH" >&2
+ exit 3
+fi
+
+# Message: from positional arg, or from stdin if none given.
+if [ -z "$message" ]; then
+ if [ -t 0 ]; then
+ echo "page-signal: no message (pass as arg or pipe via stdin)" >&2
+ exit 2
+ fi
+ message="$(cat)"
+fi
+
+# Build the send command.
+cmd=(signal-cli send -m "$message")
+if [ -n "$file" ]; then
+ cmd+=(-a "$file")
+fi
+cmd+=("${recipient_args[@]}")
+
+# Send. Capture stderr so we can surface clean failure messages.
+timestamp="$(date '+%Y-%m-%dT%H:%M:%S%z')"
+if err="$("${cmd[@]}" 2>&1 >/dev/null)"; then
+ status=ok
+else
+ status=fail
+fi
+
+if [ "$json" -eq 1 ]; then
+ printf '{"status":"%s","timestamp":"%s","recipient":"%s"' \
+ "$status" "$timestamp" "${recipient_args[0]}"
+ if [ -n "$file" ]; then
+ printf ',"attachment":"%s"' "$file"
+ fi
+ if [ "$status" = "fail" ]; then
+ printf ',"error":"%s"' "$(echo "$err" | head -1 | sed 's/"/\\"/g')"
+ fi
+ printf '}\n'
+elif [ "$quiet" -eq 0 ]; then
+ if [ "$status" = "ok" ]; then
+ echo "page-signal: sent to ${recipient_args[*]} at $timestamp"
+ else
+ echo "page-signal: FAIL — $err" >&2
+ fi
+fi
+
+[ "$status" = "ok" ]