create-egregore 0.17.4 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) 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 +11 -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/comment/SKILL.md +25 -0
  25. package/runtime/codex/.codex/skills/commit/SKILL.md +25 -0
  26. package/runtime/codex/.codex/skills/contribute/SKILL.md +25 -0
  27. package/runtime/codex/.codex/skills/dark-mode/SKILL.md +25 -0
  28. package/runtime/codex/.codex/skills/dashboard/SKILL.md +59 -0
  29. package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +70 -0
  30. package/runtime/codex/.codex/skills/delete-user/SKILL.md +25 -0
  31. package/runtime/codex/.codex/skills/edit/SKILL.md +25 -0
  32. package/runtime/codex/.codex/skills/edit-process/SKILL.md +25 -0
  33. package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +25 -0
  34. package/runtime/codex/.codex/skills/emissary/SKILL.md +41 -0
  35. package/runtime/codex/.codex/skills/env/SKILL.md +25 -0
  36. package/runtime/codex/.codex/skills/handoff/SKILL.md +67 -0
  37. package/runtime/codex/.codex/skills/harvest/SKILL.md +107 -0
  38. package/runtime/codex/.codex/skills/infra/SKILL.md +41 -0
  39. package/runtime/codex/.codex/skills/ingest/SKILL.md +25 -0
  40. package/runtime/codex/.codex/skills/ingest-google/SKILL.md +25 -0
  41. package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +41 -0
  42. package/runtime/codex/.codex/skills/invite/SKILL.md +40 -0
  43. package/runtime/codex/.codex/skills/issue/SKILL.md +41 -0
  44. package/runtime/codex/.codex/skills/me/SKILL.md +25 -0
  45. package/runtime/codex/.codex/skills/meeting/SKILL.md +41 -0
  46. package/runtime/codex/.codex/skills/note/SKILL.md +25 -0
  47. package/runtime/codex/.codex/skills/onboarding/SKILL.md +41 -0
  48. package/runtime/codex/.codex/skills/pr/SKILL.md +25 -0
  49. package/runtime/codex/.codex/skills/project/SKILL.md +41 -0
  50. package/runtime/codex/.codex/skills/pull/SKILL.md +25 -0
  51. package/runtime/codex/.codex/skills/push/SKILL.md +25 -0
  52. package/runtime/codex/.codex/skills/quest/SKILL.md +77 -0
  53. package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +25 -0
  54. package/runtime/codex/.codex/skills/reflect/SKILL.md +41 -0
  55. package/runtime/codex/.codex/skills/review-pr/SKILL.md +41 -0
  56. package/runtime/codex/.codex/skills/save/SKILL.md +62 -0
  57. package/runtime/codex/.codex/skills/setup/SKILL.md +25 -0
  58. package/runtime/codex/.codex/skills/summon/SKILL.md +41 -0
  59. package/runtime/codex/.codex/skills/sync-repos/SKILL.md +25 -0
  60. package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +25 -0
  61. package/runtime/codex/.codex/skills/telemetry/SKILL.md +25 -0
  62. package/runtime/codex/.codex/skills/test/SKILL.md +41 -0
  63. package/runtime/codex/.codex/skills/the-spiral/SKILL.md +84 -0
  64. package/runtime/codex/.codex/skills/todo/SKILL.md +41 -0
  65. package/runtime/codex/.codex/skills/tui-design/SKILL.md +41 -0
  66. package/runtime/codex/.codex/skills/tutorial/SKILL.md +41 -0
  67. package/runtime/codex/.codex/skills/update/SKILL.md +25 -0
  68. package/runtime/codex/.codex/skills/view/SKILL.md +41 -0
  69. package/runtime/codex/.codex/skills/visual-explain/SKILL.md +41 -0
  70. package/runtime/codex/.codex/skills/wrap/SKILL.md +55 -0
  71. package/runtime/codex/.codex/spec-manifest.json +108 -0
  72. package/runtime/codex/AGENTS.md +328 -0
  73. package/runtime/codex/bin/activity-data.sh +180 -0
  74. package/runtime/codex/bin/agent.sh +669 -0
  75. package/runtime/codex/bin/codex-install-skills.sh +78 -0
  76. package/runtime/codex/bin/codex-render-spec.mjs +308 -0
  77. package/runtime/codex/bin/codex-session-start.sh +238 -0
  78. package/runtime/codex/bin/codex-skill-render.mjs +315 -0
  79. package/runtime/codex/bin/codex-sync-skills.sh +223 -0
  80. package/runtime/codex/bin/dashboard-data.sh +178 -0
  81. package/runtime/codex/bin/graph-batch.sh +60 -0
  82. package/runtime/codex/bin/graph-op.sh +397 -0
  83. package/runtime/codex/bin/graph-wal.sh +228 -0
  84. package/runtime/codex/bin/graph.sh +141 -0
  85. package/runtime/codex/bin/handoff-pr-backfill.sh +86 -0
  86. package/runtime/codex/bin/handoff-run.sh +569 -0
  87. package/runtime/codex/bin/handoff-save-egregore.sh +131 -0
  88. package/runtime/codex/bin/index-handoff.sh +306 -0
  89. package/runtime/codex/bin/lib/artifact-id.sh +46 -0
  90. package/runtime/codex/bin/lib/config.sh +71 -0
  91. package/runtime/codex/bin/lib/context.sh +511 -0
  92. package/runtime/codex/bin/lib/dashboard-artifact.sh +142 -0
  93. package/runtime/codex/bin/lib/edit-loopback.mjs +652 -0
  94. package/runtime/codex/bin/lib/ensure-develop.sh +183 -0
  95. package/runtime/codex/bin/lib/git-sync.sh +262 -0
  96. package/runtime/codex/bin/lib/greeting.sh +432 -0
  97. package/runtime/codex/bin/lib/hash.sh +13 -0
  98. package/runtime/codex/bin/lib/identity.sh +164 -0
  99. package/runtime/codex/bin/lib/permissions.sh +106 -0
  100. package/runtime/codex/bin/lib/time.sh +56 -0
  101. package/runtime/codex/bin/lib/worktree-links.sh +54 -0
  102. package/runtime/codex/bin/notify.sh +207 -0
  103. package/runtime/codex/bin/publish-artifact.sh +187 -0
  104. package/runtime/codex/bin/publish-references.sh +144 -0
  105. package/runtime/codex/bin/repo-state.sh +99 -0
  106. package/runtime/codex/bin/telemetry.sh +350 -0
  107. package/runtime/codex/bin/worktree-create.sh +115 -0
  108. package/runtime/codex/bin/worktree-remove.sh +50 -0
  109. package/runtime/codex/bin/worktree.sh +199 -0
  110. package/runtime/codex/manifest.json +162 -0
@@ -0,0 +1,432 @@
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
+ # Pending questions addressed to you (one summary line — /answer to engage)
116
+ PENDING_Q=$(cat "$CTX_DIR/pending_questions" 2>/dev/null || echo "[]")
117
+ PENDING_Q_COUNT=$(echo "$PENDING_Q" | jq 'length' 2>/dev/null || echo "0")
118
+ if [ "$PENDING_Q_COUNT" -gt 0 ] 2>/dev/null && [ "$PENDING_Q_COUNT" != "0" ]; then
119
+ PQ_SENDERS=$(echo "$PENDING_Q" | jq -r '[.[].from] | unique | join(", ")' 2>/dev/null)
120
+ if [ "$PENDING_Q_COUNT" = "1" ]; then
121
+ PQ_NOUN="question"
122
+ else
123
+ PQ_NOUN="questions"
124
+ fi
125
+ if [ -n "$PQ_SENDERS" ]; then
126
+ printf " ◐ %s pending %s from %s — /answer to engage\n" "$PENDING_Q_COUNT" "$PQ_NOUN" "$PQ_SENDERS"
127
+ else
128
+ printf " ◐ %s pending %s — /answer to engage\n" "$PENDING_Q_COUNT" "$PQ_NOUN"
129
+ fi
130
+ fi
131
+
132
+ # Fallback: last activity if no handoffs and no pending questions
133
+ if { [ "$ADDRESSED_COUNT" = "0" ] || [ -z "$ADDRESSED_COUNT" ]; } \
134
+ && { [ "$PENDING_Q_COUNT" = "0" ] || [ -z "$PENDING_Q_COUNT" ]; }; then
135
+ LAST_ACTIVITY=$(cat "$CTX_DIR/activity" 2>/dev/null || echo "")
136
+ if [ -n "$LAST_ACTIVITY" ]; then
137
+ LA_TIME=$(echo "$LAST_ACTIVITY" | cut -d'|' -f1)
138
+ # Compact relative time: "20 hours ago" → "20h ago", "3 days ago" → "3d ago"
139
+ 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/')
140
+ LA_MSG=$(echo "$LAST_ACTIVITY" | cut -d'|' -f2-)
141
+ if [ ${#LA_MSG} -gt $MAX_INFO ]; then LA_MSG="${LA_MSG:0:$((MAX_INFO - 1))}…"; fi
142
+ printf " ◇ %-9s%-12s%s\n" "you" "$LA_TIME" "$LA_MSG"
143
+ fi
144
+ fi
145
+
146
+ echo ""
147
+ # --- Section 2: Around (team presence with online status) ---
148
+ if [ "$TEAM_DATA_COUNT" -gt 0 ] 2>/dev/null && [ "$TEAM_DATA_COUNT" != "0" ]; then
149
+ echo " ◦ around"
150
+ # Presence roster with online/offline dots (hide inactive >3 days)
151
+ STALE_THRESHOLD=$((RECENCY_DAYS * 86400))
152
+ 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
153
+ [ -z "$P_NAME" ] && continue
154
+ # Skip if no recent activity (within recency window)
155
+ if [ "$P_EPOCH" -le 0 ] 2>/dev/null; then
156
+ continue
157
+ elif [ $(( NOW_RENDER - P_EPOCH )) -ge $STALE_THRESHOLD ] 2>/dev/null; then
158
+ continue
159
+ fi
160
+ if [ ${#P_NAME} -gt 8 ]; then P_NAME="${P_NAME:0:8}"; fi
161
+ # Online: last seen within threshold
162
+ if [ $(( NOW_RENDER - P_EPOCH )) -lt $ONLINE_THRESHOLD ] 2>/dev/null; then
163
+ DOT="●"
164
+ else
165
+ DOT="○"
166
+ fi
167
+ if [ ${#P_BRANCHES} -gt $MAX_INFO ]; then
168
+ P_BRANCHES="${P_BRANCHES:0:$((MAX_INFO - 9))}... +more"
169
+ fi
170
+ printf " %s %-9s%-12s%s\n" "$DOT" "$P_NAME" "$P_SEEN" "$P_BRANCHES"
171
+ done
172
+ fi
173
+
174
+ # --- Section 3: Recently merged PRs + implemented handoffs ---
175
+ LIFECYCLE_JSON=$(cat "$CTX_DIR/lifecycle" 2>/dev/null || echo '{}')
176
+ MERGED_COUNT=$(echo "$LIFECYCLE_JSON" | jq '.merged_prs.values // [] | length' 2>/dev/null || echo "0")
177
+ IMPL_COUNT=$(echo "$LIFECYCLE_JSON" | jq '.implemented_handoffs.values // [] | length' 2>/dev/null || echo "0")
178
+
179
+ if [ "$MERGED_COUNT" -gt 0 ] 2>/dev/null || [ "$IMPL_COUNT" -gt 0 ] 2>/dev/null; then
180
+ echo ""
181
+ echo " ◦ merged"
182
+ if [ "$MERGED_COUNT" -gt 0 ] 2>/dev/null; then
183
+ echo "$LIFECYCLE_JSON" | jq -r '.merged_prs.values[]? // empty | " ✓ PR #\(.[0]) \(.[1])"' 2>/dev/null || true
184
+ fi
185
+ if [ "$IMPL_COUNT" -gt 0 ] 2>/dev/null; then
186
+ echo "$LIFECYCLE_JSON" | jq -r '.implemented_handoffs.values[]? // empty | " ✓ \(.[1]) worked on your handoff"' 2>/dev/null || true
187
+ fi
188
+ fi
189
+
190
+ # Show auto-save notice if work was committed from a previous branch
191
+ if [ -n "$SAVED_BRANCH" ]; then
192
+ echo " ✓ Auto-saved uncommitted work on $SAVED_BRANCH"
193
+ fi
194
+
195
+ # --- Footer: health + repos + memory (tertiary) ---
196
+ echo "$SEPARATOR"
197
+
198
+ # Build compact footer line
199
+ if [ "$LOCAL_MODE" = "true" ]; then
200
+ # Local mode: only check github + git, skip api-key/graph/telegram
201
+ HAS_FAILURE="false"
202
+ FAILED_SERVICES=""
203
+ for pair in "github:$HEALTH_GITHUB" "git:$HEALTH_GIT"; do
204
+ svc="${pair%%:*}"
205
+ svc_status="${pair#*:}"
206
+ if [ "$svc_status" = "fail" ]; then
207
+ HAS_FAILURE="true"
208
+ FAILED_SERVICES="${FAILED_SERVICES} ${svc} ✗"
209
+ fi
210
+ done
211
+
212
+ if [ "$HAS_FAILURE" = "true" ]; then
213
+ echo " ⚠${FAILED_SERVICES} — run /checkup"
214
+ else
215
+ if [ "${FRAMEWORK_UPDATED:-false}" = "true" ]; then
216
+ printf " ◆ updated\n"
217
+ else
218
+ printf " ✓ ready\n"
219
+ fi
220
+
221
+ # Managed repos on separate line
222
+ if [ -n "$REPOS_STATUS" ]; then
223
+ REPOS_COMPACT=$(printf '%s' "$REPOS_STATUS" | sed 's/^ ◇ //;s/^[[:space:]]*//' | paste -sd' ' - | sed 's/[[:space:]]*$//')
224
+ if [ -n "$REPOS_COMPACT" ]; then
225
+ printf " %s\n" "$REPOS_COMPACT"
226
+ fi
227
+ fi
228
+ fi
229
+ else
230
+ # Connected mode: check all services
231
+ HAS_FAILURE="false"
232
+ FAILED_SERVICES=""
233
+ for pair in "github:$HEALTH_GITHUB" "git:$HEALTH_GIT" "api-key:$HEALTH_APIKEY" "graph:$HEALTH_GRAPH" "telegram:$HEALTH_TELEGRAM"; do
234
+ svc="${pair%%:*}"
235
+ svc_status="${pair#*:}"
236
+ if [ "$svc_status" = "fail" ]; then
237
+ HAS_FAILURE="true"
238
+ FAILED_SERVICES="${FAILED_SERVICES} ${svc} ✗"
239
+ fi
240
+ done
241
+
242
+ if [ "$HAS_FAILURE" = "true" ]; then
243
+ echo " ⚠${FAILED_SERVICES} — run /checkup"
244
+ else
245
+ # Compact footer: ready + memory on one line, repos on next if present
246
+ FOOTER_LEFT=" ✓ ready"
247
+ FOOTER_RIGHT=""
248
+ if [ "$MEMORY_SYNCED" = "true" ]; then
249
+ FOOTER_RIGHT="◆ memory synced"
250
+ fi
251
+
252
+ FL_LEN=${#FOOTER_LEFT}
253
+ FR_LEN=${#FOOTER_RIGHT}
254
+ F_PAD=$((LINE_WIDTH - FL_LEN - FR_LEN))
255
+ if [ "$F_PAD" -lt 1 ]; then F_PAD=1; fi
256
+ printf "%s%*s%s\n" "$FOOTER_LEFT" "$F_PAD" "" "$FOOTER_RIGHT"
257
+
258
+ # Managed repos on separate line (avoids line overflow)
259
+ if [ -n "$REPOS_STATUS" ]; then
260
+ REPOS_COMPACT=$(printf '%s' "$REPOS_STATUS" | sed 's/^ ◇ //;s/^[[:space:]]*//' | paste -sd' ' - | sed 's/[[:space:]]*$//')
261
+ if [ -n "$REPOS_COMPACT" ]; then
262
+ printf " %s\n" "$REPOS_COMPACT"
263
+ fi
264
+ fi
265
+ fi
266
+ fi
267
+
268
+ # Dashboard link (OSC 8 hyperlink — clickable text in supported terminals)
269
+ if [ -n "${DASHBOARD_URL:-}" ]; then
270
+ printf " ◆ %s\n" "$DASHBOARD_URL"
271
+ fi
272
+
273
+ if [ -n "${BOARD_URL:-}" ]; then
274
+ printf " ◆ %s (board)\n" "$BOARD_URL"
275
+ fi
276
+
277
+ # Framework updates come through PRs to develop — no separate auto-update channel.
278
+ # The develop sync above already keeps framework files current.
279
+ # Use /update for manual upstream pulls when needed.
280
+
281
+ # --- One-time migration: fix aliases to use 'claude "start"' ---
282
+ # v1: 'claude start' → 'claude' (cross-instance bug)
283
+ # v2: 'claude' → 'claude "start"' (blank prompt — auto-sends first message)
284
+ ALIAS_VERSION=$(jq -r '.alias_version // 0' "$STATE_FILE" 2>/dev/null || echo "0")
285
+ if [ "$ALIAS_VERSION" -lt 2 ] 2>/dev/null; then
286
+ for profile in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile"; do
287
+ if [ -f "$profile" ] && grep -q "$SCRIPT_DIR" "$profile" 2>/dev/null; then
288
+ # Replace any egregore alias pointing to this directory with the correct command
289
+ # Handles: && claude start, && claude, && claude "start" (idempotent)
290
+ sed -i.bak "s|&& claude start|\\&\\& claude \"start\"|g; s|&& claude'|\\&\\& claude \"start\"'|g" "$profile" 2>/dev/null && rm -f "${profile}.bak" || true
291
+ fi
292
+ done
293
+ if [ -f "$STATE_FILE" ] && jq . "$STATE_FILE" >/dev/null 2>&1; then
294
+ jq '. + {"alias_fixed": true, "alias_version": 2}' "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
295
+ fi
296
+ fi
297
+
298
+ echo ""
299
+
300
+ # --- Session context (hidden, for Claude) ---
301
+ CONTEXT_HANDOFFS=$(cat "$CTX_DIR/handoffs" 2>/dev/null || echo "[]")
302
+ CONTEXT_ADDRESSED=$(cat "$CTX_DIR/addressed" 2>/dev/null || echo "[]")
303
+ CONTEXT_QUESTS=$(cat "$CTX_DIR/quests" 2>/dev/null || echo "[]")
304
+ CONTEXT_ACTIVITY=$(cat "$CTX_DIR/activity" 2>/dev/null || echo "")
305
+ CONTEXT_TEAM=$(cat "$CTX_DIR/team" 2>/dev/null || echo "[]")
306
+ CONTEXT_SOUL=$(cat "$CTX_DIR/soul_summary" 2>/dev/null || echo "")
307
+ CONTEXT_LIFECYCLE=$(cat "$CTX_DIR/lifecycle" 2>/dev/null || echo '{"merged_prs":[],"implemented_handoffs":[]}')
308
+ CONTEXT_PULSE=$(cat "$CTX_DIR/pulse_brief" 2>/dev/null || echo '{}')
309
+
310
+ # --- Write compact subagent context cache (reuses already-gathered data) ---
311
+ SUBAGENT_CTX="/tmp/egregore-subagent-ctx-${EGREGORE_SESSION_ID}.txt"
312
+ (
313
+ SA_ORG_NAME=$(jq -r '.org_name // "Unknown"' "$SCRIPT_DIR/egregore.json" 2>/dev/null || echo "Unknown")
314
+ SA_GITHUB_ORG=$(jq -r '.github_org // "unknown"' "$SCRIPT_DIR/egregore.json" 2>/dev/null || echo "unknown")
315
+
316
+ # Format quests list
317
+ SA_QUESTS="none"
318
+ if [ "$CONTEXT_QUESTS" != "[]" ]; then
319
+ SA_QUESTS=$(echo "$CONTEXT_QUESTS" | jq -r '.[]' 2>/dev/null | paste -sd', ' - 2>/dev/null || echo "none")
320
+ fi
321
+
322
+ # Format recent handoffs
323
+ SA_HANDOFFS="none"
324
+ if [ "$CONTEXT_HANDOFFS" != "[]" ]; then
325
+ SA_HANDOFFS=$(echo "$CONTEXT_HANDOFFS" | jq -r '.[] | .name' 2>/dev/null | head -5 | paste -sd', ' - 2>/dev/null || echo "none")
326
+ fi
327
+
328
+ cat > "$SUBAGENT_CTX" << SAEOF
329
+ <!-- egregore-context
330
+ Organization: $SA_ORG_NAME (github: $SA_GITHUB_ORG)
331
+ Session: $BRANCH — ${EGREGORE_SESSION_ID}
332
+ Active quests: $SA_QUESTS
333
+ Recent handoffs: $SA_HANDOFFS
334
+ Memory: memory/ is a symlink to shared knowledge base. Use bin/graph.sh for Neo4j queries.
335
+ -->
336
+ SAEOF
337
+ ) 2>/dev/null || true
338
+
339
+ cat << CTXEOF
340
+
341
+ <!-- session-context
342
+ {
343
+ "framework_version": "$FRAMEWORK_VERSION",
344
+ "time_of_day": "$TIME_OF_DAY",
345
+ "dashboard_url": "${DASHBOARD_URL:-}",
346
+ "recent_handoffs": $CONTEXT_HANDOFFS,
347
+ "addressed_to_user": $CONTEXT_ADDRESSED,
348
+ "quests": $CONTEXT_QUESTS,
349
+ "last_user_activity": "$CONTEXT_ACTIVITY",
350
+ "team_recent_memory": $CONTEXT_TEAM,
351
+ "soul_self_summary": "$CONTEXT_SOUL",
352
+ "lifecycle": $CONTEXT_LIFECYCLE,
353
+ "pulse": $CONTEXT_PULSE
354
+ }
355
+ -->
356
+ CTXEOF
357
+
358
+ # Include soul file if present
359
+ if [ -f "$SCRIPT_DIR/egregore.md" ]; then
360
+ echo ""
361
+ echo "<!-- egregore-soul"
362
+ cat "$SCRIPT_DIR/egregore.md"
363
+ echo "-->"
364
+ fi
365
+
366
+ # Include latest soul reflection if present
367
+ if [ -d "$SCRIPT_DIR/memory/soul" ]; then
368
+ LATEST_REFLECTION=$(ls -t "$SCRIPT_DIR/memory/soul/"*.md 2>/dev/null | head -1 || true)
369
+ if [ -n "$LATEST_REFLECTION" ]; then
370
+ echo "<!-- latest-reflection"
371
+ cat "$LATEST_REFLECTION"
372
+ echo "-->"
373
+ fi
374
+ fi
375
+
376
+ # --- Flush leftover telemetry from previous session (background) ---
377
+ # If the previous session exited without a clean flush (terminal closed,
378
+ # crash, kill -9), events are still in the local buffer. Drain them now.
379
+ bash "$SCRIPT_DIR/bin/telemetry.sh" flush 2>/dev/null &
380
+
381
+ # --- Emit session_start telemetry (background, non-blocking) ---
382
+ bash "$SCRIPT_DIR/bin/telemetry.sh" emit "session_start" \
383
+ "$(jq -n --arg branch "$BRANCH" '{branch: $branch}')" 2>/dev/null &
384
+
385
+ # --- Health check-in (background, non-blocking) ---
386
+ bash "$SCRIPT_DIR/bin/startup-check.sh" >/dev/null 2>&1 &
387
+
388
+ # --- Warm npx cache for egregore-artifacts (background, throttled) ---
389
+ # publish-artifact.sh spawns `npx egregore-artifacts` — first run in a
390
+ # fresh npx cache pays a 5-10s npm download. Warmup is throttled to
391
+ # once per hour via a marker file so we don't storm npm when a user
392
+ # opens many tabs, and don't start runaway retry loops when offline.
393
+ _WARM_TS_FILE="$HOME/.cache/egregore/npx-warm-ts"
394
+ mkdir -p "$(dirname "$_WARM_TS_FILE")" 2>/dev/null
395
+ _WARM_LAST=$(cat "$_WARM_TS_FILE" 2>/dev/null || echo 0)
396
+ _WARM_NOW=$(date +%s)
397
+ if [ $(( _WARM_NOW - _WARM_LAST )) -gt 3600 ]; then
398
+ (npx -y egregore-artifacts@latest --help >/dev/null 2>&1 \
399
+ && echo "$_WARM_NOW" > "$_WARM_TS_FILE" &) >/dev/null 2>&1
400
+ fi
401
+
402
+ # Clean up temp files
403
+ rm -rf "$CTX_DIR"
404
+
405
+ # --- First session welcome ---
406
+ if [ -z "$FIRST_SESSION" ] && [ -f "$STATE_FILE" ]; then
407
+ FIRST_SESSION=$(jq -r '.first_session // false' "$STATE_FILE" 2>/dev/null)
408
+ fi
409
+
410
+ if [ "$FIRST_SESSION" = "true" ]; then
411
+ echo ""
412
+ echo " Welcome! This is your first session."
413
+ echo ""
414
+ 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."
415
+ # Clear the flag so it only shows once
416
+ jq '.first_session = false' "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
417
+ else
418
+ # --- Tutorial tip (if onboarding done but tutorial not) ---
419
+ TUTORIAL_COMPLETE="true"
420
+ if [ -f "$STATE_FILE" ]; then
421
+ TUTORIAL_COMPLETE=$(jq -r '.tutorial_complete // false' "$STATE_FILE" 2>/dev/null || echo "true")
422
+ fi
423
+
424
+ if [ "$TUTORIAL_COMPLETE" != "true" ]; then
425
+ echo " Tip: Run /tutorial to learn the core loop."
426
+ fi
427
+
428
+ echo ""
429
+ 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?"
430
+ echo ""
431
+ 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."
432
+ 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"