create-egregore 0.19.33 → 0.20.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.
- package/assets/egregore-launcher.js +118 -13
- package/bin/cli.js +19 -3
- package/lib/connect-runtime.js +4 -2
- package/lib/local.js +15 -1
- package/lib/prime-runtime.js +208 -0
- package/lib/setup.js +45 -3
- package/package.json +4 -3
- package/runtime/codex/.codex/skills/ingest-notion/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/notion-connect/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/codex/.codex/spec-manifest.json +3 -3
- package/runtime/codex/AGENTS.md +11 -4
- package/runtime/codex/bin/activity-data.sh +4 -8
- package/runtime/codex/bin/attendant.sh +13 -0
- package/runtime/codex/bin/autosave.sh +27 -1
- package/runtime/codex/bin/codex-render-spec.mjs +6 -0
- package/runtime/codex/bin/codex-session-start.sh +3 -1
- package/runtime/codex/bin/graph-batch.sh +25 -6
- package/runtime/codex/bin/graph-op.sh +5 -4
- package/runtime/codex/bin/graph.sh +31 -5
- package/runtime/codex/bin/ingest.py +81 -5
- package/runtime/codex/bin/lib/context.sh +26 -1
- package/runtime/codex/bin/lib/greeting.sh +4 -1
- package/runtime/codex/bin/lib/worktree-links.sh +25 -0
- package/runtime/codex/manifest.json +7 -1
- package/runtime/pi/.codex/skills/ingest-notion/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/notion-connect/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/pi/.codex/spec-manifest.json +3 -3
- package/runtime/pi/.pi/APPEND_SYSTEM.md +3 -2
- package/runtime/pi/.pi/extensions/egregore.ts +17 -6
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +11 -4
- package/runtime/pi/bin/activity-data.sh +4 -8
- package/runtime/pi/bin/attendant.sh +13 -0
- package/runtime/pi/bin/autosave.sh +27 -1
- package/runtime/pi/bin/codex-render-spec.mjs +6 -0
- package/runtime/pi/bin/codex-session-start.sh +3 -1
- package/runtime/pi/bin/graph-batch.sh +25 -6
- package/runtime/pi/bin/graph-op.sh +5 -4
- package/runtime/pi/bin/graph.sh +31 -5
- package/runtime/pi/bin/ingest.py +81 -5
- package/runtime/pi/bin/lib/context.sh +26 -1
- package/runtime/pi/bin/lib/greeting.sh +4 -1
- package/runtime/pi/bin/lib/worktree-links.sh +25 -0
- package/runtime/pi/bin/pi-branch-policy.mjs +138 -1
- package/runtime/pi/bin/pi-product-workflows.mjs +35 -7
- package/runtime/pi/manifest.json +7 -1
- package/runtime/prime/.claude/context/notification-consent.md +66 -0
- package/runtime/prime/.codex/config.toml +17 -0
- package/runtime/prime/.codex/hooks/branch-guard.js +461 -0
- package/runtime/prime/.codex/hooks/search-hint.js +67 -0
- package/runtime/prime/.codex/hooks.json +30 -0
- package/runtime/prime/.codex/skills/activity/SKILL.md +82 -0
- package/runtime/prime/.codex/skills/announce/SKILL.md +67 -0
- package/runtime/prime/.codex/skills/archive/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/branch/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/checkup/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/commit/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/contribute/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/create-skill/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/dark-mode/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/dashboard/SKILL.md +59 -0
- package/runtime/prime/.codex/skills/deep-reflect/SKILL.md +109 -0
- package/runtime/prime/.codex/skills/delete-user/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/emissary/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/handoff/SKILL.md +166 -0
- package/runtime/prime/.codex/skills/harvest/QUESTION_PALETTE.md +76 -0
- package/runtime/prime/.codex/skills/harvest/SKILL.md +131 -0
- package/runtime/prime/.codex/skills/ingest/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/ingest-notion/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/ingest-user-interview/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/invite/SKILL.md +44 -0
- package/runtime/prime/.codex/skills/issue/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/me/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/meeting/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/note/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/notion-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/onboarding/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/pr/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/pull/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/push/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/quest/SKILL.md +80 -0
- package/runtime/prime/.codex/skills/reflect/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/review-pr/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/save/SKILL.md +125 -0
- package/runtime/prime/.codex/skills/slack-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/summon/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/sync-repos/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/telegram-connect/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/telemetry/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/test/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/the-spiral/SKILL.md +84 -0
- package/runtime/prime/.codex/skills/tui-design/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/tutorial/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/update/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/view/SKILL.md +156 -0
- package/runtime/prime/.codex/skills/view/agents/openai.yaml +4 -0
- package/runtime/prime/.codex/skills/view/assets/compose-scaffold.html +221 -0
- package/runtime/prime/.codex/skills/visual-explain/SKILL.md +45 -0
- package/runtime/prime/.codex/skills/wrap/SKILL.md +58 -0
- package/runtime/prime/.codex/spec-manifest.json +132 -0
- package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +393 -0
- package/runtime/prime/.prime/agent/extensions/egregore.ts +460 -0
- package/runtime/prime/.prime/agent/settings.json +4 -0
- package/runtime/prime/.prime/agent/skills/egregore/SKILL.md +34 -0
- package/runtime/prime/.prime/agent/skills/egregore/pyproject.toml +13 -0
- package/runtime/prime/.prime/agent/skills/egregore/src/egregore/__init__.py +156 -0
- package/runtime/prime/.prime/agent/spec-manifest.json +12 -0
- package/runtime/prime/AGENTS.md +489 -0
- package/runtime/prime/bin/activity-data.sh +238 -0
- package/runtime/prime/bin/agent.sh +775 -0
- package/runtime/prime/bin/attendant.sh +707 -0
- package/runtime/prime/bin/autosave.sh +112 -0
- package/runtime/prime/bin/capture-reconcile.sh +69 -0
- package/runtime/prime/bin/capture-run.sh +341 -0
- package/runtime/prime/bin/codex-install-skills.sh +78 -0
- package/runtime/prime/bin/codex-render-spec.mjs +440 -0
- package/runtime/prime/bin/codex-session-start.sh +265 -0
- package/runtime/prime/bin/codex-skill-render.mjs +347 -0
- package/runtime/prime/bin/codex-sync-skills.sh +232 -0
- package/runtime/prime/bin/connect-refresh.sh +33 -0
- package/runtime/prime/bin/dashboard-data.sh +178 -0
- package/runtime/prime/bin/graph-batch.sh +79 -0
- package/runtime/prime/bin/graph-op.sh +1102 -0
- package/runtime/prime/bin/graph-wal.sh +229 -0
- package/runtime/prime/bin/graph.sh +223 -0
- package/runtime/prime/bin/handoff-pr-backfill.sh +86 -0
- package/runtime/prime/bin/handoff-run.sh +671 -0
- package/runtime/prime/bin/handoff-save-egregore.sh +212 -0
- package/runtime/prime/bin/index-handoff.sh +322 -0
- package/runtime/prime/bin/ingest-graph.sh +80 -0
- package/runtime/prime/bin/ingest-surface.html +495 -0
- package/runtime/prime/bin/ingest.py +756 -0
- package/runtime/prime/bin/ingest.sh +279 -0
- package/runtime/prime/bin/ingest_graph.py +856 -0
- package/runtime/prime/bin/ingest_surface.py +544 -0
- package/runtime/prime/bin/lib/artifact-id.sh +46 -0
- package/runtime/prime/bin/lib/config.sh +116 -0
- package/runtime/prime/bin/lib/context.sh +621 -0
- package/runtime/prime/bin/lib/dashboard-artifact.sh +242 -0
- package/runtime/prime/bin/lib/ensure-develop.sh +183 -0
- package/runtime/prime/bin/lib/git-sync.sh +406 -0
- package/runtime/prime/bin/lib/greeting.sh +519 -0
- package/runtime/prime/bin/lib/hash.sh +13 -0
- package/runtime/prime/bin/lib/identity.sh +213 -0
- package/runtime/prime/bin/lib/metrics.sh +374 -0
- package/runtime/prime/bin/lib/noncode.sh +31 -0
- package/runtime/prime/bin/lib/permissions.sh +106 -0
- package/runtime/prime/bin/lib/time.sh +56 -0
- package/runtime/prime/bin/lib/worktree-links.sh +79 -0
- package/runtime/prime/bin/notify.sh +636 -0
- package/runtime/prime/bin/person.py +630 -0
- package/runtime/prime/bin/person.sh +448 -0
- package/runtime/prime/bin/pi-branch-policy.mjs +312 -0
- package/runtime/prime/bin/pi-product-workflows.mjs +394 -0
- package/runtime/prime/bin/prime-render-spec.mjs +123 -0
- package/runtime/prime/bin/prime-session-start.sh +24 -0
- package/runtime/prime/bin/publish-artifact.sh +271 -0
- package/runtime/prime/bin/publish-references.sh +144 -0
- package/runtime/prime/bin/repo-state.sh +96 -0
- package/runtime/prime/bin/search.sh +240 -0
- package/runtime/prime/bin/session-autosave.sh +209 -0
- package/runtime/prime/bin/session-log.sh +199 -0
- package/runtime/prime/bin/settings.sh +304 -0
- package/runtime/prime/bin/telemetry.sh +391 -0
- package/runtime/prime/bin/worktree-create.sh +128 -0
- package/runtime/prime/bin/worktree-remove.sh +50 -0
- package/runtime/prime/bin/worktree.sh +198 -0
- package/runtime/prime/manifest.json +178 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# One identity spine for .egregore-state.json, memory/people, Supabase, and
|
|
5
|
+
# Neo4j. GitHub's numeric id is the durable key when available.
|
|
6
|
+
|
|
7
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
8
|
+
SCRIPT_DIR="$ROOT"
|
|
9
|
+
CONFIG="$ROOT/egregore.json"
|
|
10
|
+
STATE="$ROOT/.egregore-state.json"
|
|
11
|
+
PERSON_PY="$ROOT/bin/person.py"
|
|
12
|
+
|
|
13
|
+
# shellcheck source=bin/lib/config.sh
|
|
14
|
+
source "$ROOT/bin/lib/config.sh"
|
|
15
|
+
|
|
16
|
+
usage() {
|
|
17
|
+
echo 'usage: bash bin/person.sh {sync|onboard|set-name NAME|set-email EMAIL|show|backfill [--dry-run|--apply] [--include-removed]}' >&2
|
|
18
|
+
exit 2
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
remote_config() {
|
|
22
|
+
api_url="${EGREGORE_API_URL:-$(_config_val api_url)}"
|
|
23
|
+
api_key="${EGREGORE_API_KEY:-$(_load_env_var EGREGORE_API_KEY)}"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
sync_remote_identity() {
|
|
27
|
+
identity="$1"
|
|
28
|
+
remote_config
|
|
29
|
+
|
|
30
|
+
api_status="not-configured"
|
|
31
|
+
platform_user_id=""
|
|
32
|
+
if [ -n "$api_url" ] && [ -n "$api_key" ]; then
|
|
33
|
+
api_payload="$(printf '%s' "$identity" | jq '{
|
|
34
|
+
github_username,
|
|
35
|
+
github_id,
|
|
36
|
+
github_name,
|
|
37
|
+
github_aliases,
|
|
38
|
+
display_name,
|
|
39
|
+
email,
|
|
40
|
+
emails
|
|
41
|
+
}')"
|
|
42
|
+
api_result="$(curl -sf "${api_url}/api/user/ensure" \
|
|
43
|
+
-H "Authorization: Bearer ${api_key}" \
|
|
44
|
+
-H "Content-Type: application/json" \
|
|
45
|
+
-d "$api_payload" \
|
|
46
|
+
--max-time 10 2>/dev/null || true)"
|
|
47
|
+
api_status="$(printf '%s' "$api_result" | jq -r '.status // "failed"' 2>/dev/null || echo failed)"
|
|
48
|
+
platform_user_id="$(printf '%s' "$api_result" | jq -r '.user_id // empty' 2>/dev/null || true)"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# A removed membership is terminal until an admin explicitly re-invites
|
|
52
|
+
# the person. Never let startup recreate their graph membership.
|
|
53
|
+
if [ "$api_status" = "removed" ]; then
|
|
54
|
+
printf '%s' "$identity" | jq \
|
|
55
|
+
--arg platform_user_id "$platform_user_id" \
|
|
56
|
+
'. + {
|
|
57
|
+
status:"removed",
|
|
58
|
+
supabase:"removed",
|
|
59
|
+
graph:"skipped-removed",
|
|
60
|
+
platform_user_id:$platform_user_id,
|
|
61
|
+
merged_duplicates:0
|
|
62
|
+
}'
|
|
63
|
+
return 0
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
params="$(printf '%s' "$identity" | jq --arg platformUserId "$platform_user_id" '{
|
|
67
|
+
personId:.person_id,
|
|
68
|
+
github:.github_username,
|
|
69
|
+
githubId:(if .github_id == null then "" else (.github_id|tostring) end),
|
|
70
|
+
fullName:.github_name,
|
|
71
|
+
displayName:.display_name,
|
|
72
|
+
primaryEmail:(.email // ""),
|
|
73
|
+
emails,
|
|
74
|
+
githubAliases:.github_aliases,
|
|
75
|
+
previousNames:.previous_names,
|
|
76
|
+
profilePath:.profile_path,
|
|
77
|
+
allowNameMatch:(
|
|
78
|
+
(.allow_name_match // false)
|
|
79
|
+
or (.source_profile // "") != ""
|
|
80
|
+
or ((.aliases_reconciled // []) | length) > 0
|
|
81
|
+
),
|
|
82
|
+
platformUserId:$platformUserId
|
|
83
|
+
}')"
|
|
84
|
+
|
|
85
|
+
graph_status="failed"
|
|
86
|
+
if graph_result="$(bash "$ROOT/bin/graph.sh" query "
|
|
87
|
+
OPTIONAL MATCH (legacy:Person)
|
|
88
|
+
WHERE NOT coalesce(legacy.kind, '') IN ['external', 'identity_alias']
|
|
89
|
+
AND coalesce(legacy.status, 'active') = 'active'
|
|
90
|
+
AND legacy.ingestRef IS NULL
|
|
91
|
+
AND (
|
|
92
|
+
legacy.personId = \$personId
|
|
93
|
+
OR (\$githubId <> '' AND coalesce(toString(legacy.githubId), '') = \$githubId)
|
|
94
|
+
OR toLower(coalesce(legacy.github, '')) = toLower(\$github)
|
|
95
|
+
OR toLower(coalesce(legacy.github, '')) IN
|
|
96
|
+
[alias IN \$githubAliases | toLower(alias)]
|
|
97
|
+
OR (
|
|
98
|
+
\$allowNameMatch
|
|
99
|
+
AND legacy.personId IS NULL
|
|
100
|
+
AND legacy.githubId IS NULL
|
|
101
|
+
AND (legacy.github IS NULL OR legacy.github = '')
|
|
102
|
+
AND toLower(coalesce(legacy.name, '')) = toLower(\$displayName)
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
WITH legacy
|
|
106
|
+
ORDER BY CASE
|
|
107
|
+
WHEN legacy.personId = \$personId THEN 0
|
|
108
|
+
WHEN \$githubId <> '' AND coalesce(toString(legacy.githubId), '') = \$githubId THEN 1
|
|
109
|
+
WHEN toLower(coalesce(legacy.github, '')) = toLower(\$github) THEN 2
|
|
110
|
+
ELSE 3
|
|
111
|
+
END
|
|
112
|
+
WITH head(collect(legacy)) AS legacy
|
|
113
|
+
FOREACH (_ IN CASE WHEN legacy IS NULL THEN [] ELSE [1] END |
|
|
114
|
+
SET legacy.personId = \$personId
|
|
115
|
+
)
|
|
116
|
+
MERGE (p:Person {personId: \$personId})
|
|
117
|
+
ON CREATE SET p.joined = date()
|
|
118
|
+
SET p.github = \$github,
|
|
119
|
+
p.githubId = CASE WHEN \$githubId = '' THEN p.githubId ELSE \$githubId END,
|
|
120
|
+
p.kind = 'member',
|
|
121
|
+
p.org = \$_org,
|
|
122
|
+
p.status = 'active',
|
|
123
|
+
p.removedAt = null,
|
|
124
|
+
p.identityStatus = CASE WHEN \$githubId = '' THEN 'provisional' ELSE 'verified' END,
|
|
125
|
+
p.platformUserId = CASE WHEN \$platformUserId = '' THEN p.platformUserId ELSE \$platformUserId END,
|
|
126
|
+
p.name = \$displayName,
|
|
127
|
+
p.displayName = \$displayName,
|
|
128
|
+
p.fullName = \$fullName,
|
|
129
|
+
p.email = CASE WHEN \$primaryEmail = '' THEN p.email ELSE \$primaryEmail END,
|
|
130
|
+
p.emails = reduce(acc = coalesce(p.emails, []), value IN \$emails |
|
|
131
|
+
CASE WHEN value IN acc THEN acc ELSE acc + value END),
|
|
132
|
+
p.githubAliases = reduce(acc = coalesce(p.githubAliases, []), value IN \$githubAliases |
|
|
133
|
+
CASE WHEN value IN acc THEN acc ELSE acc + value END),
|
|
134
|
+
p.previousNames = reduce(acc = coalesce(p.previousNames, []), value IN \$previousNames |
|
|
135
|
+
CASE WHEN value IN acc THEN acc ELSE acc + value END),
|
|
136
|
+
p.profilePath = \$profilePath,
|
|
137
|
+
p.identityVersion = 1,
|
|
138
|
+
p.identityUpdatedAt = datetime()
|
|
139
|
+
WITH p
|
|
140
|
+
MATCH (o:Org {id: \$_org})
|
|
141
|
+
MERGE (p)-[:MEMBER_OF]->(o)
|
|
142
|
+
RETURN p.personId AS personId
|
|
143
|
+
" "$params" 2>/dev/null)"; then
|
|
144
|
+
if printf '%s' "$graph_result" | jq -e \
|
|
145
|
+
--arg personId "$(printf '%s' "$identity" | jq -r .person_id)" \
|
|
146
|
+
'.values[0][0] == $personId' >/dev/null 2>&1; then
|
|
147
|
+
graph_status="synced"
|
|
148
|
+
fi
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
merged_duplicates=0
|
|
152
|
+
if [ "$graph_status" = "synced" ]; then
|
|
153
|
+
duplicates="$(bash "$ROOT/bin/graph.sh" query "
|
|
154
|
+
MATCH (real:Person {personId: \$personId})
|
|
155
|
+
MATCH (duplicate:Person)
|
|
156
|
+
WHERE duplicate <> real AND (
|
|
157
|
+
duplicate.personId = \$personId
|
|
158
|
+
OR (\$githubId <> '' AND coalesce(toString(duplicate.githubId), '') = \$githubId)
|
|
159
|
+
OR toLower(coalesce(duplicate.github, '')) = toLower(\$github)
|
|
160
|
+
OR toLower(coalesce(duplicate.github, '')) IN \$githubAliases
|
|
161
|
+
OR (
|
|
162
|
+
duplicate.githubId IS NULL
|
|
163
|
+
AND duplicate.personId IS NULL
|
|
164
|
+
AND (duplicate.github IS NULL OR duplicate.github = '')
|
|
165
|
+
AND any(email IN coalesce(duplicate.emails, []) WHERE email IN \$emails)
|
|
166
|
+
)
|
|
167
|
+
OR (
|
|
168
|
+
\$allowNameMatch
|
|
169
|
+
AND
|
|
170
|
+
(duplicate.github IS NULL OR duplicate.github = '')
|
|
171
|
+
AND duplicate.personId IS NULL
|
|
172
|
+
AND toLower(coalesce(duplicate.name, '')) = toLower(\$displayName)
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
AND NOT coalesce(duplicate.kind, '') IN ['external', 'identity_alias']
|
|
176
|
+
AND coalesce(duplicate.status, 'active') = 'active'
|
|
177
|
+
AND duplicate.ingestRef IS NULL
|
|
178
|
+
RETURN DISTINCT coalesce(duplicate.github, duplicate.name) AS ref
|
|
179
|
+
" "$params" 2>/dev/null || echo '{"values":[]}')"
|
|
180
|
+
while IFS= read -r duplicate; do
|
|
181
|
+
[ -n "$duplicate" ] || continue
|
|
182
|
+
if bash "$ROOT/bin/graph-op.sh" merge-person "$(printf '%s' "$identity" | jq -r .person_id)" \
|
|
183
|
+
"$duplicate" >/dev/null 2>&1; then
|
|
184
|
+
merged_duplicates=$((merged_duplicates + 1))
|
|
185
|
+
fi
|
|
186
|
+
done < <(printf '%s' "$duplicates" | jq -r '.values[]?[0] // empty')
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
status="synced"
|
|
190
|
+
[ "$api_status" = "ok" ] || status="partial"
|
|
191
|
+
[ "$graph_status" = "synced" ] || status="partial"
|
|
192
|
+
printf '%s' "$identity" | jq \
|
|
193
|
+
--arg status "$status" \
|
|
194
|
+
--arg supabase "$api_status" \
|
|
195
|
+
--arg graph "$graph_status" \
|
|
196
|
+
--arg platform_user_id "$platform_user_id" \
|
|
197
|
+
--argjson merged_duplicates "$merged_duplicates" \
|
|
198
|
+
'. + {
|
|
199
|
+
status:$status,
|
|
200
|
+
supabase:$supabase,
|
|
201
|
+
graph:$graph,
|
|
202
|
+
platform_user_id:$platform_user_id,
|
|
203
|
+
merged_duplicates:$merged_duplicates
|
|
204
|
+
}'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
backfill_people() {
|
|
208
|
+
mode="dry-run"
|
|
209
|
+
include_removed="false"
|
|
210
|
+
shift
|
|
211
|
+
while [ "$#" -gt 0 ]; do
|
|
212
|
+
case "$1" in
|
|
213
|
+
--dry-run) mode="dry-run" ;;
|
|
214
|
+
--apply) mode="apply" ;;
|
|
215
|
+
--include-removed) include_removed="true" ;;
|
|
216
|
+
*) usage ;;
|
|
217
|
+
esac
|
|
218
|
+
shift
|
|
219
|
+
done
|
|
220
|
+
|
|
221
|
+
if [ "$(_detect_mode)" = "local" ]; then
|
|
222
|
+
jq -n '{status:"unavailable",reason:"person backfill requires the hosted member roster in this configuration"}'
|
|
223
|
+
return 1
|
|
224
|
+
fi
|
|
225
|
+
command -v gh >/dev/null 2>&1 || {
|
|
226
|
+
jq -n '{status:"error",error:"GitHub CLI is required to resolve stable user ids"}'
|
|
227
|
+
return 1
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
remote_config
|
|
231
|
+
if [ -z "$api_url" ] || [ -z "$api_key" ]; then
|
|
232
|
+
jq -n '{status:"error",error:"connected API configuration is incomplete"}'
|
|
233
|
+
return 1
|
|
234
|
+
fi
|
|
235
|
+
org_slug="$(_config_val slug)"
|
|
236
|
+
members_result="$(curl -sf "${api_url}/api/org/${org_slug}/members?include_removed=true" \
|
|
237
|
+
-H "Authorization: Bearer ${api_key}" \
|
|
238
|
+
--max-time 20 2>/dev/null || true)"
|
|
239
|
+
if ! printf '%s' "$members_result" | jq -e '.members | type == "array"' >/dev/null 2>&1; then
|
|
240
|
+
jq -n '{status:"error",error:"could not load the hosted member roster"}'
|
|
241
|
+
return 1
|
|
242
|
+
fi
|
|
243
|
+
if ! printf '%s' "$members_result" | jq -e \
|
|
244
|
+
'.members | length == 0 or all(has("github_id") and has("github_aliases"))' \
|
|
245
|
+
>/dev/null 2>&1; then
|
|
246
|
+
jq -n '{status:"blocked",error:"the hosted API is still using the pre-identity member contract; deploy it before backfilling"}'
|
|
247
|
+
return 1
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
results='[]'
|
|
251
|
+
excluded_removed=0
|
|
252
|
+
while IFS= read -r member; do
|
|
253
|
+
[ -n "$member" ] || continue
|
|
254
|
+
member_status="$(printf '%s' "$member" | jq -r '.status // "active"')"
|
|
255
|
+
if [ "$member_status" != "active" ] && [ "$include_removed" != "true" ]; then
|
|
256
|
+
excluded_removed=$((excluded_removed + 1))
|
|
257
|
+
continue
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
previous_github="$(printf '%s' "$member" | jq -r '.github_username // empty')"
|
|
261
|
+
if ! printf '%s' "$previous_github" | grep -Eq '^[A-Za-z0-9-]{1,39}$'; then
|
|
262
|
+
item="$(jq -n --arg github "$previous_github" '{
|
|
263
|
+
github_username:$github,status:"skipped",reason:"invalid GitHub login"
|
|
264
|
+
}')"
|
|
265
|
+
results="$(printf '%s' "$results" | jq --argjson item "$item" '. + [$item]')"
|
|
266
|
+
continue
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
github_result="$(gh api "users/${previous_github}" \
|
|
270
|
+
--jq '{login,id,name,email}' 2>/dev/null || true)"
|
|
271
|
+
if ! printf '%s' "$github_result" | jq -e \
|
|
272
|
+
'.login and (.id | type == "number")' >/dev/null 2>&1; then
|
|
273
|
+
item="$(jq -n --arg github "$previous_github" '{
|
|
274
|
+
github_username:$github,status:"unresolved",reason:"GitHub identity could not be resolved"
|
|
275
|
+
}')"
|
|
276
|
+
results="$(printf '%s' "$results" | jq --argjson item "$item" '. + [$item]')"
|
|
277
|
+
continue
|
|
278
|
+
fi
|
|
279
|
+
|
|
280
|
+
github="$(printf '%s' "$github_result" | jq -r .login)"
|
|
281
|
+
github_id="$(printf '%s' "$github_result" | jq -r .id)"
|
|
282
|
+
github_name="$(printf '%s' "$github_result" | jq -r '.name // empty')"
|
|
283
|
+
public_email="$(printf '%s' "$github_result" | jq -r '.email // empty')"
|
|
284
|
+
display_name="$(printf '%s' "$member" | jq -r \
|
|
285
|
+
--arg githubName "$github_name" --arg github "$github" \
|
|
286
|
+
'.display_name // (if $githubName != "" then $githubName else $github end)')"
|
|
287
|
+
role="$(printf '%s' "$member" | jq -r '.member_role // .role // "Member"')"
|
|
288
|
+
joined="$(printf '%s' "$member" | jq -r '.joined_at // empty' | cut -c1-10)"
|
|
289
|
+
aliases="$(printf '%s' "$member" | jq -r \
|
|
290
|
+
--arg old "$previous_github" --arg current "$github" '
|
|
291
|
+
((.github_aliases // []) + (if ($old|ascii_downcase) != ($current|ascii_downcase) then [$old] else [] end))
|
|
292
|
+
| map(select(type == "string" and length > 0)) | unique | join(",")
|
|
293
|
+
')"
|
|
294
|
+
emails="$(printf '%s' "$member" | jq -r \
|
|
295
|
+
--arg public "$public_email" '
|
|
296
|
+
((.emails // []) + [(.primary_email // ""), $public])
|
|
297
|
+
| map(select(type == "string" and length > 0)) | unique | join(",")
|
|
298
|
+
')"
|
|
299
|
+
|
|
300
|
+
profile_args=(
|
|
301
|
+
sync-profile
|
|
302
|
+
--github "$github"
|
|
303
|
+
--github-id "$github_id"
|
|
304
|
+
--github-name "$github_name"
|
|
305
|
+
--display-name "$display_name"
|
|
306
|
+
--previous-github "$previous_github"
|
|
307
|
+
--github-aliases "$aliases"
|
|
308
|
+
--emails "$emails"
|
|
309
|
+
--role "$role"
|
|
310
|
+
--joined "$joined"
|
|
311
|
+
)
|
|
312
|
+
[ "$mode" = "apply" ] || profile_args+=(--dry-run)
|
|
313
|
+
if ! identity="$(python3 "$PERSON_PY" "${profile_args[@]}")"; then
|
|
314
|
+
item="$(jq -n --arg github "$github" '{
|
|
315
|
+
github_username:$github,status:"skipped",reason:"profile reconciliation failed"
|
|
316
|
+
}')"
|
|
317
|
+
results="$(printf '%s' "$results" | jq --argjson item "$item" '. + [$item]')"
|
|
318
|
+
continue
|
|
319
|
+
fi
|
|
320
|
+
roster_name_matches="$(printf '%s' "$members_result" | jq \
|
|
321
|
+
--arg displayName "$display_name" '
|
|
322
|
+
[.members[]
|
|
323
|
+
| (.display_name // "")
|
|
324
|
+
| select(length > 0)
|
|
325
|
+
| ascii_downcase
|
|
326
|
+
| select(. == ($displayName | ascii_downcase))]
|
|
327
|
+
| length
|
|
328
|
+
')"
|
|
329
|
+
allow_name_match="false"
|
|
330
|
+
[ "$roster_name_matches" -eq 1 ] && allow_name_match="true"
|
|
331
|
+
identity="$(printf '%s' "$identity" | jq \
|
|
332
|
+
--argjson allowNameMatch "$allow_name_match" \
|
|
333
|
+
'.allow_name_match = $allowNameMatch')"
|
|
334
|
+
|
|
335
|
+
if [ "$mode" = "apply" ]; then
|
|
336
|
+
synced="$(sync_remote_identity "$identity")"
|
|
337
|
+
report="$(printf '%s' "$synced" | jq 'del(.email,.emails,.allow_name_match)')"
|
|
338
|
+
else
|
|
339
|
+
report="$(printf '%s' "$identity" | jq \
|
|
340
|
+
'. + {status:"planned"} | del(.email,.emails,.allow_name_match)')"
|
|
341
|
+
fi
|
|
342
|
+
results="$(printf '%s' "$results" | jq --argjson item "$report" '. + [$item]')"
|
|
343
|
+
done < <(printf '%s' "$members_result" | jq -c '.members[]')
|
|
344
|
+
|
|
345
|
+
printf '%s' "$results" | jq \
|
|
346
|
+
--arg mode "$mode" \
|
|
347
|
+
--arg org "$org_slug" \
|
|
348
|
+
--argjson excluded_removed "$excluded_removed" '
|
|
349
|
+
{
|
|
350
|
+
status:(if any(.[]; .status == "partial") then "partial" else "ok" end),
|
|
351
|
+
mode:$mode,
|
|
352
|
+
org:$org,
|
|
353
|
+
summary:{
|
|
354
|
+
considered:length,
|
|
355
|
+
planned_or_synced:[.[] | select(.status == "planned" or .status == "synced")]|length,
|
|
356
|
+
partial:[.[] | select(.status == "partial")]|length,
|
|
357
|
+
unresolved:[.[] | select(.status == "unresolved")]|length,
|
|
358
|
+
skipped:[.[] | select(.status == "skipped")]|length,
|
|
359
|
+
excluded_removed:$excluded_removed
|
|
360
|
+
},
|
|
361
|
+
people:.
|
|
362
|
+
}'
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
command="${1:-}"
|
|
366
|
+
[ -n "$command" ] || usage
|
|
367
|
+
|
|
368
|
+
if [ "$command" = "show" ]; then
|
|
369
|
+
jq '{
|
|
370
|
+
person_id,
|
|
371
|
+
display_name,
|
|
372
|
+
github_username,
|
|
373
|
+
github_id,
|
|
374
|
+
github_name,
|
|
375
|
+
email,
|
|
376
|
+
emails,
|
|
377
|
+
github_aliases,
|
|
378
|
+
previous_names,
|
|
379
|
+
identity_version
|
|
380
|
+
}' "$STATE"
|
|
381
|
+
exit 0
|
|
382
|
+
fi
|
|
383
|
+
|
|
384
|
+
if [ "$command" = "backfill" ]; then
|
|
385
|
+
backfill_people "$@"
|
|
386
|
+
exit $?
|
|
387
|
+
fi
|
|
388
|
+
|
|
389
|
+
display_args=()
|
|
390
|
+
email_args=()
|
|
391
|
+
onboard_args=()
|
|
392
|
+
case "$command" in
|
|
393
|
+
sync) ;;
|
|
394
|
+
onboard) onboard_args=(--onboarded) ;;
|
|
395
|
+
set-name)
|
|
396
|
+
name="${2:-}"
|
|
397
|
+
[ -n "$name" ] || usage
|
|
398
|
+
if [ "${#name}" -gt 60 ] || ! printf '%s' "$name" | grep -Eq "^[[:alnum:]À-ž .'-]+$"; then
|
|
399
|
+
echo '{"status":"error","error":"display name must be 1-60 name characters"}'
|
|
400
|
+
exit 2
|
|
401
|
+
fi
|
|
402
|
+
display_args=(--display-name "$name")
|
|
403
|
+
;;
|
|
404
|
+
set-email)
|
|
405
|
+
email="${2:-}"
|
|
406
|
+
[ -n "$email" ] || usage
|
|
407
|
+
if [ "${#email}" -gt 254 ] || ! printf '%s' "$email" | grep -Eq '^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$'; then
|
|
408
|
+
echo '{"status":"error","error":"email must be a valid address"}'
|
|
409
|
+
exit 2
|
|
410
|
+
fi
|
|
411
|
+
email_args=(--email "$email")
|
|
412
|
+
;;
|
|
413
|
+
*) usage ;;
|
|
414
|
+
esac
|
|
415
|
+
|
|
416
|
+
github_args=()
|
|
417
|
+
state_github="$(jq -r '.github_username // empty' "$STATE" 2>/dev/null)"
|
|
418
|
+
if command -v gh >/dev/null 2>&1; then
|
|
419
|
+
gh_identity="$(gh api user --jq '{login,id,name,email}' 2>/dev/null || true)"
|
|
420
|
+
gh_login="$(printf '%s' "$gh_identity" | jq -r '.login // empty' 2>/dev/null)"
|
|
421
|
+
gh_id="$(printf '%s' "$gh_identity" | jq -r '.id // empty' 2>/dev/null)"
|
|
422
|
+
state_github_id="$(jq -r '.github_id // empty' "$STATE" 2>/dev/null)"
|
|
423
|
+
gh_login_lc="$(printf '%s' "$gh_login" | tr '[:upper:]' '[:lower:]')"
|
|
424
|
+
state_github_lc="$(printf '%s' "$state_github" | tr '[:upper:]' '[:lower:]')"
|
|
425
|
+
if [ -n "$gh_login" ] && {
|
|
426
|
+
[ -z "$state_github" ] ||
|
|
427
|
+
[ "$gh_login_lc" = "$state_github_lc" ] ||
|
|
428
|
+
{ [ -n "$gh_id" ] && [ -n "$state_github_id" ] && [ "$gh_id" = "$state_github_id" ]; }
|
|
429
|
+
}; then
|
|
430
|
+
github_args=(--github "$gh_login")
|
|
431
|
+
gh_email="$(printf '%s' "$gh_identity" | jq -r '.email // empty')"
|
|
432
|
+
[ -z "$gh_id" ] || github_args+=(--github-id "$gh_id")
|
|
433
|
+
[ -z "$gh_email" ] || github_args+=(--email "$gh_email")
|
|
434
|
+
fi
|
|
435
|
+
fi
|
|
436
|
+
|
|
437
|
+
if [ "$(jq -r '.onboarding_complete // false' "$STATE" 2>/dev/null)" = "true" ]; then
|
|
438
|
+
onboard_args=(--onboarded)
|
|
439
|
+
fi
|
|
440
|
+
identity="$(python3 "$PERSON_PY" sync-local \
|
|
441
|
+
"${display_args[@]}" "${github_args[@]}" "${email_args[@]}" "${onboard_args[@]}")"
|
|
442
|
+
|
|
443
|
+
if [ "$(_detect_mode)" = "local" ]; then
|
|
444
|
+
printf '%s' "$identity" | jq '. + {status:"synced-local",supabase:"unavailable",graph:"unavailable"}'
|
|
445
|
+
exit 0
|
|
446
|
+
fi
|
|
447
|
+
|
|
448
|
+
sync_remote_identity "$identity"
|