create-egregore 0.19.21 → 0.19.23
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/assets/egregore-launcher.js +29 -3
- package/bin/cli.js +3 -4
- package/lib/auth.js +3 -4
- package/lib/path-lookup.js +59 -0
- package/lib/setup.js +52 -6
- package/package.json +2 -2
- package/runtime/codex/.claude/context/notification-consent.md +66 -0
- package/runtime/codex/.codex/hooks/branch-guard.js +14 -3
- package/runtime/codex/.codex/skills/add/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/announce/SKILL.md +21 -8
- package/runtime/codex/.codex/skills/answer/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/archive/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/ask/SKILL.md +14 -6
- package/runtime/codex/.codex/skills/audit/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/autosave/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/branch/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/checkup/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/commit/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/contribute/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/dark-mode/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/delete-user/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/demo-mode/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/deploy-bridge/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/design/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/emissary/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/env/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/handoff/SKILL.md +81 -10
- package/runtime/codex/.codex/skills/harvest/QUESTION_PALETTE.md +76 -0
- package/runtime/codex/.codex/skills/harvest/SKILL.md +30 -6
- package/runtime/codex/.codex/skills/ingest/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/ingest-google/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/invite/SKILL.md +6 -2
- package/runtime/codex/.codex/skills/issue/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/me/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/meeting/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/minion/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/mock/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/note/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/onboarding/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/pr/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/product-voice/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/project/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/pull/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/push/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/quest/SKILL.md +6 -3
- package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/reflect/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/review-pr/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/save/SKILL.md +6 -4
- package/runtime/codex/.codex/skills/scroll/SKILL.md +92 -23
- package/runtime/codex/.codex/skills/scroll/assets/shell.html +381 -52
- package/runtime/codex/.codex/skills/session-view/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/setup/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/summon/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/sync-repos/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/task/SKILL.md +9 -5
- package/runtime/codex/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/telemetry/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/test/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/thread/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/todo/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/tui-design/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/tutorial/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/update/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/visual-explain/SKILL.md +8 -4
- package/runtime/codex/.codex/spec-manifest.json +4 -4
- package/runtime/codex/AGENTS.md +36 -14
- package/runtime/codex/bin/activity-data.sh +1 -1
- package/runtime/codex/bin/agent.sh +38 -19
- package/runtime/codex/bin/attendant.sh +8 -2
- package/runtime/codex/bin/autosave.sh +1 -1
- package/runtime/codex/bin/capture-reconcile.sh +8 -6
- package/runtime/codex/bin/capture-run.sh +1 -1
- package/runtime/codex/bin/codex-install-skills.sh +1 -1
- package/runtime/codex/bin/codex-render-spec.mjs +12 -5
- package/runtime/codex/bin/codex-session-start.sh +1 -1
- package/runtime/codex/bin/codex-skill-render.mjs +5 -0
- package/runtime/codex/bin/codex-sync-skills.sh +9 -5
- package/runtime/codex/bin/dashboard-data.sh +1 -1
- package/runtime/codex/bin/graph-batch.sh +1 -1
- package/runtime/codex/bin/graph-op.sh +1 -1
- package/runtime/codex/bin/graph-wal.sh +1 -1
- package/runtime/codex/bin/graph.sh +1 -1
- package/runtime/codex/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/codex/bin/handoff-run.sh +117 -119
- package/runtime/codex/bin/handoff-save-egregore.sh +62 -32
- package/runtime/codex/bin/index-handoff.sh +3 -2
- package/runtime/codex/bin/ingest-graph.sh +1 -1
- package/runtime/codex/bin/ingest.sh +1 -1
- package/runtime/codex/bin/lib/artifact-id.sh +1 -1
- package/runtime/codex/bin/lib/config.sh +56 -20
- package/runtime/codex/bin/lib/context.sh +7 -1
- package/runtime/codex/bin/lib/ensure-develop.sh +1 -1
- package/runtime/codex/bin/lib/git-sync.sh +145 -59
- package/runtime/codex/bin/lib/greeting.sh +14 -0
- package/runtime/codex/bin/lib/hash.sh +1 -1
- package/runtime/codex/bin/lib/noncode.sh +1 -1
- package/runtime/codex/bin/lib/time.sh +1 -1
- package/runtime/codex/bin/notify.sh +492 -153
- package/runtime/codex/bin/person.sh +1 -1
- package/runtime/codex/bin/publish-artifact.sh +64 -8
- package/runtime/codex/bin/publish-references.sh +1 -1
- package/runtime/codex/bin/repo-state.sh +7 -10
- package/runtime/codex/bin/search.sh +1 -1
- package/runtime/codex/bin/session-autosave.sh +1 -1
- package/runtime/codex/bin/session-log.sh +1 -1
- package/runtime/codex/bin/settings.sh +32 -2
- package/runtime/codex/bin/telemetry.sh +1 -1
- package/runtime/codex/bin/worktree-create.sh +14 -4
- package/runtime/codex/bin/worktree-remove.sh +1 -1
- package/runtime/codex/bin/worktree.sh +1 -1
- package/runtime/codex/manifest.json +18 -1
- package/runtime/pi/.claude/context/notification-consent.md +66 -0
- package/runtime/pi/.codex/hooks/branch-guard.js +14 -3
- package/runtime/pi/.codex/skills/add/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/announce/SKILL.md +21 -8
- package/runtime/pi/.codex/skills/answer/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/archive/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/ask/SKILL.md +14 -6
- package/runtime/pi/.codex/skills/audit/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/autosave/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/branch/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/checkup/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/commit/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/contribute/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/dark-mode/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/delete-user/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/demo-mode/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/deploy-bridge/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/design/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/egregore-voice/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/emissary/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/env/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/handoff/SKILL.md +81 -10
- package/runtime/pi/.codex/skills/harvest/QUESTION_PALETTE.md +76 -0
- package/runtime/pi/.codex/skills/harvest/SKILL.md +30 -6
- package/runtime/pi/.codex/skills/ingest/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/ingest-google/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/ingest-user-interview/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/invite/SKILL.md +6 -2
- package/runtime/pi/.codex/skills/issue/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/me/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/meeting/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/minion/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/mock/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/note/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/onboarding/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/pr/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/product-voice/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/project/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/pull/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/push/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/quest/SKILL.md +6 -3
- package/runtime/pi/.codex/skills/quest-suggest/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/reflect/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/review-pr/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/save/SKILL.md +6 -4
- package/runtime/pi/.codex/skills/scroll/SKILL.md +92 -23
- package/runtime/pi/.codex/skills/scroll/assets/shell.html +381 -52
- package/runtime/pi/.codex/skills/session-view/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/setup/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/summon/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/sync-repos/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/task/SKILL.md +9 -5
- package/runtime/pi/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/telegram-connect/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/telemetry/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/test/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/thread/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/todo/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/tui-design/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/tutorial/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/update/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/visual-explain/SKILL.md +8 -4
- package/runtime/pi/.codex/spec-manifest.json +4 -4
- package/runtime/pi/.pi/APPEND_SYSTEM.md +34 -12
- package/runtime/pi/.pi/extensions/egregore.ts +8 -1
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +36 -14
- package/runtime/pi/bin/activity-data.sh +1 -1
- package/runtime/pi/bin/agent.sh +38 -19
- package/runtime/pi/bin/attendant.sh +8 -2
- package/runtime/pi/bin/autosave.sh +1 -1
- package/runtime/pi/bin/capture-reconcile.sh +8 -6
- package/runtime/pi/bin/capture-run.sh +1 -1
- package/runtime/pi/bin/codex-install-skills.sh +1 -1
- package/runtime/pi/bin/codex-render-spec.mjs +12 -5
- package/runtime/pi/bin/codex-session-start.sh +1 -1
- package/runtime/pi/bin/codex-skill-render.mjs +5 -0
- package/runtime/pi/bin/codex-sync-skills.sh +9 -5
- package/runtime/pi/bin/dashboard-data.sh +1 -1
- package/runtime/pi/bin/graph-batch.sh +1 -1
- package/runtime/pi/bin/graph-op.sh +1 -1
- package/runtime/pi/bin/graph-wal.sh +1 -1
- package/runtime/pi/bin/graph.sh +1 -1
- package/runtime/pi/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/pi/bin/handoff-run.sh +117 -119
- package/runtime/pi/bin/handoff-save-egregore.sh +62 -32
- package/runtime/pi/bin/index-handoff.sh +3 -2
- package/runtime/pi/bin/ingest-graph.sh +1 -1
- package/runtime/pi/bin/ingest.sh +1 -1
- package/runtime/pi/bin/lib/artifact-id.sh +1 -1
- package/runtime/pi/bin/lib/config.sh +56 -20
- package/runtime/pi/bin/lib/context.sh +7 -1
- package/runtime/pi/bin/lib/ensure-develop.sh +1 -1
- package/runtime/pi/bin/lib/git-sync.sh +145 -59
- package/runtime/pi/bin/lib/greeting.sh +14 -0
- package/runtime/pi/bin/lib/hash.sh +1 -1
- package/runtime/pi/bin/lib/noncode.sh +1 -1
- package/runtime/pi/bin/lib/time.sh +1 -1
- package/runtime/pi/bin/notify.sh +492 -153
- package/runtime/pi/bin/person.sh +1 -1
- package/runtime/pi/bin/pi-product-workflows.mjs +14 -5
- package/runtime/pi/bin/pi-session-start.sh +1 -1
- package/runtime/pi/bin/publish-artifact.sh +64 -8
- package/runtime/pi/bin/publish-references.sh +1 -1
- package/runtime/pi/bin/repo-state.sh +7 -10
- package/runtime/pi/bin/search.sh +1 -1
- package/runtime/pi/bin/session-autosave.sh +1 -1
- package/runtime/pi/bin/session-log.sh +1 -1
- package/runtime/pi/bin/settings.sh +32 -2
- package/runtime/pi/bin/telemetry.sh +1 -1
- package/runtime/pi/bin/worktree-create.sh +14 -4
- package/runtime/pi/bin/worktree-remove.sh +1 -1
- package/runtime/pi/bin/worktree.sh +1 -1
- package/runtime/pi/manifest.json +18 -1
package/runtime/pi/bin/notify.sh
CHANGED
|
@@ -1,196 +1,530 @@
|
|
|
1
|
-
#!/bin/bash
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
# Human-consented notification transport.
|
|
5
|
+
#
|
|
6
|
+
# No command in this file can plan and dispatch in one step:
|
|
7
|
+
# 1. plan — resolve exact destinations and persist immutable content
|
|
8
|
+
# 2. approve — record one explicit human approval for that exact digest
|
|
9
|
+
# 3. dispatch — consume the one-use approval and send the stored payload
|
|
10
|
+
#
|
|
11
|
+
# Legacy `send` / `group` commands only create a plan and exit 4. They never
|
|
12
|
+
# dispatch. This makes the invariant fail closed for stale skills and jobs.
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="${EGREGORE_NOTIFY_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
5
15
|
CONFIG="$SCRIPT_DIR/egregore.json"
|
|
6
16
|
|
|
7
|
-
|
|
17
|
+
[ -f "$CONFIG" ] || {
|
|
8
18
|
echo "Error: egregore.json not found." >&2
|
|
9
19
|
exit 1
|
|
10
|
-
|
|
20
|
+
}
|
|
11
21
|
|
|
12
|
-
|
|
13
|
-
_MODE=$(jq -r '.mode // "connected"' "$CONFIG" 2>/dev/null)
|
|
22
|
+
umask 077
|
|
14
23
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
MODE="$(jq -r '.mode // "connected"' "$CONFIG" 2>/dev/null || echo connected)"
|
|
25
|
+
ORG_SLUG="$(jq -r '.slug // empty' "$CONFIG" 2>/dev/null || true)"
|
|
26
|
+
ORG_NAME="$(jq -r '.org_name // .slug // empty' "$CONFIG" 2>/dev/null || true)"
|
|
27
|
+
SESSION_ID="$(sed -n '1p' "$SCRIPT_DIR/.egregore-session-id" 2>/dev/null || true)"
|
|
28
|
+
SESSION_ID="${SESSION_ID:-no-session}"
|
|
29
|
+
PROJECT_HASH="$(printf '%s' "$SCRIPT_DIR" | cksum | awk '{print $1}')"
|
|
30
|
+
STATE_DIR="${EGREGORE_NOTIFY_STATE_DIR:-$HOME/.egregore/notifications/$PROJECT_HASH}"
|
|
31
|
+
RELAY_URL="${EGREGORE_NOTIFY_RELAY_URL:-https://egregore-production-55f2.up.railway.app}"
|
|
32
|
+
mkdir -p "$STATE_DIR"
|
|
33
|
+
chmod 700 "$STATE_DIR" 2>/dev/null || true
|
|
18
34
|
|
|
19
|
-
# Load specific variables from .env if it exists (safe extraction, no arbitrary code execution)
|
|
20
35
|
if [ -f "$SCRIPT_DIR/.env" ]; then
|
|
21
36
|
EGREGORE_API_URL="${EGREGORE_API_URL:-$(grep '^EGREGORE_API_URL=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
|
|
22
37
|
EGREGORE_API_KEY="${EGREGORE_API_KEY:-$(grep '^EGREGORE_API_KEY=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
|
|
23
38
|
fi
|
|
24
|
-
|
|
25
|
-
# Check if API mode or direct mode
|
|
26
|
-
# api_url comes from egregore.json (committed, non-secret)
|
|
27
|
-
# api_key comes from .env only (EGREGORE_API_KEY) — never from egregore.json
|
|
28
|
-
API_URL="${EGREGORE_API_URL:-$(jq -r '.api_url // empty' "$CONFIG")}"
|
|
39
|
+
API_URL="${EGREGORE_API_URL:-$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)}"
|
|
29
40
|
API_KEY="${EGREGORE_API_KEY:-}"
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
die() {
|
|
43
|
+
echo "Error: $*" >&2
|
|
44
|
+
exit 1
|
|
45
|
+
}
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
hash_text() {
|
|
48
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
49
|
+
shasum -a 256 | awk '{print $1}'
|
|
50
|
+
else
|
|
51
|
+
sha256sum | awk '{print $1}'
|
|
52
|
+
fi
|
|
53
|
+
}
|
|
37
54
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-H "Content-Type: application/json" \
|
|
42
|
-
-d "$(jq -n --arg to "$name" --arg message "$message" \
|
|
43
|
-
'{to: $to, message: $message}')" \
|
|
44
|
-
--max-time 10)
|
|
45
|
-
|
|
46
|
-
if echo "$response" | jq -e '.status == "sent"' >/dev/null 2>&1; then
|
|
47
|
-
echo "Sent"
|
|
48
|
-
bash "$SCRIPT_DIR/bin/telemetry.sh" emit "notification" \
|
|
49
|
-
'{"type":"send","status":"sent"}' 2>/dev/null &
|
|
50
|
-
else
|
|
51
|
-
local detail
|
|
52
|
-
detail=$(echo "$response" | jq -r '.detail // .status // "unknown error"')
|
|
53
|
-
if echo "$detail" | grep -qi "forbidden\|can't initiate\|bot was blocked"; then
|
|
54
|
-
echo "Failed: DM not available. Tell $name to message the Egregore bot first (/start)."
|
|
55
|
-
else
|
|
56
|
-
echo "Failed: $detail"
|
|
57
|
-
fi
|
|
58
|
-
bash "$SCRIPT_DIR/bin/telemetry.sh" emit "notification" \
|
|
59
|
-
'{"type":"send","status":"failed"}' 2>/dev/null &
|
|
60
|
-
fi
|
|
61
|
-
}
|
|
55
|
+
random_token() {
|
|
56
|
+
openssl rand -hex 16
|
|
57
|
+
}
|
|
62
58
|
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
plan_path() {
|
|
60
|
+
local id="$1"
|
|
61
|
+
case "$id" in
|
|
62
|
+
""|*[!a-f0-9]*) die "invalid notification plan id" ;;
|
|
63
|
+
esac
|
|
64
|
+
printf '%s/%s.json\n' "$STATE_DIR" "$id"
|
|
65
|
+
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
read_plan() {
|
|
68
|
+
local id="$1"
|
|
69
|
+
local path
|
|
70
|
+
path="$(plan_path "$id")"
|
|
71
|
+
[ -f "$path" ] || die "notification plan not found: $id"
|
|
72
|
+
jq -e . "$path" >/dev/null 2>&1 || die "notification plan is unreadable: $id"
|
|
73
|
+
printf '%s\n' "$path"
|
|
74
|
+
}
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
canonical_plan() {
|
|
77
|
+
jq -cS '{
|
|
78
|
+
version,
|
|
79
|
+
id,
|
|
80
|
+
session_id,
|
|
81
|
+
mode,
|
|
82
|
+
org_slug,
|
|
83
|
+
org_name,
|
|
84
|
+
kind,
|
|
85
|
+
recipient,
|
|
86
|
+
message,
|
|
87
|
+
channels,
|
|
88
|
+
deliveries,
|
|
89
|
+
no_fallback,
|
|
90
|
+
expires_at,
|
|
91
|
+
server_plan_token
|
|
92
|
+
}' "$1"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
verify_digest() {
|
|
96
|
+
local path="$1"
|
|
97
|
+
local stored actual
|
|
98
|
+
stored="$(jq -r '.digest' "$path")"
|
|
99
|
+
actual="$(canonical_plan "$path" | hash_text)"
|
|
100
|
+
[ "$stored" = "$actual" ] || die "notification plan changed after preview"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
verify_live_plan() {
|
|
104
|
+
local path="$1"
|
|
105
|
+
local plan_session plan_mode expires now
|
|
106
|
+
plan_session="$(jq -r '.session_id' "$path")"
|
|
107
|
+
[ "$plan_session" = "$SESSION_ID" ] ||
|
|
108
|
+
die "notification approval belongs to another session"
|
|
109
|
+
plan_mode="$(jq -r '.mode' "$path")"
|
|
110
|
+
[ "$plan_mode" = "$MODE" ] ||
|
|
111
|
+
die "notification configuration changed; prepare it again"
|
|
112
|
+
expires="$(jq -r '.expires_at' "$path")"
|
|
113
|
+
now="$(date +%s)"
|
|
114
|
+
[ "$expires" -ge "$now" ] || die "notification plan expired; prepare it again"
|
|
115
|
+
verify_digest "$path"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
write_plan() {
|
|
119
|
+
local plan_response="$1"
|
|
120
|
+
local kind="$2"
|
|
121
|
+
local recipient="$3"
|
|
122
|
+
local message="$4"
|
|
123
|
+
local id created expires path tmp
|
|
124
|
+
id="$(random_token | cut -c1-24)"
|
|
125
|
+
created="$(date +%s)"
|
|
126
|
+
expires="$(printf '%s' "$plan_response" | jq -r '.expires_at // empty')"
|
|
127
|
+
expires="${expires:-$((created + 600))}"
|
|
128
|
+
path="$(plan_path "$id")"
|
|
129
|
+
tmp="$path.tmp"
|
|
130
|
+
|
|
131
|
+
printf '%s' "$plan_response" | jq \
|
|
132
|
+
--arg id "$id" \
|
|
133
|
+
--arg session "$SESSION_ID" \
|
|
134
|
+
--arg mode "$MODE" \
|
|
135
|
+
--arg slug "$ORG_SLUG" \
|
|
136
|
+
--arg orgName "$ORG_NAME" \
|
|
137
|
+
--arg kind "$kind" \
|
|
138
|
+
--arg recipient "$recipient" \
|
|
139
|
+
--arg message "$message" \
|
|
140
|
+
--argjson created "$created" \
|
|
141
|
+
--argjson expires "$expires" \
|
|
142
|
+
'{
|
|
143
|
+
version: 1,
|
|
144
|
+
id: $id,
|
|
145
|
+
status: "proposed",
|
|
146
|
+
session_id: $session,
|
|
147
|
+
mode: $mode,
|
|
148
|
+
org_slug: (.org_slug // $slug),
|
|
149
|
+
org_name: (.org_name // $orgName),
|
|
150
|
+
kind: $kind,
|
|
151
|
+
recipient: (if $recipient == "" then null else $recipient end),
|
|
152
|
+
message: $message,
|
|
153
|
+
channels: (.channels // []),
|
|
154
|
+
deliveries: (.deliveries // []),
|
|
155
|
+
no_fallback: true,
|
|
156
|
+
created_at: $created,
|
|
157
|
+
expires_at: $expires,
|
|
158
|
+
server_plan_token: (.plan_token // ""),
|
|
159
|
+
digest: ""
|
|
160
|
+
}' > "$tmp"
|
|
161
|
+
|
|
162
|
+
local digest
|
|
163
|
+
digest="$(canonical_plan "$tmp" | hash_text)"
|
|
164
|
+
jq --arg digest "$digest" '.digest = $digest' "$tmp" > "$path"
|
|
165
|
+
rm -f "$tmp"
|
|
166
|
+
chmod 600 "$path" 2>/dev/null || true
|
|
167
|
+
jq '{
|
|
168
|
+
status: "approval_required",
|
|
169
|
+
plan_id: .id,
|
|
170
|
+
digest,
|
|
171
|
+
org: .org_name,
|
|
172
|
+
recipient,
|
|
173
|
+
channels,
|
|
174
|
+
deliveries,
|
|
175
|
+
no_fallback,
|
|
176
|
+
message,
|
|
177
|
+
expires_at
|
|
178
|
+
}' "$path"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
read_local_telegram_config() {
|
|
182
|
+
local field="$1"
|
|
183
|
+
local state_file="$SCRIPT_DIR/.egregore-state.json"
|
|
184
|
+
local value
|
|
185
|
+
value="$(jq -r ".$field // empty" "$state_file" 2>/dev/null || true)"
|
|
186
|
+
[ -n "$value" ] ||
|
|
187
|
+
value="$(jq -r ".$field // empty" "$CONFIG" 2>/dev/null || true)"
|
|
188
|
+
printf '%s\n' "$value"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
plan_connected() {
|
|
192
|
+
local kind="$1"
|
|
193
|
+
local recipient="$2"
|
|
194
|
+
local message="$3"
|
|
195
|
+
[ -n "$API_URL" ] && [ -n "$API_KEY" ] ||
|
|
196
|
+
die "notification service is not connected"
|
|
197
|
+
|
|
198
|
+
local response
|
|
199
|
+
response="$(curl -sS -X POST "${API_URL}/api/notify/plan" \
|
|
200
|
+
-H "Authorization: Bearer $API_KEY" \
|
|
201
|
+
-H "Content-Type: application/json" \
|
|
202
|
+
-d "$(jq -nc \
|
|
203
|
+
--arg kind "$kind" \
|
|
204
|
+
--arg to "$recipient" \
|
|
205
|
+
--arg message "$message" \
|
|
206
|
+
'{kind:$kind, message:$message}
|
|
207
|
+
+ (if $to == "" then {} else {to:$to} end)')" \
|
|
208
|
+
--max-time 10)" || die "notification planning failed"
|
|
209
|
+
|
|
210
|
+
printf '%s' "$response" | jq -e '.status == "planned"' >/dev/null 2>&1 ||
|
|
211
|
+
die "$(printf '%s' "$response" | jq -r '.detail // "notification planning failed"' 2>/dev/null)"
|
|
212
|
+
write_plan "$response" "$kind" "$recipient" "$message"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
plan_local() {
|
|
216
|
+
local kind="$1"
|
|
217
|
+
local recipient="$2"
|
|
218
|
+
local message="$3"
|
|
219
|
+
if [ "$kind" = "send" ]; then
|
|
220
|
+
die "direct messages are unavailable in local mode; no group fallback was sent"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
local chat_id group_link destination
|
|
224
|
+
chat_id="$(read_local_telegram_config telegram_chat_id)"
|
|
225
|
+
group_link="$(read_local_telegram_config telegram_group_link)"
|
|
226
|
+
[ -n "$chat_id" ] || [ -n "$group_link" ] ||
|
|
227
|
+
die "no Telegram group is configured"
|
|
228
|
+
destination="${group_link:-$chat_id}"
|
|
229
|
+
|
|
230
|
+
local response
|
|
231
|
+
response="$(jq -nc \
|
|
232
|
+
--arg slug "$ORG_SLUG" \
|
|
233
|
+
--arg org "$ORG_NAME" \
|
|
234
|
+
--arg destination "$destination" \
|
|
235
|
+
'{
|
|
236
|
+
status:"planned",
|
|
237
|
+
org_slug:$slug,
|
|
238
|
+
org_name:$org,
|
|
239
|
+
channels:["telegram"],
|
|
240
|
+
deliveries:[{channel:"telegram", destination:$destination, kind:"group"}],
|
|
241
|
+
no_fallback:true
|
|
242
|
+
}')"
|
|
243
|
+
write_plan "$response" "$kind" "$recipient" "$message"
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
plan_notification() {
|
|
247
|
+
local kind="$1"
|
|
248
|
+
local recipient="$2"
|
|
249
|
+
local message="$3"
|
|
250
|
+
[ -n "$message" ] || die "notification message is empty"
|
|
251
|
+
case "$MODE" in
|
|
252
|
+
connected) plan_connected "$kind" "$recipient" "$message" ;;
|
|
253
|
+
local) plan_local "$kind" "$recipient" "$message" ;;
|
|
254
|
+
*) die "notifications are unavailable in this configuration" ;;
|
|
255
|
+
esac
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
approve_plan() {
|
|
259
|
+
local id="$1"
|
|
260
|
+
local supplied_digest="$2"
|
|
261
|
+
local confirmation="$3"
|
|
262
|
+
[ "$confirmation" = "APPROVE_EXACT_NOTIFICATION" ] ||
|
|
263
|
+
die "explicit notification confirmation is required"
|
|
264
|
+
|
|
265
|
+
local path status digest token token_hash tmp
|
|
266
|
+
path="$(read_plan "$id")"
|
|
267
|
+
verify_live_plan "$path"
|
|
268
|
+
status="$(jq -r '.status' "$path")"
|
|
269
|
+
[ "$status" = "proposed" ] || die "notification plan is not awaiting approval"
|
|
270
|
+
digest="$(jq -r '.digest' "$path")"
|
|
271
|
+
[ "$supplied_digest" = "$digest" ] ||
|
|
272
|
+
die "approval does not match the previewed notification"
|
|
273
|
+
|
|
274
|
+
token="$(random_token)"
|
|
275
|
+
token_hash="$(printf '%s' "$token" | hash_text)"
|
|
276
|
+
tmp="$path.tmp"
|
|
277
|
+
jq \
|
|
278
|
+
--arg tokenHash "$token_hash" \
|
|
279
|
+
--argjson approvedAt "$(date +%s)" \
|
|
280
|
+
'.status = "approved"
|
|
281
|
+
| .approval_token_hash = $tokenHash
|
|
282
|
+
| .approved_at = $approvedAt' "$path" > "$tmp"
|
|
283
|
+
mv "$tmp" "$path"
|
|
284
|
+
jq -nc --arg id "$id" --arg token "$token" \
|
|
285
|
+
'{status:"approved", plan_id:$id, approval_token:$token}'
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
mark_plan() {
|
|
289
|
+
local path="$1"
|
|
290
|
+
local status="$2"
|
|
291
|
+
local detail="${3:-}"
|
|
292
|
+
local tmp="$path.tmp"
|
|
293
|
+
jq \
|
|
294
|
+
--arg status "$status" \
|
|
295
|
+
--arg detail "$detail" \
|
|
296
|
+
--argjson changedAt "$(date +%s)" \
|
|
297
|
+
'.status = $status
|
|
298
|
+
| .status_detail = (if $detail == "" then null else $detail end)
|
|
299
|
+
| .status_changed_at = $changedAt' "$path" > "$tmp"
|
|
300
|
+
mv "$tmp" "$path"
|
|
301
|
+
}
|
|
85
302
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
303
|
+
dispatch_connected() {
|
|
304
|
+
local path="$1"
|
|
305
|
+
local kind message plan_token response
|
|
306
|
+
kind="$(jq -r '.kind' "$path")"
|
|
307
|
+
message="$(jq -r '.message' "$path")"
|
|
308
|
+
plan_token="$(jq -r '.server_plan_token' "$path")"
|
|
309
|
+
|
|
310
|
+
if [ "$kind" = "send" ]; then
|
|
311
|
+
local recipient channel
|
|
312
|
+
recipient="$(jq -r '.recipient' "$path")"
|
|
313
|
+
channel="$(jq -r '.channels[0]' "$path")"
|
|
314
|
+
response="$(curl -sS -X POST "${API_URL}/api/notify/send" \
|
|
315
|
+
-H "Authorization: Bearer $API_KEY" \
|
|
316
|
+
-H "Content-Type: application/json" \
|
|
317
|
+
-d "$(jq -nc \
|
|
318
|
+
--arg to "$recipient" \
|
|
319
|
+
--arg message "$message" \
|
|
320
|
+
--arg channel "$channel" \
|
|
321
|
+
--arg planToken "$plan_token" \
|
|
322
|
+
'{to:$to,message:$message,channel:$channel,plan_token:$planToken}')" \
|
|
323
|
+
--max-time 10)" || return 1
|
|
324
|
+
else
|
|
325
|
+
local channels
|
|
326
|
+
channels="$(jq -c '.channels' "$path")"
|
|
327
|
+
response="$(curl -sS -X POST "${API_URL}/api/notify/group" \
|
|
89
328
|
-H "Authorization: Bearer $API_KEY" \
|
|
90
|
-
|
|
329
|
+
-H "Content-Type: application/json" \
|
|
330
|
+
-d "$(jq -nc \
|
|
331
|
+
--arg message "$message" \
|
|
332
|
+
--arg planToken "$plan_token" \
|
|
333
|
+
--argjson channels "$channels" \
|
|
334
|
+
'{message:$message,channels:$channels,plan_token:$planToken}')" \
|
|
335
|
+
--max-time 10)" || return 1
|
|
336
|
+
fi
|
|
91
337
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
echo "Failed to connect" >&2
|
|
96
|
-
echo "$response" >&2
|
|
97
|
-
exit 1
|
|
98
|
-
fi
|
|
338
|
+
printf '%s' "$response" | jq -e '.status == "sent"' >/dev/null 2>&1 || {
|
|
339
|
+
printf '%s\n' "$response"
|
|
340
|
+
return 1
|
|
99
341
|
}
|
|
342
|
+
printf '%s\n' "$response"
|
|
343
|
+
}
|
|
100
344
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
345
|
+
dispatch_local() {
|
|
346
|
+
local path="$1"
|
|
347
|
+
local message destination chat_id group_link plan_slug plan_org response
|
|
348
|
+
message="$(jq -r '.message' "$path")"
|
|
349
|
+
destination="$(jq -r '.deliveries[0].destination' "$path")"
|
|
350
|
+
case "$destination" in
|
|
351
|
+
https://t.me/*)
|
|
352
|
+
chat_id=""
|
|
353
|
+
group_link="$destination"
|
|
354
|
+
;;
|
|
355
|
+
*)
|
|
356
|
+
chat_id="$destination"
|
|
357
|
+
group_link=""
|
|
358
|
+
;;
|
|
359
|
+
esac
|
|
360
|
+
plan_slug="$(jq -r '.org_slug' "$path")"
|
|
361
|
+
plan_org="$(jq -r '.org_name' "$path")"
|
|
362
|
+
response="$(curl -sS -X POST "${RELAY_URL}/api/notify/relay" \
|
|
363
|
+
-H "Content-Type: application/json" \
|
|
364
|
+
-d "$(jq -nc \
|
|
365
|
+
--arg chat_id "$chat_id" \
|
|
366
|
+
--arg group_link "$group_link" \
|
|
367
|
+
--arg message "$message" \
|
|
368
|
+
--arg slug "$plan_slug" \
|
|
369
|
+
--arg org_name "$plan_org" \
|
|
370
|
+
'{chat_id:$chat_id,group_link:$group_link,message:$message,slug:$slug,org_name:$org_name}')" \
|
|
371
|
+
--max-time 10)" || return 1
|
|
372
|
+
printf '%s' "$response" | jq -e '.status == "sent"' >/dev/null 2>&1 || {
|
|
373
|
+
printf '%s\n' "$response"
|
|
374
|
+
return 1
|
|
113
375
|
}
|
|
376
|
+
printf '%s\n' "$response"
|
|
377
|
+
}
|
|
114
378
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
379
|
+
dispatch_plan() {
|
|
380
|
+
local id="$1"
|
|
381
|
+
local approval_token="$2"
|
|
382
|
+
local path status expected_hash actual_hash lock response
|
|
383
|
+
path="$(read_plan "$id")"
|
|
384
|
+
verify_live_plan "$path"
|
|
385
|
+
status="$(jq -r '.status' "$path")"
|
|
386
|
+
[ "$status" = "approved" ] || die "notification plan has not been explicitly approved"
|
|
387
|
+
expected_hash="$(jq -r '.approval_token_hash' "$path")"
|
|
388
|
+
actual_hash="$(printf '%s' "$approval_token" | hash_text)"
|
|
389
|
+
[ "$expected_hash" = "$actual_hash" ] || die "notification approval token is invalid"
|
|
119
390
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
local group_link
|
|
125
|
-
group_link=$(_read_telegram_config telegram_group_link)
|
|
391
|
+
lock="$path.lock"
|
|
392
|
+
mkdir "$lock" 2>/dev/null || die "notification dispatch is already in progress"
|
|
393
|
+
NOTIFY_LOCK="$lock"
|
|
394
|
+
trap '[ -z "${NOTIFY_LOCK:-}" ] || rmdir "$NOTIFY_LOCK" 2>/dev/null || true' EXIT
|
|
126
395
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
396
|
+
# Consume approval before the network call. An uncertain failure requires a
|
|
397
|
+
# new plan rather than risking a duplicate external message.
|
|
398
|
+
mark_plan "$path" "dispatching"
|
|
399
|
+
if [ "$MODE" = "connected" ]; then
|
|
400
|
+
if ! response="$(dispatch_connected "$path")"; then
|
|
401
|
+
mark_plan "$path" "failed" "dispatch failed; prepare and approve a new plan"
|
|
402
|
+
die "notification dispatch failed; no fallback was attempted"
|
|
403
|
+
fi
|
|
404
|
+
else
|
|
405
|
+
if ! response="$(dispatch_local "$path")"; then
|
|
406
|
+
mark_plan "$path" "failed" "dispatch failed; prepare and approve a new plan"
|
|
407
|
+
die "notification dispatch failed; no fallback was attempted"
|
|
130
408
|
fi
|
|
409
|
+
fi
|
|
131
410
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
411
|
+
mark_plan "$path" "sent"
|
|
412
|
+
rmdir "$lock" 2>/dev/null || true
|
|
413
|
+
NOTIFY_LOCK=""
|
|
414
|
+
trap - EXIT
|
|
415
|
+
bash "$SCRIPT_DIR/bin/telemetry.sh" emit "notification" \
|
|
416
|
+
"$(jq -nc --arg kind "$(jq -r '.kind' "$path")" \
|
|
417
|
+
'{type:$kind,status:"sent",consent:"explicit"}')" 2>/dev/null &
|
|
418
|
+
jq -nc \
|
|
419
|
+
--arg id "$id" \
|
|
420
|
+
--argjson result "$response" \
|
|
421
|
+
'{status:"sent",plan_id:$id,result:$result}'
|
|
422
|
+
}
|
|
136
423
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
424
|
+
show_plan() {
|
|
425
|
+
local path
|
|
426
|
+
path="$(read_plan "$1")"
|
|
427
|
+
jq '{
|
|
428
|
+
status,
|
|
429
|
+
plan_id:.id,
|
|
430
|
+
digest,
|
|
431
|
+
org:.org_name,
|
|
432
|
+
recipient,
|
|
433
|
+
channels,
|
|
434
|
+
deliveries,
|
|
435
|
+
no_fallback,
|
|
436
|
+
message,
|
|
437
|
+
expires_at
|
|
438
|
+
}' "$path"
|
|
439
|
+
}
|
|
148
440
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
441
|
+
cancel_plan() {
|
|
442
|
+
local path status
|
|
443
|
+
path="$(read_plan "$1")"
|
|
444
|
+
status="$(jq -r '.status' "$path")"
|
|
445
|
+
case "$status" in
|
|
446
|
+
proposed|approved|failed) mark_plan "$path" "cancelled" ;;
|
|
447
|
+
*) die "notification plan cannot be cancelled from status: $status" ;;
|
|
448
|
+
esac
|
|
449
|
+
jq -nc --arg id "$1" '{status:"cancelled",plan_id:$id}'
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
list_pending() {
|
|
453
|
+
local found="false"
|
|
454
|
+
for path in "$STATE_DIR"/*.json; do
|
|
455
|
+
[ -f "$path" ] || continue
|
|
456
|
+
if jq -e '.status == "proposed" or .status == "approved"' "$path" >/dev/null 2>&1; then
|
|
457
|
+
jq -c '{plan_id:.id,status,org:.org_name,recipient,channels,message,expires_at}' "$path"
|
|
458
|
+
found="true"
|
|
155
459
|
fi
|
|
156
|
-
|
|
460
|
+
done
|
|
461
|
+
[ "$found" = "true" ] || echo '{"status":"empty"}'
|
|
462
|
+
}
|
|
157
463
|
|
|
158
|
-
|
|
464
|
+
test_connection() {
|
|
465
|
+
if [ "$MODE" = "connected" ]; then
|
|
466
|
+
[ -n "$API_URL" ] && [ -n "$API_KEY" ] ||
|
|
467
|
+
die "notification service is not connected"
|
|
468
|
+
curl -sS -X GET "${API_URL}/api/notify/test" \
|
|
469
|
+
-H "Authorization: Bearer $API_KEY" \
|
|
470
|
+
--max-time 10
|
|
471
|
+
elif [ "$MODE" = "local" ]; then
|
|
159
472
|
local group_link
|
|
160
|
-
group_link
|
|
473
|
+
group_link="$(read_local_telegram_config telegram_group_link)"
|
|
161
474
|
if [ -n "$group_link" ]; then
|
|
162
|
-
echo "
|
|
475
|
+
echo '{"status":"configured","mode":"local"}'
|
|
163
476
|
else
|
|
164
|
-
echo "
|
|
477
|
+
echo '{"status":"offline","reason":"no_telegram_group"}'
|
|
165
478
|
fi
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
send_to_person() { echo '{"status":"offline","reason":"no_api_key"}'; }
|
|
171
|
-
send_to_group() { echo '{"status":"offline","reason":"no_api_key"}'; }
|
|
172
|
-
test_connection() { echo '{"status":"offline","reason":"no_api_key"}'; }
|
|
173
|
-
fi
|
|
479
|
+
else
|
|
480
|
+
echo '{"status":"offline","reason":"no_api_key"}'
|
|
481
|
+
fi
|
|
482
|
+
}
|
|
174
483
|
|
|
175
484
|
case "${1:-help}" in
|
|
485
|
+
plan)
|
|
486
|
+
case "${2:-}" in
|
|
487
|
+
send)
|
|
488
|
+
[ "$#" -eq 4 ] || die "Usage: notify.sh plan send <recipient> <message>"
|
|
489
|
+
plan_notification "send" "$3" "$4"
|
|
490
|
+
;;
|
|
491
|
+
group)
|
|
492
|
+
[ "$#" -eq 3 ] || die "Usage: notify.sh plan group <message>"
|
|
493
|
+
plan_notification "group" "" "$3"
|
|
494
|
+
;;
|
|
495
|
+
*) die "Usage: notify.sh plan send|group ..." ;;
|
|
496
|
+
esac
|
|
497
|
+
;;
|
|
498
|
+
approve)
|
|
499
|
+
[ "$#" -eq 4 ] || die "Usage: notify.sh approve <plan-id> <digest> APPROVE_EXACT_NOTIFICATION"
|
|
500
|
+
approve_plan "$2" "$3" "$4"
|
|
501
|
+
;;
|
|
502
|
+
dispatch)
|
|
503
|
+
[ "$#" -eq 3 ] || die "Usage: notify.sh dispatch <plan-id> <approval-token>"
|
|
504
|
+
dispatch_plan "$2" "$3"
|
|
505
|
+
;;
|
|
506
|
+
show)
|
|
507
|
+
[ "$#" -eq 2 ] || die "Usage: notify.sh show <plan-id>"
|
|
508
|
+
show_plan "$2"
|
|
509
|
+
;;
|
|
510
|
+
cancel)
|
|
511
|
+
[ "$#" -eq 2 ] || die "Usage: notify.sh cancel <plan-id>"
|
|
512
|
+
cancel_plan "$2"
|
|
513
|
+
;;
|
|
514
|
+
pending)
|
|
515
|
+
list_pending
|
|
516
|
+
;;
|
|
176
517
|
send)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
send_to_group "@${recipient}: ${message}"
|
|
182
|
-
echo "DM failed, sent to group instead. ${result}"
|
|
183
|
-
else
|
|
184
|
-
echo "$result"
|
|
185
|
-
fi
|
|
518
|
+
[ "$#" -eq 3 ] || die "Usage: notify.sh send <recipient> <message>"
|
|
519
|
+
plan_notification "send" "$2" "$3"
|
|
520
|
+
echo "Approval required — no notification was sent." >&2
|
|
521
|
+
exit 4
|
|
186
522
|
;;
|
|
187
523
|
group)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
echo "File upload not yet supported via API. Use direct mode." >&2
|
|
193
|
-
exit 1
|
|
524
|
+
[ "$#" -eq 2 ] || die "Usage: notify.sh group <message>"
|
|
525
|
+
plan_notification "group" "" "$2"
|
|
526
|
+
echo "Approval required — no notification was sent." >&2
|
|
527
|
+
exit 4
|
|
194
528
|
;;
|
|
195
529
|
test)
|
|
196
530
|
test_connection
|
|
@@ -199,9 +533,14 @@ case "${1:-help}" in
|
|
|
199
533
|
echo "Usage: notify.sh <command>"
|
|
200
534
|
echo ""
|
|
201
535
|
echo "Commands:"
|
|
202
|
-
echo " send <name> <message>
|
|
203
|
-
echo " group <message>
|
|
204
|
-
echo "
|
|
205
|
-
echo "
|
|
536
|
+
echo " plan send <name> <message> Resolve a DM without sending"
|
|
537
|
+
echo " plan group <message> Resolve group channels without sending"
|
|
538
|
+
echo " approve <id> <digest> APPROVE_EXACT_NOTIFICATION"
|
|
539
|
+
echo " Record one exact human approval"
|
|
540
|
+
echo " dispatch <id> <token> Consume approval and send stored content"
|
|
541
|
+
echo " show <id> Show exact pending content"
|
|
542
|
+
echo " cancel <id> Cancel a pending plan"
|
|
543
|
+
echo " pending List pending plans"
|
|
544
|
+
echo " test Test configuration without sending"
|
|
206
545
|
;;
|
|
207
546
|
esac
|