create-egregore 0.17.4 → 0.18.1

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 (106) hide show
  1. package/assets/egregore-launcher.js +1965 -0
  2. package/assets/egregore-launcher.sh +385 -0
  3. package/bin/cli.js +472 -81
  4. package/lib/api.js +2 -2
  5. package/lib/auth.js +255 -20
  6. package/lib/codex-runtime.js +268 -0
  7. package/lib/commands.js +31 -0
  8. package/lib/context.js +1 -1
  9. package/lib/local.js +616 -189
  10. package/lib/setup.js +822 -84
  11. package/lib/ui.js +780 -84
  12. package/package.json +12 -3
  13. package/runtime/codex/.codex/config.toml +15 -0
  14. package/runtime/codex/.codex/hooks/branch-guard.js +234 -0
  15. package/runtime/codex/.codex/hooks.json +17 -0
  16. package/runtime/codex/.codex/skills/activity/SKILL.md +70 -0
  17. package/runtime/codex/.codex/skills/add/SKILL.md +25 -0
  18. package/runtime/codex/.codex/skills/announce/SKILL.md +54 -0
  19. package/runtime/codex/.codex/skills/answer/SKILL.md +25 -0
  20. package/runtime/codex/.codex/skills/archive/SKILL.md +41 -0
  21. package/runtime/codex/.codex/skills/ask/SKILL.md +59 -0
  22. package/runtime/codex/.codex/skills/branch/SKILL.md +25 -0
  23. package/runtime/codex/.codex/skills/checkup/SKILL.md +41 -0
  24. package/runtime/codex/.codex/skills/commit/SKILL.md +25 -0
  25. package/runtime/codex/.codex/skills/contribute/SKILL.md +25 -0
  26. package/runtime/codex/.codex/skills/dark-mode/SKILL.md +25 -0
  27. package/runtime/codex/.codex/skills/dashboard/SKILL.md +59 -0
  28. package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +70 -0
  29. package/runtime/codex/.codex/skills/delete-user/SKILL.md +25 -0
  30. package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +25 -0
  31. package/runtime/codex/.codex/skills/emissary/SKILL.md +41 -0
  32. package/runtime/codex/.codex/skills/env/SKILL.md +25 -0
  33. package/runtime/codex/.codex/skills/handoff/SKILL.md +67 -0
  34. package/runtime/codex/.codex/skills/harvest/SKILL.md +107 -0
  35. package/runtime/codex/.codex/skills/infra/SKILL.md +41 -0
  36. package/runtime/codex/.codex/skills/ingest/SKILL.md +25 -0
  37. package/runtime/codex/.codex/skills/ingest-google/SKILL.md +25 -0
  38. package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +41 -0
  39. package/runtime/codex/.codex/skills/invite/SKILL.md +40 -0
  40. package/runtime/codex/.codex/skills/issue/SKILL.md +41 -0
  41. package/runtime/codex/.codex/skills/me/SKILL.md +25 -0
  42. package/runtime/codex/.codex/skills/meeting/SKILL.md +41 -0
  43. package/runtime/codex/.codex/skills/note/SKILL.md +25 -0
  44. package/runtime/codex/.codex/skills/onboarding/SKILL.md +41 -0
  45. package/runtime/codex/.codex/skills/pr/SKILL.md +25 -0
  46. package/runtime/codex/.codex/skills/project/SKILL.md +41 -0
  47. package/runtime/codex/.codex/skills/pull/SKILL.md +25 -0
  48. package/runtime/codex/.codex/skills/push/SKILL.md +25 -0
  49. package/runtime/codex/.codex/skills/quest/SKILL.md +77 -0
  50. package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +25 -0
  51. package/runtime/codex/.codex/skills/reflect/SKILL.md +41 -0
  52. package/runtime/codex/.codex/skills/review-pr/SKILL.md +41 -0
  53. package/runtime/codex/.codex/skills/save/SKILL.md +62 -0
  54. package/runtime/codex/.codex/skills/setup/SKILL.md +25 -0
  55. package/runtime/codex/.codex/skills/summon/SKILL.md +41 -0
  56. package/runtime/codex/.codex/skills/sync-repos/SKILL.md +25 -0
  57. package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +25 -0
  58. package/runtime/codex/.codex/skills/telemetry/SKILL.md +25 -0
  59. package/runtime/codex/.codex/skills/test/SKILL.md +41 -0
  60. package/runtime/codex/.codex/skills/the-spiral/SKILL.md +84 -0
  61. package/runtime/codex/.codex/skills/todo/SKILL.md +41 -0
  62. package/runtime/codex/.codex/skills/tui-design/SKILL.md +41 -0
  63. package/runtime/codex/.codex/skills/tutorial/SKILL.md +41 -0
  64. package/runtime/codex/.codex/skills/update/SKILL.md +25 -0
  65. package/runtime/codex/.codex/skills/view/SKILL.md +41 -0
  66. package/runtime/codex/.codex/skills/visual-explain/SKILL.md +41 -0
  67. package/runtime/codex/.codex/skills/wrap/SKILL.md +55 -0
  68. package/runtime/codex/.codex/spec-manifest.json +108 -0
  69. package/runtime/codex/AGENTS.md +40 -0
  70. package/runtime/codex/bin/activity-data.sh +177 -0
  71. package/runtime/codex/bin/agent.sh +669 -0
  72. package/runtime/codex/bin/codex-install-skills.sh +78 -0
  73. package/runtime/codex/bin/codex-render-spec.mjs +308 -0
  74. package/runtime/codex/bin/codex-session-start.sh +238 -0
  75. package/runtime/codex/bin/codex-skill-render.mjs +315 -0
  76. package/runtime/codex/bin/codex-sync-skills.sh +223 -0
  77. package/runtime/codex/bin/dashboard-data.sh +174 -0
  78. package/runtime/codex/bin/graph-batch.sh +60 -0
  79. package/runtime/codex/bin/graph-op.sh +380 -0
  80. package/runtime/codex/bin/graph-wal.sh +228 -0
  81. package/runtime/codex/bin/graph.sh +141 -0
  82. package/runtime/codex/bin/handoff-pr-backfill.sh +86 -0
  83. package/runtime/codex/bin/handoff-run.sh +431 -0
  84. package/runtime/codex/bin/handoff-save-egregore.sh +131 -0
  85. package/runtime/codex/bin/index-handoff.sh +306 -0
  86. package/runtime/codex/bin/lib/artifact-id.sh +46 -0
  87. package/runtime/codex/bin/lib/config.sh +71 -0
  88. package/runtime/codex/bin/lib/context.sh +463 -0
  89. package/runtime/codex/bin/lib/dashboard-artifact.sh +142 -0
  90. package/runtime/codex/bin/lib/ensure-develop.sh +183 -0
  91. package/runtime/codex/bin/lib/git-sync.sh +262 -0
  92. package/runtime/codex/bin/lib/greeting.sh +414 -0
  93. package/runtime/codex/bin/lib/hash.sh +13 -0
  94. package/runtime/codex/bin/lib/identity.sh +164 -0
  95. package/runtime/codex/bin/lib/permissions.sh +106 -0
  96. package/runtime/codex/bin/lib/time.sh +56 -0
  97. package/runtime/codex/bin/lib/worktree-links.sh +54 -0
  98. package/runtime/codex/bin/notify.sh +207 -0
  99. package/runtime/codex/bin/publish-artifact.sh +173 -0
  100. package/runtime/codex/bin/publish-references.sh +144 -0
  101. package/runtime/codex/bin/repo-state.sh +99 -0
  102. package/runtime/codex/bin/telemetry.sh +350 -0
  103. package/runtime/codex/bin/worktree-create.sh +118 -0
  104. package/runtime/codex/bin/worktree-remove.sh +50 -0
  105. package/runtime/codex/bin/worktree.sh +198 -0
  106. package/runtime/codex/manifest.json +155 -0
@@ -0,0 +1,414 @@
1
+ # shellcheck shell=bash
2
+ # greeting.sh — Greeting output renderer for session-start.sh
3
+ #
4
+ # Renders the full session greeting:
5
+ # - ASCII art banner
6
+ # - Identity line (org/repo + user + branch)
7
+ # - "For you" section (handoffs, last activity)
8
+ # - "Around" section (team presence)
9
+ # - "Merged" section (PRs, implemented handoffs)
10
+ # - Health footer
11
+ # - Alias migration
12
+ # - Session context JSON (hidden, for Claude)
13
+ # - Soul file inclusion
14
+ # - Telemetry emit
15
+ # - First session welcome
16
+ #
17
+ # Inputs: SCRIPT_DIR, STATE_FILE, CONFIG, CTX_DIR, BRANCH, COMMITS_AHEAD,
18
+ # AUTHOR, LOCAL_MODE, MEMORY_SYNCED, REPOS_STATUS, SAVED_BRANCH,
19
+ # HEALTH_*, FRAMEWORK_VERSION, TIME_OF_DAY, EGREGORE_SESSION_ID,
20
+ # FIRST_SESSION, DISPLAY_NAME_STATE, DASHBOARD_URL
21
+
22
+ # --- Output greeting for Claude to display ---
23
+ cat << 'GREETING'
24
+
25
+ ███████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗
26
+ ██╔════╝██╔════╝ ██╔══██╗██╔════╝██╔════╝ ██╔═══██╗██╔══██╗██╔════╝
27
+ █████╗ ██║ ███╗██████╔╝█████╗ ██║ ███╗██║ ██║██████╔╝█████╗
28
+ ██╔══╝ ██║ ██║██╔══██╗██╔══╝ ██║ ██║██║ ██║██╔══██╗██╔══╝
29
+ ███████╗╚██████╔╝██║ ██║███████╗╚██████╔╝╚██████╔╝██║ ██║███████╗
30
+ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
31
+
32
+ GREETING
33
+
34
+ # --- Ornamented status ---
35
+ REPO_NAME=$(jq -r '.repo_name // "egregore"' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
36
+ GITHUB_ORG_DISPLAY=$(jq -r '.github_org // ""' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
37
+ ORG_NAME=$(jq -r '.org_name // ""' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
38
+
39
+ DISPLAY_NAME=""
40
+ if [ -f "$STATE_FILE" ]; then
41
+ DISPLAY_NAME=$(jq -r '.display_name // .name // empty' "$STATE_FILE" 2>/dev/null)
42
+ fi
43
+ GREETING_NAME="${DISPLAY_NAME:-$AUTHOR}"
44
+
45
+ # --- Line 1: Org/Repo + User + Branch (primary info) ---
46
+ SEPARATOR=" ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄"
47
+
48
+ # Build identity line: Org/repo on left, user + branch on right
49
+ if [ "$LOCAL_MODE" = "true" ]; then
50
+ IDENTITY_LEFT=" Egregore"
51
+ else
52
+ IDENTITY_LEFT=" ${GITHUB_ORG_DISPLAY}/${REPO_NAME}"
53
+ fi
54
+ BRANCH_COMPACT="$BRANCH"
55
+ if [ "$COMMITS_AHEAD" -gt 0 ] 2>/dev/null; then
56
+ BRANCH_COMPACT="${BRANCH} · ${COMMITS_AHEAD}↑"
57
+ fi
58
+ IDENTITY_RIGHT="${GREETING_NAME} · ${BRANCH_COMPACT}"
59
+ LINE_WIDTH=67
60
+ ID_LEFT_LEN=${#IDENTITY_LEFT}
61
+ ID_RIGHT_LEN=${#IDENTITY_RIGHT}
62
+ ID_PADDING=$((LINE_WIDTH - ID_LEFT_LEN - ID_RIGHT_LEN))
63
+ if [ "$ID_PADDING" -lt 1 ]; then ID_PADDING=1; fi
64
+ printf "\n%s%*s%s\n" "$IDENTITY_LEFT" "$ID_PADDING" "" "$IDENTITY_RIGHT"
65
+ echo "$SEPARATOR"
66
+
67
+ # --- Shared rendering state ---
68
+ TEAM_DATA=$(cat "$CTX_DIR/team" 2>/dev/null || echo "[]")
69
+ TEAM_DATA_COUNT=$(echo "$TEAM_DATA" | jq 'length' 2>/dev/null || echo "0")
70
+ NOW_RENDER=$(date +%s)
71
+ ONLINE_THRESHOLD=7200 # 2 hours — considered "online"
72
+ RECENCY_DAYS=3
73
+ RECENCY_THRESHOLD=$((RECENCY_DAYS * 86400))
74
+ TODAY_DATE=$(date +%Y-%m-%d)
75
+ TODAY_MIDNIGHT=$(date -j -f "%Y-%m-%d" "$TODAY_DATE" +%s 2>/dev/null || \
76
+ date -d "$TODAY_DATE" +%s 2>/dev/null || echo "$NOW_RENDER")
77
+ # Column layout: 2(indent) + 2(dot+space) + 9(name) + 12(time) + 40(info) = 65
78
+ MAX_INFO=40
79
+
80
+ # --- Section 1: For you (always present) ---
81
+ echo " ◦ for you"
82
+ ADDRESSED_RICH=$(cat "$CTX_DIR/addressed_rich" 2>/dev/null || echo "[]")
83
+ ADDRESSED_COUNT=$(echo "$ADDRESSED_RICH" | jq 'length' 2>/dev/null || echo "0")
84
+
85
+ # Handoffs addressed to you
86
+ if [ "$ADDRESSED_COUNT" -gt 0 ] 2>/dev/null && [ "$ADDRESSED_COUNT" != "0" ]; then
87
+ echo "$ADDRESSED_RICH" | jq -r '.[] | "\(.author)\t\(.date)\t\(.topic)"' 2>/dev/null | while IFS=$'\t' read -r H_AUTHOR H_DATE H_TOPIC; do
88
+ [ -z "$H_AUTHOR" ] && continue
89
+ # Convert date to epoch (UTC) and compute calendar-day diff
90
+ H_DATE_ONLY="${H_DATE%%T*}"
91
+ H_EVENT_MIDNIGHT=$(date -j -f "%Y-%m-%d" "$H_DATE_ONLY" +%s 2>/dev/null || \
92
+ date -d "$H_DATE_ONLY" +%s 2>/dev/null || echo "0")
93
+ H_CAL_DAYS=$(( (TODAY_MIDNIGHT - H_EVENT_MIDNIGHT) / 86400 ))
94
+ # Skip if older than recency threshold
95
+ [ "$H_CAL_DAYS" -gt "$RECENCY_DAYS" ] 2>/dev/null && continue
96
+ H_NAME=$(echo "$H_AUTHOR" | awk '{print tolower($1)}')
97
+ if [ ${#H_NAME} -gt 8 ]; then H_NAME="${H_NAME:0:8}"; fi
98
+ # Online status from team presence data
99
+ H_SEEN_EPOCH=$(echo "$TEAM_DATA" | jq -r --arg n "$H_NAME" '[.[] | select((.name | ascii_downcase) == $n) | .last_seen_sort] | .[0] // 0' 2>/dev/null || echo "0")
100
+ if [ "$H_SEEN_EPOCH" -gt 0 ] 2>/dev/null && [ $(( NOW_RENDER - H_SEEN_EPOCH )) -lt $ONLINE_THRESHOLD ] 2>/dev/null; then
101
+ DOT="●"
102
+ else
103
+ DOT="○"
104
+ fi
105
+ # Calendar-day relative label
106
+ if [ "$H_CAL_DAYS" -le 0 ] 2>/dev/null; then H_AGO="today"
107
+ elif [ "$H_CAL_DAYS" -eq 1 ] 2>/dev/null; then H_AGO="yesterday"
108
+ else H_AGO="${H_CAL_DAYS}d ago"
109
+ fi
110
+ if [ ${#H_TOPIC} -gt $MAX_INFO ]; then H_TOPIC="${H_TOPIC:0:$((MAX_INFO - 1))}…"; fi
111
+ printf " %s %-9s%-12s%s\n" "$DOT" "$H_NAME" "$H_AGO" "$H_TOPIC"
112
+ done
113
+ fi
114
+
115
+ # Fallback: last activity if no handoffs
116
+ if [ "$ADDRESSED_COUNT" = "0" ] || [ -z "$ADDRESSED_COUNT" ]; then
117
+ LAST_ACTIVITY=$(cat "$CTX_DIR/activity" 2>/dev/null || echo "")
118
+ if [ -n "$LAST_ACTIVITY" ]; then
119
+ LA_TIME=$(echo "$LAST_ACTIVITY" | cut -d'|' -f1)
120
+ # Compact relative time: "20 hours ago" → "20h ago", "3 days ago" → "3d ago"
121
+ LA_TIME=$(echo "$LA_TIME" | sed 's/ hours\{0,1\} ago/h ago/; s/ days\{0,1\} ago/d ago/; s/ minutes\{0,1\} ago/m ago/; s/ weeks\{0,1\} ago/w ago/; s/ months\{0,1\} ago/mo ago/')
122
+ LA_MSG=$(echo "$LAST_ACTIVITY" | cut -d'|' -f2-)
123
+ if [ ${#LA_MSG} -gt $MAX_INFO ]; then LA_MSG="${LA_MSG:0:$((MAX_INFO - 1))}…"; fi
124
+ printf " ◇ %-9s%-12s%s\n" "you" "$LA_TIME" "$LA_MSG"
125
+ fi
126
+ fi
127
+
128
+ echo ""
129
+ # --- Section 2: Around (team presence with online status) ---
130
+ if [ "$TEAM_DATA_COUNT" -gt 0 ] 2>/dev/null && [ "$TEAM_DATA_COUNT" != "0" ]; then
131
+ echo " ◦ around"
132
+ # Presence roster with online/offline dots (hide inactive >3 days)
133
+ STALE_THRESHOLD=$((RECENCY_DAYS * 86400))
134
+ echo "$TEAM_DATA" | jq -r '.[] | "\(.name)\t\(.last_seen_sort)\t\(.last_seen)\t\(.branches | join(", "))"' 2>/dev/null | while IFS=$'\t' read -r P_NAME P_EPOCH P_SEEN P_BRANCHES; do
135
+ [ -z "$P_NAME" ] && continue
136
+ # Skip if no recent activity (within recency window)
137
+ if [ "$P_EPOCH" -le 0 ] 2>/dev/null; then
138
+ continue
139
+ elif [ $(( NOW_RENDER - P_EPOCH )) -ge $STALE_THRESHOLD ] 2>/dev/null; then
140
+ continue
141
+ fi
142
+ if [ ${#P_NAME} -gt 8 ]; then P_NAME="${P_NAME:0:8}"; fi
143
+ # Online: last seen within threshold
144
+ if [ $(( NOW_RENDER - P_EPOCH )) -lt $ONLINE_THRESHOLD ] 2>/dev/null; then
145
+ DOT="●"
146
+ else
147
+ DOT="○"
148
+ fi
149
+ if [ ${#P_BRANCHES} -gt $MAX_INFO ]; then
150
+ P_BRANCHES="${P_BRANCHES:0:$((MAX_INFO - 9))}... +more"
151
+ fi
152
+ printf " %s %-9s%-12s%s\n" "$DOT" "$P_NAME" "$P_SEEN" "$P_BRANCHES"
153
+ done
154
+ fi
155
+
156
+ # --- Section 3: Recently merged PRs + implemented handoffs ---
157
+ LIFECYCLE_JSON=$(cat "$CTX_DIR/lifecycle" 2>/dev/null || echo '{}')
158
+ MERGED_COUNT=$(echo "$LIFECYCLE_JSON" | jq '.merged_prs.values // [] | length' 2>/dev/null || echo "0")
159
+ IMPL_COUNT=$(echo "$LIFECYCLE_JSON" | jq '.implemented_handoffs.values // [] | length' 2>/dev/null || echo "0")
160
+
161
+ if [ "$MERGED_COUNT" -gt 0 ] 2>/dev/null || [ "$IMPL_COUNT" -gt 0 ] 2>/dev/null; then
162
+ echo ""
163
+ echo " ◦ merged"
164
+ if [ "$MERGED_COUNT" -gt 0 ] 2>/dev/null; then
165
+ echo "$LIFECYCLE_JSON" | jq -r '.merged_prs.values[]? // empty | " ✓ PR #\(.[0]) \(.[1])"' 2>/dev/null || true
166
+ fi
167
+ if [ "$IMPL_COUNT" -gt 0 ] 2>/dev/null; then
168
+ echo "$LIFECYCLE_JSON" | jq -r '.implemented_handoffs.values[]? // empty | " ✓ \(.[1]) worked on your handoff"' 2>/dev/null || true
169
+ fi
170
+ fi
171
+
172
+ # Show auto-save notice if work was committed from a previous branch
173
+ if [ -n "$SAVED_BRANCH" ]; then
174
+ echo " ✓ Auto-saved uncommitted work on $SAVED_BRANCH"
175
+ fi
176
+
177
+ # --- Footer: health + repos + memory (tertiary) ---
178
+ echo "$SEPARATOR"
179
+
180
+ # Build compact footer line
181
+ if [ "$LOCAL_MODE" = "true" ]; then
182
+ # Local mode: only check github + git, skip api-key/graph/telegram
183
+ HAS_FAILURE="false"
184
+ FAILED_SERVICES=""
185
+ for pair in "github:$HEALTH_GITHUB" "git:$HEALTH_GIT"; do
186
+ svc="${pair%%:*}"
187
+ svc_status="${pair#*:}"
188
+ if [ "$svc_status" = "fail" ]; then
189
+ HAS_FAILURE="true"
190
+ FAILED_SERVICES="${FAILED_SERVICES} ${svc} ✗"
191
+ fi
192
+ done
193
+
194
+ if [ "$HAS_FAILURE" = "true" ]; then
195
+ echo " ⚠${FAILED_SERVICES} — run /checkup"
196
+ else
197
+ if [ "${FRAMEWORK_UPDATED:-false}" = "true" ]; then
198
+ printf " ◆ updated\n"
199
+ else
200
+ printf " ✓ ready\n"
201
+ fi
202
+
203
+ # Managed repos on separate line
204
+ if [ -n "$REPOS_STATUS" ]; then
205
+ REPOS_COMPACT=$(printf '%s' "$REPOS_STATUS" | sed 's/^ ◇ //;s/^[[:space:]]*//' | paste -sd' ' - | sed 's/[[:space:]]*$//')
206
+ if [ -n "$REPOS_COMPACT" ]; then
207
+ printf " %s\n" "$REPOS_COMPACT"
208
+ fi
209
+ fi
210
+ fi
211
+ else
212
+ # Connected mode: check all services
213
+ HAS_FAILURE="false"
214
+ FAILED_SERVICES=""
215
+ for pair in "github:$HEALTH_GITHUB" "git:$HEALTH_GIT" "api-key:$HEALTH_APIKEY" "graph:$HEALTH_GRAPH" "telegram:$HEALTH_TELEGRAM"; do
216
+ svc="${pair%%:*}"
217
+ svc_status="${pair#*:}"
218
+ if [ "$svc_status" = "fail" ]; then
219
+ HAS_FAILURE="true"
220
+ FAILED_SERVICES="${FAILED_SERVICES} ${svc} ✗"
221
+ fi
222
+ done
223
+
224
+ if [ "$HAS_FAILURE" = "true" ]; then
225
+ echo " ⚠${FAILED_SERVICES} — run /checkup"
226
+ else
227
+ # Compact footer: ready + memory on one line, repos on next if present
228
+ FOOTER_LEFT=" ✓ ready"
229
+ FOOTER_RIGHT=""
230
+ if [ "$MEMORY_SYNCED" = "true" ]; then
231
+ FOOTER_RIGHT="◆ memory synced"
232
+ fi
233
+
234
+ FL_LEN=${#FOOTER_LEFT}
235
+ FR_LEN=${#FOOTER_RIGHT}
236
+ F_PAD=$((LINE_WIDTH - FL_LEN - FR_LEN))
237
+ if [ "$F_PAD" -lt 1 ]; then F_PAD=1; fi
238
+ printf "%s%*s%s\n" "$FOOTER_LEFT" "$F_PAD" "" "$FOOTER_RIGHT"
239
+
240
+ # Managed repos on separate line (avoids line overflow)
241
+ if [ -n "$REPOS_STATUS" ]; then
242
+ REPOS_COMPACT=$(printf '%s' "$REPOS_STATUS" | sed 's/^ ◇ //;s/^[[:space:]]*//' | paste -sd' ' - | sed 's/[[:space:]]*$//')
243
+ if [ -n "$REPOS_COMPACT" ]; then
244
+ printf " %s\n" "$REPOS_COMPACT"
245
+ fi
246
+ fi
247
+ fi
248
+ fi
249
+
250
+ # Dashboard link (OSC 8 hyperlink — clickable text in supported terminals)
251
+ if [ -n "${DASHBOARD_URL:-}" ]; then
252
+ printf " ◆ %s\n" "$DASHBOARD_URL"
253
+ fi
254
+
255
+ if [ -n "${BOARD_URL:-}" ]; then
256
+ printf " ◆ %s (board)\n" "$BOARD_URL"
257
+ fi
258
+
259
+ # Framework updates come through PRs to develop — no separate auto-update channel.
260
+ # The develop sync above already keeps framework files current.
261
+ # Use /update for manual upstream pulls when needed.
262
+
263
+ # --- One-time migration: fix aliases to use 'claude "start"' ---
264
+ # v1: 'claude start' → 'claude' (cross-instance bug)
265
+ # v2: 'claude' → 'claude "start"' (blank prompt — auto-sends first message)
266
+ ALIAS_VERSION=$(jq -r '.alias_version // 0' "$STATE_FILE" 2>/dev/null || echo "0")
267
+ if [ "$ALIAS_VERSION" -lt 2 ] 2>/dev/null; then
268
+ for profile in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile"; do
269
+ if [ -f "$profile" ] && grep -q "$SCRIPT_DIR" "$profile" 2>/dev/null; then
270
+ # Replace any egregore alias pointing to this directory with the correct command
271
+ # Handles: && claude start, && claude, && claude "start" (idempotent)
272
+ sed -i.bak "s|&& claude start|\\&\\& claude \"start\"|g; s|&& claude'|\\&\\& claude \"start\"'|g" "$profile" 2>/dev/null && rm -f "${profile}.bak" || true
273
+ fi
274
+ done
275
+ if [ -f "$STATE_FILE" ] && jq . "$STATE_FILE" >/dev/null 2>&1; then
276
+ jq '. + {"alias_fixed": true, "alias_version": 2}' "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
277
+ fi
278
+ fi
279
+
280
+ echo ""
281
+
282
+ # --- Session context (hidden, for Claude) ---
283
+ CONTEXT_HANDOFFS=$(cat "$CTX_DIR/handoffs" 2>/dev/null || echo "[]")
284
+ CONTEXT_ADDRESSED=$(cat "$CTX_DIR/addressed" 2>/dev/null || echo "[]")
285
+ CONTEXT_QUESTS=$(cat "$CTX_DIR/quests" 2>/dev/null || echo "[]")
286
+ CONTEXT_ACTIVITY=$(cat "$CTX_DIR/activity" 2>/dev/null || echo "")
287
+ CONTEXT_TEAM=$(cat "$CTX_DIR/team" 2>/dev/null || echo "[]")
288
+ CONTEXT_SOUL=$(cat "$CTX_DIR/soul_summary" 2>/dev/null || echo "")
289
+ CONTEXT_LIFECYCLE=$(cat "$CTX_DIR/lifecycle" 2>/dev/null || echo '{"merged_prs":[],"implemented_handoffs":[]}')
290
+ CONTEXT_PULSE=$(cat "$CTX_DIR/pulse_brief" 2>/dev/null || echo '{}')
291
+
292
+ # --- Write compact subagent context cache (reuses already-gathered data) ---
293
+ SUBAGENT_CTX="/tmp/egregore-subagent-ctx-${EGREGORE_SESSION_ID}.txt"
294
+ (
295
+ SA_ORG_NAME=$(jq -r '.org_name // "Unknown"' "$SCRIPT_DIR/egregore.json" 2>/dev/null || echo "Unknown")
296
+ SA_GITHUB_ORG=$(jq -r '.github_org // "unknown"' "$SCRIPT_DIR/egregore.json" 2>/dev/null || echo "unknown")
297
+
298
+ # Format quests list
299
+ SA_QUESTS="none"
300
+ if [ "$CONTEXT_QUESTS" != "[]" ]; then
301
+ SA_QUESTS=$(echo "$CONTEXT_QUESTS" | jq -r '.[]' 2>/dev/null | paste -sd', ' - 2>/dev/null || echo "none")
302
+ fi
303
+
304
+ # Format recent handoffs
305
+ SA_HANDOFFS="none"
306
+ if [ "$CONTEXT_HANDOFFS" != "[]" ]; then
307
+ SA_HANDOFFS=$(echo "$CONTEXT_HANDOFFS" | jq -r '.[] | .name' 2>/dev/null | head -5 | paste -sd', ' - 2>/dev/null || echo "none")
308
+ fi
309
+
310
+ cat > "$SUBAGENT_CTX" << SAEOF
311
+ <!-- egregore-context
312
+ Organization: $SA_ORG_NAME (github: $SA_GITHUB_ORG)
313
+ Session: $BRANCH — ${EGREGORE_SESSION_ID}
314
+ Active quests: $SA_QUESTS
315
+ Recent handoffs: $SA_HANDOFFS
316
+ Memory: memory/ is a symlink to shared knowledge base. Use bin/graph.sh for Neo4j queries.
317
+ -->
318
+ SAEOF
319
+ ) 2>/dev/null || true
320
+
321
+ cat << CTXEOF
322
+
323
+ <!-- session-context
324
+ {
325
+ "framework_version": "$FRAMEWORK_VERSION",
326
+ "time_of_day": "$TIME_OF_DAY",
327
+ "dashboard_url": "${DASHBOARD_URL:-}",
328
+ "recent_handoffs": $CONTEXT_HANDOFFS,
329
+ "addressed_to_user": $CONTEXT_ADDRESSED,
330
+ "quests": $CONTEXT_QUESTS,
331
+ "last_user_activity": "$CONTEXT_ACTIVITY",
332
+ "team_recent_memory": $CONTEXT_TEAM,
333
+ "soul_self_summary": "$CONTEXT_SOUL",
334
+ "lifecycle": $CONTEXT_LIFECYCLE,
335
+ "pulse": $CONTEXT_PULSE
336
+ }
337
+ -->
338
+ CTXEOF
339
+
340
+ # Include soul file if present
341
+ if [ -f "$SCRIPT_DIR/egregore.md" ]; then
342
+ echo ""
343
+ echo "<!-- egregore-soul"
344
+ cat "$SCRIPT_DIR/egregore.md"
345
+ echo "-->"
346
+ fi
347
+
348
+ # Include latest soul reflection if present
349
+ if [ -d "$SCRIPT_DIR/memory/soul" ]; then
350
+ LATEST_REFLECTION=$(ls -t "$SCRIPT_DIR/memory/soul/"*.md 2>/dev/null | head -1 || true)
351
+ if [ -n "$LATEST_REFLECTION" ]; then
352
+ echo "<!-- latest-reflection"
353
+ cat "$LATEST_REFLECTION"
354
+ echo "-->"
355
+ fi
356
+ fi
357
+
358
+ # --- Flush leftover telemetry from previous session (background) ---
359
+ # If the previous session exited without a clean flush (terminal closed,
360
+ # crash, kill -9), events are still in the local buffer. Drain them now.
361
+ bash "$SCRIPT_DIR/bin/telemetry.sh" flush 2>/dev/null &
362
+
363
+ # --- Emit session_start telemetry (background, non-blocking) ---
364
+ bash "$SCRIPT_DIR/bin/telemetry.sh" emit "session_start" \
365
+ "$(jq -n --arg branch "$BRANCH" '{branch: $branch}')" 2>/dev/null &
366
+
367
+ # --- Health check-in (background, non-blocking) ---
368
+ bash "$SCRIPT_DIR/bin/startup-check.sh" >/dev/null 2>&1 &
369
+
370
+ # --- Warm npx cache for egregore-artifacts (background, throttled) ---
371
+ # publish-artifact.sh spawns `npx egregore-artifacts` — first run in a
372
+ # fresh npx cache pays a 5-10s npm download. Warmup is throttled to
373
+ # once per hour via a marker file so we don't storm npm when a user
374
+ # opens many tabs, and don't start runaway retry loops when offline.
375
+ _WARM_TS_FILE="$HOME/.cache/egregore/npx-warm-ts"
376
+ mkdir -p "$(dirname "$_WARM_TS_FILE")" 2>/dev/null
377
+ _WARM_LAST=$(cat "$_WARM_TS_FILE" 2>/dev/null || echo 0)
378
+ _WARM_NOW=$(date +%s)
379
+ if [ $(( _WARM_NOW - _WARM_LAST )) -gt 3600 ]; then
380
+ (npx -y egregore-artifacts@latest --help >/dev/null 2>&1 \
381
+ && echo "$_WARM_NOW" > "$_WARM_TS_FILE" &) >/dev/null 2>&1
382
+ fi
383
+
384
+ # Clean up temp files
385
+ rm -rf "$CTX_DIR"
386
+
387
+ # --- First session welcome ---
388
+ if [ -z "$FIRST_SESSION" ] && [ -f "$STATE_FILE" ]; then
389
+ FIRST_SESSION=$(jq -r '.first_session // false' "$STATE_FILE" 2>/dev/null)
390
+ fi
391
+
392
+ if [ "$FIRST_SESSION" = "true" ]; then
393
+ echo ""
394
+ echo " Welcome! This is your first session."
395
+ echo ""
396
+ echo "IMPORTANT: Display the above greeting exactly as-is (ASCII art + ornamented status). Then ask the user if they'd like a quick onboarding tour (run /onboarding), or if they want to jump straight in."
397
+ # Clear the flag so it only shows once
398
+ jq '.first_session = false' "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
399
+ else
400
+ # --- Tutorial tip (if onboarding done but tutorial not) ---
401
+ TUTORIAL_COMPLETE="true"
402
+ if [ -f "$STATE_FILE" ]; then
403
+ TUTORIAL_COMPLETE=$(jq -r '.tutorial_complete // false' "$STATE_FILE" 2>/dev/null || echo "true")
404
+ fi
405
+
406
+ if [ "$TUTORIAL_COMPLETE" != "true" ]; then
407
+ echo " Tip: Run /tutorial to learn the core loop."
408
+ fi
409
+
410
+ echo ""
411
+ echo "IMPORTANT: Display the above greeting to the user exactly as-is (preserve the ASCII art formatting and ornamented status) on their first message. Then ask: What are you working on?"
412
+ echo ""
413
+ echo "BRANCH RULE: When the user responds with what they're working on, your FIRST action is to create a working branch: git fetch origin develop --quiet && git checkout -b dev/{author}/{topic-slug} origin/develop. Do this BEFORE any other work. Derive the topic slug from their description. If they ask a pure question with no work intent, skip branching."
414
+ fi
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+ # Cross-platform hash helpers — sourced by scripts that need hashing.
3
+
4
+ # Compute MD5 hash of a string (macOS + Linux compatible)
5
+ _md5() {
6
+ local input="$1"
7
+ echo -n "$input" | md5 2>/dev/null || echo -n "$input" | md5sum 2>/dev/null | cut -d' ' -f1
8
+ }
9
+
10
+ # Compute project hash (used for per-instance file isolation)
11
+ _proj_hash() {
12
+ _md5 "${SCRIPT_DIR:-$PWD}"
13
+ }
@@ -0,0 +1,164 @@
1
+ # shellcheck shell=bash
2
+ # identity.sh — User identity resolution for session-start.sh
3
+ #
4
+ # Resolves the current user's identity through multiple fallback layers:
5
+ # 1. .egregore-state.json github_username (stored during setup)
6
+ # 2. GitHub API auto-detect via GITHUB_TOKEN
7
+ # 3. Graph person lookup (existing team member on new machine)
8
+ # 4. Global git config user.name (last resort)
9
+ #
10
+ # Inputs: STATE_FILE, ENV_FILE, SCRIPT_DIR, HEALTH_GITHUB
11
+ # Outputs: AUTHOR, DISPLAY_NAME_STATE, FIRST_SESSION, HEALTH_GITHUB,
12
+ # EGREGORE_USER, EGREGORE_ORG, EGREGORE_SESSION_ID
13
+ # Writes .egregore-session-id and ~/.egregore/session-*.id
14
+
15
+ STORED_USERNAME=""
16
+ FIRST_SESSION=""
17
+ AUTHOR=""
18
+ if [ -f "$STATE_FILE" ]; then
19
+ STORED_USERNAME=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
20
+ fi
21
+
22
+ DISPLAY_NAME_STATE=""
23
+ if [ -f "$STATE_FILE" ]; then
24
+ DISPLAY_NAME_STATE=$(jq -r '.display_name // empty' "$STATE_FILE" 2>/dev/null)
25
+ fi
26
+
27
+ if [ -n "$STORED_USERNAME" ]; then
28
+ # Identity stored during setup — use it and ensure repo-local git config matches
29
+ AUTHOR="$STORED_USERNAME"
30
+ HEALTH_GITHUB="ok"
31
+ CURRENT_LOCAL=$(git config --local user.name 2>/dev/null || echo "")
32
+ if [ "$CURRENT_LOCAL" != "$STORED_USERNAME" ]; then
33
+ STORED_NAME=$(jq -r '.github_name // empty' "$STATE_FILE" 2>/dev/null)
34
+ git config user.name "${STORED_NAME:-$STORED_USERNAME}" 2>/dev/null || true
35
+ git config user.email "${STORED_USERNAME}@users.noreply.github.com" 2>/dev/null || true
36
+ fi
37
+ else
38
+ # No stored identity — try GitHub API to auto-detect (self-healing for pre-fix installs)
39
+ if [ -f "$ENV_FILE" ]; then
40
+ GH_TOKEN=$(grep '^GITHUB_TOKEN=' "$ENV_FILE" 2>/dev/null | cut -d'=' -f2-)
41
+ if [ -n "$GH_TOKEN" ]; then
42
+ GH_USER_JSON=$(curl -s -H "Authorization: token $GH_TOKEN" https://api.github.com/user --max-time 5 2>/dev/null || echo "")
43
+ GH_LOGIN=$(echo "$GH_USER_JSON" | jq -r '.login // empty' 2>/dev/null)
44
+ GH_NAME=$(echo "$GH_USER_JSON" | jq -r '.name // empty' 2>/dev/null)
45
+ if [ -n "$GH_LOGIN" ]; then
46
+ AUTHOR="$GH_LOGIN"
47
+ HEALTH_GITHUB="ok"
48
+ # Set repo-local config and save to state for next time
49
+ git config user.name "${GH_NAME:-$GH_LOGIN}" 2>/dev/null || true
50
+ git config user.email "${GH_LOGIN}@users.noreply.github.com" 2>/dev/null || true
51
+ # Save to state file so we don't need API call next time
52
+ # Include onboarding_complete + name so it doesn't re-trigger onboarding
53
+ # Determine if founder or joiner: if github_username != github_org, they're a joiner
54
+ GITHUB_ORG_CFG=$(jq -r '.github_org // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
55
+ if [ -n "$GITHUB_ORG_CFG" ] && [ "$GH_LOGIN" != "$GITHUB_ORG_CFG" ]; then
56
+ USAGE_TYPE="joiner_group"
57
+ else
58
+ USAGE_TYPE="founder_group"
59
+ fi
60
+ if [ -f "$STATE_FILE" ]; then
61
+ # Existing state file — update identity but preserve onboarding status
62
+ HAS_ONBOARDING=$(jq -r '.onboarding_complete // "unset"' "$STATE_FILE" 2>/dev/null)
63
+ if [ "$HAS_ONBOARDING" = "unset" ]; then
64
+ # State file exists but no onboarding flag — set to false to trigger it
65
+ jq --arg u "$GH_LOGIN" --arg n "${GH_NAME:-$GH_LOGIN}" --arg ut "$USAGE_TYPE" \
66
+ '.github_username = $u | .github_name = $n | .name = $n | .onboarding_complete = false | .usage_type = $ut' "$STATE_FILE" > "$STATE_FILE.tmp" \
67
+ && mv "$STATE_FILE.tmp" "$STATE_FILE"
68
+ else
69
+ jq --arg u "$GH_LOGIN" --arg n "${GH_NAME:-$GH_LOGIN}" --arg ut "$USAGE_TYPE" \
70
+ '.github_username = $u | .github_name = $n | .name = $n | .usage_type = $ut' "$STATE_FILE" > "$STATE_FILE.tmp" \
71
+ && mv "$STATE_FILE.tmp" "$STATE_FILE"
72
+ fi
73
+ FIRST_SESSION="false"
74
+ else
75
+ # No state file — check graph to see if this person already exists
76
+ # (handles fresh clone / new machine for existing team members)
77
+ GRAPH_PERSON=""
78
+ GRAPH_DISPLAY_NAME=""
79
+ GRAPH_PERSON=$(bash "$SCRIPT_DIR/bin/graph.sh" query \
80
+ "MATCH (p:Person {github: \$github}) RETURN p.name AS name" \
81
+ "$(jq -n --arg github "$GH_LOGIN" '{github: $github}')" 2>/dev/null || echo "")
82
+ if echo "$GRAPH_PERSON" | jq -e '.values | length > 0' &>/dev/null; then
83
+ GRAPH_DISPLAY_NAME=$(echo "$GRAPH_PERSON" | jq -r '.values[0][0] // empty' 2>/dev/null)
84
+ # Existing team member — skip onboarding
85
+ cat > "$STATE_FILE" << STATEEOF
86
+ {
87
+ "github_username": "$GH_LOGIN",
88
+ "github_name": "${GH_NAME:-$GH_LOGIN}",
89
+ "name": "${GRAPH_DISPLAY_NAME:-${GH_NAME:-$GH_LOGIN}}",
90
+ "display_name": "${GRAPH_DISPLAY_NAME:-}",
91
+ "onboarding_complete": true,
92
+ "usage_type": "$USAGE_TYPE",
93
+ "session_tracking": true,
94
+ "transcript_sharing": true,
95
+ "telemetry": true,
96
+ "contact_preference": "all",
97
+ "telemetry_noticed": true
98
+ }
99
+ STATEEOF
100
+ FIRST_SESSION="false"
101
+ else
102
+ # Genuinely new user — trigger onboarding
103
+ cat > "$STATE_FILE" << STATEEOF
104
+ {
105
+ "github_username": "$GH_LOGIN",
106
+ "github_name": "${GH_NAME:-$GH_LOGIN}",
107
+ "name": "${GH_NAME:-$GH_LOGIN}",
108
+ "onboarding_complete": false,
109
+ "usage_type": "$USAGE_TYPE",
110
+ "first_session": true
111
+ }
112
+ STATEEOF
113
+ FIRST_SESSION="true"
114
+ fi
115
+ fi
116
+ fi
117
+ fi
118
+ fi
119
+
120
+ # Final fallback: git config user.name (global or local)
121
+ if [ -z "$AUTHOR" ]; then
122
+ FULLNAME=$(git config user.name 2>/dev/null || echo "")
123
+ AUTHOR=$(echo "$FULLNAME" | tr '[:upper:]' '[:lower:]' | cut -d' ' -f1)
124
+ fi
125
+ fi
126
+
127
+ if [ -z "$AUTHOR" ]; then
128
+ AUTHOR="unknown"
129
+ HEALTH_GITHUB="fail"
130
+ fi
131
+
132
+ # --- Validate gh CLI identity matches git identity ---
133
+ # The gh CLI has its own auth (keyring) separate from .env GITHUB_TOKEN.
134
+ # If they diverge, PRs get created under the wrong account.
135
+ if command -v gh &>/dev/null; then
136
+ GH_CLI_USER=$(gh api user --jq '.login' 2>/dev/null || true)
137
+ if [ -n "$GH_CLI_USER" ] && [ -n "$AUTHOR" ] && [ "$AUTHOR" != "unknown" ]; then
138
+ AUTHOR_LC=$(echo "$AUTHOR" | tr '[:upper:]' '[:lower:]')
139
+ GH_CLI_LC=$(echo "$GH_CLI_USER" | tr '[:upper:]' '[:lower:]')
140
+ if [ "$AUTHOR_LC" != "$GH_CLI_LC" ]; then
141
+ echo ""
142
+ echo "WARNING: Identity mismatch detected."
143
+ echo " Git identity (.env): $AUTHOR"
144
+ echo " gh CLI (active): $GH_CLI_USER"
145
+ echo " PRs will be created as '$GH_CLI_USER', not '$AUTHOR'."
146
+ echo " Fix: gh auth switch --user $AUTHOR"
147
+ echo ""
148
+ fi
149
+ fi
150
+ fi
151
+
152
+ # --- Export telemetry identity env vars ---
153
+ export EGREGORE_USER="$AUTHOR"
154
+ export EGREGORE_ORG="$(jq -r '.slug // .github_org // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null || true)"
155
+ export EGREGORE_SESSION_ID="$(date -u +%Y%m%dT%H%M%S)-${AUTHOR}-$$"
156
+
157
+ # Persist session ID to file so telemetry.sh can read it
158
+ # (env vars from hooks don't propagate into the Claude Code agent)
159
+ SESSION_ID_DIR="$HOME/.egregore"
160
+ mkdir -p "$SESSION_ID_DIR"
161
+ PROJ_HASH=$(echo -n "$SCRIPT_DIR" | md5 2>/dev/null || echo -n "$SCRIPT_DIR" | md5sum 2>/dev/null | cut -d' ' -f1)
162
+ echo "$EGREGORE_SESSION_ID" > "$SESSION_ID_DIR/session-${PROJ_HASH}.id"
163
+ # Also write project-local file (CLAUDE.md reads this — no glob needed)
164
+ echo "$EGREGORE_SESSION_ID" > "$SCRIPT_DIR/.egregore-session-id"