@ekkos/cli 1.0.35 → 1.1.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 (36) hide show
  1. package/README.md +57 -0
  2. package/dist/commands/dashboard.js +561 -186
  3. package/dist/deploy/settings.js +13 -26
  4. package/package.json +2 -4
  5. package/templates/CLAUDE.md +135 -23
  6. package/templates/ekkos-manifest.json +8 -8
  7. package/templates/hooks/assistant-response.ps1 +256 -160
  8. package/templates/hooks/assistant-response.sh +130 -66
  9. package/templates/hooks/hooks.json +24 -6
  10. package/templates/hooks/lib/contract.sh +43 -31
  11. package/templates/hooks/lib/count-tokens.cjs +0 -0
  12. package/templates/hooks/lib/ekkos-reminders.sh +0 -0
  13. package/templates/hooks/lib/state.sh +53 -1
  14. package/templates/hooks/session-start.ps1 +91 -391
  15. package/templates/hooks/session-start.sh +201 -166
  16. package/templates/hooks/stop.ps1 +202 -341
  17. package/templates/hooks/stop.sh +275 -948
  18. package/templates/hooks/user-prompt-submit.ps1 +224 -548
  19. package/templates/hooks/user-prompt-submit.sh +382 -456
  20. package/templates/plan-template.md +0 -0
  21. package/templates/spec-template.md +0 -0
  22. package/templates/windsurf-hooks/hooks.json +9 -2
  23. package/templates/windsurf-hooks/install.sh +0 -0
  24. package/templates/windsurf-hooks/lib/contract.sh +2 -0
  25. package/templates/windsurf-hooks/post-cascade-response.sh +0 -0
  26. package/templates/windsurf-hooks/pre-user-prompt.sh +0 -0
  27. package/templates/agents/README.md +0 -182
  28. package/templates/agents/code-reviewer.md +0 -166
  29. package/templates/agents/debug-detective.md +0 -169
  30. package/templates/agents/ekkOS_Vercel.md +0 -99
  31. package/templates/agents/extension-manager.md +0 -229
  32. package/templates/agents/git-companion.md +0 -185
  33. package/templates/agents/github-test-agent.md +0 -321
  34. package/templates/agents/railway-manager.md +0 -179
  35. package/templates/windsurf-hooks/before-submit-prompt.sh +0 -238
  36. package/templates/windsurf-skills/ekkos-memory/SKILL.md +0 -219
@@ -1,446 +1,146 @@
1
1
  # ═══════════════════════════════════════════════════════════════════════════
2
- # ekkOS_ Hook: SessionStart - MINIMAL + AUTO-RESTORE + TIME MACHINE CONTINUE
2
+ # ekkOS_ Hook: SessionStart - Initialize session (Windows)
3
3
  # MANAGED BY ekkos-connect - DO NOT EDIT DIRECTLY
4
4
  # EKKOS_MANAGED=1
5
- # ═══════════════════════════════════════════════════════════════════════════
6
- # This hook does THREE things:
7
- # 1. Check for pending Time Machine "Continue from here" requests
8
- # 2. Initialize session tracking
9
- # 3. Auto-restore from L2 if recent turns exist (FAST TRIM support)
10
- #
11
- # TIME MACHINE FLOW:
12
- # User clicks "Continue from here" on web -> API queues request ->
13
- # User runs `claude` -> This hook detects pending request ->
14
- # Restores THAT session's context -> Seamless time travel!
15
- #
16
- # FAST TRIM FLOW:
17
- # User runs /clear -> session-start detects fresh session ->
18
- # Checks L2 for recent turns -> Auto-injects last 15 turns -> Seamless continuity
5
+ # EKKOS_MANIFEST_SHA256=<computed-at-build>
6
+ # EKKOS_TEMPLATE_VERSION=1.0.0
19
7
  #
20
- # Per spec v1.2 Addendum: NO jq dependency
8
+ # Per ekkOS Onboarding Spec v1.2 FINAL + ADDENDUM:
9
+ # - All persisted records MUST include: instanceId, sessionId, sessionName
10
+ # - Uses EKKOS_INSTANCE_ID env var for multi-session isolation
21
11
  # ═══════════════════════════════════════════════════════════════════════════
22
12
 
23
13
  $ErrorActionPreference = "SilentlyContinue"
24
14
 
25
- $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
26
- $ProjectRoot = Split-Path -Parent (Split-Path -Parent $ScriptDir)
27
-
28
15
  # ═══════════════════════════════════════════════════════════════════════════
29
- # CONFIG PATHS - Per spec v1.2 Addendum
16
+ # CONFIG PATHS - No hardcoded word arrays per spec v1.2 Addendum
30
17
  # ═══════════════════════════════════════════════════════════════════════════
31
- $EkkosConfigDir = if ($env:EKKOS_CONFIG_DIR) { $env:EKKOS_CONFIG_DIR } else { Join-Path $env:USERPROFILE ".ekkos" }
32
- $JsonParseHelper = Join-Path $EkkosConfigDir ".helpers\json-parse.cjs"
33
-
34
- $INPUT = [Console]::In.ReadToEnd()
18
+ $EkkosConfigDir = if ($env:EKKOS_CONFIG_DIR) { $env:EKKOS_CONFIG_DIR } else { "$env:USERPROFILE\.ekkos" }
19
+ $SessionWordsJson = "$EkkosConfigDir\session-words.json"
20
+ $SessionWordsDefault = "$EkkosConfigDir\.defaults\session-words.json"
35
21
 
36
22
  # ═══════════════════════════════════════════════════════════════════════════
37
- # JSON parsing helper (no jq) - pipes JSON to node, extracts dot-path value
23
+ # INSTANCE ID - Multi-session isolation per v1.2 ADDENDUM
38
24
  # ═══════════════════════════════════════════════════════════════════════════
39
- function Parse-JsonValue {
40
- param(
41
- [string]$Json,
42
- [string]$Path
43
- )
44
- if (-not $Json) { return "" }
45
- try {
46
- $result = $Json | node -e "
47
- const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
48
- const path = '$Path'.replace(/^\./,'').split('.').filter(Boolean);
49
- let result = data;
50
- for (const p of path) {
51
- if (result === undefined || result === null) { result = undefined; break; }
52
- result = result[p];
53
- }
54
- if (result !== undefined && result !== null) console.log(result);
55
- " 2>$null
56
- if ($result) { return $result.Trim() } else { return "" }
57
- } catch {
58
- return ""
59
- }
60
- }
61
-
62
- $SESSION_ID = Parse-JsonValue $INPUT ".session_id"
63
- if (-not $SESSION_ID) { $SESSION_ID = "unknown" }
64
-
65
- $TRANSCRIPT_PATH = Parse-JsonValue $INPUT ".transcript_path"
66
- $SOURCE = Parse-JsonValue $INPUT ".source"
67
- if (-not $SOURCE) { $SOURCE = "unknown" }
25
+ $EkkosInstanceId = if ($env:EKKOS_INSTANCE_ID) { $env:EKKOS_INSTANCE_ID } else { "default" }
68
26
 
69
27
  # ═══════════════════════════════════════════════════════════════════════════
70
- # Load auth
28
+ # Load session words from JSON file - NO HARDCODED ARRAYS
71
29
  # ═══════════════════════════════════════════════════════════════════════════
72
- $EKKOS_CONFIG = Join-Path $env:USERPROFILE ".ekkos\config.json"
73
- $AUTH_TOKEN = ""
74
- $USER_ID = ""
30
+ $script:SessionWords = $null
75
31
 
76
- if ((Test-Path $EKKOS_CONFIG) -and (Test-Path $JsonParseHelper)) {
77
- $AUTH_TOKEN = & node $JsonParseHelper $EKKOS_CONFIG ".hookApiKey" 2>$null
78
- if (-not $AUTH_TOKEN) {
79
- $AUTH_TOKEN = & node $JsonParseHelper $EKKOS_CONFIG ".apiKey" 2>$null
80
- }
81
- $USER_ID = & node $JsonParseHelper $EKKOS_CONFIG ".userId" 2>$null
82
- }
32
+ function Load-SessionWords {
33
+ $wordsFile = $SessionWordsJson
83
34
 
84
- if (-not $AUTH_TOKEN) {
85
- $envLocalFile = Join-Path $ProjectRoot ".env.local"
86
- if (Test-Path $envLocalFile) {
87
- $envLines = Get-Content $envLocalFile
88
- foreach ($line in $envLines) {
89
- if ($line -match '^SUPABASE_SECRET_KEY=(.+)$') {
90
- $AUTH_TOKEN = $Matches[1].Trim('"', "'", ' ', "`r")
91
- break
92
- }
93
- }
35
+ if (-not (Test-Path $wordsFile)) {
36
+ $wordsFile = $SessionWordsDefault
94
37
  }
95
- }
96
-
97
- if (-not $AUTH_TOKEN) { exit 0 }
98
-
99
- $MEMORY_API_URL = "https://mcp.ekkos.dev"
100
-
101
- # ═══════════════════════════════════════════════════════════════════════════
102
- # COLORS (ANSI escape sequences via [char]27)
103
- # ═══════════════════════════════════════════════════════════════════════════
104
- $ESC = [char]27
105
- $CYAN = "$ESC[0;36m"
106
- $GREEN = "$ESC[0;32m"
107
- $YELLOW = "$ESC[1;33m"
108
- $MAGENTA = "$ESC[0;35m"
109
- $DIM = "$ESC[2m"
110
- $BOLD = "$ESC[1m"
111
- $RESET = "$ESC[0m"
112
38
 
113
- # ═══════════════════════════════════════════════════════════════════════════
114
- # TIME MACHINE: Check for pending "Continue from here" requests
115
- # ═══════════════════════════════════════════════════════════════════════════
116
- $RESTORE_REQUEST_ID = $env:EKKOS_RESTORE
117
- $TIME_MACHINE_SESSION = ""
118
- $TIME_MACHINE_FROM_TURN = ""
119
- $TIME_MACHINE_TO_TURN = ""
120
-
121
- # Check via env var first, then API
122
- if ($RESTORE_REQUEST_ID) {
123
- [Console]::Error.WriteLine("$MAGENTA Time Machine request detected: $RESTORE_REQUEST_ID$RESET")
124
- }
39
+ if (-not (Test-Path $wordsFile)) {
40
+ return $null
41
+ }
125
42
 
126
- # Check API for pending requests (if we have user_id)
127
- if ((-not $TIME_MACHINE_SESSION) -and $USER_ID) {
128
- $PENDING_RESPONSE_RAW = ""
129
43
  try {
130
- $PENDING_RESPONSE_RAW = Invoke-RestMethod -Uri "$MEMORY_API_URL/api/v1/context/restore-request/pending?user_id=$USER_ID" `
131
- -Method GET `
132
- -Headers @{ Authorization = "Bearer $AUTH_TOKEN" } `
133
- -TimeoutSec 3 `
134
- -ErrorAction Stop
44
+ $script:SessionWords = Get-Content $wordsFile -Raw | ConvertFrom-Json
135
45
  } catch {
136
- $PENDING_RESPONSE_RAW = ""
46
+ return $null
137
47
  }
48
+ }
138
49
 
139
- if ($PENDING_RESPONSE_RAW) {
140
- # Convert response object to JSON string for Parse-JsonValue
141
- $PENDING_JSON = ""
142
- if ($PENDING_RESPONSE_RAW -is [string]) {
143
- $PENDING_JSON = $PENDING_RESPONSE_RAW
144
- } else {
145
- try {
146
- $PENDING_JSON = $PENDING_RESPONSE_RAW | ConvertTo-Json -Depth 10 -Compress
147
- } catch {
148
- $PENDING_JSON = "{}"
149
- }
150
- }
50
+ function Convert-UuidToWords {
51
+ param([string]$uuid)
151
52
 
152
- $IS_PENDING = Parse-JsonValue $PENDING_JSON ".pending"
53
+ if (-not $script:SessionWords) {
54
+ Load-SessionWords
55
+ }
153
56
 
154
- if ($IS_PENDING -eq "true" -or $IS_PENDING -eq "True") {
155
- $TIME_MACHINE_SESSION = Parse-JsonValue $PENDING_JSON ".request.session_id"
156
- $TIME_MACHINE_FROM_TURN = Parse-JsonValue $PENDING_JSON ".request.from_turn"
157
- $TIME_MACHINE_TO_TURN = Parse-JsonValue $PENDING_JSON ".request.to_turn"
158
- $RESTORE_REQUEST_ID = Parse-JsonValue $PENDING_JSON ".request.request_id"
57
+ if (-not $script:SessionWords) {
58
+ return "unknown-session"
59
+ }
159
60
 
160
- if ($TIME_MACHINE_SESSION) {
161
- [Console]::Error.WriteLine("")
162
- [Console]::Error.WriteLine("$MAGENTA------------------------------------------------------------------------$RESET")
163
- [Console]::Error.WriteLine("$MAGENTA${BOLD} TIME MACHINE$RESET $DIM| Restoring session from web request...$RESET")
164
- [Console]::Error.WriteLine("$MAGENTA------------------------------------------------------------------------$RESET")
61
+ $adjectives = $script:SessionWords.adjectives
62
+ $nouns = $script:SessionWords.nouns
63
+ $verbs = $script:SessionWords.verbs
165
64
 
166
- # Mark request as consumed (background, non-blocking)
167
- $consumeBody = "{`"request_id`": `"$RESTORE_REQUEST_ID`"}"
168
- Start-Job -ScriptBlock {
169
- param($url, $token, $body)
170
- try {
171
- Invoke-RestMethod -Uri "$url/api/v1/context/restore-request/consume" `
172
- -Method POST `
173
- -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
174
- -Body ([System.Text.Encoding]::UTF8.GetBytes($body)) `
175
- -TimeoutSec 3 `
176
- -ErrorAction SilentlyContinue | Out-Null
177
- } catch {}
178
- } -ArgumentList $MEMORY_API_URL, $AUTH_TOKEN, $consumeBody | Out-Null
179
- }
180
- }
65
+ if (-not $adjectives -or -not $nouns -or -not $verbs) {
66
+ return "unknown-session"
181
67
  }
182
- }
183
68
 
184
- # ═══════════════════════════════════════════════════════════════════════════
185
- # Session ID persistence - PROJECT-LOCAL for isolation
186
- # ═══════════════════════════════════════════════════════════════════════════
187
- $STATE_DIR = Join-Path $ProjectRoot ".claude\state"
188
- if (-not (Test-Path $STATE_DIR)) {
189
- New-Item -ItemType Directory -Path $STATE_DIR -Force | Out-Null
190
- }
191
- $SESSION_FILE = Join-Path $STATE_DIR "current-session.json"
192
-
193
- # Project-local session storage (isolated per project)
194
- $PROJECT_SESSION_DIR = Join-Path $STATE_DIR "sessions"
195
- if (-not (Test-Path $PROJECT_SESSION_DIR)) {
196
- New-Item -ItemType Directory -Path $PROJECT_SESSION_DIR -Force | Out-Null
197
- }
69
+ if (-not $uuid -or $uuid -eq "unknown") { return "unknown-session" }
198
70
 
199
- # Use Claude's RAW_SESSION_ID directly (from session_id field)
200
- $CURRENT_SESSION_ID = $SESSION_ID
71
+ $clean = $uuid -replace "-", ""
72
+ if ($clean.Length -lt 12) { return "unknown-session" }
201
73
 
202
- # Find most recent session in THIS PROJECT for auto-restore
203
- $MOST_RECENT_SESSION = ""
204
- $SAVED_TURN_COUNT = 0
74
+ try {
75
+ $a = [Convert]::ToInt32($clean.Substring(0,4), 16) % $adjectives.Length
76
+ $n = [Convert]::ToInt32($clean.Substring(4,4), 16) % $nouns.Length
77
+ $an = [Convert]::ToInt32($clean.Substring(8,4), 16) % $verbs.Length
205
78
 
206
- if ($CURRENT_SESSION_ID -and $CURRENT_SESSION_ID -ne "unknown") {
207
- # Check if THIS session has saved turns (for /clear continuity)
208
- $TURN_COUNTER_FILE = Join-Path $PROJECT_SESSION_DIR "$CURRENT_SESSION_ID.turn"
209
- if (Test-Path $TURN_COUNTER_FILE) {
210
- try {
211
- $SAVED_TURN_COUNT = [int](Get-Content $TURN_COUNTER_FILE -Raw).Trim()
212
- } catch {
213
- $SAVED_TURN_COUNT = 0
214
- }
215
- $MOST_RECENT_SESSION = $CURRENT_SESSION_ID
216
- } else {
217
- # Fresh start: find most recent session in project
218
- $turnFiles = Get-ChildItem -Path $PROJECT_SESSION_DIR -Filter "*.turn" -ErrorAction SilentlyContinue |
219
- Sort-Object LastWriteTime -Descending |
220
- Select-Object -First 1
221
- if ($turnFiles) {
222
- $MOST_RECENT_SESSION = [System.IO.Path]::GetFileNameWithoutExtension($turnFiles.Name)
223
- try {
224
- $SAVED_TURN_COUNT = [int](Get-Content $turnFiles.FullName -Raw).Trim()
225
- } catch {
226
- $SAVED_TURN_COUNT = 0
227
- }
228
- }
79
+ return "$($adjectives[$a])-$($nouns[$n])-$($verbs[$an])"
80
+ } catch {
81
+ return "unknown-session"
229
82
  }
230
83
  }
231
84
 
232
- # Save current session info
233
- if ($CURRENT_SESSION_ID) {
234
- $TIMESTAMP_UTC = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
235
- $projectRootNorm = $ProjectRoot -replace '\\', '/'
236
- $sessionInfoJson = "{`"session_id`": `"$CURRENT_SESSION_ID`", `"timestamp`": `"$TIMESTAMP_UTC`", `"project_root`": `"$projectRootNorm`"}"
237
- Set-Content -Path $SESSION_FILE -Value $sessionInfoJson -Force
238
- }
239
-
240
85
  # ═══════════════════════════════════════════════════════════════════════════
241
- # GOLDEN LOOP: Initialize session tracking file
86
+ # READ INPUT
242
87
  # ═══════════════════════════════════════════════════════════════════════════
243
- $EKKOS_DIR = Join-Path $ProjectRoot ".ekkos"
244
- if (-not (Test-Path $EKKOS_DIR)) {
245
- New-Item -ItemType Directory -Path $EKKOS_DIR -Force | Out-Null
246
- }
247
- $GOLDEN_LOOP_FILE = Join-Path $EKKOS_DIR "golden-loop-current.json"
88
+ $inputJson = [Console]::In.ReadToEnd()
248
89
 
249
- # Initialize with session start state using Node (no jq)
250
- $glPathEscaped = $GOLDEN_LOOP_FILE -replace '\\', '\\\\'
251
90
  try {
252
- node -e "
253
- const fs = require('fs');
254
- const data = {
255
- phase: 'idle',
256
- turn: 0,
257
- session: '$CURRENT_SESSION_ID',
258
- timestamp: new Date().toISOString(),
259
- stats: { retrieved: 0, applied: 0, forged: 0 }
260
- };
261
- fs.writeFileSync('$glPathEscaped', JSON.stringify(data, null, 2));
262
- " 2>$null
263
- } catch {}
91
+ $input = $inputJson | ConvertFrom-Json
92
+ $sessionId = $input.session_id
93
+ } catch {
94
+ $sessionId = "unknown"
95
+ }
96
+
97
+ $sessionName = Convert-UuidToWords $sessionId
264
98
 
265
99
  # ═══════════════════════════════════════════════════════════════════════════
266
- # AUTO-RESTORE REMOVED: Manual /continue only (saves 79% token burn!)
267
- # ═══════════════════════════════════════════════════════════════════════════
268
- # WHY REMOVED:
269
- # - Auto-restore burned 5,000 tokens per turn on session start
270
- # - Manual /continue: one-time cost + clean slate (79% token savings!)
271
- # - Manual /continue is 10x more powerful (Bash + multi-source + narrative)
272
- #
273
- # KEPT: Time Machine feature (explicit user request)
100
+ # INITIALIZE STATE DIRECTORY
274
101
  # ═══════════════════════════════════════════════════════════════════════════
102
+ $stateDir = Join-Path $env:USERPROFILE ".claude\state"
103
+ if (-not (Test-Path $stateDir)) {
104
+ New-Item -ItemType Directory -Path $stateDir -Force | Out-Null
105
+ }
275
106
 
276
- # Handle Time Machine requests (explicit user action)
277
- if ($TIME_MACHINE_SESSION) {
278
- $tmPreview = $TIME_MACHINE_SESSION
279
- if ($tmPreview.Length -gt 12) {
280
- $tmPreview = $tmPreview.Substring(0, 12)
281
- }
282
-
283
- [Console]::Error.WriteLine("")
284
- [Console]::Error.WriteLine("$MAGENTA${BOLD} TIME MACHINE$RESET $DIM| Restoring past session: ${tmPreview}...$RESET")
285
-
286
- # Build recall request with turn range
287
- $RECALL_BODY = "{`"session_id`": `"$TIME_MACHINE_SESSION`", `"last_n`": 15, `"format`": `"summary`"}"
288
-
289
- if ($TIME_MACHINE_FROM_TURN -and $TIME_MACHINE_TO_TURN) {
290
- $RECALL_BODY = "{`"session_id`": `"$TIME_MACHINE_SESSION`", `"from_turn`": $TIME_MACHINE_FROM_TURN, `"to_turn`": $TIME_MACHINE_TO_TURN, `"format`": `"summary`"}"
291
- } elseif ($TIME_MACHINE_FROM_TURN) {
292
- $RECALL_BODY = "{`"session_id`": `"$TIME_MACHINE_SESSION`", `"from_turn`": $TIME_MACHINE_FROM_TURN, `"format`": `"summary`"}"
293
- }
294
-
295
- # Fetch turns from L2
296
- $RESTORE_RESPONSE = $null
297
- try {
298
- $RESTORE_RESPONSE = Invoke-RestMethod -Uri "$MEMORY_API_URL/api/v1/turns/recall" `
299
- -Method POST `
300
- -Headers @{ Authorization = "Bearer $AUTH_TOKEN"; "Content-Type" = "application/json" } `
301
- -Body ([System.Text.Encoding]::UTF8.GetBytes($RECALL_BODY)) `
302
- -TimeoutSec 5 `
303
- -ErrorAction Stop
304
- } catch {
305
- $RESTORE_RESPONSE = $null
306
- }
307
-
308
- # Check if we got turns back
309
- $RESTORED_COUNT = 0
310
- $RESTORED_TURNS = @()
311
- if ($RESTORE_RESPONSE -and $RESTORE_RESPONSE.turns) {
312
- $RESTORED_TURNS = @($RESTORE_RESPONSE.turns)
313
- $RESTORED_COUNT = $RESTORED_TURNS.Count
314
- }
315
-
316
- if ($RESTORED_COUNT -gt 0) {
317
- [Console]::Error.WriteLine("$MAGENTA $RESET Restored $RESTORED_COUNT turns from past session")
318
- [Console]::Error.WriteLine("")
319
- [Console]::Error.WriteLine("$MAGENTA${BOLD}## Time Machine Context$RESET")
320
- [Console]::Error.WriteLine("")
107
+ # Reset turn counter for new session
108
+ $stateFile = Join-Path $stateDir "hook-state.json"
109
+ $state = @{
110
+ turn = 0
111
+ session_id = $sessionId
112
+ session_name = $sessionName
113
+ instance_id = $EkkosInstanceId
114
+ started_at = (Get-Date).ToString("o")
115
+ } | ConvertTo-Json -Depth 10
321
116
 
322
- # Build turns output for both stderr (display) and stdout (context injection)
323
- $turnsOutput = ""
324
- foreach ($t in $RESTORED_TURNS) {
325
- $q = ""
326
- if ($t.user_query) {
327
- $q = $t.user_query
328
- if ($q.Length -gt 100) { $q = $q.Substring(0, 100) }
329
- }
330
- $r = ""
331
- if ($t.assistant_response) {
332
- $r = $t.assistant_response
333
- if ($r.Length -gt 200) { $r = $r.Substring(0, 200) }
334
- }
335
- $tn = if ($t.turn_number) { $t.turn_number } else { "?" }
117
+ Set-Content -Path $stateFile -Value $state -Force
336
118
 
337
- $turnLine = "**Turn $tn**: $q..."
338
- $responseLine = "> $r..."
339
- $turnsOutput += "$turnLine`n$responseLine`n`n"
340
- }
119
+ # Save session ID for other hooks
120
+ $sessionFile = Join-Path $stateDir "current-session.json"
121
+ $sessionData = @{
122
+ session_id = $sessionId
123
+ session_name = $sessionName
124
+ instance_id = $EkkosInstanceId
125
+ } | ConvertTo-Json -Depth 10
341
126
 
342
- # Output to stderr (visible in terminal)
343
- [Console]::Error.WriteLine($turnsOutput)
344
- # Output to stdout (injected into context)
345
- Write-Output $turnsOutput
346
-
347
- [Console]::Error.WriteLine("")
348
- [Console]::Error.WriteLine("${DIM}You've traveled to a past session. Continue from here!$RESET")
349
- [Console]::Error.WriteLine("")
350
- }
351
- }
127
+ Set-Content -Path $sessionFile -Value $sessionData -Force
352
128
 
353
129
  # ═══════════════════════════════════════════════════════════════════════════
354
- # DIRECTIVE RETRIEVAL: Fetch user's MUST/NEVER/PREFER/AVOID rules
130
+ # LOCAL CACHE: Initialize session in Tier 0 cache
131
+ # Per v1.2 ADDENDUM: Pass instanceId for namespacing
355
132
  # ═══════════════════════════════════════════════════════════════════════════
356
- $DIRECTIVES_INJECTED = $false
357
- $DIRECTIVE_COUNT = 0
358
-
359
- # Only fetch if we have auth
360
- if ($AUTH_TOKEN) {
361
- # Fetch directives (top 20 by priority to avoid token bloat)
362
- $DIRECTIVES_RESPONSE_RAW = $null
133
+ $captureCmd = Get-Command "ekkos-capture" -ErrorAction SilentlyContinue
134
+ if ($captureCmd -and $sessionId -ne "unknown") {
363
135
  try {
364
- $DIRECTIVES_RESPONSE_RAW = Invoke-RestMethod -Uri "$MEMORY_API_URL/api/v1/memory/directives?limit=20" `
365
- -Method GET `
366
- -Headers @{ Authorization = "Bearer $AUTH_TOKEN" } `
367
- -TimeoutSec 3 `
368
- -ErrorAction Stop
369
- } catch {
370
- $DIRECTIVES_RESPONSE_RAW = $null
371
- }
372
-
373
- if ($DIRECTIVES_RESPONSE_RAW) {
374
- # Convert response object to JSON string for Parse-JsonValue / Node
375
- $DIRECTIVES_JSON = ""
376
- if ($DIRECTIVES_RESPONSE_RAW -is [string]) {
377
- $DIRECTIVES_JSON = $DIRECTIVES_RESPONSE_RAW
378
- } else {
136
+ # NEW format: ekkos-capture init <instance_id> <session_id> <session_name>
137
+ Start-Job -ScriptBlock {
138
+ param($instanceId, $sessId, $sessName)
379
139
  try {
380
- $DIRECTIVES_JSON = $DIRECTIVES_RESPONSE_RAW | ConvertTo-Json -Depth 10 -Compress
381
- } catch {
382
- $DIRECTIVES_JSON = "{}"
383
- }
384
- }
385
-
386
- $DIRECTIVE_COUNT_STR = Parse-JsonValue $DIRECTIVES_JSON ".count"
387
- if ($DIRECTIVE_COUNT_STR -match '^\d+$') {
388
- $DIRECTIVE_COUNT = [int]$DIRECTIVE_COUNT_STR
389
- }
390
-
391
- if ($DIRECTIVE_COUNT -gt 0) {
392
- $DIRECTIVES_INJECTED = $true
393
-
394
- # Extract MUST/NEVER/PREFER/AVOID arrays using Node
395
- Write-Output "<system-reminder>"
396
- Write-Output "USER DIRECTIVES (FOLLOW THESE):"
397
- Write-Output ""
398
-
399
- $directiveOutput = $DIRECTIVES_JSON | node -e "
400
- const d = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
401
- const types = ['MUST', 'NEVER', 'PREFER', 'AVOID'];
402
- types.forEach(type => {
403
- const rules = (d[type] || []).slice(0, 5);
404
- if (rules.length > 0) {
405
- console.log(type + ':');
406
- rules.forEach(r => console.log(' - ' + (r.rule || '')));
407
- }
408
- });
409
- " 2>$null
410
-
411
- if ($directiveOutput) {
412
- Write-Output $directiveOutput
413
- }
414
-
415
- Write-Output "</system-reminder>"
416
- [Console]::Error.WriteLine("$GREEN $DIRECTIVE_COUNT directives loaded$RESET")
417
- }
418
- }
419
- }
420
-
421
- # Simple status display (no auto-restore)
422
- if ($SAVED_TURN_COUNT -gt 0) {
423
- [Console]::Error.WriteLine("")
424
- $displaySessionId = if ($CURRENT_SESSION_ID) { $CURRENT_SESSION_ID } else { $SESSION_ID }
425
- [Console]::Error.WriteLine("$CYAN${BOLD} ekkOS$RESET $DIM|$RESET Session: $displaySessionId $DIM|$RESET $GREEN$SAVED_TURN_COUNT turns$RESET")
426
- }
427
-
428
- # Final confirmation that's always visible
429
- if ($TIME_MACHINE_SESSION) {
430
- $tmFinalPreview = $TIME_MACHINE_SESSION
431
- if ($tmFinalPreview.Length -gt 12) {
432
- $tmFinalPreview = $tmFinalPreview.Substring(0, 12)
433
- }
434
- [Console]::Error.WriteLine("$MAGENTA------------------------------------------------------------------------$RESET")
435
- [Console]::Error.WriteLine("$MAGENTA $RESET Time Machine active - Restored from session ${tmFinalPreview}...")
436
- [Console]::Error.WriteLine("$MAGENTA------------------------------------------------------------------------$RESET")
437
- } elseif ($SAVED_TURN_COUNT -gt 0) {
438
- [Console]::Error.WriteLine("$GREEN------------------------------------------------------------------------$RESET")
439
- [Console]::Error.WriteLine("${GREEN}$RESET Session continued - $SAVED_TURN_COUNT turns preserved - Ready to resume")
440
- [Console]::Error.WriteLine("$GREEN------------------------------------------------------------------------$RESET")
441
- } else {
442
- [Console]::Error.WriteLine("${CYAN}$RESET New session started")
140
+ & ekkos-capture init $instanceId $sessId $sessName 2>&1 | Out-Null
141
+ } catch {}
142
+ } -ArgumentList $EkkosInstanceId, $sessionId, $sessionName | Out-Null
143
+ } catch {}
443
144
  }
444
- [Console]::Error.WriteLine("")
445
145
 
446
- exit 0
146
+ Write-Output "ekkOS session initialized"