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,265 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -o pipefail
|
|
3
|
+
|
|
4
|
+
# Codex does not run Claude Code SessionStart hooks. This is the runtime-neutral
|
|
5
|
+
# startup card: it reuses the same identity, git sync, and context-gathering
|
|
6
|
+
# libraries as Claude's SessionStart, then renders a terminal greeting before
|
|
7
|
+
# Codex itself starts.
|
|
8
|
+
|
|
9
|
+
FRAMEWORK_VERSION="7"
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
11
|
+
cd "$SCRIPT_DIR" || exit 1
|
|
12
|
+
|
|
13
|
+
IS_WORKTREE="false"
|
|
14
|
+
MAIN_PROJECT_DIR="$SCRIPT_DIR"
|
|
15
|
+
if [ -f "$SCRIPT_DIR/.git" ]; then
|
|
16
|
+
IS_WORKTREE="true"
|
|
17
|
+
WT_GITDIR=$(sed 's/^gitdir: //' "$SCRIPT_DIR/.git" 2>/dev/null)
|
|
18
|
+
MAIN_PROJECT_DIR=$(cd "$WT_GITDIR/../../.." 2>/dev/null && pwd)
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Clear any branch-guard consent from a previous session — consent to write on
|
|
22
|
+
# a protected branch is asked fresh each session (see AGENTS.md branch-guard protocol).
|
|
23
|
+
rm -f "$SCRIPT_DIR/.egregore-branch-consent" "$MAIN_PROJECT_DIR/.egregore-branch-consent" 2>/dev/null
|
|
24
|
+
# Same for boundary-crossing consent — grants are session-scoped (AGENTS.md
|
|
25
|
+
# environment-isolation protocol); durable grants belong in .egregore-boundary.local.json.
|
|
26
|
+
rm -f "$SCRIPT_DIR/.egregore-boundary-consent" "$MAIN_PROJECT_DIR/.egregore-boundary-consent" 2>/dev/null
|
|
27
|
+
|
|
28
|
+
if [ -f "$SCRIPT_DIR/bin/lib/worktree-links.sh" ]; then
|
|
29
|
+
source "$SCRIPT_DIR/bin/lib/worktree-links.sh" >/dev/null 2>/dev/null || true
|
|
30
|
+
egregore_link_shared_state "$SCRIPT_DIR" "$MAIN_PROJECT_DIR" >/dev/null 2>/dev/null || true
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
HEALTH_GITHUB="skip"
|
|
34
|
+
HEALTH_GIT="skip"
|
|
35
|
+
HEALTH_APIKEY="skip"
|
|
36
|
+
HEALTH_GRAPH="skip"
|
|
37
|
+
HEALTH_TELEGRAM="skip"
|
|
38
|
+
|
|
39
|
+
STATE_FILE="$SCRIPT_DIR/.egregore-state.json"
|
|
40
|
+
ENV_FILE="$SCRIPT_DIR/.env"
|
|
41
|
+
CONFIG="$SCRIPT_DIR/egregore.json"
|
|
42
|
+
DATE=$(date +%Y-%m-%d)
|
|
43
|
+
|
|
44
|
+
source "$SCRIPT_DIR/bin/lib/config.sh"
|
|
45
|
+
source "$SCRIPT_DIR/bin/lib/hash.sh"
|
|
46
|
+
source "$SCRIPT_DIR/bin/lib/time.sh"
|
|
47
|
+
source "$SCRIPT_DIR/bin/lib/identity.sh" >/dev/null 2>/dev/null
|
|
48
|
+
|
|
49
|
+
# Keep the durable local identity current before gathering context, then project
|
|
50
|
+
# it to Supabase + Neo4j in the background so startup is never network-blocked.
|
|
51
|
+
if [ -f "$STATE_FILE" ] && [ -f "$SCRIPT_DIR/bin/person.py" ]; then
|
|
52
|
+
python3 "$SCRIPT_DIR/bin/person.py" sync-local >/dev/null 2>&1 || true
|
|
53
|
+
fi
|
|
54
|
+
if [ -f "$SCRIPT_DIR/bin/person.sh" ]; then
|
|
55
|
+
( bash "$SCRIPT_DIR/bin/person.sh" sync >/dev/null 2>&1 & ) 2>/dev/null
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
LOCAL_MODE="false"
|
|
59
|
+
[ "$(_detect_mode)" = "local" ] && LOCAL_MODE="true"
|
|
60
|
+
MODE_BADGE="$(_runtime_mode_badge)"
|
|
61
|
+
|
|
62
|
+
if [ "$LOCAL_MODE" != "true" ]; then
|
|
63
|
+
if [ -f "$ENV_FILE" ] && grep -q '^EGREGORE_API_KEY=.' "$ENV_FILE" 2>/dev/null; then
|
|
64
|
+
HEALTH_APIKEY="ok"
|
|
65
|
+
else
|
|
66
|
+
HEALTH_APIKEY="fail"
|
|
67
|
+
fi
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
source "$SCRIPT_DIR/bin/lib/git-sync.sh" >/dev/null 2>/dev/null
|
|
71
|
+
# Warm-state parity with the Claude SessionStart path: hand context.sh the
|
|
72
|
+
# pre-baked snapshot and route greeting reads through the warm graph cache.
|
|
73
|
+
# The attendant daemon is deliberately NOT started from the Codex/Pi path —
|
|
74
|
+
# its dead-session detection reads Claude transcripts only, so a live Codex
|
|
75
|
+
# or Pi session with 12 quiet minutes would look dead and could have its
|
|
76
|
+
# unfinished work ripcorded/auto-pushed. Codex sessions still benefit from
|
|
77
|
+
# any snapshot a Claude session's attendant maintains; runtime-neutral
|
|
78
|
+
# liveness tracking is the prerequisite for starting the daemon here.
|
|
79
|
+
if [ -n "${_ATT_KEY:-}" ]; then
|
|
80
|
+
export CTX_SEED_TAR="${ATTENDANT_HOME:-$HOME/.egregore/attendant}/${_ATT_KEY}.ctx.tar"
|
|
81
|
+
fi
|
|
82
|
+
export EGREGORE_GRAPH_CACHE_TTL=600
|
|
83
|
+
source "$SCRIPT_DIR/bin/lib/context.sh" >/dev/null 2>/dev/null
|
|
84
|
+
unset EGREGORE_GRAPH_CACHE_TTL CTX_SEED_TAR
|
|
85
|
+
source "$SCRIPT_DIR/bin/lib/metrics.sh" >/dev/null 2>/dev/null
|
|
86
|
+
trap 'rm -rf "${CTX_DIR:-}"' EXIT
|
|
87
|
+
|
|
88
|
+
# Rescue non-coding leftovers from sessions that died without ceremony
|
|
89
|
+
# (terminal closed). Gated + idle-guarded inside the script; fire-and-forget.
|
|
90
|
+
( bash "$SCRIPT_DIR/bin/session-autosave.sh" --sweep >/dev/null 2>&1 & ) 2>/dev/null
|
|
91
|
+
|
|
92
|
+
# Connected instances pick up newly released Connect skills without
|
|
93
|
+
# re-running the launcher. Local mode exits instantly inside the script.
|
|
94
|
+
# Pi shares this path (pi-session-start.sh delegates here).
|
|
95
|
+
( bash "$SCRIPT_DIR/bin/connect-refresh.sh" >/dev/null 2>&1 & ) 2>/dev/null
|
|
96
|
+
|
|
97
|
+
read_json() {
|
|
98
|
+
local file="$1"
|
|
99
|
+
local expr="$2"
|
|
100
|
+
local fallback="${3:-}"
|
|
101
|
+
if [ -f "$file" ] && command -v jq >/dev/null 2>&1; then
|
|
102
|
+
jq -r "$expr // empty" "$file" 2>/dev/null | head -1
|
|
103
|
+
else
|
|
104
|
+
printf '%s\n' "$fallback"
|
|
105
|
+
fi
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ORG_NAME="$(read_json "$CONFIG" '.org_name' '')"
|
|
109
|
+
[ -z "$ORG_NAME" ] && ORG_NAME="$(read_json "$CONFIG" '.slug' 'Egregore')"
|
|
110
|
+
GITHUB_ORG="$(read_json "$CONFIG" '.github_org' '')"
|
|
111
|
+
REPO_NAME="$(read_json "$CONFIG" '.repo_name' 'egregore')"
|
|
112
|
+
SLUG="$(read_json "$CONFIG" '.slug' '')"
|
|
113
|
+
DISPLAY_NAME="$(read_json "$STATE_FILE" '.display_name' '')"
|
|
114
|
+
[ -z "$DISPLAY_NAME" ] && DISPLAY_NAME="${DISPLAY_NAME_STATE:-$AUTHOR}"
|
|
115
|
+
ONBOARDING_COMPLETE="$(read_json "$STATE_FILE" '.onboarding_complete' '')"
|
|
116
|
+
[ -z "$ONBOARDING_COMPLETE" ] && ONBOARDING_COMPLETE="unknown"
|
|
117
|
+
|
|
118
|
+
ADDRESSED_RICH="$(cat "$CTX_DIR/addressed_rich" 2>/dev/null || echo "[]")"
|
|
119
|
+
PENDING_Q="$(cat "$CTX_DIR/pending_questions" 2>/dev/null || echo "[]")"
|
|
120
|
+
ADDRESSED_COUNT="$(echo "$ADDRESSED_RICH" | jq 'length' 2>/dev/null || echo "0")"
|
|
121
|
+
PENDING_Q_COUNT="$(echo "$PENDING_Q" | jq 'length' 2>/dev/null || echo "0")"
|
|
122
|
+
|
|
123
|
+
render_pending_line() {
|
|
124
|
+
if [ "$PENDING_Q_COUNT" -gt 0 ] 2>/dev/null; then
|
|
125
|
+
local senders noun
|
|
126
|
+
senders="$(echo "$PENDING_Q" | jq -r '[.[].from] | unique | join(", ")' 2>/dev/null)"
|
|
127
|
+
[ "$PENDING_Q_COUNT" = "1" ] && noun="question" || noun="questions"
|
|
128
|
+
if [ -n "$senders" ]; then
|
|
129
|
+
printf ' ◐ %s pending %s from %s - bin/agent.sh answer\n' "$PENDING_Q_COUNT" "$noun" "$senders"
|
|
130
|
+
else
|
|
131
|
+
printf ' ◐ %s pending %s - bin/agent.sh answer\n' "$PENDING_Q_COUNT" "$noun"
|
|
132
|
+
fi
|
|
133
|
+
fi
|
|
134
|
+
if [ "$ADDRESSED_COUNT" -gt 0 ] 2>/dev/null; then
|
|
135
|
+
printf ' ◇ %s handoffs for %s - say "show my handoffs" to review or close\n' "$ADDRESSED_COUNT" "$AUTHOR"
|
|
136
|
+
fi
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
banner() {
|
|
140
|
+
cat <<'EOF'
|
|
141
|
+
|
|
142
|
+
███████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗
|
|
143
|
+
██╔════╝██╔════╝ ██╔══██╗██╔════╝██╔════╝ ██╔═══██╗██╔══██╗██╔════╝
|
|
144
|
+
█████╗ ██║ ███╗██████╔╝█████╗ ██║ ███╗██║ ██║██████╔╝█████╗
|
|
145
|
+
██╔══╝ ██║ ██║██╔══██╗██╔══╝ ██║ ██║██║ ██║██╔══██╗██╔══╝
|
|
146
|
+
███████╗╚██████╔╝██║ ██║███████╗╚██████╔╝╚██████╔╝██║ ██║███████╗
|
|
147
|
+
╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝
|
|
148
|
+
|
|
149
|
+
EOF
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
render_card() {
|
|
153
|
+
local separator identity_left identity_right line_width pad footer_left footer_right board_url has_failure failed_services
|
|
154
|
+
separator=" ..................................................................."
|
|
155
|
+
identity_left=" ${GITHUB_ORG:-$ORG_NAME}/${REPO_NAME} · ${MODE_BADGE}"
|
|
156
|
+
identity_right="${DISPLAY_NAME} · ${BRANCH:-?}"
|
|
157
|
+
line_width=67
|
|
158
|
+
pad=$((line_width - ${#identity_left} - ${#identity_right}))
|
|
159
|
+
[ "$pad" -lt 1 ] && pad=1
|
|
160
|
+
|
|
161
|
+
banner
|
|
162
|
+
printf '%s%*s%s\n' "$identity_left" "$pad" "" "$identity_right"
|
|
163
|
+
printf '%s\n' "$separator"
|
|
164
|
+
SEPARATOR="$separator" LINE_WIDTH=$line_width _render_momentum_board
|
|
165
|
+
render_pending_line
|
|
166
|
+
printf '%s\n' "$separator"
|
|
167
|
+
|
|
168
|
+
has_failure="false"
|
|
169
|
+
failed_services=""
|
|
170
|
+
for pair in "github:$HEALTH_GITHUB" "git:$HEALTH_GIT" "api-key:$HEALTH_APIKEY" "graph:$HEALTH_GRAPH" "telegram:$HEALTH_TELEGRAM"; do
|
|
171
|
+
svc="${pair%%:*}"
|
|
172
|
+
svc_status="${pair#*:}"
|
|
173
|
+
if [ "$svc_status" = "fail" ]; then
|
|
174
|
+
has_failure="true"
|
|
175
|
+
failed_services="${failed_services} ${svc} ✗"
|
|
176
|
+
fi
|
|
177
|
+
done
|
|
178
|
+
|
|
179
|
+
if [ "$has_failure" = "true" ]; then
|
|
180
|
+
printf ' ⚠%s\n' "$failed_services"
|
|
181
|
+
else
|
|
182
|
+
footer_left=" ✓ ready"
|
|
183
|
+
if [ "${MEMORY_SYNCED:-false}" = "true" ]; then
|
|
184
|
+
footer_right="◆ memory synced"
|
|
185
|
+
else
|
|
186
|
+
footer_right="◆ memory not synced"
|
|
187
|
+
fi
|
|
188
|
+
pad=$((line_width - ${#footer_left} - ${#footer_right}))
|
|
189
|
+
[ "$pad" -lt 1 ] && pad=1
|
|
190
|
+
printf '%s%*s%s\n' "$footer_left" "$pad" "" "$footer_right"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# Autosave trail — background saves since the last greeting (same ledger
|
|
194
|
+
# the Claude greeting reads; sweep rescues become visible here).
|
|
195
|
+
autosave_log="${EGREGORE_AUTOSAVE_LOG:-$HOME/.egregore/autosave.log}"
|
|
196
|
+
autosave_seen="${autosave_log}.seen"
|
|
197
|
+
if [ -f "$autosave_log" ]; then
|
|
198
|
+
total_as=$(grep -c '|autosave|' "$autosave_log" 2>/dev/null || echo 0)
|
|
199
|
+
seen_as=$(cat "$autosave_seen" 2>/dev/null || echo 0)
|
|
200
|
+
case "$seen_as" in ''|*[!0-9]*) seen_as=0 ;; esac
|
|
201
|
+
if [ "$total_as" -gt "$seen_as" ]; then
|
|
202
|
+
new_as=$((total_as - seen_as))
|
|
203
|
+
merged_as=$(grep '|autosave|' "$autosave_log" 2>/dev/null | tail -n "$new_as" | grep -c '|merged|' || echo 0)
|
|
204
|
+
if [ "$merged_as" -gt 0 ]; then
|
|
205
|
+
printf ' ⟲ auto-saved %s non-coding save(s) → develop while you were away\n' "$merged_as"
|
|
206
|
+
fi
|
|
207
|
+
fi
|
|
208
|
+
printf '%s' "$total_as" > "$autosave_seen" 2>/dev/null || true
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# Staged auto-saves awaiting the user's merge (the autosave consent gate).
|
|
212
|
+
# Live gh query so the line persists until the PRs actually merge.
|
|
213
|
+
# Shown only while the autosave experiment is enabled.
|
|
214
|
+
autosave_on=$(jq -r '.autosave_enabled // false' "$STATE_FILE" 2>/dev/null)
|
|
215
|
+
if [ "$autosave_on" = "true" ] && command -v gh >/dev/null 2>&1; then
|
|
216
|
+
gh_self=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
|
|
217
|
+
if [ -n "$gh_self" ]; then
|
|
218
|
+
waiting_as=$(gh pr list --state open --author "$gh_self" --limit 20 \
|
|
219
|
+
--json number,title --jq '[.[] | select(.title | startswith("Auto-save:")) | .number] | join(", #")' 2>/dev/null || true)
|
|
220
|
+
if [ -n "$waiting_as" ]; then
|
|
221
|
+
printf ' ⟲ auto-saves awaiting your merge: PR #%s — \$autosave merge (or bin/agent.sh autosave merge)\n' "$waiting_as"
|
|
222
|
+
fi
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
# Greeting links. Org config `pinned_links` in egregore.json replaces the
|
|
227
|
+
# default board link — same contract as bin/lib/greeting.sh.
|
|
228
|
+
pinned_links=$(jq -c '.pinned_links // []' "$CONFIG" 2>/dev/null || echo "[]")
|
|
229
|
+
if [ "$(printf '%s' "$pinned_links" | jq 'length' 2>/dev/null || echo 0)" -gt 0 ]; then
|
|
230
|
+
printf '%s' "$pinned_links" | jq -r '.[] |
|
|
231
|
+
if type == "string" then " ◆ \(.)"
|
|
232
|
+
elif (.label // "") != "" then " ◆ \(.label): \(.url)"
|
|
233
|
+
else " ◆ \(.url)" end' 2>/dev/null
|
|
234
|
+
elif [ -n "$SLUG" ]; then
|
|
235
|
+
board_url="https://egregore.xyz/view/${SLUG}/board"
|
|
236
|
+
printf ' ◆ %s\n' "$board_url"
|
|
237
|
+
fi
|
|
238
|
+
printf '\nWhat are you working on?\n'
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
case "${1:---card}" in
|
|
242
|
+
--card|card)
|
|
243
|
+
render_card
|
|
244
|
+
;;
|
|
245
|
+
--prompt|prompt)
|
|
246
|
+
cat <<EOF
|
|
247
|
+
You are Codex running inside Egregore for ${ORG_NAME} as ${AUTHOR}.
|
|
248
|
+
The launcher already rendered the Egregore startup card using the shared identity, git-sync, graph, and filesystem context pipeline.
|
|
249
|
+
This session is Trusted Codex: full shell/network access is enabled and approval prompts are disabled by the launcher.
|
|
250
|
+
Do not rerun startup checks or narrate startup unless the user asks.
|
|
251
|
+
Follow AGENTS.md before code changes.
|
|
252
|
+
After the user names what they are working on, use bin/agent.sh branch --topic "<work topic>" if you are not already in an appropriate task worktree, then continue file work from the printed worktree path.
|
|
253
|
+
Use bin/agent.sh to update shared activity: handoff, ask, and answer write to memory/ and push when possible.
|
|
254
|
+
Use bin/agent.sh handoff for internal team session-handoffs. Portable external capsules are emissaries; use egregore-emissary when installed, not the deprecated egregore-handoff CLI.
|
|
255
|
+
Codex reserves leading slash commands for built-ins, so custom Egregore /activity-style commands will not reach the agent. Egregore native Codex skills are: \$activity, \$handoff, \$wrap, \$announce, \$harvest, \$the-spiral, \$dashboard, \$deep-reflect, \$quest, \$invite, \$ask, \$save, \$view, and \$scroll. Natural-language requests such as "show activity", "make a handoff", or "render this as a scroll" work too. Remaining mirrored workflows are adapter skills for long-tail coverage.
|
|
256
|
+
\$save is the user-facing abstraction for committing, pushing, opening or reusing pull requests, and syncing memory; do not make users manage the git workflow by hand.
|
|
257
|
+
Egregore Codex workflows are skill invocations, not Claude Code slash commands. Do not rely on Claude Code-only command machinery.
|
|
258
|
+
If onboarding_complete is false, use the memory protocol directly and make sure memory/people/${AUTHOR}.md exists before the first handoff.
|
|
259
|
+
EOF
|
|
260
|
+
;;
|
|
261
|
+
*)
|
|
262
|
+
echo "usage: bin/codex-session-start.sh [--card|--prompt]" >&2
|
|
263
|
+
exit 2
|
|
264
|
+
;;
|
|
265
|
+
esac
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
const WIDTH = 72;
|
|
5
|
+
|
|
6
|
+
function usage() {
|
|
7
|
+
console.error(`usage:
|
|
8
|
+
node bin/codex-skill-render.mjs classify-graph [--mode connected|local] [--attempt initial|retry] [file|-]
|
|
9
|
+
node bin/codex-skill-render.mjs activity-card [file|-]
|
|
10
|
+
node bin/codex-skill-render.mjs dashboard-card [file|-]
|
|
11
|
+
node bin/codex-skill-render.mjs handoff-card [file|-]`);
|
|
12
|
+
process.exit(2);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readInput(file) {
|
|
16
|
+
if (!file || file === '-') {
|
|
17
|
+
return fs.readFileSync(0, 'utf8');
|
|
18
|
+
}
|
|
19
|
+
return fs.readFileSync(file, 'utf8');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readJson(file) {
|
|
23
|
+
const raw = readInput(file).trim() || '{}';
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(raw);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(`invalid JSON${file ? ` in ${file}` : ''}: ${error.message}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseFlags(args) {
|
|
33
|
+
const flags = {};
|
|
34
|
+
const rest = [];
|
|
35
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
36
|
+
const arg = args[i];
|
|
37
|
+
if (arg === '--mode' || arg === '--attempt') {
|
|
38
|
+
flags[arg.slice(2)] = args[i + 1];
|
|
39
|
+
i += 1;
|
|
40
|
+
} else {
|
|
41
|
+
rest.push(arg);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { flags, rest };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function asArray(value) {
|
|
48
|
+
return Array.isArray(value) ? value : [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function firstArray(data, paths) {
|
|
52
|
+
for (const p of paths) {
|
|
53
|
+
const value = p.split('.').reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), data);
|
|
54
|
+
if (Array.isArray(value)) return value;
|
|
55
|
+
}
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function valueOf(item, keys, fallback = '') {
|
|
60
|
+
for (const key of keys) {
|
|
61
|
+
if (item && item[key] !== undefined && item[key] !== null && String(item[key]).trim() !== '') {
|
|
62
|
+
return String(item[key]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function truncate(value, max) {
|
|
69
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
70
|
+
if (text.length <= max) return text;
|
|
71
|
+
return `${text.slice(0, Math.max(0, max - 3))}...`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function padLine(left, right = '') {
|
|
75
|
+
const inner = WIDTH - 4;
|
|
76
|
+
const r = right ? truncate(right, Math.min(18, inner - 1)) : '';
|
|
77
|
+
const l = truncate(left, inner - (r ? r.length + 1 : 0));
|
|
78
|
+
const space = Math.max(0, inner - l.length - r.length);
|
|
79
|
+
return `│ ${l}${' '.repeat(space)}${r} │`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function topRule() {
|
|
83
|
+
return `┌${'─'.repeat(WIDTH - 2)}┐`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function rule() {
|
|
87
|
+
return `├${'─'.repeat(WIDTH - 2)}┤`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function bottomRule() {
|
|
91
|
+
return `└${'─'.repeat(WIDTH - 2)}┘`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function graphFields(data) {
|
|
95
|
+
return {
|
|
96
|
+
status: data.graph_status || data.graphStatus || data.graph?.status || '',
|
|
97
|
+
reason: data.graph_reason || data.graphReason || data.graph?.reason || '',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function classifyGraph(data, options = {}) {
|
|
102
|
+
const mode = options.mode || data.mode || 'connected';
|
|
103
|
+
const attempt = options.attempt || 'initial';
|
|
104
|
+
const { status, reason } = graphFields(data);
|
|
105
|
+
|
|
106
|
+
if (mode === 'local') {
|
|
107
|
+
return {
|
|
108
|
+
status: 'local',
|
|
109
|
+
retry: false,
|
|
110
|
+
reason: reason || 'local_mode',
|
|
111
|
+
message: 'Local mode uses memory files as the source of truth.',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (status === 'connected') {
|
|
116
|
+
return {
|
|
117
|
+
status: 'connected',
|
|
118
|
+
retry: false,
|
|
119
|
+
reason: '',
|
|
120
|
+
message: 'Graph connected.',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (reason === 'unreachable' && attempt !== 'retry') {
|
|
125
|
+
return {
|
|
126
|
+
status: 'retry',
|
|
127
|
+
retry: true,
|
|
128
|
+
reason,
|
|
129
|
+
message: 'Connected-mode graph was unreachable; retry with network escalation.',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const finalReason = reason || status || 'offline';
|
|
134
|
+
return {
|
|
135
|
+
status: 'offline',
|
|
136
|
+
retry: false,
|
|
137
|
+
reason: finalReason,
|
|
138
|
+
message: `Graph offline (${finalReason}). Render filesystem fallback.`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function renderActivity(data) {
|
|
143
|
+
const graph = classifyGraph(data, { mode: data.mode || 'connected', attempt: 'retry' });
|
|
144
|
+
const org = data.org || data.organization || 'Egregore';
|
|
145
|
+
const date = data.date || '';
|
|
146
|
+
const me = typeof data.me === 'string'
|
|
147
|
+
? data.me
|
|
148
|
+
: data.me?.github || data.me?.name || data.github_username || '';
|
|
149
|
+
const handoffs = firstArray(data, ['handoffs_to_me', 'handoffs', 'pending_handoffs', 'me.handoffs', 'addressed_handoffs']);
|
|
150
|
+
const questions = firstArray(data, ['questions', 'pending_questions', 'me.questions']);
|
|
151
|
+
const prs = asArray(data.prs);
|
|
152
|
+
const mine = firstArray(data, ['local_sessions.my_sessions', 'my_sessions', 'sessions']);
|
|
153
|
+
const team = firstArray(data, ['local_sessions.team_sessions', 'team_sessions', 'team.sessions']);
|
|
154
|
+
|
|
155
|
+
const brand = `${truncate(org, 20).toUpperCase()} EGREGORE ✦ ACTIVITY DASHBOARD`;
|
|
156
|
+
const lines = [topRule(), padLine(brand), padLine([me, date].filter(Boolean).join(' · ')), rule()];
|
|
157
|
+
if (graph.status !== 'connected') {
|
|
158
|
+
lines.push(padLine(`graph: ${graph.status}`, graph.reason));
|
|
159
|
+
lines.push(rule());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
lines.push(padLine('FOR YOU'));
|
|
163
|
+
if (questions.length === 0 && handoffs.length === 0) {
|
|
164
|
+
lines.push(padLine('nothing pending'));
|
|
165
|
+
} else {
|
|
166
|
+
if (questions.length > 0) {
|
|
167
|
+
lines.push(padLine('QUESTIONS'));
|
|
168
|
+
questions.slice(0, 4).forEach((q) => {
|
|
169
|
+
lines.push(padLine(`● ${valueOf(q, ['from', 'author', 'asker'], 'someone')}: ${valueOf(q, ['topic', 'title', 'question'], 'question')}`));
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
if (handoffs.length > 0) {
|
|
173
|
+
lines.push(padLine('HANDOFFS'));
|
|
174
|
+
const glyph = { pending: '●', read: '◐', claimed: '◆', done: '✓', completed: '✓', expired: '×' };
|
|
175
|
+
handoffs.slice(0, 6).forEach((h, index) => {
|
|
176
|
+
const status = valueOf(h, ['status'], 'pending');
|
|
177
|
+
const age = valueOf(h, ['ageDays', 'age_days'], '');
|
|
178
|
+
const intent = valueOf(h, ['intent'], '');
|
|
179
|
+
const meta = [
|
|
180
|
+
age === '' ? '' : `${age}d`,
|
|
181
|
+
intent && intent !== 'unclassified' ? intent : '',
|
|
182
|
+
status,
|
|
183
|
+
].filter(Boolean).join(' · ');
|
|
184
|
+
lines.push(padLine(
|
|
185
|
+
`[${index + 1}] ${glyph[status] || '○'} ⇌ ${valueOf(h, ['author', 'from'], 'someone')}: ${valueOf(h, ['topic', 'title'], 'handoff')}`,
|
|
186
|
+
meta,
|
|
187
|
+
));
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
lines.push(rule());
|
|
193
|
+
lines.push(padLine('RECENT WORK'));
|
|
194
|
+
if (mine.length === 0) {
|
|
195
|
+
lines.push(padLine('no recent local sessions found'));
|
|
196
|
+
} else {
|
|
197
|
+
mine.slice(0, 5).forEach((s) => {
|
|
198
|
+
lines.push(padLine(`${valueOf(s, ['date', 'when'], '')} ${valueOf(s, ['topic', 'title', 'branch'], 'session')}`, valueOf(s, ['type', 'status', 'duration'], '')));
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (team.length > 0 || prs.length > 0) {
|
|
203
|
+
lines.push(rule());
|
|
204
|
+
lines.push(padLine('AROUND'));
|
|
205
|
+
team.slice(0, 3).forEach((s) => {
|
|
206
|
+
lines.push(padLine(`${valueOf(s, ['author', 'name'], 'teammate')}: ${valueOf(s, ['topic', 'title', 'branch'], 'session')}`));
|
|
207
|
+
});
|
|
208
|
+
prs.slice(0, 3).forEach((pr) => {
|
|
209
|
+
lines.push(padLine(`#${valueOf(pr, ['number'], '')} ${valueOf(pr, ['title'], 'pull request')}`, valueOf(pr.author || {}, ['login', 'name'], '')));
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
lines.push(rule());
|
|
214
|
+
if (handoffs.length > 0) {
|
|
215
|
+
lines.push(padLine('Handoff actions: done N · expire N · reopen N'));
|
|
216
|
+
lines.push(padLine('Type a number to act, or keep working.'));
|
|
217
|
+
} else {
|
|
218
|
+
lines.push(padLine('Focus choices: questions, recent work, dashboard, done'));
|
|
219
|
+
}
|
|
220
|
+
lines.push(bottomRule());
|
|
221
|
+
return lines.join('\n');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function humanTopic(session) {
|
|
225
|
+
const topic = valueOf(session, ['topic', 'title'], '');
|
|
226
|
+
if (topic) return topic;
|
|
227
|
+
const branch = valueOf(session, ['branch'], '');
|
|
228
|
+
if (branch && !['develop', 'main', 'master'].includes(branch)) {
|
|
229
|
+
return branch.replace(/^dev\/[^/]+\//, '').replace(/[-_]+/g, ' ');
|
|
230
|
+
}
|
|
231
|
+
return valueOf(session, ['id', 'date'], 'session');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function renderDashboard(data) {
|
|
235
|
+
const graph = classifyGraph(data, { mode: data.mode || 'connected', attempt: 'retry' });
|
|
236
|
+
const org = data.org || 'Egregore';
|
|
237
|
+
const date = data.date || '';
|
|
238
|
+
const me = data.me?.github || data.me?.name || data.github_username || '';
|
|
239
|
+
const current = data.current_session || {};
|
|
240
|
+
const sessions = firstArray(data, ['sessions', 'local_sessions.sessions']);
|
|
241
|
+
const todos = firstArray(data, ['todos', 'open_todos']);
|
|
242
|
+
const quests = firstArray(data, ['quests', 'active_quests']);
|
|
243
|
+
const handoffs = firstArray(data, ['handoffs', 'pending_handoffs']);
|
|
244
|
+
const threads = firstArray(data, ['open_threads', 'threads']);
|
|
245
|
+
const stats = data.stats || {};
|
|
246
|
+
|
|
247
|
+
const lines = [topRule(), padLine('DASHBOARD', [org, me, date].filter(Boolean).join(' · ')), rule()];
|
|
248
|
+
if (graph.status !== 'connected') {
|
|
249
|
+
lines.push(padLine(`graph: ${graph.status}`, graph.reason));
|
|
250
|
+
lines.push(rule());
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
lines.push(padLine(`* ${humanTopic(current) || '(starting...)'}`, 'active'));
|
|
254
|
+
|
|
255
|
+
if (threads.length > 0) {
|
|
256
|
+
lines.push(rule());
|
|
257
|
+
lines.push(padLine('PICK UP WHERE YOU LEFT OFF'));
|
|
258
|
+
threads.slice(0, 5).forEach((thread) => {
|
|
259
|
+
lines.push(padLine(`- ${valueOf(thread, ['text', 'thread', 'title', 'topic'], 'open thread')}`));
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (todos.length > 0) {
|
|
264
|
+
lines.push(rule());
|
|
265
|
+
lines.push(padLine(`OPEN TODOS (${todos.length})`));
|
|
266
|
+
todos.slice(0, 3).forEach((todo, index) => {
|
|
267
|
+
lines.push(padLine(`[${index + 1}] ${valueOf(todo, ['text', 'title'], 'todo')}`, valueOf(todo, ['status'], '')));
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (handoffs.length > 0) {
|
|
272
|
+
lines.push(rule());
|
|
273
|
+
lines.push(padLine(`HANDOFFS (${handoffs.length} pending)`));
|
|
274
|
+
handoffs.slice(0, 3).forEach((handoff) => {
|
|
275
|
+
lines.push(padLine(`${valueOf(handoff, ['author', 'from'], 'someone')}: ${valueOf(handoff, ['topic', 'title'], 'handoff')}`, valueOf(handoff, ['when', 'date'], '')));
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
lines.push(rule());
|
|
280
|
+
lines.push(padLine(`SESSIONS (${data.range_label || 'recent'})`));
|
|
281
|
+
if (sessions.length === 0) {
|
|
282
|
+
lines.push(padLine('No activity recorded yet'));
|
|
283
|
+
} else {
|
|
284
|
+
sessions.slice(0, 5).forEach((session) => {
|
|
285
|
+
lines.push(padLine(`${valueOf(session, ['date', 'when'], '')} ${humanTopic(session)}`, valueOf(session, ['status', 'type'], '')));
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (quests.length > 0) {
|
|
290
|
+
lines.push(rule());
|
|
291
|
+
lines.push(padLine(`QUESTS (${quests.length} active)`));
|
|
292
|
+
quests.slice(0, 3).forEach((quest) => {
|
|
293
|
+
lines.push(padLine(valueOf(quest, ['id', 'quest', 'slug', 'title'], 'quest'), valueOf(quest, ['status', 'summary'], '')));
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
lines.push(rule());
|
|
298
|
+
const total = stats.totalSessions ?? data.local_sessions?.session_count ?? sessions.length;
|
|
299
|
+
const wrapped = stats.wrappedSessions ?? sessions.filter((s) => valueOf(s, ['status', 'type'], '').includes('wrap')).length;
|
|
300
|
+
lines.push(padLine(`${total} sessions · ${wrapped}/${total} wrapped · ${stats.openTodos ?? todos.length} open todos`));
|
|
301
|
+
lines.push(padLine("What's next?"));
|
|
302
|
+
lines.push(bottomRule());
|
|
303
|
+
return lines.join('\n');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function renderHandoff(data) {
|
|
307
|
+
const file = data.file ? `memory/${data.file}` : '';
|
|
308
|
+
const statuses = [
|
|
309
|
+
data.graphStatus ? `graph=${data.graphStatus}` : '',
|
|
310
|
+
data.memoryStatus ? `memory=${data.memoryStatus}` : '',
|
|
311
|
+
data.notifyStatus ? `notify=${data.notifyStatus}` : '',
|
|
312
|
+
data.publishStatus ? `publish=${data.publishStatus}` : '',
|
|
313
|
+
].filter(Boolean).join(' · ');
|
|
314
|
+
|
|
315
|
+
const lines = [topRule(), padLine('⇌ HANDOFF', data.author || ''), rule()];
|
|
316
|
+
lines.push(padLine(`Topic: ${data.topic || '(untitled)'}`));
|
|
317
|
+
if (data.recipient) lines.push(padLine(`To: ${data.recipient}`));
|
|
318
|
+
if (file) lines.push(padLine(`Path: ${file}`));
|
|
319
|
+
if (data.artifactUrl) lines.push(padLine(`Link: ${data.artifactUrl}`));
|
|
320
|
+
if (statuses) lines.push(padLine(statuses));
|
|
321
|
+
if (data.publishStatus === 'fidelity-failed') {
|
|
322
|
+
lines.push(rule());
|
|
323
|
+
lines.push(padLine('Artifact not published — restore missing content and preview again.'));
|
|
324
|
+
}
|
|
325
|
+
lines.push(bottomRule());
|
|
326
|
+
return lines.join('\n');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const [command, ...rawArgs] = process.argv.slice(2);
|
|
330
|
+
if (!command) usage();
|
|
331
|
+
|
|
332
|
+
if (command === 'classify-graph') {
|
|
333
|
+
const { flags, rest } = parseFlags(rawArgs);
|
|
334
|
+
const data = readJson(rest[0]);
|
|
335
|
+
console.log(JSON.stringify(classifyGraph(data, flags)));
|
|
336
|
+
} else if (command === 'activity-card') {
|
|
337
|
+
const data = readJson(rawArgs[0]);
|
|
338
|
+
console.log(renderActivity(data));
|
|
339
|
+
} else if (command === 'dashboard-card') {
|
|
340
|
+
const data = readJson(rawArgs[0]);
|
|
341
|
+
console.log(renderDashboard(data));
|
|
342
|
+
} else if (command === 'handoff-card') {
|
|
343
|
+
const data = readJson(rawArgs[0]);
|
|
344
|
+
console.log(renderHandoff(data));
|
|
345
|
+
} else {
|
|
346
|
+
usage();
|
|
347
|
+
}
|