create-egregore 0.19.5 → 0.19.6
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 +407 -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,391 @@
|
|
|
1
|
+
#!/bin/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
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# WorktreeCreate hook — replaces EnterWorktree's default behavior.
|
|
3
|
+
# Receives {"name": "<slug>"} on stdin. Must print absolute worktree path to stdout.
|
|
4
|
+
# All diagnostic output goes to stderr or /dev/null — stdout is ONLY for the path.
|
|
5
|
+
# Must complete in under 2 seconds.
|
|
6
|
+
|
|
7
|
+
# No set -e — a jq failure must not crash the hook
|
|
8
|
+
set -o pipefail
|
|
9
|
+
|
|
10
|
+
# --- Read input ---
|
|
11
|
+
INPUT=$(cat /dev/stdin 2>/dev/null || echo '{}')
|
|
12
|
+
SLUG=$(echo "$INPUT" | jq -r '.name // empty' 2>/dev/null)
|
|
13
|
+
|
|
14
|
+
if [ -z "$SLUG" ]; then
|
|
15
|
+
echo "Error: no name provided" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# --- Resolve main repo root ---
|
|
20
|
+
# CLAUDE_PROJECT_DIR may point to main repo or a worktree
|
|
21
|
+
REPO_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
22
|
+
if [ -f "$REPO_ROOT/.git" ]; then
|
|
23
|
+
# In a worktree — trace back to main repo
|
|
24
|
+
WT_GITDIR=$(sed 's/^gitdir: //' "$REPO_ROOT/.git" 2>/dev/null)
|
|
25
|
+
REPO_ROOT=$(cd "$WT_GITDIR/../../.." 2>/dev/null && pwd)
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# --- Read author ---
|
|
29
|
+
STATE_FILE="$REPO_ROOT/.egregore-state.json"
|
|
30
|
+
AUTHOR=$(jq -r '.github_username // .display_name // "unknown"' "$STATE_FILE" 2>/dev/null) || AUTHOR="unknown"
|
|
31
|
+
|
|
32
|
+
# --- Branch and worktree names ---
|
|
33
|
+
BRANCH="dev/${AUTHOR}/${SLUG}"
|
|
34
|
+
WT_PATH="$REPO_ROOT/.claude/worktrees/${SLUG}"
|
|
35
|
+
|
|
36
|
+
# --- Ensure origin/develop is available ---
|
|
37
|
+
git -C "$REPO_ROOT" fetch origin develop --quiet 2>/dev/null || true
|
|
38
|
+
|
|
39
|
+
# --- Create branch (idempotent — skip if exists) ---
|
|
40
|
+
if ! git -C "$REPO_ROOT" show-ref --verify --quiet "refs/heads/$BRANCH" 2>/dev/null; then
|
|
41
|
+
git -C "$REPO_ROOT" branch "$BRANCH" origin/develop >/dev/null 2>&1 || {
|
|
42
|
+
echo "Error: failed to create branch $BRANCH" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
}
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# --- Handle stale worktree at same path ---
|
|
48
|
+
if [ -d "$WT_PATH" ]; then
|
|
49
|
+
git -C "$REPO_ROOT" worktree remove "$WT_PATH" --force 2>/dev/null || rm -rf "$WT_PATH" 2>/dev/null
|
|
50
|
+
git -C "$REPO_ROOT" worktree prune 2>/dev/null || true
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# --- Create worktree on our branch ---
|
|
54
|
+
mkdir -p "$REPO_ROOT/.claude/worktrees" 2>/dev/null
|
|
55
|
+
git -C "$REPO_ROOT" worktree add "$WT_PATH" "$BRANCH" --quiet 2>/dev/null || {
|
|
56
|
+
echo "Error: failed to create worktree at $WT_PATH" >&2
|
|
57
|
+
exit 1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# --- Symlinks (same as worktree.sh setup) ---
|
|
61
|
+
# Memory — may be a symlink (follow to its target) or a real directory.
|
|
62
|
+
MEMORY_TARGET=""
|
|
63
|
+
if [ -L "$REPO_ROOT/memory" ]; then
|
|
64
|
+
MEMORY_TARGET=$(realpath "$REPO_ROOT/memory" 2>/dev/null)
|
|
65
|
+
elif [ -d "$REPO_ROOT/memory" ]; then
|
|
66
|
+
MEMORY_TARGET="$REPO_ROOT/memory"
|
|
67
|
+
fi
|
|
68
|
+
if [ -n "$MEMORY_TARGET" ] && [ -d "$MEMORY_TARGET" ]; then
|
|
69
|
+
ln -sfn "$MEMORY_TARGET" "$WT_PATH/memory"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# .env
|
|
73
|
+
[ -f "$REPO_ROOT/.env" ] && ln -sfn "$REPO_ROOT/.env" "$WT_PATH/.env"
|
|
74
|
+
|
|
75
|
+
# .egregore-state.json
|
|
76
|
+
[ -f "$REPO_ROOT/.egregore-state.json" ] && ln -sfn "$REPO_ROOT/.egregore-state.json" "$WT_PATH/.egregore-state.json"
|
|
77
|
+
|
|
78
|
+
# .egregore-session-id
|
|
79
|
+
[ -f "$REPO_ROOT/.egregore-session-id" ] && ln -sfn "$REPO_ROOT/.egregore-session-id" "$WT_PATH/.egregore-session-id"
|
|
80
|
+
|
|
81
|
+
# egregore.json
|
|
82
|
+
[ -f "$REPO_ROOT/egregore.json" ] && [ ! -f "$WT_PATH/egregore.json" ] && ln -sfn "$REPO_ROOT/egregore.json" "$WT_PATH/egregore.json"
|
|
83
|
+
|
|
84
|
+
# --- Compute boundary for worktree (so isolation hook works) ---
|
|
85
|
+
# session-start.sh doesn't re-run for worktrees, so we compute it here.
|
|
86
|
+
HASH=$(echo -n "$WT_PATH" | md5 2>/dev/null || echo -n "$WT_PATH" | md5sum 2>/dev/null | cut -d' ' -f1)
|
|
87
|
+
BOUNDARY_FILE="/tmp/egregore-boundary-${HASH}.json"
|
|
88
|
+
MEMORY_DIR=""
|
|
89
|
+
[ -L "$WT_PATH/memory" ] && MEMORY_DIR=$(realpath "$WT_PATH/memory" 2>/dev/null || true)
|
|
90
|
+
# Managed repos: include main repo (worktree symlinks resolve there) + sibling repos
|
|
91
|
+
MANAGED_REPOS_JSON="[\"$REPO_ROOT\""
|
|
92
|
+
PARENT_DIR="$(dirname "$REPO_ROOT")"
|
|
93
|
+
_repos=$(jq -r '(.repos[]? // empty) | if type == "object" then .name else . end' "$WT_PATH/egregore.json" 2>/dev/null || true)
|
|
94
|
+
for _r in $_repos; do
|
|
95
|
+
[[ "$_r" == *".."* ]] && continue
|
|
96
|
+
_resolved=$(realpath "$PARENT_DIR/$_r" 2>/dev/null || true)
|
|
97
|
+
[ -z "$_resolved" ] && continue
|
|
98
|
+
MANAGED_REPOS_JSON="$MANAGED_REPOS_JSON,\"$_resolved\""
|
|
99
|
+
done
|
|
100
|
+
MANAGED_REPOS_JSON="$MANAGED_REPOS_JSON]"
|
|
101
|
+
# Denied paths: other instances, but NOT the main repo (worktree is its child)
|
|
102
|
+
DENIED_JSON="[]"
|
|
103
|
+
REGISTRY="$HOME/.egregore/instances.json"
|
|
104
|
+
if [ -f "$REGISTRY" ]; then
|
|
105
|
+
DENIED_JSON=$(jq --arg self "$REPO_ROOT" --arg wt "$WT_PATH" \
|
|
106
|
+
'[.[] | select(.path != $self and .path != $wt) | .path]' \
|
|
107
|
+
"$REGISTRY" 2>/dev/null || echo "[]")
|
|
108
|
+
fi
|
|
109
|
+
jq -n \
|
|
110
|
+
--arg project_dir "$WT_PATH" \
|
|
111
|
+
--arg memory_dir "$MEMORY_DIR" \
|
|
112
|
+
--argjson managed_repos "$MANAGED_REPOS_JSON" \
|
|
113
|
+
--argjson denied_paths "$DENIED_JSON" \
|
|
114
|
+
'{project_dir: $project_dir, memory_dir: $memory_dir, managed_repos: $managed_repos, denied_paths: $denied_paths}' \
|
|
115
|
+
> "$BOUNDARY_FILE.tmp" 2>/dev/null && mv "$BOUNDARY_FILE.tmp" "$BOUNDARY_FILE" 2>/dev/null || true
|
|
116
|
+
|
|
117
|
+
# --- Output the worktree path (ONLY stdout line) ---
|
|
118
|
+
echo "$WT_PATH"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# WorktreeRemove hook — handles cleanup when a worktree is removed.
|
|
3
|
+
# Receives {"worktree_path": "<path>"} on stdin. Best-effort, always exit 0.
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat /dev/stdin 2>/dev/null || echo '{}')
|
|
6
|
+
WT_PATH=$(echo "$INPUT" | jq -r '.worktree_path // empty' 2>/dev/null)
|
|
7
|
+
|
|
8
|
+
if [ -z "$WT_PATH" ] || [ ! -d "$WT_PATH" ]; then
|
|
9
|
+
exit 0
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
# --- Resolve main repo for git operations ---
|
|
13
|
+
REPO_ROOT="${CLAUDE_PROJECT_DIR:-}"
|
|
14
|
+
if [ -z "$REPO_ROOT" ] || [ -f "$REPO_ROOT/.git" ]; then
|
|
15
|
+
# In a worktree or no CLAUDE_PROJECT_DIR — trace from the worktree path
|
|
16
|
+
if [ -f "$WT_PATH/.git" ]; then
|
|
17
|
+
WT_GITDIR=$(sed 's/^gitdir: //' "$WT_PATH/.git" 2>/dev/null)
|
|
18
|
+
REPO_ROOT=$(cd "$WT_GITDIR/../../.." 2>/dev/null && pwd) || REPO_ROOT=""
|
|
19
|
+
fi
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# --- Remove from instance registry ---
|
|
23
|
+
REGISTRY="$HOME/.egregore/instances.json"
|
|
24
|
+
if [ -f "$REGISTRY" ]; then
|
|
25
|
+
RESOLVED_WT=$(realpath "$WT_PATH" 2>/dev/null || echo "$WT_PATH")
|
|
26
|
+
UPDATED=$(jq --arg p "$RESOLVED_WT" '[.[] | select(.path != $p)]' "$REGISTRY" 2>/dev/null)
|
|
27
|
+
if [ -n "$UPDATED" ]; then
|
|
28
|
+
echo "$UPDATED" > "$REGISTRY.tmp" && mv "$REGISTRY.tmp" "$REGISTRY"
|
|
29
|
+
fi
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# --- Remove worktree ---
|
|
33
|
+
if [ -n "$REPO_ROOT" ] && [ -d "$REPO_ROOT/.git" ]; then
|
|
34
|
+
git -C "$REPO_ROOT" worktree remove "$WT_PATH" --force 2>/dev/null || true
|
|
35
|
+
git -C "$REPO_ROOT" worktree prune 2>/dev/null || true
|
|
36
|
+
else
|
|
37
|
+
# Fallback: just remove the directory
|
|
38
|
+
rm -rf "$WT_PATH" 2>/dev/null || true
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# --- Clean up stale marker files ---
|
|
42
|
+
for MARKER_FILE in "$HOME/.egregore"/worktree-cleanup-*.marker; do
|
|
43
|
+
[ -f "$MARKER_FILE" ] || continue
|
|
44
|
+
MARKER_PATH=$(cat "$MARKER_FILE" 2>/dev/null)
|
|
45
|
+
if [ "$MARKER_PATH" = "$WT_PATH" ] || [ ! -d "$MARKER_PATH" ]; then
|
|
46
|
+
rm -f "$MARKER_FILE" 2>/dev/null || true
|
|
47
|
+
fi
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
exit 0
|