diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-29 14:51:53 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-29 14:51:53 -0500 |
| commit | 664bf01ceaccf730cb636463cc8587cd1d966192 (patch) | |
| tree | e964f6c88d986454c5a2acfc99dfb55964fbba2b /claude-templates/bin | |
| parent | c3cf9a592ea6779ad59f0d79577e29777fce49f6 (diff) | |
| download | rulesets-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-x | claude-templates/bin/page-signal | 126 |
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" ] |
