@ekkos/cli 1.2.17 → 1.3.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/dist/cache/capture.js +0 -0
- package/dist/commands/dashboard.js +57 -49
- package/dist/commands/hooks.d.ts +25 -36
- package/dist/commands/hooks.js +43 -615
- package/dist/commands/init.js +7 -23
- package/dist/commands/run.js +97 -11
- package/dist/commands/setup.js +10 -352
- package/dist/deploy/hooks.d.ts +8 -5
- package/dist/deploy/hooks.js +12 -105
- package/dist/deploy/settings.d.ts +8 -2
- package/dist/deploy/settings.js +22 -51
- package/dist/index.js +17 -39
- package/dist/utils/state.js +7 -2
- package/package.json +1 -1
- package/templates/CLAUDE.md +82 -292
- package/templates/cursor-rules/ekkos-memory.md +48 -108
- package/templates/windsurf-rules/ekkos-memory.md +62 -64
- package/templates/cursor-hooks/after-agent-response.sh +0 -117
- package/templates/cursor-hooks/before-submit-prompt.sh +0 -419
- package/templates/cursor-hooks/hooks.json +0 -20
- package/templates/cursor-hooks/lib/contract.sh +0 -320
- package/templates/cursor-hooks/stop.sh +0 -75
- package/templates/hooks/assistant-response.ps1 +0 -256
- package/templates/hooks/assistant-response.sh +0 -160
- package/templates/hooks/hooks.json +0 -40
- package/templates/hooks/lib/contract.sh +0 -332
- package/templates/hooks/lib/count-tokens.cjs +0 -86
- package/templates/hooks/lib/ekkos-reminders.sh +0 -98
- package/templates/hooks/lib/state.sh +0 -210
- package/templates/hooks/session-start.ps1 +0 -146
- package/templates/hooks/session-start.sh +0 -353
- package/templates/hooks/stop.ps1 +0 -349
- package/templates/hooks/stop.sh +0 -382
- package/templates/hooks/user-prompt-submit.ps1 +0 -419
- package/templates/hooks/user-prompt-submit.sh +0 -516
- package/templates/project-stubs/session-start.ps1 +0 -63
- package/templates/project-stubs/session-start.sh +0 -55
- package/templates/project-stubs/stop.ps1 +0 -63
- package/templates/project-stubs/stop.sh +0 -55
- package/templates/project-stubs/user-prompt-submit.ps1 +0 -63
- package/templates/project-stubs/user-prompt-submit.sh +0 -55
- package/templates/windsurf-hooks/README.md +0 -212
- package/templates/windsurf-hooks/hooks.json +0 -17
- package/templates/windsurf-hooks/install.sh +0 -148
- package/templates/windsurf-hooks/lib/contract.sh +0 -322
- package/templates/windsurf-hooks/post-cascade-response.sh +0 -251
- package/templates/windsurf-hooks/pre-user-prompt.sh +0 -435
|
@@ -1,516 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
3
|
-
# ekkOS_ Hook: UserPromptSubmit - SEAMLESS CONTEXT CONTINUITY
|
|
4
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
5
|
-
# ZERO USER ACTION NEEDED:
|
|
6
|
-
# 1. Tracks turn number and context size
|
|
7
|
-
# 2. Detects when compaction happened (context dropped from high to low)
|
|
8
|
-
# 3. AUTO-INJECTS restored context - user just keeps working
|
|
9
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
10
|
-
|
|
11
|
-
set +e
|
|
12
|
-
|
|
13
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
-
PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
|
15
|
-
|
|
16
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
17
|
-
# PROXY MODE DETECTION
|
|
18
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
19
|
-
PROXY_MODE=false
|
|
20
|
-
if [[ "$ANTHROPIC_BASE_URL" == *"proxy.ekkos.dev"* ]]; then
|
|
21
|
-
PROXY_MODE=true
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
25
|
-
# CONFIG PATHS - No jq dependency (v1.2 spec)
|
|
26
|
-
# Session words live in ~/.ekkos/ so they work in ANY project
|
|
27
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
28
|
-
EKKOS_CONFIG_DIR="${EKKOS_CONFIG_DIR:-$HOME/.ekkos}"
|
|
29
|
-
SESSION_WORDS_JSON="$EKKOS_CONFIG_DIR/session-words.json"
|
|
30
|
-
SESSION_WORDS_DEFAULT="$EKKOS_CONFIG_DIR/.defaults/session-words.json"
|
|
31
|
-
JSON_PARSE_HELPER="$EKKOS_CONFIG_DIR/.helpers/json-parse.cjs"
|
|
32
|
-
|
|
33
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
34
|
-
# JSON PARSING HELPER - No jq required
|
|
35
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
36
|
-
parse_json_value() {
|
|
37
|
-
local json="$1"
|
|
38
|
-
local path="$2"
|
|
39
|
-
echo "$json" | node -e "
|
|
40
|
-
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
41
|
-
const path = '$path'.replace(/^\./,'').split('.').filter(Boolean);
|
|
42
|
-
let result = data;
|
|
43
|
-
for (const p of path) {
|
|
44
|
-
if (result === undefined || result === null) { result = undefined; break; }
|
|
45
|
-
result = result[p];
|
|
46
|
-
}
|
|
47
|
-
if (result !== undefined && result !== null) console.log(result);
|
|
48
|
-
" 2>/dev/null || echo ""
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
INPUT=$(cat)
|
|
52
|
-
USER_QUERY=$(parse_json_value "$INPUT" '.query')
|
|
53
|
-
[ -z "$USER_QUERY" ] || [ "$USER_QUERY" = "null" ] && USER_QUERY=$(parse_json_value "$INPUT" '.message')
|
|
54
|
-
[ -z "$USER_QUERY" ] || [ "$USER_QUERY" = "null" ] && USER_QUERY=$(parse_json_value "$INPUT" '.prompt')
|
|
55
|
-
RAW_SESSION_ID=$(parse_json_value "$INPUT" '.session_id')
|
|
56
|
-
[ -z "$RAW_SESSION_ID" ] && RAW_SESSION_ID="unknown"
|
|
57
|
-
TRANSCRIPT_PATH=$(parse_json_value "$INPUT" '.transcript_path')
|
|
58
|
-
|
|
59
|
-
[ -z "$USER_QUERY" ] || [ "$USER_QUERY" = "null" ] && exit 0
|
|
60
|
-
|
|
61
|
-
# Fallback: read session_id from saved state if not in INPUT
|
|
62
|
-
if [ "$RAW_SESSION_ID" = "unknown" ] || [ "$RAW_SESSION_ID" = "null" ] || [ -z "$RAW_SESSION_ID" ]; then
|
|
63
|
-
STATE_FILE="$HOME/.claude/state/current-session.json"
|
|
64
|
-
if [ -f "$STATE_FILE" ] && [ -f "$JSON_PARSE_HELPER" ]; then
|
|
65
|
-
RAW_SESSION_ID=$(node "$JSON_PARSE_HELPER" "$STATE_FILE" '.session_id' 2>/dev/null || echo "unknown")
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
# VSCode extension fallback: Extract session ID from transcript path
|
|
69
|
-
# Path format: ~/.claude/projects/<project>/<session-uuid>.jsonl
|
|
70
|
-
if [ "$RAW_SESSION_ID" = "unknown" ] && [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
|
|
71
|
-
RAW_SESSION_ID=$(basename "$TRANSCRIPT_PATH" .jsonl)
|
|
72
|
-
fi
|
|
73
|
-
fi
|
|
74
|
-
|
|
75
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
76
|
-
# PROXY MODE: Slim hook path (cosmetic output + local state only)
|
|
77
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
78
|
-
if [ "$PROXY_MODE" = "true" ]; then
|
|
79
|
-
SESSION_ID="$RAW_SESSION_ID"
|
|
80
|
-
UUID_REGEX='^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
|
|
81
|
-
SESSION_NAME="$SESSION_ID"
|
|
82
|
-
|
|
83
|
-
if [[ "$SESSION_ID" =~ $UUID_REGEX ]] && [ -f "$JSON_PARSE_HELPER" ]; then
|
|
84
|
-
SESSION_NAME=$(node -e "
|
|
85
|
-
const fs = require('fs');
|
|
86
|
-
const sid = process.argv[1] || '';
|
|
87
|
-
const wordsFile = process.argv[2];
|
|
88
|
-
const fallbackFile = process.argv[3];
|
|
89
|
-
const helper = process.argv[4];
|
|
90
|
-
let wordsPath = wordsFile;
|
|
91
|
-
if (!fs.existsSync(wordsPath)) wordsPath = fallbackFile;
|
|
92
|
-
if (!fs.existsSync(wordsPath) || !fs.existsSync(helper)) {
|
|
93
|
-
console.log(sid || 'unknown-session');
|
|
94
|
-
process.exit(0);
|
|
95
|
-
}
|
|
96
|
-
const cp = require('child_process');
|
|
97
|
-
function readList(path) {
|
|
98
|
-
const out = cp.spawnSync('node', [helper, wordsPath, path], { encoding: 'utf8' });
|
|
99
|
-
if (out.status !== 0) return [];
|
|
100
|
-
return (out.stdout || '').split('\\n').map(s => s.trim()).filter(Boolean);
|
|
101
|
-
}
|
|
102
|
-
const adjectives = readList('.adjectives');
|
|
103
|
-
const nouns = readList('.nouns');
|
|
104
|
-
const verbs = readList('.verbs');
|
|
105
|
-
if (!adjectives.length || !nouns.length || !verbs.length) {
|
|
106
|
-
console.log(sid);
|
|
107
|
-
process.exit(0);
|
|
108
|
-
}
|
|
109
|
-
const hex = sid.replace(/-/g, '').slice(0, 12);
|
|
110
|
-
if (!/^[0-9a-fA-F]{12}$/.test(hex)) {
|
|
111
|
-
console.log(sid);
|
|
112
|
-
process.exit(0);
|
|
113
|
-
}
|
|
114
|
-
const adj = parseInt(hex.slice(0, 4), 16) % adjectives.length;
|
|
115
|
-
const noun = parseInt(hex.slice(4, 8), 16) % nouns.length;
|
|
116
|
-
const verb = parseInt(hex.slice(8, 12), 16) % verbs.length;
|
|
117
|
-
console.log(adjectives[adj] + '-' + nouns[noun] + '-' + verbs[verb]);
|
|
118
|
-
" "$SESSION_ID" "$SESSION_WORDS_JSON" "$SESSION_WORDS_DEFAULT" "$JSON_PARSE_HELPER" 2>/dev/null || echo "$SESSION_ID")
|
|
119
|
-
fi
|
|
120
|
-
|
|
121
|
-
TIMESTAMP_UTC=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
122
|
-
DISPLAY_TIME=$(date "+%Y-%m-%d %I:%M:%S %p %Z")
|
|
123
|
-
mkdir -p "$HOME/.ekkos" "$HOME/.claude/state" 2>/dev/null || true
|
|
124
|
-
echo "{\"session_id\":\"$SESSION_ID\",\"session_name\":\"$SESSION_NAME\",\"project\":\"$PROJECT_ROOT\",\"timestamp\":\"$TIMESTAMP_UTC\"}" > "$HOME/.ekkos/current-session.json" 2>/dev/null || true
|
|
125
|
-
echo "{\"session_id\":\"$SESSION_ID\",\"session_name\":\"$SESSION_NAME\",\"timestamp\":\"$TIMESTAMP_UTC\"}" > "$HOME/.claude/state/current-session.json" 2>/dev/null || true
|
|
126
|
-
|
|
127
|
-
ACTIVE_SESSIONS_FILE="$HOME/.ekkos/active-sessions.json"
|
|
128
|
-
node -e "
|
|
129
|
-
const fs = require('fs');
|
|
130
|
-
const file = process.argv[1];
|
|
131
|
-
const sid = process.argv[2];
|
|
132
|
-
const sname = process.argv[3];
|
|
133
|
-
const ts = process.argv[4];
|
|
134
|
-
const project = process.argv[5];
|
|
135
|
-
let sessions = [];
|
|
136
|
-
try {
|
|
137
|
-
if (fs.existsSync(file)) {
|
|
138
|
-
sessions = JSON.parse(fs.readFileSync(file, 'utf8') || '[]');
|
|
139
|
-
}
|
|
140
|
-
} catch {}
|
|
141
|
-
const index = sessions.findIndex(s => s.sessionId === sid);
|
|
142
|
-
if (index >= 0) {
|
|
143
|
-
sessions[index] = { ...sessions[index], sessionName: sname, projectPath: project, lastHeartbeat: ts };
|
|
144
|
-
} else {
|
|
145
|
-
sessions.push({ sessionId: sid, sessionName: sname, projectPath: project, startedAt: ts, lastHeartbeat: ts, pid: 0 });
|
|
146
|
-
}
|
|
147
|
-
fs.writeFileSync(file, JSON.stringify(sessions, null, 2));
|
|
148
|
-
" "$ACTIVE_SESSIONS_FILE" "$SESSION_ID" "$SESSION_NAME" "$TIMESTAMP_UTC" "$PROJECT_ROOT" 2>/dev/null || true
|
|
149
|
-
|
|
150
|
-
# Dashboard/local tooling hint file
|
|
151
|
-
echo "{\"sessionName\":\"$SESSION_NAME\",\"sessionId\":\"$SESSION_ID\",\"projectPath\":\"$PROJECT_ROOT\",\"ts\":$(date +%s)000}" > "$HOME/.ekkos/hook-session-hint.json" 2>/dev/null || true
|
|
152
|
-
|
|
153
|
-
CYAN='\033[0;36m'
|
|
154
|
-
DIM='\033[2m'
|
|
155
|
-
BOLD='\033[1m'
|
|
156
|
-
RESET='\033[0m'
|
|
157
|
-
echo -e "${CYAN}${BOLD}🧠 ekkOS Memory${RESET} ${DIM}| ${SESSION_NAME} | ${DISPLAY_TIME}${RESET}"
|
|
158
|
-
exit 0
|
|
159
|
-
fi
|
|
160
|
-
|
|
161
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
162
|
-
# SKILL AUTO-FIRE: Detect keywords and inject skill reminders
|
|
163
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
164
|
-
SKILL_REMINDER=""
|
|
165
|
-
QUERY_LOWER=$(echo "$USER_QUERY" | tr '[:upper:]' '[:lower:]')
|
|
166
|
-
|
|
167
|
-
# Memory First triggers
|
|
168
|
-
if echo "$QUERY_LOWER" | grep -qE '(how do i|debug|error|bug|fix|not working|broken|fails|issue)'; then
|
|
169
|
-
SKILL_REMINDER="🔧 SKILL REQUIRED: Call Skill(skill: \"ekkOS_Memory_First\") FIRST before debugging"
|
|
170
|
-
fi
|
|
171
|
-
|
|
172
|
-
# Recall triggers
|
|
173
|
-
if echo "$QUERY_LOWER" | grep -qE '(yesterday|last week|remember when|what did we|where did we leave)'; then
|
|
174
|
-
SKILL_REMINDER="📅 SKILL REQUIRED: Call Skill(skill: \"ekkOS_Recall\") for time-based memory"
|
|
175
|
-
fi
|
|
176
|
-
|
|
177
|
-
# Preferences triggers
|
|
178
|
-
if echo "$QUERY_LOWER" | grep -qE '(always |never |i prefer|dont |don.t |avoid )'; then
|
|
179
|
-
SKILL_REMINDER="⚙️ SKILL REQUIRED: Call Skill(skill: \"ekkOS_Preferences\") to capture directive"
|
|
180
|
-
fi
|
|
181
|
-
|
|
182
|
-
# Safety triggers
|
|
183
|
-
if echo "$QUERY_LOWER" | grep -qE '(delete|drop |rm -rf|deploy|push.*main|push.*master)'; then
|
|
184
|
-
SKILL_REMINDER="⚠️ SKILL REQUIRED: Call Skill(skill: \"ekkOS_Safety\") before destructive action"
|
|
185
|
-
fi
|
|
186
|
-
|
|
187
|
-
# Schema triggers
|
|
188
|
-
if echo "$QUERY_LOWER" | grep -qE '(sql|query|supabase|prisma|database|table|column)'; then
|
|
189
|
-
SKILL_REMINDER="🗄️ SKILL REQUIRED: Call Skill(skill: \"ekkOS_Schema\") for correct field names"
|
|
190
|
-
fi
|
|
191
|
-
|
|
192
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
193
|
-
# Load auth - No jq
|
|
194
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
195
|
-
EKKOS_CONFIG="$HOME/.ekkos/config.json"
|
|
196
|
-
AUTH_TOKEN=""
|
|
197
|
-
if [ -f "$EKKOS_CONFIG" ] && [ -f "$JSON_PARSE_HELPER" ]; then
|
|
198
|
-
AUTH_TOKEN=$(node "$JSON_PARSE_HELPER" "$EKKOS_CONFIG" '.hookApiKey' 2>/dev/null || echo "")
|
|
199
|
-
if [ -z "$AUTH_TOKEN" ]; then
|
|
200
|
-
AUTH_TOKEN=$(node "$JSON_PARSE_HELPER" "$EKKOS_CONFIG" '.apiKey' 2>/dev/null || echo "")
|
|
201
|
-
fi
|
|
202
|
-
fi
|
|
203
|
-
[ -z "$AUTH_TOKEN" ] && AUTH_TOKEN=$(grep -E "^SUPABASE_SECRET_KEY=" "$PROJECT_ROOT/.env.local" 2>/dev/null | cut -d'=' -f2- | tr -d '"' | tr -d "'" || echo "")
|
|
204
|
-
|
|
205
|
-
MEMORY_API_URL="https://mcp.ekkos.dev"
|
|
206
|
-
|
|
207
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
208
|
-
# Session ID
|
|
209
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
210
|
-
STATE_DIR="$PROJECT_ROOT/.claude/state"
|
|
211
|
-
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
|
212
|
-
SESSION_FILE="$STATE_DIR/current-session.json"
|
|
213
|
-
|
|
214
|
-
SESSION_ID="$RAW_SESSION_ID"
|
|
215
|
-
|
|
216
|
-
if [ -z "$SESSION_ID" ] || [ "$SESSION_ID" = "unknown" ] || [ "$SESSION_ID" = "null" ]; then
|
|
217
|
-
exit 0
|
|
218
|
-
fi
|
|
219
|
-
|
|
220
|
-
# Check if SESSION_ID is a UUID (8-4-4-4-12 format)
|
|
221
|
-
UUID_REGEX='^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
|
|
222
|
-
IS_UUID=false
|
|
223
|
-
if [[ "$SESSION_ID" =~ $UUID_REGEX ]]; then
|
|
224
|
-
IS_UUID=true
|
|
225
|
-
fi
|
|
226
|
-
|
|
227
|
-
echo "{\"session_id\": \"$SESSION_ID\", \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > "$SESSION_FILE"
|
|
228
|
-
|
|
229
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
230
|
-
# Turn counter - PROJECT-LOCAL storage
|
|
231
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
232
|
-
PROJECT_SESSION_DIR="$STATE_DIR/sessions"
|
|
233
|
-
mkdir -p "$PROJECT_SESSION_DIR" 2>/dev/null || true
|
|
234
|
-
TURN_COUNTER_FILE="$PROJECT_SESSION_DIR/${SESSION_ID}.turn"
|
|
235
|
-
|
|
236
|
-
# Count API round-trips from transcript to match TUI turn counter
|
|
237
|
-
TURN_NUMBER=1
|
|
238
|
-
if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
|
|
239
|
-
TURN_NUMBER=$(grep -c '"type":"user"' "$TRANSCRIPT_PATH" 2>/dev/null || echo "1")
|
|
240
|
-
[ "$TURN_NUMBER" -eq 0 ] && TURN_NUMBER=1
|
|
241
|
-
fi
|
|
242
|
-
|
|
243
|
-
# Detect post-clear: saved count higher than transcript means /clear happened
|
|
244
|
-
SAVED_TURN_COUNT=0
|
|
245
|
-
[ -f "$TURN_COUNTER_FILE" ] && SAVED_TURN_COUNT=$(cat "$TURN_COUNTER_FILE" 2>/dev/null || echo "0")
|
|
246
|
-
POST_CLEAR_DETECTED=false
|
|
247
|
-
|
|
248
|
-
if [ "$SAVED_TURN_COUNT" -gt "$TURN_NUMBER" ]; then
|
|
249
|
-
POST_CLEAR_DETECTED=true
|
|
250
|
-
TURN_NUMBER=$((SAVED_TURN_COUNT + 1))
|
|
251
|
-
fi
|
|
252
|
-
|
|
253
|
-
echo "$TURN_NUMBER" > "$TURN_COUNTER_FILE"
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
258
|
-
# COLORS
|
|
259
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
260
|
-
CYAN='\033[0;36m'
|
|
261
|
-
GREEN='\033[0;32m'
|
|
262
|
-
MAGENTA='\033[0;35m'
|
|
263
|
-
DIM='\033[2m'
|
|
264
|
-
BOLD='\033[1m'
|
|
265
|
-
RESET='\033[0m'
|
|
266
|
-
|
|
267
|
-
CURRENT_TIME=$(date "+%Y-%m-%d %I:%M:%S %p %Z")
|
|
268
|
-
|
|
269
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
270
|
-
# WORD-BASED SESSION NAMES - No jq, uses ~/.ekkos/ config (works in ANY project)
|
|
271
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
272
|
-
declare -a ADJECTIVES
|
|
273
|
-
declare -a NOUNS
|
|
274
|
-
declare -a VERBS
|
|
275
|
-
SESSION_WORDS_LOADED=false
|
|
276
|
-
|
|
277
|
-
load_session_words() {
|
|
278
|
-
if [ "$SESSION_WORDS_LOADED" = "true" ]; then return 0; fi
|
|
279
|
-
|
|
280
|
-
local words_file="$SESSION_WORDS_JSON"
|
|
281
|
-
[ ! -f "$words_file" ] && words_file="$SESSION_WORDS_DEFAULT"
|
|
282
|
-
|
|
283
|
-
if [ ! -f "$words_file" ] || [ ! -f "$JSON_PARSE_HELPER" ]; then
|
|
284
|
-
ADJECTIVES=("unknown"); NOUNS=("session"); VERBS=("starts")
|
|
285
|
-
return 1
|
|
286
|
-
fi
|
|
287
|
-
|
|
288
|
-
if command -v node &>/dev/null; then
|
|
289
|
-
if [ "${BASH_VERSINFO[0]}" -ge 4 ]; then
|
|
290
|
-
readarray -t ADJECTIVES < <(node "$JSON_PARSE_HELPER" "$words_file" '.adjectives' 2>/dev/null)
|
|
291
|
-
readarray -t NOUNS < <(node "$JSON_PARSE_HELPER" "$words_file" '.nouns' 2>/dev/null)
|
|
292
|
-
readarray -t VERBS < <(node "$JSON_PARSE_HELPER" "$words_file" '.verbs' 2>/dev/null)
|
|
293
|
-
else
|
|
294
|
-
local i=0
|
|
295
|
-
while IFS= read -r line; do ADJECTIVES[i]="$line"; ((i++)); done < <(node "$JSON_PARSE_HELPER" "$words_file" '.adjectives' 2>/dev/null)
|
|
296
|
-
i=0
|
|
297
|
-
while IFS= read -r line; do NOUNS[i]="$line"; ((i++)); done < <(node "$JSON_PARSE_HELPER" "$words_file" '.nouns' 2>/dev/null)
|
|
298
|
-
i=0
|
|
299
|
-
while IFS= read -r line; do VERBS[i]="$line"; ((i++)); done < <(node "$JSON_PARSE_HELPER" "$words_file" '.verbs' 2>/dev/null)
|
|
300
|
-
fi
|
|
301
|
-
[ ${#ADJECTIVES[@]} -eq 0 ] && ADJECTIVES=("unknown")
|
|
302
|
-
[ ${#NOUNS[@]} -eq 0 ] && NOUNS=("session")
|
|
303
|
-
[ ${#VERBS[@]} -eq 0 ] && VERBS=("starts")
|
|
304
|
-
SESSION_WORDS_LOADED=true
|
|
305
|
-
return 0
|
|
306
|
-
fi
|
|
307
|
-
ADJECTIVES=("unknown"); NOUNS=("session"); VERBS=("starts")
|
|
308
|
-
return 1
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
uuid_to_words() {
|
|
312
|
-
local uuid="$1"
|
|
313
|
-
load_session_words
|
|
314
|
-
|
|
315
|
-
local hex="${uuid//-/}"
|
|
316
|
-
hex="${hex:0:12}"
|
|
317
|
-
[[ ! "$hex" =~ ^[0-9a-fA-F]+$ ]] && echo "unknown-session-starts" && return
|
|
318
|
-
|
|
319
|
-
local adj_seed=$((16#${hex:0:4}))
|
|
320
|
-
local noun_seed=$((16#${hex:4:4}))
|
|
321
|
-
local verb_seed=$((16#${hex:8:4}))
|
|
322
|
-
local adj_idx=$((adj_seed % ${#ADJECTIVES[@]}))
|
|
323
|
-
local noun_idx=$((noun_seed % ${#NOUNS[@]}))
|
|
324
|
-
local verb_idx=$((verb_seed % ${#VERBS[@]}))
|
|
325
|
-
echo "${ADJECTIVES[$adj_idx]}-${NOUNS[$noun_idx]}-${VERBS[$verb_idx]}"
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
# Generate session name (or use as-is if already word-based)
|
|
329
|
-
SESSION_NAME=""
|
|
330
|
-
if [ -n "$SESSION_ID" ] && [ "$SESSION_ID" != "unknown" ] && [ "$SESSION_ID" != "null" ]; then
|
|
331
|
-
if [ "$IS_UUID" = true ]; then
|
|
332
|
-
SESSION_NAME=$(uuid_to_words "$SESSION_ID")
|
|
333
|
-
else
|
|
334
|
-
SESSION_NAME="$SESSION_ID"
|
|
335
|
-
fi
|
|
336
|
-
fi
|
|
337
|
-
|
|
338
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
339
|
-
# SINGLE SOURCE OF TRUTH: Update ALL session tracking systems
|
|
340
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
341
|
-
if [ -n "$SESSION_NAME" ]; then
|
|
342
|
-
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
343
|
-
|
|
344
|
-
# 1. Project-level state file
|
|
345
|
-
echo "{\"session_id\": \"$SESSION_ID\", \"session_name\": \"$SESSION_NAME\", \"timestamp\": \"$TIMESTAMP\"}" > "$SESSION_FILE" 2>/dev/null || true
|
|
346
|
-
|
|
347
|
-
# 2. Global ekkOS state (for extension LOCAL-FIRST read)
|
|
348
|
-
EKKOS_GLOBAL_STATE="$HOME/.ekkos/current-session.json"
|
|
349
|
-
mkdir -p "$HOME/.ekkos" 2>/dev/null || true
|
|
350
|
-
echo "{\"session_id\": \"$SESSION_ID\", \"session_name\": \"$SESSION_NAME\", \"project\": \"$PROJECT_ROOT\", \"timestamp\": \"$TIMESTAMP\"}" > "$EKKOS_GLOBAL_STATE" 2>/dev/null || true
|
|
351
|
-
|
|
352
|
-
# 3. CLI state file
|
|
353
|
-
CLI_STATE_FILE="$HOME/.ekkos/state.json"
|
|
354
|
-
echo "{\"sessionId\": \"$SESSION_ID\", \"sessionName\": \"$SESSION_NAME\", \"turnNumber\": $TURN_NUMBER, \"lastUpdated\": \"$TIMESTAMP\", \"projectPath\": \"$PROJECT_ROOT\"}" > "$CLI_STATE_FILE" 2>/dev/null || true
|
|
355
|
-
|
|
356
|
-
# 4. Multi-session tracking - No jq
|
|
357
|
-
ACTIVE_SESSIONS_FILE="$HOME/.ekkos/active-sessions.json"
|
|
358
|
-
node -e "
|
|
359
|
-
const fs = require('fs');
|
|
360
|
-
const sid = process.argv[1], sname = process.argv[2], ts = process.argv[3], proj = process.argv[4];
|
|
361
|
-
try {
|
|
362
|
-
let sessions = [];
|
|
363
|
-
if (fs.existsSync('$ACTIVE_SESSIONS_FILE')) {
|
|
364
|
-
sessions = JSON.parse(fs.readFileSync('$ACTIVE_SESSIONS_FILE', 'utf8') || '[]');
|
|
365
|
-
}
|
|
366
|
-
const idx = sessions.findIndex(s => s.sessionId === sid);
|
|
367
|
-
if (idx >= 0) {
|
|
368
|
-
sessions[idx] = {...sessions[idx], sessionName: sname, lastHeartbeat: ts, projectPath: proj};
|
|
369
|
-
} else {
|
|
370
|
-
sessions.push({sessionId: sid, sessionName: sname, pid: 0, startedAt: ts, projectPath: proj, lastHeartbeat: ts});
|
|
371
|
-
}
|
|
372
|
-
fs.writeFileSync('$ACTIVE_SESSIONS_FILE', JSON.stringify(sessions, null, 2));
|
|
373
|
-
} catch(e) {}
|
|
374
|
-
" "$SESSION_ID" "$SESSION_NAME" "$TIMESTAMP" "$PROJECT_ROOT" 2>/dev/null || true
|
|
375
|
-
|
|
376
|
-
# 5. Update Redis via API
|
|
377
|
-
if [ -n "$AUTH_TOKEN" ]; then
|
|
378
|
-
curl -s -X POST "$MEMORY_API_URL/api/v1/working/session/current" \
|
|
379
|
-
-H "Authorization: Bearer $AUTH_TOKEN" \
|
|
380
|
-
-H "Content-Type: application/json" \
|
|
381
|
-
-d "{\"session_name\": \"$SESSION_NAME\"}" \
|
|
382
|
-
--connect-timeout 2 \
|
|
383
|
-
--max-time 3 >/dev/null 2>&1 &
|
|
384
|
-
fi
|
|
385
|
-
|
|
386
|
-
# Session binding removed — proxy now self-resolves _pending → word-based
|
|
387
|
-
# names inline via uuidToSessionName(). No external bind call needed.
|
|
388
|
-
fi
|
|
389
|
-
|
|
390
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
391
|
-
# "/continue" COMMAND: Run AFTER /clear to restore last 5 turns
|
|
392
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
393
|
-
QUERY_LOWER=$(echo "$USER_QUERY" | tr '[:upper:]' '[:lower:]')
|
|
394
|
-
|
|
395
|
-
if [[ "$USER_QUERY" == "/continue" ]] || [[ "$USER_QUERY" =~ ^/continue[[:space:]] ]] || [[ "$QUERY_LOWER" == "continue" ]] || [[ "$QUERY_LOWER" == "continue." ]]; then
|
|
396
|
-
if [ -n "$AUTH_TOKEN" ]; then
|
|
397
|
-
RESTORE_RESPONSE=$(curl -s -X POST "$MEMORY_API_URL/api/v1/turns/recall" \
|
|
398
|
-
-H "Authorization: Bearer $AUTH_TOKEN" \
|
|
399
|
-
-H "Content-Type: application/json" \
|
|
400
|
-
-d "{\"session_id\": \"current\", \"last_n\": 5, \"format\": \"detailed\"}" \
|
|
401
|
-
--connect-timeout 3 \
|
|
402
|
-
--max-time 5 2>/dev/null || echo '{"turns":[]}')
|
|
403
|
-
|
|
404
|
-
RESTORED_COUNT=$(echo "$RESTORE_RESPONSE" | node -e "
|
|
405
|
-
const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
406
|
-
console.log((d.turns || []).length);
|
|
407
|
-
" 2>/dev/null || echo "0")
|
|
408
|
-
|
|
409
|
-
LAST_TASK=$(echo "$RESTORE_RESPONSE" | node -e "
|
|
410
|
-
const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
411
|
-
const turns = d.turns || [];
|
|
412
|
-
console.log((turns[turns.length-1]?.user_query || 'unknown task').substring(0, 200));
|
|
413
|
-
" 2>/dev/null || echo "unknown task")
|
|
414
|
-
|
|
415
|
-
LAST_RESPONSE=$(echo "$RESTORE_RESPONSE" | node -e "
|
|
416
|
-
const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
417
|
-
const turns = d.turns || [];
|
|
418
|
-
console.log((turns[turns.length-1]?.assistant_response || '').substring(0, 500));
|
|
419
|
-
" 2>/dev/null || echo "")
|
|
420
|
-
|
|
421
|
-
echo ""
|
|
422
|
-
echo -e "${GREEN}${BOLD}✓ Session continued${RESET} ${DIM}(${RESTORED_COUNT} turns restored)${RESET}"
|
|
423
|
-
echo ""
|
|
424
|
-
|
|
425
|
-
echo "<system-reminder>"
|
|
426
|
-
echo "═══════════════════════════════════════════════════════════════════════"
|
|
427
|
-
echo "CONTEXT RESTORED - Resume seamlessly. DO NOT ask 'what were we doing?'"
|
|
428
|
-
echo "═══════════════════════════════════════════════════════════════════════"
|
|
429
|
-
echo ""
|
|
430
|
-
echo "## Last User Request:"
|
|
431
|
-
echo "$LAST_TASK"
|
|
432
|
-
echo ""
|
|
433
|
-
echo "## Your Last Response (truncated):"
|
|
434
|
-
echo "$LAST_RESPONSE"
|
|
435
|
-
echo ""
|
|
436
|
-
|
|
437
|
-
if [ "$RESTORED_COUNT" -gt 1 ]; then
|
|
438
|
-
echo "## Recent Context (older → newer):"
|
|
439
|
-
echo "$RESTORE_RESPONSE" | node -e "
|
|
440
|
-
const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
441
|
-
const turns = d.turns || [];
|
|
442
|
-
turns.slice(0, -1).forEach(t => {
|
|
443
|
-
const q = (t.user_query || '...').substring(0, 100);
|
|
444
|
-
console.log('- Turn ' + (t.turn_number || '?') + ': ' + q + '...');
|
|
445
|
-
});
|
|
446
|
-
" 2>/dev/null || true
|
|
447
|
-
echo ""
|
|
448
|
-
fi
|
|
449
|
-
|
|
450
|
-
echo "═══════════════════════════════════════════════════════════════════════"
|
|
451
|
-
echo "INSTRUCTION: Start your response with '✓ **Continuing** -' then pick up"
|
|
452
|
-
echo "exactly where you left off. If mid-task, continue it. If done, ask what's next."
|
|
453
|
-
echo "═══════════════════════════════════════════════════════════════════════"
|
|
454
|
-
echo "</system-reminder>"
|
|
455
|
-
|
|
456
|
-
echo -e "${CYAN}${BOLD}🧠 ekkOS Memory${RESET} ${DIM}| ${CURRENT_TIME}${RESET}"
|
|
457
|
-
exit 0
|
|
458
|
-
fi
|
|
459
|
-
fi
|
|
460
|
-
|
|
461
|
-
if [ "$POST_CLEAR_DETECTED" = true ] && [ -n "$AUTH_TOKEN" ]; then
|
|
462
|
-
# /clear detected - show visible restoration banner
|
|
463
|
-
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" >&2
|
|
464
|
-
echo -e "${GREEN}${BOLD}🔄 SESSION CONTINUED${RESET} ${DIM}| ${TURN_NUMBER} turns preserved | Context restored${RESET}" >&2
|
|
465
|
-
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" >&2
|
|
466
|
-
|
|
467
|
-
echo ""
|
|
468
|
-
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
469
|
-
echo -e "${GREEN}${BOLD}🔄 SESSION CONTINUED${RESET} ${DIM}| ${TURN_NUMBER} turns preserved | Restoring context...${RESET}"
|
|
470
|
-
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
471
|
-
|
|
472
|
-
RESTORE_RESPONSE=$(curl -s -X POST "$MEMORY_API_URL/api/v1/turns/recall" \
|
|
473
|
-
-H "Authorization: Bearer $AUTH_TOKEN" \
|
|
474
|
-
-H "Content-Type: application/json" \
|
|
475
|
-
-d "{\"session_id\": \"${SESSION_ID}\", \"last_n\": 10, \"format\": \"summary\"}" \
|
|
476
|
-
--connect-timeout 3 \
|
|
477
|
-
--max-time 5 2>/dev/null || echo '{"turns":[]}')
|
|
478
|
-
|
|
479
|
-
RESTORED_COUNT=$(echo "$RESTORE_RESPONSE" | node -e "
|
|
480
|
-
const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
481
|
-
console.log((d.turns || []).length);
|
|
482
|
-
" 2>/dev/null || echo "0")
|
|
483
|
-
|
|
484
|
-
if [ "$RESTORED_COUNT" -gt 0 ]; then
|
|
485
|
-
echo -e "${GREEN} ✓${RESET} Restored ${RESTORED_COUNT} recent turns"
|
|
486
|
-
echo ""
|
|
487
|
-
|
|
488
|
-
echo "$RESTORE_RESPONSE" | node -e "
|
|
489
|
-
const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
490
|
-
(d.turns || []).forEach(t => {
|
|
491
|
-
const q = (t.query_preview || t.user_query || '...').substring(0, 80);
|
|
492
|
-
const a = (t.response_preview || t.assistant_response || '...').substring(0, 150);
|
|
493
|
-
console.log('**Turn ' + (t.turn_number || '?') + '**: ' + q + '...\n> ' + a + '...\n');
|
|
494
|
-
});
|
|
495
|
-
" 2>/dev/null || true
|
|
496
|
-
else
|
|
497
|
-
echo -e "${GREEN} ✓${RESET} History preserved (${TURN_NUMBER} turns)"
|
|
498
|
-
fi
|
|
499
|
-
|
|
500
|
-
echo ""
|
|
501
|
-
echo -e "${DIM}Full history: \"recall\" or \"turns 1-${TURN_NUMBER}\"${RESET}"
|
|
502
|
-
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
503
|
-
echo ""
|
|
504
|
-
echo -e "${CYAN}${BOLD}🧠 ekkOS Memory${RESET} ${DIM}| ${SESSION_NAME} | ${CURRENT_TIME}${RESET}"
|
|
505
|
-
|
|
506
|
-
else
|
|
507
|
-
echo -e "${CYAN}${BOLD}🧠 ekkOS Memory${RESET} ${DIM}| ${SESSION_NAME} | ${CURRENT_TIME}${RESET}"
|
|
508
|
-
fi
|
|
509
|
-
|
|
510
|
-
# Output skill reminder if detected
|
|
511
|
-
if [ -n "$SKILL_REMINDER" ]; then
|
|
512
|
-
echo ""
|
|
513
|
-
echo -e "${MAGENTA}${BOLD}$SKILL_REMINDER${RESET}"
|
|
514
|
-
fi
|
|
515
|
-
|
|
516
|
-
exit 0
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
-
# ekkOS Project Hook Stub: session-start.ps1
|
|
3
|
-
# EKKOS_MANAGED=1
|
|
4
|
-
# EKKOS_MANIFEST_SHA256=project-stub-v1
|
|
5
|
-
# EKKOS_TEMPLATE_VERSION=1.0.0
|
|
6
|
-
#
|
|
7
|
-
# This is a DELEGATING STUB that:
|
|
8
|
-
# 1. Executes the global hook first
|
|
9
|
-
# 2. Then loads project-specific overrides if present
|
|
10
|
-
#
|
|
11
|
-
# Do not modify this file directly. Customizations go in:
|
|
12
|
-
# <project>\.ekkos\hooks.local.ps1
|
|
13
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
14
|
-
|
|
15
|
-
$ErrorActionPreference = 'Stop'
|
|
16
|
-
|
|
17
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
18
|
-
# RESOLVE PATHS
|
|
19
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
20
|
-
|
|
21
|
-
# Get absolute path to this stub
|
|
22
|
-
$StubPath = $PSScriptRoot
|
|
23
|
-
$StubFile = $MyInvocation.MyCommand.Path
|
|
24
|
-
|
|
25
|
-
# Project root is two levels up from .claude\hooks\
|
|
26
|
-
$ProjectRoot = (Get-Item "$StubPath\..\..").FullName
|
|
27
|
-
|
|
28
|
-
# Global hooks directory
|
|
29
|
-
$GlobalHooksDir = Join-Path $env:USERPROFILE ".claude\hooks"
|
|
30
|
-
|
|
31
|
-
# Global hook path
|
|
32
|
-
$GlobalHook = Join-Path $GlobalHooksDir "session-start.ps1"
|
|
33
|
-
|
|
34
|
-
# Project override file
|
|
35
|
-
$ProjectOverride = Join-Path $ProjectRoot ".ekkos\hooks.local.ps1"
|
|
36
|
-
|
|
37
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
38
|
-
# EXECUTE GLOBAL HOOK
|
|
39
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
40
|
-
|
|
41
|
-
if (Test-Path $GlobalHook) {
|
|
42
|
-
try {
|
|
43
|
-
# Dot-source the global hook (inherits all env vars and args)
|
|
44
|
-
. $GlobalHook
|
|
45
|
-
} catch {
|
|
46
|
-
Write-Error "[ekkOS] Error executing global hook: $_"
|
|
47
|
-
}
|
|
48
|
-
} else {
|
|
49
|
-
Write-Warning "[ekkOS] Global hook not found: $GlobalHook"
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
53
|
-
# LOAD PROJECT OVERRIDES (optional)
|
|
54
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
55
|
-
|
|
56
|
-
if (Test-Path $ProjectOverride) {
|
|
57
|
-
try {
|
|
58
|
-
# Dot-source project-specific customizations
|
|
59
|
-
. $ProjectOverride
|
|
60
|
-
} catch {
|
|
61
|
-
Write-Warning "[ekkOS] Error loading project override: $_"
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
3
|
-
# ekkOS Project Hook Stub: session-start.sh
|
|
4
|
-
# EKKOS_MANAGED=1
|
|
5
|
-
# EKKOS_MANIFEST_SHA256=project-stub-v1
|
|
6
|
-
# EKKOS_TEMPLATE_VERSION=1.0.0
|
|
7
|
-
#
|
|
8
|
-
# This is a DELEGATING STUB that:
|
|
9
|
-
# 1. Executes the global hook first
|
|
10
|
-
# 2. Then loads project-specific overrides if present
|
|
11
|
-
#
|
|
12
|
-
# Do not modify this file directly. Customizations go in:
|
|
13
|
-
# <project>/.ekkos/hooks.local.sh
|
|
14
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
15
|
-
set -euo pipefail
|
|
16
|
-
|
|
17
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
18
|
-
# RESOLVE PATHS
|
|
19
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
20
|
-
|
|
21
|
-
# Get absolute path to this stub
|
|
22
|
-
STUB_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
|
|
23
|
-
|
|
24
|
-
# Project root is two levels up from .claude/hooks/
|
|
25
|
-
PROJECT_ROOT="$(cd "$(dirname "$STUB_PATH")/../.." && pwd)"
|
|
26
|
-
|
|
27
|
-
# Global hooks directory
|
|
28
|
-
GLOBAL_HOOKS_DIR="$HOME/.claude/hooks"
|
|
29
|
-
|
|
30
|
-
# Global hook path
|
|
31
|
-
GLOBAL_HOOK="$GLOBAL_HOOKS_DIR/session-start.sh"
|
|
32
|
-
|
|
33
|
-
# Project override file
|
|
34
|
-
PROJECT_OVERRIDE="$PROJECT_ROOT/.ekkos/hooks.local.sh"
|
|
35
|
-
|
|
36
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
37
|
-
# EXECUTE GLOBAL HOOK
|
|
38
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
39
|
-
|
|
40
|
-
if [ -f "$GLOBAL_HOOK" ] && [ -x "$GLOBAL_HOOK" ]; then
|
|
41
|
-
# Source the global hook (inherits all env vars and args)
|
|
42
|
-
source "$GLOBAL_HOOK"
|
|
43
|
-
else
|
|
44
|
-
echo "[ekkOS] Warning: Global hook not found: $GLOBAL_HOOK" >&2
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
48
|
-
# LOAD PROJECT OVERRIDES (optional)
|
|
49
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
50
|
-
|
|
51
|
-
if [ -f "$PROJECT_OVERRIDE" ]; then
|
|
52
|
-
# Source project-specific customizations
|
|
53
|
-
# These can override functions or add project-specific behavior
|
|
54
|
-
source "$PROJECT_OVERRIDE"
|
|
55
|
-
fi
|