@ekkos/cli 1.2.18 → 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.
Files changed (47) hide show
  1. package/dist/cache/capture.js +0 -0
  2. package/dist/commands/dashboard.js +57 -49
  3. package/dist/commands/hooks.d.ts +25 -36
  4. package/dist/commands/hooks.js +43 -615
  5. package/dist/commands/init.js +7 -23
  6. package/dist/commands/run.js +90 -3
  7. package/dist/commands/setup.js +10 -352
  8. package/dist/deploy/hooks.d.ts +8 -5
  9. package/dist/deploy/hooks.js +12 -105
  10. package/dist/deploy/settings.d.ts +8 -2
  11. package/dist/deploy/settings.js +22 -51
  12. package/dist/index.js +17 -39
  13. package/dist/utils/state.js +7 -2
  14. package/package.json +1 -1
  15. package/templates/CLAUDE.md +82 -292
  16. package/templates/cursor-rules/ekkos-memory.md +48 -108
  17. package/templates/windsurf-rules/ekkos-memory.md +62 -64
  18. package/templates/cursor-hooks/after-agent-response.sh +0 -117
  19. package/templates/cursor-hooks/before-submit-prompt.sh +0 -419
  20. package/templates/cursor-hooks/hooks.json +0 -20
  21. package/templates/cursor-hooks/lib/contract.sh +0 -320
  22. package/templates/cursor-hooks/stop.sh +0 -75
  23. package/templates/hooks/assistant-response.ps1 +0 -256
  24. package/templates/hooks/assistant-response.sh +0 -160
  25. package/templates/hooks/hooks.json +0 -40
  26. package/templates/hooks/lib/contract.sh +0 -332
  27. package/templates/hooks/lib/count-tokens.cjs +0 -86
  28. package/templates/hooks/lib/ekkos-reminders.sh +0 -98
  29. package/templates/hooks/lib/state.sh +0 -210
  30. package/templates/hooks/session-start.ps1 +0 -146
  31. package/templates/hooks/session-start.sh +0 -353
  32. package/templates/hooks/stop.ps1 +0 -349
  33. package/templates/hooks/stop.sh +0 -382
  34. package/templates/hooks/user-prompt-submit.ps1 +0 -419
  35. package/templates/hooks/user-prompt-submit.sh +0 -516
  36. package/templates/project-stubs/session-start.ps1 +0 -63
  37. package/templates/project-stubs/session-start.sh +0 -55
  38. package/templates/project-stubs/stop.ps1 +0 -63
  39. package/templates/project-stubs/stop.sh +0 -55
  40. package/templates/project-stubs/user-prompt-submit.ps1 +0 -63
  41. package/templates/project-stubs/user-prompt-submit.sh +0 -55
  42. package/templates/windsurf-hooks/README.md +0 -212
  43. package/templates/windsurf-hooks/hooks.json +0 -17
  44. package/templates/windsurf-hooks/install.sh +0 -148
  45. package/templates/windsurf-hooks/lib/contract.sh +0 -322
  46. package/templates/windsurf-hooks/post-cascade-response.sh +0 -251
  47. 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