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.
Files changed (130) hide show
  1. package/assets/egregore-launcher.js +407 -25
  2. package/bin/cli.js +34 -20
  3. package/lib/local.js +21 -5
  4. package/lib/pi-runtime.js +186 -0
  5. package/lib/setup.js +56 -11
  6. package/lib/ui.js +1 -1
  7. package/package.json +7 -5
  8. package/runtime/codex/.codex/spec-manifest.json +1 -1
  9. package/runtime/codex/AGENTS.md +7 -2
  10. package/runtime/codex/bin/codex-render-spec.mjs +6 -1
  11. package/runtime/codex/bin/codex-session-start.sh +5 -59
  12. package/runtime/codex/bin/codex-skill-render.mjs +7 -5
  13. package/runtime/codex/bin/lib/context.sh +8 -1
  14. package/runtime/codex/bin/lib/greeting.sh +12 -108
  15. package/runtime/codex/bin/lib/metrics.sh +374 -0
  16. package/runtime/codex/bin/publish-artifact.sh +14 -0
  17. package/runtime/codex/bin/settings.sh +274 -0
  18. package/runtime/codex/manifest.json +2 -0
  19. package/runtime/pi/.codex/config.toml +15 -0
  20. package/runtime/pi/.codex/hooks/branch-guard.js +242 -0
  21. package/runtime/pi/.codex/hooks/search-hint.js +65 -0
  22. package/runtime/pi/.codex/hooks.json +30 -0
  23. package/runtime/pi/.codex/skills/activity/SKILL.md +70 -0
  24. package/runtime/pi/.codex/skills/add/SKILL.md +25 -0
  25. package/runtime/pi/.codex/skills/announce/SKILL.md +54 -0
  26. package/runtime/pi/.codex/skills/answer/SKILL.md +25 -0
  27. package/runtime/pi/.codex/skills/archive/SKILL.md +41 -0
  28. package/runtime/pi/.codex/skills/ask/SKILL.md +59 -0
  29. package/runtime/pi/.codex/skills/branch/SKILL.md +25 -0
  30. package/runtime/pi/.codex/skills/checkup/SKILL.md +41 -0
  31. package/runtime/pi/.codex/skills/commit/SKILL.md +25 -0
  32. package/runtime/pi/.codex/skills/contribute/SKILL.md +25 -0
  33. package/runtime/pi/.codex/skills/dark-mode/SKILL.md +25 -0
  34. package/runtime/pi/.codex/skills/dashboard/SKILL.md +59 -0
  35. package/runtime/pi/.codex/skills/deep-reflect/SKILL.md +74 -0
  36. package/runtime/pi/.codex/skills/delete-user/SKILL.md +25 -0
  37. package/runtime/pi/.codex/skills/egregore-voice/SKILL.md +25 -0
  38. package/runtime/pi/.codex/skills/emissary/SKILL.md +41 -0
  39. package/runtime/pi/.codex/skills/env/SKILL.md +25 -0
  40. package/runtime/pi/.codex/skills/handoff/SKILL.md +67 -0
  41. package/runtime/pi/.codex/skills/harvest/SKILL.md +107 -0
  42. package/runtime/pi/.codex/skills/ingest/SKILL.md +25 -0
  43. package/runtime/pi/.codex/skills/ingest-google/SKILL.md +25 -0
  44. package/runtime/pi/.codex/skills/ingest-user-interview/SKILL.md +41 -0
  45. package/runtime/pi/.codex/skills/invite/SKILL.md +40 -0
  46. package/runtime/pi/.codex/skills/issue/SKILL.md +41 -0
  47. package/runtime/pi/.codex/skills/me/SKILL.md +25 -0
  48. package/runtime/pi/.codex/skills/meeting/SKILL.md +41 -0
  49. package/runtime/pi/.codex/skills/minion/SKILL.md +25 -0
  50. package/runtime/pi/.codex/skills/note/SKILL.md +25 -0
  51. package/runtime/pi/.codex/skills/onboarding/SKILL.md +41 -0
  52. package/runtime/pi/.codex/skills/pr/SKILL.md +25 -0
  53. package/runtime/pi/.codex/skills/project/SKILL.md +41 -0
  54. package/runtime/pi/.codex/skills/pull/SKILL.md +25 -0
  55. package/runtime/pi/.codex/skills/push/SKILL.md +25 -0
  56. package/runtime/pi/.codex/skills/quest/SKILL.md +77 -0
  57. package/runtime/pi/.codex/skills/quest-suggest/SKILL.md +25 -0
  58. package/runtime/pi/.codex/skills/reflect/SKILL.md +41 -0
  59. package/runtime/pi/.codex/skills/review-pr/SKILL.md +41 -0
  60. package/runtime/pi/.codex/skills/save/SKILL.md +62 -0
  61. package/runtime/pi/.codex/skills/scroll/SKILL.md +168 -0
  62. package/runtime/pi/.codex/skills/scroll/agents/openai.yaml +4 -0
  63. package/runtime/pi/.codex/skills/scroll/assets/shell.html +542 -0
  64. package/runtime/pi/.codex/skills/search/SKILL.md +56 -0
  65. package/runtime/pi/.codex/skills/session-view/SKILL.md +25 -0
  66. package/runtime/pi/.codex/skills/setup/SKILL.md +25 -0
  67. package/runtime/pi/.codex/skills/summon/SKILL.md +41 -0
  68. package/runtime/pi/.codex/skills/sync-repos/SKILL.md +25 -0
  69. package/runtime/pi/.codex/skills/telegram-connect/SKILL.md +25 -0
  70. package/runtime/pi/.codex/skills/telemetry/SKILL.md +25 -0
  71. package/runtime/pi/.codex/skills/test/SKILL.md +41 -0
  72. package/runtime/pi/.codex/skills/the-spiral/SKILL.md +84 -0
  73. package/runtime/pi/.codex/skills/thread/SKILL.md +25 -0
  74. package/runtime/pi/.codex/skills/todo/SKILL.md +41 -0
  75. package/runtime/pi/.codex/skills/tui-design/SKILL.md +41 -0
  76. package/runtime/pi/.codex/skills/tutorial/SKILL.md +41 -0
  77. package/runtime/pi/.codex/skills/update/SKILL.md +25 -0
  78. package/runtime/pi/.codex/skills/view/SKILL.md +156 -0
  79. package/runtime/pi/.codex/skills/view/agents/openai.yaml +4 -0
  80. package/runtime/pi/.codex/skills/view/assets/compose-scaffold.html +221 -0
  81. package/runtime/pi/.codex/skills/visual-explain/SKILL.md +41 -0
  82. package/runtime/pi/.codex/skills/wrap/SKILL.md +55 -0
  83. package/runtime/pi/.codex/spec-manifest.json +114 -0
  84. package/runtime/pi/.pi/APPEND_SYSTEM.md +255 -0
  85. package/runtime/pi/.pi/extensions/egregore.ts +324 -0
  86. package/runtime/pi/.pi/settings.json +6 -0
  87. package/runtime/pi/.pi/spec-manifest.json +12 -0
  88. package/runtime/pi/AGENTS.md +350 -0
  89. package/runtime/pi/bin/activity-data.sh +242 -0
  90. package/runtime/pi/bin/agent.sh +677 -0
  91. package/runtime/pi/bin/codex-install-skills.sh +78 -0
  92. package/runtime/pi/bin/codex-render-spec.mjs +394 -0
  93. package/runtime/pi/bin/codex-session-start.sh +188 -0
  94. package/runtime/pi/bin/codex-skill-render.mjs +323 -0
  95. package/runtime/pi/bin/codex-sync-skills.sh +227 -0
  96. package/runtime/pi/bin/dashboard-data.sh +178 -0
  97. package/runtime/pi/bin/graph-batch.sh +60 -0
  98. package/runtime/pi/bin/graph-op.sh +431 -0
  99. package/runtime/pi/bin/graph-wal.sh +228 -0
  100. package/runtime/pi/bin/graph.sh +177 -0
  101. package/runtime/pi/bin/handoff-pr-backfill.sh +86 -0
  102. package/runtime/pi/bin/handoff-run.sh +581 -0
  103. package/runtime/pi/bin/handoff-save-egregore.sh +136 -0
  104. package/runtime/pi/bin/index-handoff.sh +312 -0
  105. package/runtime/pi/bin/lib/artifact-id.sh +46 -0
  106. package/runtime/pi/bin/lib/config.sh +71 -0
  107. package/runtime/pi/bin/lib/context.sh +542 -0
  108. package/runtime/pi/bin/lib/dashboard-artifact.sh +142 -0
  109. package/runtime/pi/bin/lib/ensure-develop.sh +183 -0
  110. package/runtime/pi/bin/lib/git-sync.sh +288 -0
  111. package/runtime/pi/bin/lib/greeting.sh +340 -0
  112. package/runtime/pi/bin/lib/hash.sh +13 -0
  113. package/runtime/pi/bin/lib/identity.sh +174 -0
  114. package/runtime/pi/bin/lib/metrics.sh +374 -0
  115. package/runtime/pi/bin/lib/permissions.sh +106 -0
  116. package/runtime/pi/bin/lib/time.sh +56 -0
  117. package/runtime/pi/bin/lib/worktree-links.sh +54 -0
  118. package/runtime/pi/bin/notify.sh +207 -0
  119. package/runtime/pi/bin/pi-branch-policy.mjs +15 -0
  120. package/runtime/pi/bin/pi-product-workflows.mjs +324 -0
  121. package/runtime/pi/bin/pi-render-spec.mjs +114 -0
  122. package/runtime/pi/bin/pi-session-start.sh +24 -0
  123. package/runtime/pi/bin/publish-artifact.sh +201 -0
  124. package/runtime/pi/bin/publish-references.sh +144 -0
  125. package/runtime/pi/bin/repo-state.sh +99 -0
  126. package/runtime/pi/bin/telemetry.sh +391 -0
  127. package/runtime/pi/bin/worktree-create.sh +118 -0
  128. package/runtime/pi/bin/worktree-remove.sh +50 -0
  129. package/runtime/pi/bin/worktree.sh +198 -0
  130. 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