create-egregore 0.19.32 → 0.20.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.
- package/assets/egregore-launcher.js +118 -13
- package/bin/cli.js +48 -3
- package/lib/connect-runtime.js +4 -2
- package/lib/local.js +15 -1
- package/lib/prime-runtime.js +208 -0
- package/lib/setup.js +45 -3
- package/package.json +5 -4
- package/runtime/codex/.codex/skills/ingest-notion/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/notion-connect/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/codex/.codex/spec-manifest.json +3 -3
- package/runtime/codex/AGENTS.md +11 -4
- package/runtime/codex/bin/activity-data.sh +4 -8
- package/runtime/codex/bin/attendant.sh +13 -0
- package/runtime/codex/bin/autosave.sh +27 -1
- package/runtime/codex/bin/codex-render-spec.mjs +6 -0
- package/runtime/codex/bin/codex-session-start.sh +8 -1
- package/runtime/codex/bin/connect-refresh.sh +33 -0
- package/runtime/codex/bin/graph-batch.sh +25 -6
- package/runtime/codex/bin/graph-op.sh +5 -4
- package/runtime/codex/bin/graph.sh +31 -5
- package/runtime/codex/bin/ingest.py +81 -5
- package/runtime/codex/bin/lib/context.sh +26 -1
- package/runtime/codex/bin/lib/greeting.sh +4 -1
- package/runtime/codex/bin/lib/worktree-links.sh +25 -0
- package/runtime/codex/manifest.json +8 -1
- package/runtime/pi/.codex/skills/ingest-notion/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/notion-connect/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/pi/.codex/spec-manifest.json +3 -3
- package/runtime/pi/.pi/APPEND_SYSTEM.md +3 -2
- package/runtime/pi/.pi/extensions/egregore.ts +17 -6
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +11 -4
- package/runtime/pi/bin/activity-data.sh +4 -8
- package/runtime/pi/bin/attendant.sh +13 -0
- package/runtime/pi/bin/autosave.sh +27 -1
- package/runtime/pi/bin/codex-render-spec.mjs +6 -0
- package/runtime/pi/bin/codex-session-start.sh +8 -1
- package/runtime/pi/bin/connect-refresh.sh +33 -0
- package/runtime/pi/bin/graph-batch.sh +25 -6
- package/runtime/pi/bin/graph-op.sh +5 -4
- package/runtime/pi/bin/graph.sh +31 -5
- package/runtime/pi/bin/ingest.py +81 -5
- package/runtime/pi/bin/lib/context.sh +26 -1
- package/runtime/pi/bin/lib/greeting.sh +4 -1
- package/runtime/pi/bin/lib/worktree-links.sh +25 -0
- package/runtime/pi/bin/pi-branch-policy.mjs +138 -1
- package/runtime/pi/bin/pi-product-workflows.mjs +35 -7
- package/runtime/pi/manifest.json +8 -1
- package/runtime/prime/.claude/context/notification-consent.md +66 -0
- package/runtime/prime/.codex/config.toml +17 -0
- package/runtime/prime/.codex/hooks/branch-guard.js +461 -0
- package/runtime/prime/.codex/hooks/search-hint.js +67 -0
- package/runtime/prime/.codex/hooks.json +30 -0
- package/runtime/prime/.codex/skills/activity/SKILL.md +82 -0
- package/runtime/prime/.codex/skills/announce/SKILL.md +67 -0
- package/runtime/prime/.codex/skills/archive/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/branch/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/checkup/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/commit/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/contribute/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/create-skill/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/dark-mode/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/dashboard/SKILL.md +59 -0
- package/runtime/prime/.codex/skills/deep-reflect/SKILL.md +109 -0
- package/runtime/prime/.codex/skills/delete-user/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/emissary/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/handoff/SKILL.md +166 -0
- package/runtime/prime/.codex/skills/harvest/QUESTION_PALETTE.md +76 -0
- package/runtime/prime/.codex/skills/harvest/SKILL.md +131 -0
- package/runtime/prime/.codex/skills/ingest/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/ingest-notion/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/ingest-user-interview/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/invite/SKILL.md +44 -0
- package/runtime/prime/.codex/skills/issue/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/me/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/meeting/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/note/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/notion-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/onboarding/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/pr/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/pull/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/push/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/quest/SKILL.md +80 -0
- package/runtime/prime/.codex/skills/reflect/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/review-pr/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/save/SKILL.md +125 -0
- package/runtime/prime/.codex/skills/slack-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/summon/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/sync-repos/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/telegram-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/telemetry/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/test/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/the-spiral/SKILL.md +84 -0
- package/runtime/prime/.codex/skills/tui-design/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/tutorial/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/update/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/view/SKILL.md +156 -0
- package/runtime/prime/.codex/skills/view/agents/openai.yaml +4 -0
- package/runtime/prime/.codex/skills/view/assets/compose-scaffold.html +221 -0
- package/runtime/prime/.codex/skills/visual-explain/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/wrap/SKILL.md +58 -0
- package/runtime/prime/.codex/spec-manifest.json +132 -0
- package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +393 -0
- package/runtime/prime/.prime/agent/extensions/egregore.ts +460 -0
- package/runtime/prime/.prime/agent/settings.json +4 -0
- package/runtime/prime/.prime/agent/skills/egregore/SKILL.md +34 -0
- package/runtime/prime/.prime/agent/skills/egregore/pyproject.toml +13 -0
- package/runtime/prime/.prime/agent/skills/egregore/src/egregore/__init__.py +156 -0
- package/runtime/prime/.prime/agent/spec-manifest.json +12 -0
- package/runtime/prime/AGENTS.md +489 -0
- package/runtime/prime/bin/activity-data.sh +238 -0
- package/runtime/prime/bin/agent.sh +775 -0
- package/runtime/prime/bin/attendant.sh +707 -0
- package/runtime/prime/bin/autosave.sh +112 -0
- package/runtime/prime/bin/capture-reconcile.sh +69 -0
- package/runtime/prime/bin/capture-run.sh +341 -0
- package/runtime/prime/bin/codex-install-skills.sh +78 -0
- package/runtime/prime/bin/codex-render-spec.mjs +440 -0
- package/runtime/prime/bin/codex-session-start.sh +265 -0
- package/runtime/prime/bin/codex-skill-render.mjs +347 -0
- package/runtime/prime/bin/codex-sync-skills.sh +232 -0
- package/runtime/prime/bin/connect-refresh.sh +33 -0
- package/runtime/prime/bin/dashboard-data.sh +178 -0
- package/runtime/prime/bin/graph-batch.sh +79 -0
- package/runtime/prime/bin/graph-op.sh +1102 -0
- package/runtime/prime/bin/graph-wal.sh +229 -0
- package/runtime/prime/bin/graph.sh +223 -0
- package/runtime/prime/bin/handoff-pr-backfill.sh +86 -0
- package/runtime/prime/bin/handoff-run.sh +671 -0
- package/runtime/prime/bin/handoff-save-egregore.sh +212 -0
- package/runtime/prime/bin/index-handoff.sh +322 -0
- package/runtime/prime/bin/ingest-graph.sh +80 -0
- package/runtime/prime/bin/ingest-surface.html +495 -0
- package/runtime/prime/bin/ingest.py +756 -0
- package/runtime/prime/bin/ingest.sh +279 -0
- package/runtime/prime/bin/ingest_graph.py +856 -0
- package/runtime/prime/bin/ingest_surface.py +544 -0
- package/runtime/prime/bin/lib/artifact-id.sh +46 -0
- package/runtime/prime/bin/lib/config.sh +116 -0
- package/runtime/prime/bin/lib/context.sh +621 -0
- package/runtime/prime/bin/lib/dashboard-artifact.sh +242 -0
- package/runtime/prime/bin/lib/ensure-develop.sh +183 -0
- package/runtime/prime/bin/lib/git-sync.sh +406 -0
- package/runtime/prime/bin/lib/greeting.sh +519 -0
- package/runtime/prime/bin/lib/hash.sh +13 -0
- package/runtime/prime/bin/lib/identity.sh +213 -0
- package/runtime/prime/bin/lib/metrics.sh +374 -0
- package/runtime/prime/bin/lib/noncode.sh +31 -0
- package/runtime/prime/bin/lib/permissions.sh +106 -0
- package/runtime/prime/bin/lib/time.sh +56 -0
- package/runtime/prime/bin/lib/worktree-links.sh +79 -0
- package/runtime/prime/bin/notify.sh +636 -0
- package/runtime/prime/bin/person.py +630 -0
- package/runtime/prime/bin/person.sh +448 -0
- package/runtime/prime/bin/pi-branch-policy.mjs +312 -0
- package/runtime/prime/bin/pi-product-workflows.mjs +394 -0
- package/runtime/prime/bin/prime-render-spec.mjs +123 -0
- package/runtime/prime/bin/prime-session-start.sh +24 -0
- package/runtime/prime/bin/publish-artifact.sh +271 -0
- package/runtime/prime/bin/publish-references.sh +144 -0
- package/runtime/prime/bin/repo-state.sh +96 -0
- package/runtime/prime/bin/search.sh +240 -0
- package/runtime/prime/bin/session-autosave.sh +209 -0
- package/runtime/prime/bin/session-log.sh +199 -0
- package/runtime/prime/bin/settings.sh +304 -0
- package/runtime/prime/bin/telemetry.sh +391 -0
- package/runtime/prime/bin/worktree-create.sh +128 -0
- package/runtime/prime/bin/worktree-remove.sh +50 -0
- package/runtime/prime/bin/worktree.sh +198 -0
- package/runtime/prime/manifest.json +178 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# settings.sh — deterministic config verbs for an Egregore instance.
|
|
5
|
+
#
|
|
6
|
+
# Edits egregore.json atomically. No agent, no network, no session. Idempotent:
|
|
7
|
+
# re-adding an existing item is a no-op success; removing a missing one is too.
|
|
8
|
+
# This is the deterministic core the launcher settings screen (and slash
|
|
9
|
+
# commands) call — the reliability lives here, the surfaces are thin callers.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# settings.sh hosting status|on|off
|
|
13
|
+
# settings.sh relay status|on|off
|
|
14
|
+
# settings.sh repo list | add <name> [description] | remove <name>
|
|
15
|
+
# settings.sh admin list | add <github-handle> | remove <github-handle>
|
|
16
|
+
# settings.sh dump # full settings snapshot as JSON (for the launcher)
|
|
17
|
+
#
|
|
18
|
+
# Add --json to a read verb (status/list) for machine-readable output.
|
|
19
|
+
#
|
|
20
|
+
# Exit codes: 0 ok · 1 usage/validation error · 2 config not found.
|
|
21
|
+
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
23
|
+
CONFIG="$SCRIPT_DIR/egregore.json"
|
|
24
|
+
|
|
25
|
+
# ── helpers ──────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
_die() { echo "$1" >&2; exit "${2:-1}"; }
|
|
28
|
+
|
|
29
|
+
_valid_token() {
|
|
30
|
+
# non-empty, GitHub-handle / repo-name safe charset
|
|
31
|
+
[ -n "${1:-}" ] && printf '%s' "$1" | grep -qE '^[A-Za-z0-9._-]+$'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_save() {
|
|
35
|
+
# _save '<jq-filter>' [jq-args...] — apply filter to CONFIG, write atomically.
|
|
36
|
+
local filter="$1"; shift
|
|
37
|
+
local tmp
|
|
38
|
+
tmp="$(mktemp "$CONFIG.XXXXXX")" || _die "cannot create temp file" 1
|
|
39
|
+
if jq "$@" "$filter" "$CONFIG" > "$tmp" 2>/dev/null && [ -s "$tmp" ]; then
|
|
40
|
+
mv "$tmp" "$CONFIG"
|
|
41
|
+
else
|
|
42
|
+
rm -f "$tmp"
|
|
43
|
+
_die "failed to update $CONFIG (invalid jq filter or unwritable file)" 1
|
|
44
|
+
fi
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_has_repo() { jq -e --arg n "$1" 'any(.repos[]?; (if type=="object" then .name else . end) == $n)' "$CONFIG" >/dev/null; }
|
|
48
|
+
_has_admin() { jq -e --arg h "$1" 'any(.admins[]?; . == $h)' "$CONFIG" >/dev/null; }
|
|
49
|
+
|
|
50
|
+
usage() {
|
|
51
|
+
cat >&2 <<'EOF'
|
|
52
|
+
settings.sh — Egregore instance settings
|
|
53
|
+
|
|
54
|
+
settings.sh hosting status|on|off
|
|
55
|
+
settings.sh relay status|on|off
|
|
56
|
+
settings.sh repo list | add <name> [description] | remove <name>
|
|
57
|
+
settings.sh admin list | add <github-handle> | remove <github-handle>
|
|
58
|
+
settings.sh people list | add <github-handle> | remove <github-handle>
|
|
59
|
+
settings.sh dump full snapshot (JSON)
|
|
60
|
+
|
|
61
|
+
--json machine-readable output for status/list
|
|
62
|
+
EOF
|
|
63
|
+
exit "${1:-1}"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# ── verbs ────────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
cmd_hosting() {
|
|
69
|
+
local action="${1:-status}"
|
|
70
|
+
# NEVER use jq's `// "true"` here: `//` treats an explicit `false` as empty and
|
|
71
|
+
# would return the default, silently defeating the off-switch. Test `!= false`
|
|
72
|
+
# so absent/true => enabled, explicit false => disabled. `enabled` is the
|
|
73
|
+
# resolved boolean string ("true"/"false").
|
|
74
|
+
local enabled; enabled="$(jq -r '.features.publishing != false' "$CONFIG" 2>/dev/null || echo "true")"
|
|
75
|
+
case "$action" in
|
|
76
|
+
status)
|
|
77
|
+
if [ "$JSON" = 1 ]; then
|
|
78
|
+
jq -n --argjson on "$enabled" '{domain:"hosting", publishing:$on}'
|
|
79
|
+
elif [ "$enabled" = "false" ]; then echo "Hosting (egregore.xyz): OFF"
|
|
80
|
+
else echo "Hosting (egregore.xyz): ON"; fi ;;
|
|
81
|
+
on)
|
|
82
|
+
_save '.features.publishing = true'
|
|
83
|
+
echo "Hosting (egregore.xyz): ON" ;;
|
|
84
|
+
off)
|
|
85
|
+
_save '.features.publishing = false'
|
|
86
|
+
echo "Hosting (egregore.xyz): OFF — artifacts will no longer upload to egregore.xyz" ;;
|
|
87
|
+
*) _die "usage: settings.sh hosting status|on|off" 1 ;;
|
|
88
|
+
esac
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# The public share relay is the only upload route when there is no org API
|
|
92
|
+
# key. It is unauthenticated, readable by anyone with the URL, and expires
|
|
93
|
+
# after 7 days, so it is OFF unless explicitly enabled.
|
|
94
|
+
cmd_relay() {
|
|
95
|
+
local action="${1:-status}"
|
|
96
|
+
local enabled; enabled="$(jq -r 'if .features.public_relay == true then "true" else "false" end' "$CONFIG" 2>/dev/null || echo "false")"
|
|
97
|
+
case "$action" in
|
|
98
|
+
status)
|
|
99
|
+
if [ "$JSON" = 1 ]; then
|
|
100
|
+
jq -n --argjson on "$enabled" '{domain:"relay", public_relay:$on}'
|
|
101
|
+
elif [ "$enabled" = "true" ]; then
|
|
102
|
+
echo "Public share relay: ON — artifacts published without an org API key upload to a public, unauthenticated URL (expires after 7 days)"
|
|
103
|
+
else
|
|
104
|
+
echo "Public share relay: OFF — without an org API key, nothing uploads anywhere"
|
|
105
|
+
fi ;;
|
|
106
|
+
on)
|
|
107
|
+
_save '.features.public_relay = true'
|
|
108
|
+
echo "Public share relay: ON"
|
|
109
|
+
echo " Artifacts published without an org API key now upload to a public, unauthenticated URL that anyone with the link can read for 7 days." ;;
|
|
110
|
+
off)
|
|
111
|
+
_save '.features.public_relay = false'
|
|
112
|
+
echo "Public share relay: OFF — without an org API key, nothing uploads anywhere" ;;
|
|
113
|
+
*) _die "usage: settings.sh relay status|on|off" 1 ;;
|
|
114
|
+
esac
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
cmd_repo() {
|
|
118
|
+
local action="${1:-list}"; shift || true
|
|
119
|
+
case "$action" in
|
|
120
|
+
list)
|
|
121
|
+
if [ "$JSON" = 1 ]; then
|
|
122
|
+
jq '[.repos[]? | if type=="object" then . else {name:.} end]' "$CONFIG"
|
|
123
|
+
else
|
|
124
|
+
jq -r '.repos[]? | if type=="object" then " " + .name + (if .description then " — " + .description else "" end) else " " + . end' "$CONFIG"
|
|
125
|
+
fi ;;
|
|
126
|
+
add)
|
|
127
|
+
local name="${1:-}"; shift || true
|
|
128
|
+
local desc="${*:-}"
|
|
129
|
+
_valid_token "$name" || _die "usage: settings.sh repo add <name> [description]" 1
|
|
130
|
+
if _has_repo "$name"; then echo "repo '$name' already managed"; return 0; fi
|
|
131
|
+
if [ -n "$desc" ]; then
|
|
132
|
+
_save '.repos = ((.repos // []) + [{name:$n, description:$d}])' --arg n "$name" --arg d "$desc"
|
|
133
|
+
else
|
|
134
|
+
_save '.repos = ((.repos // []) + [{name:$n}])' --arg n "$name"
|
|
135
|
+
fi
|
|
136
|
+
echo "added repo '$name'"
|
|
137
|
+
echo " clone it with: /sync-repos (or reopen the egregore)" ;;
|
|
138
|
+
remove)
|
|
139
|
+
local name="${1:-}"
|
|
140
|
+
_valid_token "$name" || _die "usage: settings.sh repo remove <name>" 1
|
|
141
|
+
if ! _has_repo "$name"; then echo "repo '$name' is not managed"; return 0; fi
|
|
142
|
+
_save '.repos = [.repos[]? | select((if type=="object" then .name else . end) != $n)]' --arg n "$name"
|
|
143
|
+
echo "removed repo '$name' from config"
|
|
144
|
+
echo " note: the local ../$name checkout is left untouched" ;;
|
|
145
|
+
*) _die "usage: settings.sh repo list|add|remove" 1 ;;
|
|
146
|
+
esac
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
cmd_admin() {
|
|
150
|
+
local action="${1:-list}"; shift || true
|
|
151
|
+
case "$action" in
|
|
152
|
+
list)
|
|
153
|
+
if [ "$JSON" = 1 ]; then jq '{admins:(.admins // [])}' "$CONFIG"
|
|
154
|
+
else jq -r '.admins[]? | " " + .' "$CONFIG"; fi ;;
|
|
155
|
+
add)
|
|
156
|
+
local h="${1:-}"
|
|
157
|
+
_valid_token "$h" || _die "usage: settings.sh admin add <github-handle>" 1
|
|
158
|
+
if _has_admin "$h"; then echo "'$h' is already an admin"; return 0; fi
|
|
159
|
+
_save '.admins = ((.admins // []) + [$h])' --arg h "$h"
|
|
160
|
+
echo "added admin '$h'" ;;
|
|
161
|
+
remove)
|
|
162
|
+
local h="${1:-}"
|
|
163
|
+
_valid_token "$h" || _die "usage: settings.sh admin remove <github-handle>" 1
|
|
164
|
+
if ! _has_admin "$h"; then echo "'$h' is not an admin"; return 0; fi
|
|
165
|
+
local count; count="$(jq '(.admins // []) | length' "$CONFIG")"
|
|
166
|
+
[ "$count" -le 1 ] && _die "refusing to remove the last admin ('$h') — add another admin first" 1
|
|
167
|
+
_save '.admins = [.admins[]? | select(. != $h)]' --arg h "$h"
|
|
168
|
+
echo "removed admin '$h'" ;;
|
|
169
|
+
*) _die "usage: settings.sh admin list|add|remove" 1 ;;
|
|
170
|
+
esac
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# ── people ───────────────────────────────────────────────────────────────
|
|
174
|
+
# People live as files in memory/people/. add/remove manage that directory AND
|
|
175
|
+
# GitHub repo access (the meaningful local grant). Connected-mode extras — the
|
|
176
|
+
# invite link + Telegram (/invite) and Supabase/Neo4j teardown (/delete-user) —
|
|
177
|
+
# are pointed to, not duplicated here.
|
|
178
|
+
|
|
179
|
+
_people_dir() { echo "$SCRIPT_DIR/memory/people"; }
|
|
180
|
+
|
|
181
|
+
_people_names() {
|
|
182
|
+
local pdir; pdir="$(_people_dir)"
|
|
183
|
+
[ -d "$pdir" ] || return 0
|
|
184
|
+
ls "$pdir"/*.md 2>/dev/null | while IFS= read -r f; do basename "$f" .md; done \
|
|
185
|
+
| grep -viE '^(index|readme)$' || true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
_people_json() {
|
|
189
|
+
local names; names="$(_people_names)"
|
|
190
|
+
if [ -z "$names" ]; then echo '[]'; return; fi
|
|
191
|
+
printf '%s\n' "$names" | jq -R . | jq -sc 'map(select(length>0))'
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
_has_person() { [ -f "$(_people_dir)/$1.md" ]; }
|
|
195
|
+
|
|
196
|
+
# add|remove a GitHub push-collaborator across the instance's repos. Best-effort:
|
|
197
|
+
# no token/org => skip with a note; per-repo failures never abort.
|
|
198
|
+
_people_github() {
|
|
199
|
+
local user="$1" op="$2" token org repos r
|
|
200
|
+
token="$(grep '^GITHUB_TOKEN=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d= -f2- || true)"
|
|
201
|
+
org="$(jq -r '.github_org // empty' "$CONFIG" 2>/dev/null)"
|
|
202
|
+
if [ -z "$token" ] || [ -z "$org" ]; then
|
|
203
|
+
echo " (no GitHub token/org — skipped repo access; run bin/github-auth.sh)" >&2
|
|
204
|
+
return 0
|
|
205
|
+
fi
|
|
206
|
+
repos="$(jq -r '[.repo_name, (.memory_repo // "" | split("/") | last | sub("\\.git$";"")), (.repos[]? | if type=="object" then .name else . end)] | map(select(. != null and . != "")) | unique | .[]' "$CONFIG" 2>/dev/null)"
|
|
207
|
+
for r in $repos; do
|
|
208
|
+
if [ "$op" = "add" ]; then
|
|
209
|
+
curl -s -o /dev/null -X PUT -H "Authorization: Bearer $token" -H "Accept: application/vnd.github+json" \
|
|
210
|
+
"https://api.github.com/repos/$org/$r/collaborators/$user" -d '{"permission":"push"}' 2>/dev/null || true
|
|
211
|
+
else
|
|
212
|
+
curl -s -o /dev/null -X DELETE -H "Authorization: Bearer $token" -H "Accept: application/vnd.github+json" \
|
|
213
|
+
"https://api.github.com/repos/$org/$r/collaborators/$user" 2>/dev/null || true
|
|
214
|
+
fi
|
|
215
|
+
done
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# create|remove memory/people/<user>.md, committing ONLY the people dir.
|
|
219
|
+
_people_file() {
|
|
220
|
+
local user="$1" op="$2" pdir f inviter today
|
|
221
|
+
pdir="$(_people_dir)"
|
|
222
|
+
[ -d "$SCRIPT_DIR/memory" ] || { echo " (no memory dir — skipped person file)" >&2; return 0; }
|
|
223
|
+
mkdir -p "$pdir"
|
|
224
|
+
f="$pdir/$user.md"
|
|
225
|
+
if [ "$op" = "add" ]; then
|
|
226
|
+
if [ ! -f "$f" ]; then
|
|
227
|
+
inviter="$(jq -r '.display_name // .github_username // "admin"' "$SCRIPT_DIR/.egregore-state.json" 2>/dev/null || echo admin)"
|
|
228
|
+
today="$(date -u +%Y-%m-%d)"
|
|
229
|
+
printf -- '---\nname: %s\nperson_id: github-login:%s\ngithub: %s\ninvited_by: %s\njoined: %s\n---\n' "$user" "$(printf '%s' "$user" | tr '[:upper:]' '[:lower:]')" "$user" "$inviter" "$today" > "$f"
|
|
230
|
+
fi
|
|
231
|
+
else
|
|
232
|
+
rm -f "$f"
|
|
233
|
+
fi
|
|
234
|
+
( cd "$SCRIPT_DIR/memory" 2>/dev/null && git add -A people/ 2>/dev/null \
|
|
235
|
+
&& git commit -q -m "settings: people $op $user" 2>/dev/null \
|
|
236
|
+
&& git push -q 2>/dev/null ) || true
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
cmd_people() {
|
|
240
|
+
local action="${1:-list}"; shift || true
|
|
241
|
+
case "$action" in
|
|
242
|
+
list)
|
|
243
|
+
if [ "$JSON" = 1 ]; then _people_json
|
|
244
|
+
else
|
|
245
|
+
local names; names="$(_people_names)"
|
|
246
|
+
if [ -n "$names" ]; then printf ' %s\n' $names; else echo " (none)"; fi
|
|
247
|
+
fi ;;
|
|
248
|
+
add)
|
|
249
|
+
local h="${1:-}"
|
|
250
|
+
_valid_token "$h" || _die "usage: settings.sh people add <github-handle>" 1
|
|
251
|
+
if _has_person "$h"; then echo "'$h' is already in people"; return 0; fi
|
|
252
|
+
_people_github "$h" "add"
|
|
253
|
+
_people_file "$h" "add"
|
|
254
|
+
echo "added '$h' (repo access + person file)"
|
|
255
|
+
[ -n "$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)" ] && echo " connected org: for the invite link + Telegram, run /invite $h" ;;
|
|
256
|
+
remove)
|
|
257
|
+
local h="${1:-}"
|
|
258
|
+
_valid_token "$h" || _die "usage: settings.sh people remove <github-handle>" 1
|
|
259
|
+
if ! _has_person "$h"; then echo "'$h' is not in people"; return 0; fi
|
|
260
|
+
_people_github "$h" "remove"
|
|
261
|
+
_people_file "$h" "remove"
|
|
262
|
+
echo "removed '$h' (repo access + person file)"
|
|
263
|
+
[ -n "$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)" ] && echo " connected org: for full deprovision (Supabase/Neo4j), run /delete-user $h" ;;
|
|
264
|
+
*) _die "usage: settings.sh people list|add|remove" 1 ;;
|
|
265
|
+
esac
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
cmd_dump() {
|
|
269
|
+
# Always JSON — the launcher calls this once to render the whole settings screen.
|
|
270
|
+
local people; people="$(_people_json)"
|
|
271
|
+
jq --argjson people "$people" '{
|
|
272
|
+
org: .org_name,
|
|
273
|
+
slug: .slug,
|
|
274
|
+
hosting: (.features.publishing != false),
|
|
275
|
+
public_relay: (.features.public_relay == true),
|
|
276
|
+
repos: [.repos[]? | if type=="object" then . else {name:.} end],
|
|
277
|
+
admins: (.admins // []),
|
|
278
|
+
people: $people
|
|
279
|
+
}' "$CONFIG"
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# ── dispatch ─────────────────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
[ -f "$CONFIG" ] || _die "egregore.json not found at $CONFIG" 2
|
|
285
|
+
|
|
286
|
+
# Pull out --json (bash 3.2-safe array handling), leave the rest as positionals.
|
|
287
|
+
JSON=0
|
|
288
|
+
_args=()
|
|
289
|
+
for _a in "$@"; do
|
|
290
|
+
if [ "$_a" = "--json" ]; then JSON=1; else _args+=("$_a"); fi
|
|
291
|
+
done
|
|
292
|
+
set -- "${_args[@]+"${_args[@]}"}"
|
|
293
|
+
|
|
294
|
+
DOMAIN="${1:-}"; shift || true
|
|
295
|
+
case "$DOMAIN" in
|
|
296
|
+
hosting) cmd_hosting "$@" ;;
|
|
297
|
+
relay) cmd_relay "$@" ;;
|
|
298
|
+
repo) cmd_repo "$@" ;;
|
|
299
|
+
admin) cmd_admin "$@" ;;
|
|
300
|
+
people) cmd_people "$@" ;;
|
|
301
|
+
dump) cmd_dump ;;
|
|
302
|
+
""|-h|--help|help) usage 0 ;;
|
|
303
|
+
*) _die "unknown settings domain: '$DOMAIN' (try: hosting, relay, repo, admin, people, dump)" 1 ;;
|
|
304
|
+
esac
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Egregore telemetry — privacy-respecting product analytics.
|
|
3
|
+
# Mirrors bin/graph.sh and bin/notify.sh patterns.
|
|
4
|
+
#
|
|
5
|
+
# Subcommands:
|
|
6
|
+
# emit <type> <json> Append event to local JSONL buffer (no network)
|
|
7
|
+
# flush Send buffered events to API and clear buffer
|
|
8
|
+
# status Show telemetry status (enabled/disabled, buffer size)
|
|
9
|
+
# enable Enable telemetry
|
|
10
|
+
# disable Disable telemetry
|
|
11
|
+
# clear Clear local buffer
|
|
12
|
+
# show [n] Show last N events from buffer (default 10)
|
|
13
|
+
#
|
|
14
|
+
# Consent: opt-out via EGREGORE_NO_TELEMETRY=1, DO_NOT_TRACK=1,
|
|
15
|
+
# or .egregore-state.json → "telemetry": false
|
|
16
|
+
#
|
|
17
|
+
# Never collected: file paths, file contents, code, env var values,
|
|
18
|
+
# conversation content, command arguments with user content.
|
|
19
|
+
# Only collected: command names, timestamps, durations, error codes, branch names.
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
23
|
+
BUFFER_DIR="$HOME/.egregore"
|
|
24
|
+
BUFFER_FILE="$BUFFER_DIR/telemetry.jsonl"
|
|
25
|
+
STATE_FILE="$SCRIPT_DIR/.egregore-state.json"
|
|
26
|
+
CONFIG="$SCRIPT_DIR/egregore.json"
|
|
27
|
+
ENV_FILE="$SCRIPT_DIR/.env"
|
|
28
|
+
MAX_BUFFER_BYTES=1048576 # 1MB
|
|
29
|
+
MAX_EVENTS_AFTER_TRUNCATE=500
|
|
30
|
+
LOCK_DIR="$BUFFER_DIR/telemetry.lock"
|
|
31
|
+
# Public relay for local/OSS egregores without API keys
|
|
32
|
+
TELEMETRY_RELAY_URL="https://egregore-production-55f2.up.railway.app"
|
|
33
|
+
|
|
34
|
+
# --- Cross-platform file locking via mkdir (atomic on all POSIX systems) ---
|
|
35
|
+
_lock() {
|
|
36
|
+
local attempts=0
|
|
37
|
+
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
|
38
|
+
attempts=$((attempts + 1))
|
|
39
|
+
if [ "$attempts" -gt 50 ]; then
|
|
40
|
+
local holder_pid
|
|
41
|
+
holder_pid=$(cat "$LOCK_DIR/pid" 2>/dev/null || echo "")
|
|
42
|
+
if [ -n "$holder_pid" ] && ! kill -0 "$holder_pid" 2>/dev/null; then
|
|
43
|
+
rm -rf "$LOCK_DIR" 2>/dev/null
|
|
44
|
+
continue
|
|
45
|
+
fi
|
|
46
|
+
return 1
|
|
47
|
+
fi
|
|
48
|
+
sleep 0.1
|
|
49
|
+
done
|
|
50
|
+
echo $$ > "$LOCK_DIR/pid" 2>/dev/null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_unlock() {
|
|
54
|
+
rm -rf "$LOCK_DIR" 2>/dev/null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
trap '_unlock' EXIT
|
|
58
|
+
|
|
59
|
+
# --- Consent check (fast path) ---
|
|
60
|
+
_check_consent() {
|
|
61
|
+
# Environment variable opt-outs (fastest check)
|
|
62
|
+
[ "${EGREGORE_NO_TELEMETRY:-}" = "1" ] && return 1
|
|
63
|
+
[ "${DO_NOT_TRACK:-}" = "1" ] && return 1
|
|
64
|
+
|
|
65
|
+
# State file opt-out
|
|
66
|
+
if [ -f "$STATE_FILE" ]; then
|
|
67
|
+
local val
|
|
68
|
+
val=$(jq -r '.telemetry // "true"' "$STATE_FILE" 2>/dev/null || echo "true")
|
|
69
|
+
[ "$val" = "false" ] && return 1
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
return 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# --- Debug mode ---
|
|
76
|
+
_is_debug() {
|
|
77
|
+
[ "${EGREGORE_TELEMETRY_DEBUG:-}" = "1" ]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# --- Resolve identity (from env vars first, fallback to config files) ---
|
|
81
|
+
_resolve_user() {
|
|
82
|
+
if [ -n "${EGREGORE_USER:-}" ]; then
|
|
83
|
+
echo "$EGREGORE_USER"
|
|
84
|
+
elif [ -f "$STATE_FILE" ]; then
|
|
85
|
+
jq -r '.github_username // .name // "unknown"' "$STATE_FILE" 2>/dev/null || echo "unknown"
|
|
86
|
+
else
|
|
87
|
+
echo "unknown"
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_resolve_org() {
|
|
92
|
+
if [ -n "${EGREGORE_ORG:-}" ]; then
|
|
93
|
+
echo "$EGREGORE_ORG"
|
|
94
|
+
elif [ -f "$CONFIG" ]; then
|
|
95
|
+
jq -r '.slug // .github_org // "unknown"' "$CONFIG" 2>/dev/null || echo "unknown"
|
|
96
|
+
else
|
|
97
|
+
echo "unknown"
|
|
98
|
+
fi
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_resolve_proj_hash() {
|
|
102
|
+
# Use git common dir so worktrees hash to the same path as the main checkout
|
|
103
|
+
local repo_root
|
|
104
|
+
repo_root=$(git -C "$SCRIPT_DIR" rev-parse --path-format=absolute --git-common-dir 2>/dev/null | sed 's|/\.git$||') || repo_root="$SCRIPT_DIR"
|
|
105
|
+
echo -n "$repo_root" | md5 2>/dev/null || echo -n "$repo_root" | md5sum 2>/dev/null | cut -d' ' -f1
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_resolve_session_id() {
|
|
109
|
+
if [ -n "${EGREGORE_SESSION_ID:-}" ]; then
|
|
110
|
+
echo "$EGREGORE_SESSION_ID"
|
|
111
|
+
return
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# Fallback: read from file written by session-start.sh
|
|
115
|
+
# (env vars from hooks don't propagate into the Claude Code agent)
|
|
116
|
+
local proj_hash
|
|
117
|
+
proj_hash=$(_resolve_proj_hash)
|
|
118
|
+
local sid_file="$HOME/.egregore/session-${proj_hash}.id"
|
|
119
|
+
if [ -f "$sid_file" ]; then
|
|
120
|
+
cat "$sid_file" 2>/dev/null || echo "unknown"
|
|
121
|
+
else
|
|
122
|
+
echo "unknown"
|
|
123
|
+
fi
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
_resolve_model() {
|
|
127
|
+
local proj_hash
|
|
128
|
+
proj_hash=$(_resolve_proj_hash)
|
|
129
|
+
local model_file="$HOME/.egregore/session-model-${proj_hash}"
|
|
130
|
+
if [ ! -s "$model_file" ]; then
|
|
131
|
+
return 0
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
local model=""
|
|
135
|
+
IFS= read -r model < "$model_file" || true
|
|
136
|
+
if [ -n "$model" ]; then
|
|
137
|
+
printf '%s\n' "$model"
|
|
138
|
+
fi
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_stamp_model_if_absent() {
|
|
142
|
+
local compact_data="$1"
|
|
143
|
+
local has_model=""
|
|
144
|
+
has_model=$(printf '%s' "$compact_data" | jq -e 'has("model")' 2>/dev/null || true)
|
|
145
|
+
if [ "$has_model" != "false" ]; then
|
|
146
|
+
printf '%s' "$compact_data"
|
|
147
|
+
return 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
local model=""
|
|
151
|
+
model=$(_resolve_model 2>/dev/null || true)
|
|
152
|
+
if [ -z "$model" ]; then
|
|
153
|
+
printf '%s' "$compact_data"
|
|
154
|
+
return 0
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
local stamped_data=""
|
|
158
|
+
if stamped_data=$(printf '%s' "$compact_data" | jq -c --arg m "$model" 'if has("model") then . else . + {model:$m} end' 2>/dev/null); then
|
|
159
|
+
printf '%s' "$stamped_data"
|
|
160
|
+
else
|
|
161
|
+
printf '%s' "$compact_data"
|
|
162
|
+
fi
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# --- emit: append event to local JSONL buffer ---
|
|
166
|
+
cmd_emit() {
|
|
167
|
+
local event_type="${1:?Usage: telemetry.sh emit <type> <json>}"
|
|
168
|
+
local data="${2:-"{}"}"
|
|
169
|
+
|
|
170
|
+
# Consent gate
|
|
171
|
+
if ! _check_consent; then
|
|
172
|
+
return 0
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
local ts
|
|
176
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
177
|
+
# Compact data to single line — callers may pass pretty-printed JSON
|
|
178
|
+
local compact_data
|
|
179
|
+
compact_data=$(printf '%s' "$data" | tr -d '\n' | tr -s ' ')
|
|
180
|
+
compact_data=$(_stamp_model_if_absent "$compact_data")
|
|
181
|
+
|
|
182
|
+
# Debug mode: print to stderr instead of writing
|
|
183
|
+
if _is_debug; then
|
|
184
|
+
printf '{"ts":"%s","type":"%s","sid":"%s","org":"%s","user":"%s","data":%s}\n' \
|
|
185
|
+
"$ts" "$event_type" "$(_resolve_session_id)" "$(_resolve_org)" "$(_resolve_user)" "$compact_data" >&2
|
|
186
|
+
return 0
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Ensure buffer directory exists
|
|
190
|
+
mkdir -p "$BUFFER_DIR"
|
|
191
|
+
|
|
192
|
+
# Build event line (outside lock for minimal hold time)
|
|
193
|
+
local line
|
|
194
|
+
line=$(printf '{"ts":"%s","type":"%s","sid":"%s","org":"%s","user":"%s","data":%s}' \
|
|
195
|
+
"$ts" "$event_type" "$(_resolve_session_id)" "$(_resolve_org)" "$(_resolve_user)" "$compact_data")
|
|
196
|
+
|
|
197
|
+
# Append under lock
|
|
198
|
+
_lock || { echo "$line" >> "$BUFFER_FILE" 2>/dev/null; return 0; }
|
|
199
|
+
echo "$line" >> "$BUFFER_FILE"
|
|
200
|
+
|
|
201
|
+
# Buffer guard: if file exceeds max size, truncate to last N events
|
|
202
|
+
if [ -f "$BUFFER_FILE" ]; then
|
|
203
|
+
local size
|
|
204
|
+
size=$(wc -c < "$BUFFER_FILE" 2>/dev/null | tr -d ' ')
|
|
205
|
+
if [ "$size" -gt "$MAX_BUFFER_BYTES" ] 2>/dev/null; then
|
|
206
|
+
local tmp="$BUFFER_FILE.truncate.$$"
|
|
207
|
+
tail -n "$MAX_EVENTS_AFTER_TRUNCATE" "$BUFFER_FILE" > "$tmp" 2>/dev/null \
|
|
208
|
+
&& mv "$tmp" "$BUFFER_FILE" \
|
|
209
|
+
|| rm -f "$tmp"
|
|
210
|
+
fi
|
|
211
|
+
fi
|
|
212
|
+
_unlock
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# --- flush: send buffered events to API ---
|
|
216
|
+
cmd_flush() {
|
|
217
|
+
if [ ! -f "$BUFFER_FILE" ] || [ ! -s "$BUFFER_FILE" ]; then
|
|
218
|
+
return 0
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
# Read API config
|
|
222
|
+
local api_url=""
|
|
223
|
+
local api_key=""
|
|
224
|
+
local endpoint=""
|
|
225
|
+
local auth_header=""
|
|
226
|
+
if [ -f "$CONFIG" ]; then
|
|
227
|
+
api_url=$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null || true)
|
|
228
|
+
fi
|
|
229
|
+
if [ -f "$ENV_FILE" ]; then
|
|
230
|
+
api_key=$(grep '^EGREGORE_API_KEY=' "$ENV_FILE" 2>/dev/null | cut -d'=' -f2- || true)
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
if [ -n "$api_url" ] && [ -n "$api_key" ]; then
|
|
234
|
+
# Connected mode: authenticated ingest
|
|
235
|
+
endpoint="${api_url}/api/telemetry/ingest"
|
|
236
|
+
auth_header="Authorization: Bearer $api_key"
|
|
237
|
+
else
|
|
238
|
+
# Local/OSS mode: public relay (no auth needed)
|
|
239
|
+
endpoint="${TELEMETRY_RELAY_URL}/api/telemetry/relay"
|
|
240
|
+
auth_header=""
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
# Atomic rotate under lock: move buffer so new events can keep writing
|
|
244
|
+
_lock || return 0
|
|
245
|
+
local flush_file="$BUFFER_FILE.flush.$$"
|
|
246
|
+
mv "$BUFFER_FILE" "$flush_file" 2>/dev/null || { _unlock; return 0; }
|
|
247
|
+
_unlock
|
|
248
|
+
|
|
249
|
+
# POST to API
|
|
250
|
+
local response
|
|
251
|
+
local http_code
|
|
252
|
+
local curl_args=(-s -o /dev/null -w '%{http_code}' -X POST "$endpoint"
|
|
253
|
+
-H "Content-Type: application/x-ndjson"
|
|
254
|
+
--data-binary "@$flush_file"
|
|
255
|
+
--max-time 15)
|
|
256
|
+
if [ -n "$auth_header" ]; then
|
|
257
|
+
curl_args+=(-H "$auth_header")
|
|
258
|
+
fi
|
|
259
|
+
http_code=$(curl "${curl_args[@]}" 2>/dev/null || echo "000")
|
|
260
|
+
|
|
261
|
+
if [ "$http_code" -ge 200 ] 2>/dev/null && [ "$http_code" -lt 300 ] 2>/dev/null; then
|
|
262
|
+
# Success — remove flush file
|
|
263
|
+
rm -f "$flush_file"
|
|
264
|
+
else
|
|
265
|
+
# Failure — move events back so they aren't lost (under lock to prevent races)
|
|
266
|
+
_lock || { mv "$flush_file" "$BUFFER_FILE" 2>/dev/null || true; return 1; }
|
|
267
|
+
if [ -f "$BUFFER_FILE" ]; then
|
|
268
|
+
# New events were written while we were flushing; prepend old events
|
|
269
|
+
cat "$flush_file" "$BUFFER_FILE" > "$BUFFER_FILE.merge.$$" 2>/dev/null \
|
|
270
|
+
&& mv "$BUFFER_FILE.merge.$$" "$BUFFER_FILE" \
|
|
271
|
+
&& rm -f "$flush_file" \
|
|
272
|
+
|| rm -f "$BUFFER_FILE.merge.$$"
|
|
273
|
+
else
|
|
274
|
+
mv "$flush_file" "$BUFFER_FILE" 2>/dev/null || true
|
|
275
|
+
fi
|
|
276
|
+
_unlock
|
|
277
|
+
fi
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# --- status: show telemetry status ---
|
|
281
|
+
cmd_status() {
|
|
282
|
+
echo "Telemetry status:"
|
|
283
|
+
echo ""
|
|
284
|
+
|
|
285
|
+
# Consent
|
|
286
|
+
if _check_consent; then
|
|
287
|
+
echo " Enabled: yes"
|
|
288
|
+
else
|
|
289
|
+
echo " Enabled: no"
|
|
290
|
+
if [ "${EGREGORE_NO_TELEMETRY:-}" = "1" ]; then
|
|
291
|
+
echo " Reason: EGREGORE_NO_TELEMETRY=1"
|
|
292
|
+
elif [ "${DO_NOT_TRACK:-}" = "1" ]; then
|
|
293
|
+
echo " Reason: DO_NOT_TRACK=1"
|
|
294
|
+
else
|
|
295
|
+
echo " Reason: telemetry=false in .egregore-state.json"
|
|
296
|
+
fi
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
# Buffer
|
|
300
|
+
if [ -f "$BUFFER_FILE" ]; then
|
|
301
|
+
local count size
|
|
302
|
+
count=$(grep -c '^{' "$BUFFER_FILE" 2>/dev/null || echo 0)
|
|
303
|
+
size=$(wc -c < "$BUFFER_FILE" 2>/dev/null | tr -d ' ')
|
|
304
|
+
echo " Buffer: $count events ($size bytes)"
|
|
305
|
+
echo " Path: $BUFFER_FILE"
|
|
306
|
+
else
|
|
307
|
+
echo " Buffer: empty"
|
|
308
|
+
fi
|
|
309
|
+
|
|
310
|
+
echo ""
|
|
311
|
+
echo " Opt-out: set EGREGORE_NO_TELEMETRY=1 in .env, or run /telemetry off"
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
# --- enable: turn telemetry on ---
|
|
315
|
+
cmd_enable() {
|
|
316
|
+
if [ -f "$STATE_FILE" ]; then
|
|
317
|
+
jq '.telemetry = true' "$STATE_FILE" > "$STATE_FILE.tmp" \
|
|
318
|
+
&& mv "$STATE_FILE.tmp" "$STATE_FILE"
|
|
319
|
+
fi
|
|
320
|
+
echo "Telemetry enabled."
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
# --- disable: turn telemetry off ---
|
|
324
|
+
cmd_disable() {
|
|
325
|
+
if [ -f "$STATE_FILE" ]; then
|
|
326
|
+
jq '.telemetry = false' "$STATE_FILE" > "$STATE_FILE.tmp" \
|
|
327
|
+
&& mv "$STATE_FILE.tmp" "$STATE_FILE"
|
|
328
|
+
else
|
|
329
|
+
echo '{"telemetry": false}' > "$STATE_FILE"
|
|
330
|
+
fi
|
|
331
|
+
echo "Telemetry disabled."
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# --- clear: delete local buffer ---
|
|
335
|
+
cmd_clear() {
|
|
336
|
+
rm -f "$BUFFER_FILE"
|
|
337
|
+
echo "Telemetry buffer cleared."
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
# --- show: display last N events ---
|
|
341
|
+
cmd_show() {
|
|
342
|
+
local n="${1:-10}"
|
|
343
|
+
if [ ! -f "$BUFFER_FILE" ] || [ ! -s "$BUFFER_FILE" ]; then
|
|
344
|
+
echo "No buffered events."
|
|
345
|
+
return 0
|
|
346
|
+
fi
|
|
347
|
+
echo "Last $n events:"
|
|
348
|
+
echo ""
|
|
349
|
+
# Extract JSON objects (handles both single-line and legacy multi-line format)
|
|
350
|
+
grep '^{' "$BUFFER_FILE" | tail -n "$n" | jq -c . 2>/dev/null \
|
|
351
|
+
|| grep '^{' "$BUFFER_FILE" | tail -n "$n"
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
# --- Main dispatch ---
|
|
355
|
+
case "${1:-status}" in
|
|
356
|
+
emit)
|
|
357
|
+
shift
|
|
358
|
+
cmd_emit "$@"
|
|
359
|
+
;;
|
|
360
|
+
flush)
|
|
361
|
+
cmd_flush
|
|
362
|
+
;;
|
|
363
|
+
status)
|
|
364
|
+
cmd_status
|
|
365
|
+
;;
|
|
366
|
+
enable)
|
|
367
|
+
cmd_enable
|
|
368
|
+
;;
|
|
369
|
+
disable)
|
|
370
|
+
cmd_disable
|
|
371
|
+
;;
|
|
372
|
+
clear)
|
|
373
|
+
cmd_clear
|
|
374
|
+
;;
|
|
375
|
+
show)
|
|
376
|
+
shift
|
|
377
|
+
cmd_show "${1:-10}"
|
|
378
|
+
;;
|
|
379
|
+
help|*)
|
|
380
|
+
echo "Usage: telemetry.sh <command>"
|
|
381
|
+
echo ""
|
|
382
|
+
echo "Commands:"
|
|
383
|
+
echo " emit <type> <json> Append event to local buffer (no network)"
|
|
384
|
+
echo " flush Send buffered events to API"
|
|
385
|
+
echo " status Show telemetry status"
|
|
386
|
+
echo " enable Enable telemetry"
|
|
387
|
+
echo " disable Disable telemetry"
|
|
388
|
+
echo " clear Clear local buffer"
|
|
389
|
+
echo " show [n] Show last N events (default 10)"
|
|
390
|
+
;;
|
|
391
|
+
esac
|