@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.
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 +97 -11
  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,353 +0,0 @@
1
- #!/bin/bash
2
- # ═══════════════════════════════════════════════════════════════════════════
3
- # ekkOS_ Hook: SessionStart - MINIMAL + AUTO-RESTORE + TIME MACHINE CONTINUE
4
- # MANAGED BY ekkos-connect - DO NOT EDIT DIRECTLY
5
- # EKKOS_MANAGED=1
6
- # ═══════════════════════════════════════════════════════════════════════════
7
- # This hook does THREE things:
8
- # 1. Check for pending Time Machine "Continue from here" requests
9
- # 2. Initialize session tracking
10
- # 3. Auto-restore from L2 if recent turns exist (FAST TRIM support)
11
- #
12
- # TIME MACHINE FLOW:
13
- # User clicks "Continue from here" on web → API queues request →
14
- # User runs `claude` → This hook detects pending request →
15
- # Restores THAT session's context → Seamless time travel!
16
- #
17
- # FAST TRIM FLOW:
18
- # User runs /clear → session-start detects fresh session →
19
- # Checks L2 for recent turns → Auto-injects last 15 turns → Seamless continuity
20
- #
21
- # Per spec v1.2 Addendum: NO jq dependency
22
- # ═══════════════════════════════════════════════════════════════════════════
23
-
24
- set +e
25
-
26
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
- PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
28
-
29
- # ═══════════════════════════════════════════════════════════════════════════
30
- # CONFIG PATHS - Per spec v1.2 Addendum
31
- # ═══════════════════════════════════════════════════════════════════════════
32
- EKKOS_CONFIG_DIR="${EKKOS_CONFIG_DIR:-$HOME/.ekkos}"
33
- JSON_PARSE_HELPER="$EKKOS_CONFIG_DIR/.helpers/json-parse.cjs"
34
-
35
- INPUT=$(cat)
36
-
37
- # Parse input using Node (no jq)
38
- parse_json_value() {
39
- local json="$1"
40
- local path="$2"
41
- echo "$json" | node -e "
42
- const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
43
- const path = '$path'.replace(/^\./,'').split('.').filter(Boolean);
44
- let result = data;
45
- for (const p of path) {
46
- if (result === undefined || result === null) { result = undefined; break; }
47
- result = result[p];
48
- }
49
- if (result !== undefined && result !== null) console.log(result);
50
- " 2>/dev/null || echo ""
51
- }
52
-
53
- SESSION_ID=$(parse_json_value "$INPUT" '.session_id')
54
- [ -z "$SESSION_ID" ] && SESSION_ID="unknown"
55
-
56
- TRANSCRIPT_PATH=$(parse_json_value "$INPUT" '.transcript_path')
57
- SOURCE=$(parse_json_value "$INPUT" '.source')
58
- [ -z "$SOURCE" ] && SOURCE="unknown"
59
-
60
- # ═══════════════════════════════════════════════════════════════════════════
61
- # Load auth
62
- # ═══════════════════════════════════════════════════════════════════════════
63
- EKKOS_CONFIG="$HOME/.ekkos/config.json"
64
- AUTH_TOKEN=""
65
- USER_ID=""
66
-
67
- if [ -f "$EKKOS_CONFIG" ] && [ -f "$JSON_PARSE_HELPER" ]; then
68
- AUTH_TOKEN=$(node "$JSON_PARSE_HELPER" "$EKKOS_CONFIG" '.hookApiKey' 2>/dev/null || echo "")
69
- if [ -z "$AUTH_TOKEN" ]; then
70
- AUTH_TOKEN=$(node "$JSON_PARSE_HELPER" "$EKKOS_CONFIG" '.apiKey' 2>/dev/null || echo "")
71
- fi
72
- USER_ID=$(node "$JSON_PARSE_HELPER" "$EKKOS_CONFIG" '.userId' 2>/dev/null || echo "")
73
- fi
74
-
75
- if [ -z "$AUTH_TOKEN" ] && [ -f "$PROJECT_ROOT/.env.local" ]; then
76
- AUTH_TOKEN=$(grep -E "^SUPABASE_SECRET_KEY=" "$PROJECT_ROOT/.env.local" | cut -d'=' -f2- | tr -d '"' | tr -d "'" | tr -d '\r')
77
- fi
78
-
79
- [ -z "$AUTH_TOKEN" ] && exit 0
80
-
81
- MEMORY_API_URL="https://mcp.ekkos.dev"
82
-
83
- # ═══════════════════════════════════════════════════════════════════════════
84
- # TIME MACHINE: Check for pending "Continue from here" requests
85
- # ═══════════════════════════════════════════════════════════════════════════
86
- RESTORE_REQUEST_ID="${EKKOS_RESTORE:-}"
87
- TIME_MACHINE_SESSION=""
88
- TIME_MACHINE_FROM_TURN=""
89
- TIME_MACHINE_TO_TURN=""
90
-
91
- # Check via env var first, then API
92
- if [ -n "$RESTORE_REQUEST_ID" ]; then
93
- echo -e "\033[0;35m Time Machine request detected: $RESTORE_REQUEST_ID\033[0m" >&2
94
- fi
95
-
96
- # Check API for pending requests (if we have user_id)
97
- if [ -z "$TIME_MACHINE_SESSION" ] && [ -n "$USER_ID" ]; then
98
- PENDING_RESPONSE=$(curl -s -X GET "$MEMORY_API_URL/api/v1/context/restore-request/pending?user_id=$USER_ID" \
99
- -H "Authorization: Bearer $AUTH_TOKEN" \
100
- --connect-timeout 2 \
101
- --max-time 3 2>/dev/null || echo '{}')
102
-
103
- # Parse using Node (no jq)
104
- IS_PENDING=$(echo "$PENDING_RESPONSE" | node -e "
105
- const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
106
- console.log(data.pending || false);
107
- " 2>/dev/null || echo "false")
108
-
109
- if [ "$IS_PENDING" = "true" ]; then
110
- TIME_MACHINE_SESSION=$(echo "$PENDING_RESPONSE" | node -e "
111
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
112
- console.log(d.request?.session_id || '');
113
- " 2>/dev/null || echo "")
114
- TIME_MACHINE_FROM_TURN=$(echo "$PENDING_RESPONSE" | node -e "
115
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
116
- console.log(d.request?.from_turn || '');
117
- " 2>/dev/null || echo "")
118
- TIME_MACHINE_TO_TURN=$(echo "$PENDING_RESPONSE" | node -e "
119
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
120
- console.log(d.request?.to_turn || '');
121
- " 2>/dev/null || echo "")
122
- RESTORE_REQUEST_ID=$(echo "$PENDING_RESPONSE" | node -e "
123
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
124
- console.log(d.request?.request_id || '');
125
- " 2>/dev/null || echo "")
126
-
127
- if [ -n "$TIME_MACHINE_SESSION" ]; then
128
- echo "" >&2
129
- echo -e "\033[0;35m------------------------------------------------------------------------\033[0m" >&2
130
- echo -e "\033[0;35m\033[1m TIME MACHINE\033[0m \033[2m| Restoring session from web request...\033[0m" >&2
131
- echo -e "\033[0;35m------------------------------------------------------------------------\033[0m" >&2
132
-
133
- # Mark request as consumed
134
- curl -s -X POST "$MEMORY_API_URL/api/v1/context/restore-request/consume" \
135
- -H "Authorization: Bearer $AUTH_TOKEN" \
136
- -H "Content-Type: application/json" \
137
- -d "{\"request_id\": \"$RESTORE_REQUEST_ID\"}" \
138
- --connect-timeout 2 \
139
- --max-time 3 >/dev/null 2>&1 || true
140
- fi
141
- fi
142
- fi
143
-
144
- # ═══════════════════════════════════════════════════════════════════════════
145
- # Session ID persistence - PROJECT-LOCAL for isolation
146
- # ═══════════════════════════════════════════════════════════════════════════
147
- STATE_DIR="$PROJECT_ROOT/.claude/state"
148
- mkdir -p "$STATE_DIR" 2>/dev/null || true
149
- SESSION_FILE="$STATE_DIR/current-session.json"
150
-
151
- # Project-local session storage (isolated per project)
152
- PROJECT_SESSION_DIR="$STATE_DIR/sessions"
153
- mkdir -p "$PROJECT_SESSION_DIR" 2>/dev/null || true
154
-
155
- # Use Claude's RAW_SESSION_ID directly (from session_id field)
156
- CURRENT_SESSION_ID="$SESSION_ID"
157
-
158
- # Find most recent session in THIS PROJECT for auto-restore
159
- MOST_RECENT_SESSION=""
160
- SAVED_TURN_COUNT=0
161
-
162
- if [ -n "$CURRENT_SESSION_ID" ] && [ "$CURRENT_SESSION_ID" != "unknown" ]; then
163
- # Check if THIS session has saved turns (for /clear continuity)
164
- TURN_COUNTER_FILE="$PROJECT_SESSION_DIR/${CURRENT_SESSION_ID}.turn"
165
- if [ -f "$TURN_COUNTER_FILE" ]; then
166
- SAVED_TURN_COUNT=$(cat "$TURN_COUNTER_FILE" 2>/dev/null || echo "0")
167
- MOST_RECENT_SESSION="$CURRENT_SESSION_ID"
168
- else
169
- # Fresh start: find most recent session in project
170
- MOST_RECENT_FILE=$(ls -t "$PROJECT_SESSION_DIR"/*.turn 2>/dev/null | head -1)
171
- if [ -n "$MOST_RECENT_FILE" ]; then
172
- MOST_RECENT_SESSION=$(basename "$MOST_RECENT_FILE" .turn)
173
- SAVED_TURN_COUNT=$(cat "$MOST_RECENT_FILE" 2>/dev/null || echo "0")
174
- fi
175
- fi
176
- fi
177
-
178
- # Save current session info
179
- if [ -n "$CURRENT_SESSION_ID" ]; then
180
- cat > "$SESSION_FILE" << EOF
181
- {
182
- "session_id": "$CURRENT_SESSION_ID",
183
- "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
184
- "project_root": "$PROJECT_ROOT"
185
- }
186
- EOF
187
- fi
188
-
189
- # ═══════════════════════════════════════════════════════════════════════════
190
- # GOLDEN LOOP: Initialize session tracking file
191
- # ═══════════════════════════════════════════════════════════════════════════
192
- GOLDEN_LOOP_FILE="$PROJECT_ROOT/.ekkos/golden-loop-current.json"
193
- mkdir -p "$PROJECT_ROOT/.ekkos" 2>/dev/null || true
194
-
195
- # Initialize with session start state using Node (no jq)
196
- node -e "
197
- const fs = require('fs');
198
- const data = {
199
- phase: 'idle',
200
- turn: 0,
201
- session: '$CURRENT_SESSION_ID',
202
- timestamp: new Date().toISOString(),
203
- stats: { retrieved: 0, applied: 0, forged: 0 }
204
- };
205
- fs.writeFileSync('$GOLDEN_LOOP_FILE', JSON.stringify(data, null, 2));
206
- " 2>/dev/null || true
207
-
208
- # ═══════════════════════════════════════════════════════════════════════════
209
- # COLORS
210
- # ═══════════════════════════════════════════════════════════════════════════
211
- CYAN='\033[0;36m'
212
- GREEN='\033[0;32m'
213
- YELLOW='\033[1;33m'
214
- MAGENTA='\033[0;35m'
215
- DIM='\033[2m'
216
- BOLD='\033[1m'
217
- RESET='\033[0m'
218
-
219
- # ═══════════════════════════════════════════════════════════════════════════
220
- # AUTO-RESTORE REMOVED: Manual /continue only (saves 79% token burn!)
221
- # ═══════════════════════════════════════════════════════════════════════════
222
- # WHY REMOVED:
223
- # - Auto-restore burned 5,000 tokens per turn on session start
224
- # - Manual /continue: one-time cost + clean slate (79% token savings!)
225
- # - Manual /continue is 10x more powerful (Bash + multi-source + narrative)
226
- #
227
- # KEPT: Time Machine feature (explicit user request)
228
- # ═══════════════════════════════════════════════════════════════════════════
229
-
230
- # Handle Time Machine requests (explicit user action)
231
- if [ -n "$TIME_MACHINE_SESSION" ]; then
232
- echo "" >&2
233
- echo -e "${MAGENTA}${BOLD} TIME MACHINE${RESET} ${DIM}| Restoring past session: ${TIME_MACHINE_SESSION:0:12}...${RESET}" >&2
234
-
235
- # Build recall request with turn range
236
- RECALL_BODY="{\"session_id\": \"${TIME_MACHINE_SESSION}\", \"last_n\": 15, \"format\": \"summary\"}"
237
-
238
- if [ -n "$TIME_MACHINE_FROM_TURN" ] && [ -n "$TIME_MACHINE_TO_TURN" ]; then
239
- RECALL_BODY="{\"session_id\": \"${TIME_MACHINE_SESSION}\", \"from_turn\": ${TIME_MACHINE_FROM_TURN}, \"to_turn\": ${TIME_MACHINE_TO_TURN}, \"format\": \"summary\"}"
240
- elif [ -n "$TIME_MACHINE_FROM_TURN" ]; then
241
- RECALL_BODY="{\"session_id\": \"${TIME_MACHINE_SESSION}\", \"from_turn\": ${TIME_MACHINE_FROM_TURN}, \"format\": \"summary\"}"
242
- fi
243
-
244
- # Fetch turns from L2
245
- RESTORE_RESPONSE=$(curl -s -X POST "$MEMORY_API_URL/api/v1/turns/recall" \
246
- -H "Authorization: Bearer $AUTH_TOKEN" \
247
- -H "Content-Type: application/json" \
248
- -d "$RECALL_BODY" \
249
- --connect-timeout 3 \
250
- --max-time 5 2>/dev/null || echo '{"error":"timeout"}')
251
-
252
- # Check if we got turns back using Node (no jq)
253
- RESTORED_COUNT=$(echo "$RESTORE_RESPONSE" | node -e "
254
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
255
- console.log((d.turns || []).length);
256
- " 2>/dev/null || echo "0")
257
-
258
- if [ "$RESTORED_COUNT" -gt 0 ]; then
259
- echo -e "${MAGENTA} ✓${RESET} Restored ${RESTORED_COUNT} turns from past session" >&2
260
- echo "" >&2
261
- echo -e "${MAGENTA}${BOLD}## Time Machine Context${RESET}" >&2
262
- echo "" >&2
263
-
264
- # Output the turns as context using Node (no jq)
265
- TURNS_OUTPUT=$(echo "$RESTORE_RESPONSE" | node -e "
266
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
267
- (d.turns || []).forEach(t => {
268
- const q = (t.user_query || '').substring(0, 100);
269
- const r = (t.assistant_response || '').substring(0, 200);
270
- console.log('**Turn ' + (t.turn_number || '?') + '**: ' + q + '...');
271
- console.log('> ' + r + '...');
272
- console.log('');
273
- });
274
- " 2>/dev/null || echo "")
275
- echo "$TURNS_OUTPUT" >&2
276
- echo "$TURNS_OUTPUT"
277
-
278
- echo "" >&2
279
- echo -e "${DIM}You've traveled to a past session. Continue from here!${RESET}" >&2
280
- echo "" >&2
281
- fi
282
- fi
283
-
284
- # ═══════════════════════════════════════════════════════════════════════════
285
- # DIRECTIVE RETRIEVAL: Fetch user's MUST/NEVER/PREFER/AVOID rules
286
- # ═══════════════════════════════════════════════════════════════════════════
287
- DIRECTIVES_INJECTED=false
288
- DIRECTIVE_COUNT=0
289
-
290
- # Only fetch if we have auth
291
- if [ -n "$AUTH_TOKEN" ]; then
292
- # Fetch directives (top 20 by priority to avoid token bloat)
293
- DIRECTIVES_RESPONSE=$(curl -s -X GET "$MEMORY_API_URL/api/v1/memory/directives?limit=20" \
294
- -H "Authorization: Bearer $AUTH_TOKEN" \
295
- --connect-timeout 2 \
296
- --max-time 3 2>/dev/null || echo '{}')
297
-
298
- # Parse response using Node (no jq)
299
- DIRECTIVE_COUNT=$(echo "$DIRECTIVES_RESPONSE" | node -e "
300
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
301
- console.log(d.count || 0);
302
- " 2>/dev/null || echo "0")
303
-
304
- if [ "$DIRECTIVE_COUNT" -gt 0 ]; then
305
- DIRECTIVES_INJECTED=true
306
-
307
- # Extract MUST/NEVER/PREFER/AVOID arrays using Node
308
- echo "<system-reminder>"
309
- echo "USER DIRECTIVES (FOLLOW THESE):"
310
- echo ""
311
-
312
- echo "$DIRECTIVES_RESPONSE" | node -e "
313
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
314
- const types = ['MUST', 'NEVER', 'PREFER', 'AVOID'];
315
- types.forEach(type => {
316
- const rules = (d[type] || []).slice(0, 5);
317
- if (rules.length > 0) {
318
- console.log(type + ':');
319
- rules.forEach(r => console.log(' - ' + (r.rule || '')));
320
- }
321
- });
322
- " 2>/dev/null || true
323
-
324
- echo "</system-reminder>"
325
- echo -e "${GREEN} ${DIRECTIVE_COUNT} directives loaded${RESET}" >&2
326
- fi
327
- fi
328
-
329
- # Simple status display (no auto-restore)
330
- if [ "$SAVED_TURN_COUNT" -gt 0 ]; then
331
- echo "" >&2
332
- if [ "$SAVED_TURN_COUNT" -gt 0 ]; then
333
- echo -e "${CYAN}${BOLD} ekkOS${RESET} ${DIM}|${RESET} Session: ${CURRENT_SESSION_ID:-$SESSION_ID} ${DIM}|${RESET} ${GREEN}${SAVED_TURN_COUNT} turns${RESET}" >&2
334
- else
335
- echo -e "${CYAN}${BOLD} ekkOS${RESET} ${DIM}|${RESET} Session: ${CURRENT_SESSION_ID:-$SESSION_ID} ${DIM}| New session${RESET}" >&2
336
- fi
337
- fi
338
-
339
- # Final confirmation that's always visible
340
- if [ -n "$TIME_MACHINE_SESSION" ]; then
341
- echo -e "${MAGENTA}------------------------------------------------------------------------${RESET}" >&2
342
- echo -e "${MAGENTA} ${RESET} Time Machine active - Restored from session ${TIME_MACHINE_SESSION:0:12}..." >&2
343
- echo -e "${MAGENTA}------------------------------------------------------------------------${RESET}" >&2
344
- elif [ "$SAVED_TURN_COUNT" -gt 0 ]; then
345
- echo -e "${GREEN}------------------------------------------------------------------------${RESET}" >&2
346
- echo -e "${GREEN}✓${RESET} Session continued - ${SAVED_TURN_COUNT} turns preserved - Ready to resume" >&2
347
- echo -e "${GREEN}------------------------------------------------------------------------${RESET}" >&2
348
- else
349
- echo -e "${CYAN}✓${RESET} New session started" >&2
350
- fi
351
- echo "" >&2
352
-
353
- exit 0