invite
SkillDev toolsInvite a GitHub user into this Egregore instance from Codex when the user invokes /invite or $invite, or asks to invite someone without exposing credentials.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the invite skill
What this skill tells your AI
The instructions your AI receives, as published by egregore-labs/egregore in .codex/skills/invite/SKILL.md and read by ahel’s review.
Invite someone to this Egregore. Handles GitHub org invitation + Egregore setup link.
When to invoke
User says: "/invite ", "invite ", "add to the org", or any request to invite a new person to this Egregore.
Not this: removing a member → /delete-user.
Arguments: $ARGUMENTS (Required: GitHub username of the person to invite)
Execution rules
Neo4j-first (connected mode only). In connected mode, all queries via bash bin/graph.sh query "...". No MCP. No direct curl to Neo4j. In local mode, Step 1b routes to Step 2L (GitHub API only) — Steps 2–4b (graph + notify) are skipped entirely. Do NOT show any graph-related messaging in local mode (no "Person node created", no "recorded in graph", no Neo4j references).
Notifications use the plan → approve → dispatch flow in
.claude/context/notification-consent.md (connected mode only). No direct
curl to Telegram. Inviting the person is not notification consent.
CRITICAL: Suppress raw output. Never show raw JSON to the user. All bin/graph.sh and bin/notify.sh calls MUST capture output in a variable and only show formatted status lines.
CRITICAL: Never expose credentials in tool output.
- Never read tokens in a separate bash call — always inline.
- Never pass tokens as visible arguments — read from
.envinside the script. - All credential handling happens inside a single bash call that only outputs the formatted result.
Step 1: Validate
If $ARGUMENTS is empty, show usage and stop:
Usage: /invite <github-username>
Example: /invite newuser
Step 1b: Local mode detection
Check api_url from egregore.json:
API_URL=$(jq -r '.api_url // empty' egregore.json 2>/dev/null)
If api_url is empty → use LOCAL INVITE FLOW (Step 2L below). Skip Steps 2-4b entirely.
If api_url is set → use CONNECTED INVITE FLOW (Step 2 below).
Step 2L: Local mode invite (GitHub API only)
Run ONE bash call with description "Inviting {username} via GitHub":
bash -c '
USERNAME="$1"
GH_TOKEN=$(grep "^GITHUB_TOKEN=" .env | cut -d"=" -f2-)
GITHUB_ORG=$(jq -r ".github_org" egregore.json)
REPO_NAME=$(jq -r ".repo_name // empty" egregore.json)
MEMORY_REPO=$(jq -r ".memory_repo // empty" egregore.json)
MEMORY_NAME=$(basename "$MEMORY_REPO" .git)
REPOS=$(jq -r ".repos[]? // empty" egregore.json)
if [ -z "$GH_TOKEN" ]; then
echo "ERROR: No GitHub token. Run: bash bin/github-auth.sh"
exit 1
fi
if [ -z "$REPO_NAME" ]; then
echo "ERROR: repo_name not set in egregore.json"
exit 1
fi
# Add collaborator to core repo
CORE=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$GITHUB_ORG/$REPO_NAME/collaborators/$USERNAME" \
-d "{\"permission\":\"push\"}")
# Add collaborator to memory repo
MEM=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$GITHUB_ORG/$MEMORY_NAME/collaborators/$USERNAME" \
-d "{\"permission\":\"push\"}")
# Add to managed repos
for REPO in $REPOS; do
curl -s -o /dev/null -X PUT \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$GITHUB_ORG/$REPO/collaborators/$USERNAME" \
-d "{\"permission\":\"push\"}" 2>/dev/null || true
done
echo "{\"core\":\"$CORE\",\"memory\":\"$MEM\",\"username\":\"$USERNAME\",\"org\":\"$GITHUB_ORG\",\"repo\":\"$REPO_NAME\"}"
' -- "$ARGUMENTS"
Create person file in memory:
INVITE_USER="$ARGUMENTS"
INVITER=$(jq -r '.display_name // .github_username' .egregore-state.json 2>/dev/null)
TODAY=$(date -u +%Y-%m-%d)
cat > "memory/people/${INVITE_USER}.md" << EOF
---
name: ${INVITE_USER}
person_id: github-login:$(printf '%s' "$INVITE_USER" | tr '[:upper:]' '[:lower:]')
github: ${INVITE_USER}
invited_by: ${INVITER}
joined: ${TODAY}
---
EOF
cd memory && git add -A && git commit -m "Invite ${INVITE_USER}" && git push 2>/dev/null && cd -
Display result:
If core HTTP status is 201 or 204:
Invited {username} to {org_name}.
Core repo access: ✓ added
Memory repo access: ✓ added
Person file: ✓ created
Tell them to run:
npx -y create-egregore@latest join {github_org}/{repo_name}
Telegram group link: After the join command, check egregore.json for telegram_group_link. If present, append:
Telegram group: {telegram_group_link}
If core HTTP status is 422: user was already a collaborator — show "already has access."
If core HTTP status is 403 or other error:
Could not add {username} as a collaborator.
You may need org admin permissions. Add them manually:
https://github.com/{github_org}/{repo_name}/settings/access
After local invite, STOP. Do not proceed to Steps 3-5 (they require the API).
Step 2: Send invite (single call — credentials stay hidden)
Run ONE bash call with description "Sending invite to {username}":
bash -c '
USERNAME="$1"
GH_TOKEN=$(grep "^GITHUB_TOKEN=" .env | cut -d"=" -f2-)
API_KEY=$(grep "^EGREGORE_API_KEY=" .env | cut -d"=" -f2-)
GITHUB_ORG=$(jq -r ".github_org" egregore.json)
API_URL=$(jq -r ".api_url" egregore.json)
ORG_NAME=$(jq -r ".org_name" egregore.json)
REPO_NAME=$(jq -r ".repo_name // empty" egregore.json)
if [ -z "$REPO_NAME" ]; then echo "ERROR: repo_name not set in egregore.json"; exit 1; fi
SLUG=$(jq -r ".slug" egregore.json)
if [ -z "$GH_TOKEN" ]; then
echo "ERROR: No GitHub token found. Run: bash bin/github-auth.sh"
exit 1
fi
if [ -z "$API_KEY" ]; then
echo "ERROR: No Egregore API key found. Check .env for EGREGORE_API_KEY"
exit 1
fi
RESP=$(curl -s -X POST "$API_URL/api/org/invite" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d "{\"github_org\": \"$GITHUB_ORG\", \"github_username\": \"$USERNAME\", \"repo_name\": \"$REPO_NAME\", \"slug\": \"$SLUG\", \"github_token\": \"$GH_TOKEN\"}")
# Output structured JSON for parsing
echo "$RESP" | jq -c "{
ok: (if .invite_url then true else false end),
invite_url: .invite_url,
invite_token: .invite_token,
github_invite: .github_invite,
memory_access: .memory_access,
org_name: \"$ORG_NAME\",
github_org: \"$GITHUB_ORG\",
username: \"$USERNAME\",
error: .detail
}"
' -- "$ARGUMENTS"
Step 3: Display result
Parse the JSON output from Step 2. Never show raw JSON to the user.
Success (ok=true):
Inviting {username} to {org_name}...
GitHub org invitation: sent
Memory repo access: added
Invite link: created
Share this link with {username}:
{invite_url}
They'll authenticate with GitHub, accept the org invite,
and get a one-line install command.
Telegram group link: After the invite link block, check egregore.json for telegram_group_link. If present, append:
Telegram group: {telegram_group_link}
GitHub invite failed (github_invite.status == "collaborator_failed"):
Inviting {username} to {org_name}...
GitHub org invitation: failed ({reason})
Invite link: created
You'll need to invite {username} to the GitHub org manually:
https://github.com/orgs/{github_org}/people
Then share this link:
{invite_url}
Already an org member (github_invite.status == "already_org_member"):
Inviting {username} to {org_name}...
GitHub: {username} is already a member of {github_org} — access via org role
Invite link: created
Share this link with {username}:
{invite_url}
Org invite pending (github_invite.status == "org_invite_pending"):
Inviting {username} to {org_name}...
GitHub: org invitation already pending for {username} — they need to accept it
Invite link: created
Share this link with {username}:
{invite_url}
API error (ok=false):
Failed to invite {username}: {error}
Step 4: Record in graph, then separately offer notification
Record the invite first. Do not combine this with notification approval.
Create Person node + sync to Supabase:
bash bin/graph.sh query "MERGE (p:Person {github: \$github}) ON CREATE SET p.personId = 'github-login:' + toLower(\$github), p.name = \$github, p.identityStatus = 'invited', p.invited = date(), p.invitedBy = \$inviter RETURN p.name" '{"github": "USERNAME", "inviter": "INVITER"}'
Then sync to Supabase (non-fatal):
API_URL=$(jq -r '.api_url // empty' egregore.json)
API_KEY=$(grep '^EGREGORE_API_KEY=' .env | cut -d'=' -f2-)
curl -sf "${API_URL}/api/user/ensure" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"github_username":"USERNAME"}' \
--max-time 5 >/dev/null 2>&1 || true
Get the inviter name from git config user.name (derive short handle: lowercase first word).
Check Telegram + notify:
bash bin/graph.sh query "MATCH (p:Person) WHERE p.github = \$username OR p.name = \$username RETURN p.telegramId" '{"username": "USERNAME"}'
If they have a telegramId, prepare the exact invite notification without sending:
PLAN_JSON=$(bash bin/notify.sh plan send "USERNAME" "You've been invited to ORG_NAME on Egregore! Join here: INVITE_URL")
Show the organization, recipient, channel, and exact message in a dedicated Send / Edit / Cancel checkpoint. Dispatch only after the user approves that preview. If no direct destination exists, say so; never fall back to the group.
Step 4b: Provision Coder user (if remote hosting enabled)
Check if the org has remote hosting enabled. Run ONE bash call (fire-and-forget, must not delay response):
bash -c '
API_URL=$(jq -r ".api_url" egregore.json)
API_KEY=$(grep "^EGREGORE_API_KEY=" .env | cut -d"=" -f2-)
ORG_SLUG=$(jq -r ".slug" egregore.json)
USERNAME="$1"
# Check if hosting is enabled for this org
HOSTING=$(curl -sf "${API_URL}/api/hosting/info/${ORG_SLUG}" \
-H "Authorization: Bearer $(grep "^GITHUB_TOKEN=" .env | cut -d"=" -f2-)" \
--max-time 5 2>/dev/null || echo "{}")
ENABLED=$(echo "$HOSTING" | jq -r ".hosting_enabled // false")
if [ "$ENABLED" = "true" ]; then
# Create Coder user (non-fatal, fire-and-forget)
curl -sf -X POST "${API_URL}/api/hosting/user/${ORG_SLUG}" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"username\": \"$USERNAME\"}" \
--max-time 10 >/dev/null 2>&1 || true
echo "coder_user_created"
else
echo "no_hosting"
fi
' -- "USERNAME"
If the output is coder_user_created, include in the summary:
Coder workspace: user created
Step 5: Summary line
After all steps complete, show one truthful final status:
Notification sent via Telegram
or
Notification not sent — share the link manually
Example
> /invite newuser
Inviting newuser to Acme Org...
GitHub org invitation: sent
Memory repo access: added
Invite link: created
Share this link with newuser:
https://egregore.xyz/join?invite=inv_a1b2c3d4e5f6
They'll authenticate with GitHub, accept the org invite,
and get a one-line install command.
Notified via Telegram
If already a member
> /invite newuser
newuser is already a member of acme-org.
They can join Egregore directly at: https://egregore.xyz/setup
Rules
- Only org admins can invite — the API verifies this
- GitHub invitation + Egregore invite are bundled — one command does both
- Invite links expire in 7 days — if expired, just run
/inviteagain - Memory repo access is granted automatically — invitee gets push access
- Never expose tokens — all credential reads happen inside bash scripts, never as separate tool calls
- Always use
bin/graph.shfor Neo4j — never MCP - Always use the exact consent flow in
bin/notify.sh— never construct API calls directly or treat the invite request as notification approval
Signals
- GitHub stars
- 288
- Forks
- 21
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
invite- Source
- github.com/egregore-labs/egregore