create-egregore 0.19.5 → 0.19.7
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 +411 -25
- package/bin/cli.js +34 -20
- package/lib/local.js +21 -5
- package/lib/pi-runtime.js +186 -0
- package/lib/setup.js +56 -11
- package/lib/ui.js +1 -1
- package/package.json +7 -5
- package/runtime/codex/.codex/spec-manifest.json +1 -1
- package/runtime/codex/AGENTS.md +7 -2
- package/runtime/codex/bin/codex-render-spec.mjs +6 -1
- package/runtime/codex/bin/codex-session-start.sh +5 -59
- package/runtime/codex/bin/codex-skill-render.mjs +7 -5
- package/runtime/codex/bin/lib/context.sh +8 -1
- package/runtime/codex/bin/lib/greeting.sh +12 -108
- package/runtime/codex/bin/lib/metrics.sh +374 -0
- package/runtime/codex/bin/publish-artifact.sh +14 -0
- package/runtime/codex/bin/settings.sh +274 -0
- package/runtime/codex/manifest.json +2 -0
- package/runtime/pi/.codex/config.toml +15 -0
- package/runtime/pi/.codex/hooks/branch-guard.js +242 -0
- package/runtime/pi/.codex/hooks/search-hint.js +65 -0
- package/runtime/pi/.codex/hooks.json +30 -0
- package/runtime/pi/.codex/skills/activity/SKILL.md +70 -0
- package/runtime/pi/.codex/skills/add/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/announce/SKILL.md +54 -0
- package/runtime/pi/.codex/skills/answer/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/archive/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/ask/SKILL.md +59 -0
- package/runtime/pi/.codex/skills/branch/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/checkup/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/commit/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/contribute/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/dark-mode/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/dashboard/SKILL.md +59 -0
- package/runtime/pi/.codex/skills/deep-reflect/SKILL.md +74 -0
- package/runtime/pi/.codex/skills/delete-user/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/egregore-voice/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/emissary/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/env/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/handoff/SKILL.md +67 -0
- package/runtime/pi/.codex/skills/harvest/SKILL.md +107 -0
- package/runtime/pi/.codex/skills/ingest/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/ingest-google/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/ingest-user-interview/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/invite/SKILL.md +40 -0
- package/runtime/pi/.codex/skills/issue/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/me/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/meeting/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/minion/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/note/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/onboarding/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/pr/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/project/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/pull/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/push/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/quest/SKILL.md +77 -0
- package/runtime/pi/.codex/skills/quest-suggest/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/reflect/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/review-pr/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/save/SKILL.md +62 -0
- package/runtime/pi/.codex/skills/scroll/SKILL.md +168 -0
- package/runtime/pi/.codex/skills/scroll/agents/openai.yaml +4 -0
- package/runtime/pi/.codex/skills/scroll/assets/shell.html +542 -0
- package/runtime/pi/.codex/skills/search/SKILL.md +56 -0
- package/runtime/pi/.codex/skills/session-view/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/setup/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/summon/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/sync-repos/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/telegram-connect/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/telemetry/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/test/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/the-spiral/SKILL.md +84 -0
- package/runtime/pi/.codex/skills/thread/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/todo/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/tui-design/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/tutorial/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/update/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/view/SKILL.md +156 -0
- package/runtime/pi/.codex/skills/view/agents/openai.yaml +4 -0
- package/runtime/pi/.codex/skills/view/assets/compose-scaffold.html +221 -0
- package/runtime/pi/.codex/skills/visual-explain/SKILL.md +41 -0
- package/runtime/pi/.codex/skills/wrap/SKILL.md +55 -0
- package/runtime/pi/.codex/spec-manifest.json +114 -0
- package/runtime/pi/.pi/APPEND_SYSTEM.md +255 -0
- package/runtime/pi/.pi/extensions/egregore.ts +324 -0
- package/runtime/pi/.pi/settings.json +6 -0
- package/runtime/pi/.pi/spec-manifest.json +12 -0
- package/runtime/pi/AGENTS.md +350 -0
- package/runtime/pi/bin/activity-data.sh +242 -0
- package/runtime/pi/bin/agent.sh +677 -0
- package/runtime/pi/bin/codex-install-skills.sh +78 -0
- package/runtime/pi/bin/codex-render-spec.mjs +394 -0
- package/runtime/pi/bin/codex-session-start.sh +188 -0
- package/runtime/pi/bin/codex-skill-render.mjs +323 -0
- package/runtime/pi/bin/codex-sync-skills.sh +227 -0
- package/runtime/pi/bin/dashboard-data.sh +178 -0
- package/runtime/pi/bin/graph-batch.sh +60 -0
- package/runtime/pi/bin/graph-op.sh +431 -0
- package/runtime/pi/bin/graph-wal.sh +228 -0
- package/runtime/pi/bin/graph.sh +177 -0
- package/runtime/pi/bin/handoff-pr-backfill.sh +86 -0
- package/runtime/pi/bin/handoff-run.sh +581 -0
- package/runtime/pi/bin/handoff-save-egregore.sh +136 -0
- package/runtime/pi/bin/index-handoff.sh +312 -0
- package/runtime/pi/bin/lib/artifact-id.sh +46 -0
- package/runtime/pi/bin/lib/config.sh +71 -0
- package/runtime/pi/bin/lib/context.sh +542 -0
- package/runtime/pi/bin/lib/dashboard-artifact.sh +142 -0
- package/runtime/pi/bin/lib/ensure-develop.sh +183 -0
- package/runtime/pi/bin/lib/git-sync.sh +288 -0
- package/runtime/pi/bin/lib/greeting.sh +340 -0
- package/runtime/pi/bin/lib/hash.sh +13 -0
- package/runtime/pi/bin/lib/identity.sh +174 -0
- package/runtime/pi/bin/lib/metrics.sh +374 -0
- package/runtime/pi/bin/lib/permissions.sh +106 -0
- package/runtime/pi/bin/lib/time.sh +56 -0
- package/runtime/pi/bin/lib/worktree-links.sh +54 -0
- package/runtime/pi/bin/notify.sh +207 -0
- package/runtime/pi/bin/pi-branch-policy.mjs +15 -0
- package/runtime/pi/bin/pi-product-workflows.mjs +324 -0
- package/runtime/pi/bin/pi-render-spec.mjs +114 -0
- package/runtime/pi/bin/pi-session-start.sh +24 -0
- package/runtime/pi/bin/publish-artifact.sh +201 -0
- package/runtime/pi/bin/publish-references.sh +144 -0
- package/runtime/pi/bin/repo-state.sh +99 -0
- package/runtime/pi/bin/telemetry.sh +391 -0
- package/runtime/pi/bin/worktree-create.sh +118 -0
- package/runtime/pi/bin/worktree-remove.sh +50 -0
- package/runtime/pi/bin/worktree.sh +198 -0
- package/runtime/pi/manifest.json +176 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# metrics.sh — Momentum metrics gathering and rendering for startup greetings.
|
|
3
|
+
#
|
|
4
|
+
# Function-only library. Safe to source from bash or zsh.
|
|
5
|
+
|
|
6
|
+
_metrics_zero_json() {
|
|
7
|
+
printf '%s\n' '{"source":"local","sessions":{"buckets":[0,0,0,0,0],"week":0,"prev":0,"all":0},"commits":{"buckets":[0,0,0,0,0],"week":0,"prev":0,"authors":0,"all":0},"handoffs":{"buckets":[0,0,0,0,0],"week":0,"prev":0,"avg_resolution_days":null,"all":0},"knowledge":{"buckets":[0,0,0,0,0],"week":0,"prev":0,"all":0},"members":{"active":0,"total":0}}'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
_num() {
|
|
11
|
+
awk '{n=$1+0; if (n < 0) n=0; printf "%d\n", n}' 2>/dev/null <<EOF
|
|
12
|
+
$1
|
|
13
|
+
EOF
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
_num_float() {
|
|
17
|
+
awk '{n=$1+0; if (n < 0) n=0; printf "%.1f\n", n}' 2>/dev/null <<EOF
|
|
18
|
+
$1
|
|
19
|
+
EOF
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_normalize_buckets() {
|
|
23
|
+
set -- $(printf '%s' "$1")
|
|
24
|
+
printf '%d %d %d %d %d\n' "$(_num "${1:-0}")" "$(_num "${2:-0}")" "$(_num "${3:-0}")" "$(_num "${4:-0}")" "$(_num "${5:-0}")"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_buckets_json() {
|
|
28
|
+
set -- $(printf '%s' "$(_normalize_buckets "$1")")
|
|
29
|
+
jq -n --argjson a "$(_num "${1:-0}")" --argjson b "$(_num "${2:-0}")" \
|
|
30
|
+
--argjson c "$(_num "${3:-0}")" --argjson d "$(_num "${4:-0}")" \
|
|
31
|
+
--argjson e "$(_num "${5:-0}")" '[$a,$b,$c,$d,$e]' 2>/dev/null || echo '[0,0,0,0,0]'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_bucket_week() {
|
|
35
|
+
set -- $(printf '%s' "$(_normalize_buckets "$1")")
|
|
36
|
+
_num "${5:-0}"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_bucket_prev() {
|
|
40
|
+
set -- $(printf '%s' "$(_normalize_buckets "$1")")
|
|
41
|
+
_num "${4:-0}"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_spark() {
|
|
45
|
+
local values max out v idx glyph
|
|
46
|
+
values="${1:-0 0 0 0 0}"
|
|
47
|
+
max=0
|
|
48
|
+
set -- $(printf '%s' "$values")
|
|
49
|
+
[ "$#" -eq 0 ] && set -- 0 0 0 0 0
|
|
50
|
+
for v in "$@"; do
|
|
51
|
+
v=$(_num "$v")
|
|
52
|
+
[ "$v" -gt "$max" ] 2>/dev/null && max="$v"
|
|
53
|
+
done
|
|
54
|
+
out=""
|
|
55
|
+
for v in "$@"; do
|
|
56
|
+
v=$(_num "$v")
|
|
57
|
+
if [ "$max" -le 0 ] 2>/dev/null; then
|
|
58
|
+
idx=0
|
|
59
|
+
else
|
|
60
|
+
idx=$(( (v * 7) / max ))
|
|
61
|
+
fi
|
|
62
|
+
# Select glyphs explicitly: substring slicing can split UTF-8 characters
|
|
63
|
+
# under Bash 3.2 or a C locale.
|
|
64
|
+
case "$idx" in
|
|
65
|
+
0) glyph="▁" ;;
|
|
66
|
+
1) glyph="▂" ;;
|
|
67
|
+
2) glyph="▃" ;;
|
|
68
|
+
3) glyph="▄" ;;
|
|
69
|
+
4) glyph="▅" ;;
|
|
70
|
+
5) glyph="▆" ;;
|
|
71
|
+
6) glyph="▇" ;;
|
|
72
|
+
*) glyph="█" ;;
|
|
73
|
+
esac
|
|
74
|
+
out="${out}${glyph}"
|
|
75
|
+
done
|
|
76
|
+
printf '%s\n' "$out"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_pct_delta() {
|
|
80
|
+
local cur prev pct
|
|
81
|
+
cur=$(_num "${1:-0}")
|
|
82
|
+
prev=$(_num "${2:-0}")
|
|
83
|
+
[ "$prev" -le 0 ] 2>/dev/null && return 0
|
|
84
|
+
pct=$(( ((cur - prev) * 100) / prev ))
|
|
85
|
+
if [ "$pct" -gt 0 ] 2>/dev/null; then
|
|
86
|
+
printf '↑ %d%%\n' "$pct"
|
|
87
|
+
elif [ "$pct" -lt 0 ] 2>/dev/null; then
|
|
88
|
+
printf '↓ %d%%\n' "$(( -pct ))"
|
|
89
|
+
else
|
|
90
|
+
printf '→ 0%%\n'
|
|
91
|
+
fi
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_commafy() {
|
|
95
|
+
awk '
|
|
96
|
+
function comma(s, out, sign) {
|
|
97
|
+
s = int(s + 0)
|
|
98
|
+
if (s < 0) { sign = "-"; s = -s } else { sign = "" }
|
|
99
|
+
s = sprintf("%d", s)
|
|
100
|
+
out = ""
|
|
101
|
+
while (length(s) > 3) {
|
|
102
|
+
out = "," substr(s, length(s) - 2, 3) out
|
|
103
|
+
s = substr(s, 1, length(s) - 3)
|
|
104
|
+
}
|
|
105
|
+
return sign s out
|
|
106
|
+
}
|
|
107
|
+
{ print comma($1) }
|
|
108
|
+
' 2>/dev/null <<EOF
|
|
109
|
+
${1:-0}
|
|
110
|
+
EOF
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_git_added_buckets() {
|
|
114
|
+
local repo now paths
|
|
115
|
+
repo="$1"
|
|
116
|
+
shift
|
|
117
|
+
now=$(date +%s)
|
|
118
|
+
if [ -d "$repo" ] || [ -f "$repo" ]; then
|
|
119
|
+
git -C "$repo" log --diff-filter=A --since="35 days ago" --format='%ct' -- "$@" 2>/dev/null | \
|
|
120
|
+
awk -v now="$now" '
|
|
121
|
+
BEGIN { for (i=0;i<5;i++) b[i]=0 }
|
|
122
|
+
/^[0-9]+$/ {
|
|
123
|
+
days = int((now - $1) / 86400)
|
|
124
|
+
idx = 4 - int(days / 7)
|
|
125
|
+
if (idx >= 0 && idx < 5) b[idx]++
|
|
126
|
+
}
|
|
127
|
+
END { printf "%d %d %d %d %d\n", b[0], b[1], b[2], b[3], b[4] }
|
|
128
|
+
' 2>/dev/null || echo "0 0 0 0 0"
|
|
129
|
+
else
|
|
130
|
+
echo "0 0 0 0 0"
|
|
131
|
+
fi
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
_find_count_md() {
|
|
135
|
+
find -L "$@" -name '*.md' ! -name 'index*' 2>/dev/null | wc -l | awk '{print $1+0}' 2>/dev/null || echo 0
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_metrics_gather() {
|
|
139
|
+
local _root _mem _scratch _API_URL _API_KEY _connected
|
|
140
|
+
_root="${SCRIPT_DIR:-$PWD}"
|
|
141
|
+
_mem="$_root/memory"
|
|
142
|
+
_scratch=$(mktemp -d 2>/dev/null || mktemp -d -t egregore-metrics)
|
|
143
|
+
_API_URL=$(jq -r '.api_url // empty' "$_root/egregore.json" 2>/dev/null || echo "")
|
|
144
|
+
_API_KEY=$(grep '^EGREGORE_API_KEY=' "${ENV_FILE:-$_root/.env}" 2>/dev/null | cut -d'=' -f2- || true)
|
|
145
|
+
_connected=false
|
|
146
|
+
[ -n "$_API_URL" ] && [ -n "$_API_KEY" ] && _connected=true
|
|
147
|
+
|
|
148
|
+
(
|
|
149
|
+
if [ "$_connected" = true ]; then
|
|
150
|
+
raw=$(bash "$_root/bin/graph.sh" query "MATCH (s:Session) WHERE date(s.date) >= date() - duration('P35D')
|
|
151
|
+
OPTIONAL MATCH (s)-[:BY]->(p:Person)
|
|
152
|
+
WITH duration.inDays(date(s.date), date()).days / 7 AS weeksAgo,
|
|
153
|
+
count(s) AS sessions, count(DISTINCT p) AS people
|
|
154
|
+
RETURN weeksAgo, sessions, people ORDER BY weeksAgo" 2>/dev/null || echo "")
|
|
155
|
+
printf '%s' "$raw" | jq -r '
|
|
156
|
+
(.values // []) as $v
|
|
157
|
+
| if ($v|length) == 0 then empty else
|
|
158
|
+
reduce $v[] as $r ({b:[0,0,0,0,0], a:0, ok:0};
|
|
159
|
+
($r[0] // 99 | tonumber? // 99) as $w
|
|
160
|
+
| if $w >= 0 and $w <= 4 then
|
|
161
|
+
.b[4 - $w] = (($r[1] // 0 | tonumber? // 0) | floor)
|
|
162
|
+
| .ok = 1
|
|
163
|
+
| if $w == 0 then .a = (($r[2] // 0 | tonumber? // 0) | floor) else . end
|
|
164
|
+
else . end)
|
|
165
|
+
| "\(.b|join(" "))|\(.a)|\(.ok)"
|
|
166
|
+
end
|
|
167
|
+
' 2>/dev/null || echo "0 0 0 0 0|0|0"
|
|
168
|
+
else
|
|
169
|
+
echo "0 0 0 0 0|0|0"
|
|
170
|
+
fi
|
|
171
|
+
) > "$_scratch/graph_sessions" 2>/dev/null &
|
|
172
|
+
|
|
173
|
+
(
|
|
174
|
+
if [ "$_connected" = true ]; then
|
|
175
|
+
raw=$(bash "$_root/bin/graph.sh" query "MATCH (s:Session) WITH count(s) AS totalSessions MATCH (p:Person)-[:MEMBER_OF]->(:Org) RETURN totalSessions, count(DISTINCT p) AS members" 2>/dev/null || echo "")
|
|
176
|
+
total=$(printf '%s' "$raw" | jq -r '.values[0][0] // 0' 2>/dev/null || echo 0)
|
|
177
|
+
members=$(printf '%s' "$raw" | jq -r '.values[0][1] // 0' 2>/dev/null || echo 0)
|
|
178
|
+
if [ "$(_num "$members")" -eq 0 ] 2>/dev/null; then
|
|
179
|
+
raw2=$(bash "$_root/bin/graph.sh" query "MATCH (p:Person) RETURN count(p)" 2>/dev/null || echo "")
|
|
180
|
+
members=$(printf '%s' "$raw2" | jq -r '.values[0][0] // 0' 2>/dev/null || echo 0)
|
|
181
|
+
fi
|
|
182
|
+
printf '%s|%s\n' "$(_num "$total")" "$(_num "$members")"
|
|
183
|
+
else
|
|
184
|
+
echo "0|0"
|
|
185
|
+
fi
|
|
186
|
+
) > "$_scratch/graph_totals" 2>/dev/null &
|
|
187
|
+
|
|
188
|
+
(
|
|
189
|
+
if [ "$_connected" = true ]; then
|
|
190
|
+
raw=$(bash "$_root/bin/graph.sh" query "MATCH (s:Session)-[:HANDED_TO]->(:Person)
|
|
191
|
+
WHERE s.handoffStatus = 'done' AND date(s.date) >= date() - duration('P30D')
|
|
192
|
+
AND s.handoffReadDate IS NOT NULL
|
|
193
|
+
WITH duration.inDays(date(s.date), date(s.handoffReadDate)).days AS d
|
|
194
|
+
RETURN avg(d) AS avgDays, count(*) AS n" 2>/dev/null || echo "")
|
|
195
|
+
avg=$(printf '%s' "$raw" | jq -r '.values[0][0] // empty' 2>/dev/null || echo "")
|
|
196
|
+
n=$(printf '%s' "$raw" | jq -r '.values[0][1] // 0' 2>/dev/null || echo 0)
|
|
197
|
+
if [ "$(_num "$n")" -gt 0 ] 2>/dev/null && [ -n "$avg" ]; then _num_float "$avg"; else echo ""; fi
|
|
198
|
+
else
|
|
199
|
+
echo ""
|
|
200
|
+
fi
|
|
201
|
+
) > "$_scratch/handoff_resolution" 2>/dev/null &
|
|
202
|
+
|
|
203
|
+
(
|
|
204
|
+
buckets=$(_git_added_buckets "$_mem" sessions wraps)
|
|
205
|
+
all=$(_find_count_md "$_root/memory/sessions" "$_root/memory/wraps")
|
|
206
|
+
printf '%s|%s\n' "$buckets" "$all"
|
|
207
|
+
) > "$_scratch/local_sessions" 2>/dev/null &
|
|
208
|
+
|
|
209
|
+
(
|
|
210
|
+
now=$(date +%s)
|
|
211
|
+
if [ -d "$_mem" ] || [ -f "$_mem" ]; then
|
|
212
|
+
git -C "$_mem" log --since="35 days ago" --format='%ct|%ae' 2>/dev/null | \
|
|
213
|
+
awk -F'|' -v now="$now" '
|
|
214
|
+
BEGIN { for (i=0;i<5;i++) b[i]=0 }
|
|
215
|
+
{
|
|
216
|
+
days = int((now - $1) / 86400)
|
|
217
|
+
idx = 4 - int(days / 7)
|
|
218
|
+
if (idx >= 0 && idx < 5) {
|
|
219
|
+
b[idx]++
|
|
220
|
+
if (idx == 4 && $2 != "") a[$2]=1
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
END {
|
|
224
|
+
n=0; for (k in a) n++
|
|
225
|
+
printf "%d %d %d %d %d|%d\n", b[0], b[1], b[2], b[3], b[4], n
|
|
226
|
+
}
|
|
227
|
+
' 2>/dev/null || echo "0 0 0 0 0|0"
|
|
228
|
+
else
|
|
229
|
+
echo "0 0 0 0 0|0"
|
|
230
|
+
fi
|
|
231
|
+
) > "$_scratch/commits" 2>/dev/null &
|
|
232
|
+
|
|
233
|
+
(
|
|
234
|
+
if [ -d "$_mem" ] || [ -f "$_mem" ]; then git -C "$_mem" rev-list --count HEAD 2>/dev/null || echo 0; else echo 0; fi
|
|
235
|
+
) > "$_scratch/commits_all" 2>/dev/null &
|
|
236
|
+
|
|
237
|
+
(
|
|
238
|
+
buckets=$(_git_added_buckets "$_mem" handoffs)
|
|
239
|
+
all=$(_find_count_md "$_root/memory/handoffs")
|
|
240
|
+
printf '%s|%s\n' "$buckets" "$all"
|
|
241
|
+
) > "$_scratch/handoffs" 2>/dev/null &
|
|
242
|
+
|
|
243
|
+
(
|
|
244
|
+
buckets=$(_git_added_buckets "$_mem" knowledge/decisions knowledge/findings knowledge/patterns)
|
|
245
|
+
all=$(_find_count_md "$_root/memory/knowledge/decisions" "$_root/memory/knowledge/findings" "$_root/memory/knowledge/patterns")
|
|
246
|
+
printf '%s|%s\n' "$buckets" "$all"
|
|
247
|
+
) > "$_scratch/knowledge" 2>/dev/null &
|
|
248
|
+
|
|
249
|
+
(
|
|
250
|
+
_find_count_md "$_root/memory/people"
|
|
251
|
+
) > "$_scratch/members_total_local" 2>/dev/null &
|
|
252
|
+
|
|
253
|
+
wait
|
|
254
|
+
|
|
255
|
+
gs=$(cat "$_scratch/graph_sessions" 2>/dev/null || echo "0 0 0 0 0|0|0")
|
|
256
|
+
gt=$(cat "$_scratch/graph_totals" 2>/dev/null || echo "0|0")
|
|
257
|
+
lsess=$(cat "$_scratch/local_sessions" 2>/dev/null || echo "0 0 0 0 0|0")
|
|
258
|
+
commits=$(cat "$_scratch/commits" 2>/dev/null || echo "0 0 0 0 0|0")
|
|
259
|
+
handoffs=$(cat "$_scratch/handoffs" 2>/dev/null || echo "0 0 0 0 0|0")
|
|
260
|
+
knowledge=$(cat "$_scratch/knowledge" 2>/dev/null || echo "0 0 0 0 0|0")
|
|
261
|
+
|
|
262
|
+
graph_valid=$(_num "$(printf '%s' "$gs" | awk -F'|' '{print $3}')")
|
|
263
|
+
if [ "$graph_valid" -gt 0 ] 2>/dev/null; then
|
|
264
|
+
source_name="graph"
|
|
265
|
+
session_buckets=$(_normalize_buckets "$(printf '%s' "$gs" | awk -F'|' '{print $1}')")
|
|
266
|
+
session_all=$(_num "$(printf '%s' "$gt" | awk -F'|' '{print $1}')")
|
|
267
|
+
members_active=$(_num "$(printf '%s' "$gs" | awk -F'|' '{print $2}')")
|
|
268
|
+
members_total=$(_num "$(printf '%s' "$gt" | awk -F'|' '{print $2}')")
|
|
269
|
+
if [ "$session_all" -le 0 ] 2>/dev/null; then
|
|
270
|
+
session_all=$(_num "$(printf '%s' "$lsess" | awk -F'|' '{print $2}')")
|
|
271
|
+
fi
|
|
272
|
+
if [ "$members_total" -le 0 ] 2>/dev/null; then
|
|
273
|
+
members_total=$(_num "$(cat "$_scratch/members_total_local" 2>/dev/null || echo 0)")
|
|
274
|
+
fi
|
|
275
|
+
else
|
|
276
|
+
source_name="local"
|
|
277
|
+
session_buckets=$(_normalize_buckets "$(printf '%s' "$lsess" | awk -F'|' '{print $1}')")
|
|
278
|
+
session_all=$(_num "$(printf '%s' "$lsess" | awk -F'|' '{print $2}')")
|
|
279
|
+
members_active=$(_num "$(printf '%s' "$commits" | awk -F'|' '{print $2}')")
|
|
280
|
+
members_total=$(_num "$(cat "$_scratch/members_total_local" 2>/dev/null || echo 0)")
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
commit_buckets=$(_normalize_buckets "$(printf '%s' "$commits" | awk -F'|' '{print $1}')")
|
|
284
|
+
handoff_buckets=$(_normalize_buckets "$(printf '%s' "$handoffs" | awk -F'|' '{print $1}')")
|
|
285
|
+
knowledge_buckets=$(_normalize_buckets "$(printf '%s' "$knowledge" | awk -F'|' '{print $1}')")
|
|
286
|
+
avg_res=$(cat "$_scratch/handoff_resolution" 2>/dev/null || echo "")
|
|
287
|
+
|
|
288
|
+
session_buckets_json=$(_buckets_json "$session_buckets")
|
|
289
|
+
commit_buckets_json=$(_buckets_json "$commit_buckets")
|
|
290
|
+
handoff_buckets_json=$(_buckets_json "$handoff_buckets")
|
|
291
|
+
knowledge_buckets_json=$(_buckets_json "$knowledge_buckets")
|
|
292
|
+
|
|
293
|
+
if [ -n "$avg_res" ]; then
|
|
294
|
+
jq -n --arg source "$source_name" \
|
|
295
|
+
--argjson sb "$session_buckets_json" --argjson sw "$(_bucket_week "$session_buckets")" --argjson sp "$(_bucket_prev "$session_buckets")" --argjson sall "$session_all" \
|
|
296
|
+
--argjson cb "$commit_buckets_json" --argjson cw "$(_bucket_week "$commit_buckets")" --argjson cp "$(_bucket_prev "$commit_buckets")" --argjson ca "$(_num "$(printf '%s' "$commits" | awk -F'|' '{print $2}')")" --argjson call "$(_num "$(cat "$_scratch/commits_all" 2>/dev/null || echo 0)")" \
|
|
297
|
+
--argjson hb "$handoff_buckets_json" --argjson hw "$(_bucket_week "$handoff_buckets")" --argjson hp "$(_bucket_prev "$handoff_buckets")" --argjson havg "$avg_res" --argjson hall "$(_num "$(printf '%s' "$handoffs" | awk -F'|' '{print $2}')")" \
|
|
298
|
+
--argjson kb "$knowledge_buckets_json" --argjson kw "$(_bucket_week "$knowledge_buckets")" --argjson kp "$(_bucket_prev "$knowledge_buckets")" --argjson kall "$(_num "$(printf '%s' "$knowledge" | awk -F'|' '{print $2}')")" \
|
|
299
|
+
--argjson ma "$members_active" --argjson mt "$members_total" \
|
|
300
|
+
'{source:$source,sessions:{buckets:$sb,week:$sw,prev:$sp,all:$sall},commits:{buckets:$cb,week:$cw,prev:$cp,authors:$ca,all:$call},handoffs:{buckets:$hb,week:$hw,prev:$hp,avg_resolution_days:$havg,all:$hall},knowledge:{buckets:$kb,week:$kw,prev:$kp,all:$kall},members:{active:$ma,total:$mt}}' 2>/dev/null || _metrics_zero_json
|
|
301
|
+
else
|
|
302
|
+
jq -n --arg source "$source_name" \
|
|
303
|
+
--argjson sb "$session_buckets_json" --argjson sw "$(_bucket_week "$session_buckets")" --argjson sp "$(_bucket_prev "$session_buckets")" --argjson sall "$session_all" \
|
|
304
|
+
--argjson cb "$commit_buckets_json" --argjson cw "$(_bucket_week "$commit_buckets")" --argjson cp "$(_bucket_prev "$commit_buckets")" --argjson ca "$(_num "$(printf '%s' "$commits" | awk -F'|' '{print $2}')")" --argjson call "$(_num "$(cat "$_scratch/commits_all" 2>/dev/null || echo 0)")" \
|
|
305
|
+
--argjson hb "$handoff_buckets_json" --argjson hw "$(_bucket_week "$handoff_buckets")" --argjson hp "$(_bucket_prev "$handoff_buckets")" --argjson hall "$(_num "$(printf '%s' "$handoffs" | awk -F'|' '{print $2}')")" \
|
|
306
|
+
--argjson kb "$knowledge_buckets_json" --argjson kw "$(_bucket_week "$knowledge_buckets")" --argjson kp "$(_bucket_prev "$knowledge_buckets")" --argjson kall "$(_num "$(printf '%s' "$knowledge" | awk -F'|' '{print $2}')")" \
|
|
307
|
+
--argjson ma "$members_active" --argjson mt "$members_total" \
|
|
308
|
+
'{source:$source,sessions:{buckets:$sb,week:$sw,prev:$sp,all:$sall},commits:{buckets:$cb,week:$cw,prev:$cp,authors:$ca,all:$call},handoffs:{buckets:$hb,week:$hw,prev:$hp,avg_resolution_days:null,all:$hall},knowledge:{buckets:$kb,week:$kw,prev:$kp,all:$kall},members:{active:$ma,total:$mt}}' 2>/dev/null || _metrics_zero_json
|
|
309
|
+
fi
|
|
310
|
+
rm -rf "$_scratch" 2>/dev/null || true
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
_metrics_read() {
|
|
314
|
+
local file filter fallback
|
|
315
|
+
file="${CTX_DIR:-}/metrics"
|
|
316
|
+
filter="$1"
|
|
317
|
+
fallback="$2"
|
|
318
|
+
if [ -f "$file" ]; then
|
|
319
|
+
jq -r "$filter" "$file" 2>/dev/null || echo "$fallback"
|
|
320
|
+
else
|
|
321
|
+
echo "$fallback"
|
|
322
|
+
fi
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
_render_momentum_board() {
|
|
326
|
+
local separator line_width header_left header_right pad
|
|
327
|
+
local sb sw sp sall cb cw ca call hb hw havg hall kb kw kall active total
|
|
328
|
+
local sdelta sann cann hann kann know_week
|
|
329
|
+
separator="${SEPARATOR:- ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄}"
|
|
330
|
+
line_width="${LINE_WIDTH:-67}"
|
|
331
|
+
|
|
332
|
+
sb=$(_metrics_read '(.sessions.buckets // [0,0,0,0,0]) | .[0:5] | map(. // 0) | join(" ")' "0 0 0 0 0")
|
|
333
|
+
sw=$(_num "$(_metrics_read '.sessions.week // 0' "0")")
|
|
334
|
+
sp=$(_num "$(_metrics_read '.sessions.prev // 0' "0")")
|
|
335
|
+
sall=$(_num "$(_metrics_read '.sessions.all // 0' "0")")
|
|
336
|
+
cb=$(_metrics_read '(.commits.buckets // [0,0,0,0,0]) | .[0:5] | map(. // 0) | join(" ")' "0 0 0 0 0")
|
|
337
|
+
cw=$(_num "$(_metrics_read '.commits.week // 0' "0")")
|
|
338
|
+
ca=$(_num "$(_metrics_read '.commits.authors // 0' "0")")
|
|
339
|
+
call=$(_num "$(_metrics_read '.commits.all // 0' "0")")
|
|
340
|
+
hb=$(_metrics_read '(.handoffs.buckets // [0,0,0,0,0]) | .[0:5] | map(. // 0) | join(" ")' "0 0 0 0 0")
|
|
341
|
+
hw=$(_num "$(_metrics_read '.handoffs.week // 0' "0")")
|
|
342
|
+
havg=$(_metrics_read '.handoffs.avg_resolution_days // empty' "")
|
|
343
|
+
hall=$(_num "$(_metrics_read '.handoffs.all // 0' "0")")
|
|
344
|
+
kb=$(_metrics_read '(.knowledge.buckets // [0,0,0,0,0]) | .[0:5] | map(. // 0) | join(" ")' "0 0 0 0 0")
|
|
345
|
+
kw=$(_num "$(_metrics_read '.knowledge.week // 0' "0")")
|
|
346
|
+
kall=$(_num "$(_metrics_read '.knowledge.all // 0' "0")")
|
|
347
|
+
active=$(_num "$(_metrics_read '.members.active // 0' "0")")
|
|
348
|
+
total=$(_num "$(_metrics_read '.members.total // 0' "0")")
|
|
349
|
+
|
|
350
|
+
header_left=" ◦ momentum · this week"
|
|
351
|
+
header_right="${active} of ${total} active"
|
|
352
|
+
pad=$((line_width - ${#header_left} - ${#header_right}))
|
|
353
|
+
[ "$pad" -lt 1 ] && pad=1
|
|
354
|
+
printf "%s%*s%s\n" "$header_left" "$pad" "" "$header_right"
|
|
355
|
+
|
|
356
|
+
sdelta=$(_pct_delta "$sw" "$sp")
|
|
357
|
+
sann=""
|
|
358
|
+
[ -n "$sdelta" ] && sann="${sdelta} vs last week"
|
|
359
|
+
cann=""
|
|
360
|
+
if [ "$ca" -gt 0 ] 2>/dev/null; then
|
|
361
|
+
if [ "$ca" -eq 1 ] 2>/dev/null; then cann="1 author"; else cann="${ca} authors"; fi
|
|
362
|
+
fi
|
|
363
|
+
hann=""
|
|
364
|
+
if [ -n "$havg" ]; then hann="$(_num_float "$havg")d avg resolution"; fi
|
|
365
|
+
kann="$(_commafy "$kall") artifacts total"
|
|
366
|
+
know_week="+${kw}"
|
|
367
|
+
|
|
368
|
+
printf " %-11s%5s 5-week %s %s\n\n" "sessions" "$sw" "$(_spark "$sb")" "$sann"
|
|
369
|
+
printf " %-11s%5s 5-week %s %s\n\n" "commits" "$cw" "$(_spark "$cb")" "$cann"
|
|
370
|
+
printf " %-11s%5s 5-week %s %s\n\n" "handoffs" "$hw" "$(_spark "$hb")" "$hann"
|
|
371
|
+
printf " %-11s%5s 5-week %s %s\n" "knowledge" "$know_week" "$(_spark "$kb")" "$kann"
|
|
372
|
+
echo "$separator"
|
|
373
|
+
printf " %s sessions · %s commits · %s handoffs all-time\n" "$(_commafy "$sall")" "$(_commafy "$call")" "$(_commafy "$hall")"
|
|
374
|
+
}
|
|
@@ -95,6 +95,20 @@ fi
|
|
|
95
95
|
# Public relay for OSS share endpoint
|
|
96
96
|
RELAY_URL="https://egregore-production-55f2.up.railway.app"
|
|
97
97
|
|
|
98
|
+
# --- Artifact hosting off-switch (compliance / privacy) ---
|
|
99
|
+
# An instance can disable ALL egregore.xyz publishing via `bin/settings.sh hosting off`
|
|
100
|
+
# (sets features.publishing=false). Absent/true means ON, so existing instances are
|
|
101
|
+
# unaffected. We bail BEFORE any render or upload so "off" truly does nothing and costs
|
|
102
|
+
# nothing. Exit 3 is distinct from usage errors; fire-and-forget callers ignore it.
|
|
103
|
+
# NOTE: test `!= false`, not jq's `// "true"` — `//` treats an explicit `false` as empty
|
|
104
|
+
# and would return the default, silently defeating the off-switch.
|
|
105
|
+
PUBLISHING_ENABLED="$(jq -r 'if .features.publishing == false then "false" else "true" end' "$CONFIG" 2>/dev/null || echo "true")"
|
|
106
|
+
if [ "$PUBLISHING_ENABLED" = "false" ]; then
|
|
107
|
+
echo "Artifact hosting is off for this egregore (features.publishing=false); nothing uploaded." >&2
|
|
108
|
+
echo "Turn it on with: bin/settings.sh hosting on (or: egregore settings → Hosting)." >&2
|
|
109
|
+
exit 3
|
|
110
|
+
fi
|
|
111
|
+
|
|
98
112
|
# --- Prepare HTML ---
|
|
99
113
|
TMP_HTML="/tmp/egregore-artifacts/publish-$$.html"
|
|
100
114
|
mkdir -p /tmp/egregore-artifacts
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/bin/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 repo list | add <name> [description] | remove <name>
|
|
14
|
+
# settings.sh admin list | add <github-handle> | remove <github-handle>
|
|
15
|
+
# settings.sh dump # full settings snapshot as JSON (for the launcher)
|
|
16
|
+
#
|
|
17
|
+
# Add --json to a read verb (status/list) for machine-readable output.
|
|
18
|
+
#
|
|
19
|
+
# Exit codes: 0 ok · 1 usage/validation error · 2 config not found.
|
|
20
|
+
|
|
21
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
22
|
+
CONFIG="$SCRIPT_DIR/egregore.json"
|
|
23
|
+
|
|
24
|
+
# ── helpers ──────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
_die() { echo "$1" >&2; exit "${2:-1}"; }
|
|
27
|
+
|
|
28
|
+
_valid_token() {
|
|
29
|
+
# non-empty, GitHub-handle / repo-name safe charset
|
|
30
|
+
[ -n "${1:-}" ] && printf '%s' "$1" | grep -qE '^[A-Za-z0-9._-]+$'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_save() {
|
|
34
|
+
# _save '<jq-filter>' [jq-args...] — apply filter to CONFIG, write atomically.
|
|
35
|
+
local filter="$1"; shift
|
|
36
|
+
local tmp
|
|
37
|
+
tmp="$(mktemp "$CONFIG.XXXXXX")" || _die "cannot create temp file" 1
|
|
38
|
+
if jq "$@" "$filter" "$CONFIG" > "$tmp" 2>/dev/null && [ -s "$tmp" ]; then
|
|
39
|
+
mv "$tmp" "$CONFIG"
|
|
40
|
+
else
|
|
41
|
+
rm -f "$tmp"
|
|
42
|
+
_die "failed to update $CONFIG (invalid jq filter or unwritable file)" 1
|
|
43
|
+
fi
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_has_repo() { jq -e --arg n "$1" 'any(.repos[]?; (if type=="object" then .name else . end) == $n)' "$CONFIG" >/dev/null; }
|
|
47
|
+
_has_admin() { jq -e --arg h "$1" 'any(.admins[]?; . == $h)' "$CONFIG" >/dev/null; }
|
|
48
|
+
|
|
49
|
+
usage() {
|
|
50
|
+
cat >&2 <<'EOF'
|
|
51
|
+
settings.sh — Egregore instance settings
|
|
52
|
+
|
|
53
|
+
settings.sh hosting status|on|off
|
|
54
|
+
settings.sh repo list | add <name> [description] | remove <name>
|
|
55
|
+
settings.sh admin list | add <github-handle> | remove <github-handle>
|
|
56
|
+
settings.sh people list | add <github-handle> | remove <github-handle>
|
|
57
|
+
settings.sh dump full snapshot (JSON)
|
|
58
|
+
|
|
59
|
+
--json machine-readable output for status/list
|
|
60
|
+
EOF
|
|
61
|
+
exit "${1:-1}"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# ── verbs ────────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
cmd_hosting() {
|
|
67
|
+
local action="${1:-status}"
|
|
68
|
+
# NEVER use jq's `// "true"` here: `//` treats an explicit `false` as empty and
|
|
69
|
+
# would return the default, silently defeating the off-switch. Test `!= false`
|
|
70
|
+
# so absent/true => enabled, explicit false => disabled. `enabled` is the
|
|
71
|
+
# resolved boolean string ("true"/"false").
|
|
72
|
+
local enabled; enabled="$(jq -r '.features.publishing != false' "$CONFIG" 2>/dev/null || echo "true")"
|
|
73
|
+
case "$action" in
|
|
74
|
+
status)
|
|
75
|
+
if [ "$JSON" = 1 ]; then
|
|
76
|
+
jq -n --argjson on "$enabled" '{domain:"hosting", publishing:$on}'
|
|
77
|
+
elif [ "$enabled" = "false" ]; then echo "Hosting (egregore.xyz): OFF"
|
|
78
|
+
else echo "Hosting (egregore.xyz): ON"; fi ;;
|
|
79
|
+
on)
|
|
80
|
+
_save '.features.publishing = true'
|
|
81
|
+
echo "Hosting (egregore.xyz): ON" ;;
|
|
82
|
+
off)
|
|
83
|
+
_save '.features.publishing = false'
|
|
84
|
+
echo "Hosting (egregore.xyz): OFF — artifacts will no longer upload to egregore.xyz" ;;
|
|
85
|
+
*) _die "usage: settings.sh hosting status|on|off" 1 ;;
|
|
86
|
+
esac
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
cmd_repo() {
|
|
90
|
+
local action="${1:-list}"; shift || true
|
|
91
|
+
case "$action" in
|
|
92
|
+
list)
|
|
93
|
+
if [ "$JSON" = 1 ]; then
|
|
94
|
+
jq '[.repos[]? | if type=="object" then . else {name:.} end]' "$CONFIG"
|
|
95
|
+
else
|
|
96
|
+
jq -r '.repos[]? | if type=="object" then " " + .name + (if .description then " — " + .description else "" end) else " " + . end' "$CONFIG"
|
|
97
|
+
fi ;;
|
|
98
|
+
add)
|
|
99
|
+
local name="${1:-}"; shift || true
|
|
100
|
+
local desc="${*:-}"
|
|
101
|
+
_valid_token "$name" || _die "usage: settings.sh repo add <name> [description]" 1
|
|
102
|
+
if _has_repo "$name"; then echo "repo '$name' already managed"; return 0; fi
|
|
103
|
+
if [ -n "$desc" ]; then
|
|
104
|
+
_save '.repos = ((.repos // []) + [{name:$n, description:$d}])' --arg n "$name" --arg d "$desc"
|
|
105
|
+
else
|
|
106
|
+
_save '.repos = ((.repos // []) + [{name:$n}])' --arg n "$name"
|
|
107
|
+
fi
|
|
108
|
+
echo "added repo '$name'"
|
|
109
|
+
echo " clone it with: /sync-repos (or reopen the egregore)" ;;
|
|
110
|
+
remove)
|
|
111
|
+
local name="${1:-}"
|
|
112
|
+
_valid_token "$name" || _die "usage: settings.sh repo remove <name>" 1
|
|
113
|
+
if ! _has_repo "$name"; then echo "repo '$name' is not managed"; return 0; fi
|
|
114
|
+
_save '.repos = [.repos[]? | select((if type=="object" then .name else . end) != $n)]' --arg n "$name"
|
|
115
|
+
echo "removed repo '$name' from config"
|
|
116
|
+
echo " note: the local ../$name checkout is left untouched" ;;
|
|
117
|
+
*) _die "usage: settings.sh repo list|add|remove" 1 ;;
|
|
118
|
+
esac
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
cmd_admin() {
|
|
122
|
+
local action="${1:-list}"; shift || true
|
|
123
|
+
case "$action" in
|
|
124
|
+
list)
|
|
125
|
+
if [ "$JSON" = 1 ]; then jq '{admins:(.admins // [])}' "$CONFIG"
|
|
126
|
+
else jq -r '.admins[]? | " " + .' "$CONFIG"; fi ;;
|
|
127
|
+
add)
|
|
128
|
+
local h="${1:-}"
|
|
129
|
+
_valid_token "$h" || _die "usage: settings.sh admin add <github-handle>" 1
|
|
130
|
+
if _has_admin "$h"; then echo "'$h' is already an admin"; return 0; fi
|
|
131
|
+
_save '.admins = ((.admins // []) + [$h])' --arg h "$h"
|
|
132
|
+
echo "added admin '$h'" ;;
|
|
133
|
+
remove)
|
|
134
|
+
local h="${1:-}"
|
|
135
|
+
_valid_token "$h" || _die "usage: settings.sh admin remove <github-handle>" 1
|
|
136
|
+
if ! _has_admin "$h"; then echo "'$h' is not an admin"; return 0; fi
|
|
137
|
+
local count; count="$(jq '(.admins // []) | length' "$CONFIG")"
|
|
138
|
+
[ "$count" -le 1 ] && _die "refusing to remove the last admin ('$h') — add another admin first" 1
|
|
139
|
+
_save '.admins = [.admins[]? | select(. != $h)]' --arg h "$h"
|
|
140
|
+
echo "removed admin '$h'" ;;
|
|
141
|
+
*) _die "usage: settings.sh admin list|add|remove" 1 ;;
|
|
142
|
+
esac
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# ── people ───────────────────────────────────────────────────────────────
|
|
146
|
+
# People live as files in memory/people/. add/remove manage that directory AND
|
|
147
|
+
# GitHub repo access (the meaningful local grant). Connected-mode extras — the
|
|
148
|
+
# invite link + Telegram (/invite) and Supabase/Neo4j teardown (/delete-user) —
|
|
149
|
+
# are pointed to, not duplicated here.
|
|
150
|
+
|
|
151
|
+
_people_dir() { echo "$SCRIPT_DIR/memory/people"; }
|
|
152
|
+
|
|
153
|
+
_people_names() {
|
|
154
|
+
local pdir; pdir="$(_people_dir)"
|
|
155
|
+
[ -d "$pdir" ] || return 0
|
|
156
|
+
ls "$pdir"/*.md 2>/dev/null | while IFS= read -r f; do basename "$f" .md; done \
|
|
157
|
+
| grep -viE '^(index|readme)$' || true
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
_people_json() {
|
|
161
|
+
local names; names="$(_people_names)"
|
|
162
|
+
if [ -z "$names" ]; then echo '[]'; return; fi
|
|
163
|
+
printf '%s\n' "$names" | jq -R . | jq -sc 'map(select(length>0))'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_has_person() { [ -f "$(_people_dir)/$1.md" ]; }
|
|
167
|
+
|
|
168
|
+
# add|remove a GitHub push-collaborator across the instance's repos. Best-effort:
|
|
169
|
+
# no token/org => skip with a note; per-repo failures never abort.
|
|
170
|
+
_people_github() {
|
|
171
|
+
local user="$1" op="$2" token org repos r
|
|
172
|
+
token="$(grep '^GITHUB_TOKEN=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d= -f2- || true)"
|
|
173
|
+
org="$(jq -r '.github_org // empty' "$CONFIG" 2>/dev/null)"
|
|
174
|
+
if [ -z "$token" ] || [ -z "$org" ]; then
|
|
175
|
+
echo " (no GitHub token/org — skipped repo access; run bin/github-auth.sh)" >&2
|
|
176
|
+
return 0
|
|
177
|
+
fi
|
|
178
|
+
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)"
|
|
179
|
+
for r in $repos; do
|
|
180
|
+
if [ "$op" = "add" ]; then
|
|
181
|
+
curl -s -o /dev/null -X PUT -H "Authorization: Bearer $token" -H "Accept: application/vnd.github+json" \
|
|
182
|
+
"https://api.github.com/repos/$org/$r/collaborators/$user" -d '{"permission":"push"}' 2>/dev/null || true
|
|
183
|
+
else
|
|
184
|
+
curl -s -o /dev/null -X DELETE -H "Authorization: Bearer $token" -H "Accept: application/vnd.github+json" \
|
|
185
|
+
"https://api.github.com/repos/$org/$r/collaborators/$user" 2>/dev/null || true
|
|
186
|
+
fi
|
|
187
|
+
done
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# create|remove memory/people/<user>.md, committing ONLY the people dir.
|
|
191
|
+
_people_file() {
|
|
192
|
+
local user="$1" op="$2" pdir f inviter today
|
|
193
|
+
pdir="$(_people_dir)"
|
|
194
|
+
[ -d "$SCRIPT_DIR/memory" ] || { echo " (no memory dir — skipped person file)" >&2; return 0; }
|
|
195
|
+
mkdir -p "$pdir"
|
|
196
|
+
f="$pdir/$user.md"
|
|
197
|
+
if [ "$op" = "add" ]; then
|
|
198
|
+
if [ ! -f "$f" ]; then
|
|
199
|
+
inviter="$(jq -r '.display_name // .github_username // "admin"' "$SCRIPT_DIR/.egregore-state.json" 2>/dev/null || echo admin)"
|
|
200
|
+
today="$(date -u +%Y-%m-%d)"
|
|
201
|
+
printf -- '---\nname: %s\ngithub: %s\ninvited_by: %s\njoined: %s\n---\n' "$user" "$user" "$inviter" "$today" > "$f"
|
|
202
|
+
fi
|
|
203
|
+
else
|
|
204
|
+
rm -f "$f"
|
|
205
|
+
fi
|
|
206
|
+
( cd "$SCRIPT_DIR/memory" 2>/dev/null && git add -A people/ 2>/dev/null \
|
|
207
|
+
&& git commit -q -m "settings: people $op $user" 2>/dev/null \
|
|
208
|
+
&& git push -q 2>/dev/null ) || true
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
cmd_people() {
|
|
212
|
+
local action="${1:-list}"; shift || true
|
|
213
|
+
case "$action" in
|
|
214
|
+
list)
|
|
215
|
+
if [ "$JSON" = 1 ]; then _people_json
|
|
216
|
+
else
|
|
217
|
+
local names; names="$(_people_names)"
|
|
218
|
+
if [ -n "$names" ]; then printf ' %s\n' $names; else echo " (none)"; fi
|
|
219
|
+
fi ;;
|
|
220
|
+
add)
|
|
221
|
+
local h="${1:-}"
|
|
222
|
+
_valid_token "$h" || _die "usage: settings.sh people add <github-handle>" 1
|
|
223
|
+
if _has_person "$h"; then echo "'$h' is already in people"; return 0; fi
|
|
224
|
+
_people_github "$h" "add"
|
|
225
|
+
_people_file "$h" "add"
|
|
226
|
+
echo "added '$h' (repo access + person file)"
|
|
227
|
+
[ -n "$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)" ] && echo " connected org: for the invite link + Telegram, run /invite $h" ;;
|
|
228
|
+
remove)
|
|
229
|
+
local h="${1:-}"
|
|
230
|
+
_valid_token "$h" || _die "usage: settings.sh people remove <github-handle>" 1
|
|
231
|
+
if ! _has_person "$h"; then echo "'$h' is not in people"; return 0; fi
|
|
232
|
+
_people_github "$h" "remove"
|
|
233
|
+
_people_file "$h" "remove"
|
|
234
|
+
echo "removed '$h' (repo access + person file)"
|
|
235
|
+
[ -n "$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)" ] && echo " connected org: for full deprovision (Supabase/Neo4j), run /delete-user $h" ;;
|
|
236
|
+
*) _die "usage: settings.sh people list|add|remove" 1 ;;
|
|
237
|
+
esac
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
cmd_dump() {
|
|
241
|
+
# Always JSON — the launcher calls this once to render the whole settings screen.
|
|
242
|
+
local people; people="$(_people_json)"
|
|
243
|
+
jq --argjson people "$people" '{
|
|
244
|
+
org: .org_name,
|
|
245
|
+
slug: .slug,
|
|
246
|
+
hosting: (.features.publishing != false),
|
|
247
|
+
repos: [.repos[]? | if type=="object" then . else {name:.} end],
|
|
248
|
+
admins: (.admins // []),
|
|
249
|
+
people: $people
|
|
250
|
+
}' "$CONFIG"
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# ── dispatch ─────────────────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
[ -f "$CONFIG" ] || _die "egregore.json not found at $CONFIG" 2
|
|
256
|
+
|
|
257
|
+
# Pull out --json (bash 3.2-safe array handling), leave the rest as positionals.
|
|
258
|
+
JSON=0
|
|
259
|
+
_args=()
|
|
260
|
+
for _a in "$@"; do
|
|
261
|
+
if [ "$_a" = "--json" ]; then JSON=1; else _args+=("$_a"); fi
|
|
262
|
+
done
|
|
263
|
+
set -- "${_args[@]+"${_args[@]}"}"
|
|
264
|
+
|
|
265
|
+
DOMAIN="${1:-}"; shift || true
|
|
266
|
+
case "$DOMAIN" in
|
|
267
|
+
hosting) cmd_hosting "$@" ;;
|
|
268
|
+
repo) cmd_repo "$@" ;;
|
|
269
|
+
admin) cmd_admin "$@" ;;
|
|
270
|
+
people) cmd_people "$@" ;;
|
|
271
|
+
dump) cmd_dump ;;
|
|
272
|
+
""|-h|--help|help) usage 0 ;;
|
|
273
|
+
*) _die "unknown settings domain: '$DOMAIN' (try: hosting, repo, admin, people, dump)" 1 ;;
|
|
274
|
+
esac
|