@ekkos/cli 1.0.36 → 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.
@@ -1,27 +1,28 @@
1
1
  # ═══════════════════════════════════════════════════════════════════════════
2
- # ekkOS_ Hook: SessionStart - MINIMAL + TIME MACHINE + DIRECTIVES (Windows)
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 + Golden Loop
9
- # 3. Fetch and inject user directives (MUST/NEVER/PREFER/AVOID)
5
+ # EKKOS_MANIFEST_SHA256=<computed-at-build>
6
+ # EKKOS_TEMPLATE_VERSION=1.0.0
10
7
  #
11
- # Per spec v1.2 Addendum: NO hardcoded arrays, uses session-words.json
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
12
11
  # ═══════════════════════════════════════════════════════════════════════════
13
12
 
14
13
  $ErrorActionPreference = "SilentlyContinue"
15
14
 
16
15
  # ═══════════════════════════════════════════════════════════════════════════
17
- # CONFIG PATHS
16
+ # CONFIG PATHS - No hardcoded word arrays per spec v1.2 Addendum
18
17
  # ═══════════════════════════════════════════════════════════════════════════
19
18
  $EkkosConfigDir = if ($env:EKKOS_CONFIG_DIR) { $env:EKKOS_CONFIG_DIR } else { "$env:USERPROFILE\.ekkos" }
20
19
  $SessionWordsJson = "$EkkosConfigDir\session-words.json"
21
20
  $SessionWordsDefault = "$EkkosConfigDir\.defaults\session-words.json"
22
- $EkkosInstanceId = if ($env:EKKOS_INSTANCE_ID) { $env:EKKOS_INSTANCE_ID } else { "default" }
23
21
 
24
- $MemoryApiUrl = "https://api.ekkos.dev"
22
+ # ═══════════════════════════════════════════════════════════════════════════
23
+ # INSTANCE ID - Multi-session isolation per v1.2 ADDENDUM
24
+ # ═══════════════════════════════════════════════════════════════════════════
25
+ $EkkosInstanceId = if ($env:EKKOS_INSTANCE_ID) { $env:EKKOS_INSTANCE_ID } else { "default" }
25
26
 
26
27
  # ═══════════════════════════════════════════════════════════════════════════
27
28
  # Load session words from JSON file - NO HARDCODED ARRAYS
@@ -30,22 +31,41 @@ $script:SessionWords = $null
30
31
 
31
32
  function Load-SessionWords {
32
33
  $wordsFile = $SessionWordsJson
33
- if (-not (Test-Path $wordsFile)) { $wordsFile = $SessionWordsDefault }
34
- if (-not (Test-Path $wordsFile)) { return $null }
34
+
35
+ if (-not (Test-Path $wordsFile)) {
36
+ $wordsFile = $SessionWordsDefault
37
+ }
38
+
39
+ if (-not (Test-Path $wordsFile)) {
40
+ return $null
41
+ }
42
+
35
43
  try {
36
44
  $script:SessionWords = Get-Content $wordsFile -Raw | ConvertFrom-Json
37
- } catch { return $null }
45
+ } catch {
46
+ return $null
47
+ }
38
48
  }
39
49
 
40
50
  function Convert-UuidToWords {
41
51
  param([string]$uuid)
42
- if (-not $script:SessionWords) { Load-SessionWords }
43
- if (-not $script:SessionWords) { return "unknown-session" }
52
+
53
+ if (-not $script:SessionWords) {
54
+ Load-SessionWords
55
+ }
56
+
57
+ if (-not $script:SessionWords) {
58
+ return "unknown-session"
59
+ }
44
60
 
45
61
  $adjectives = $script:SessionWords.adjectives
46
62
  $nouns = $script:SessionWords.nouns
47
63
  $verbs = $script:SessionWords.verbs
48
- if (-not $adjectives -or -not $nouns -or -not $verbs) { return "unknown-session" }
64
+
65
+ if (-not $adjectives -or -not $nouns -or -not $verbs) {
66
+ return "unknown-session"
67
+ }
68
+
49
69
  if (-not $uuid -or $uuid -eq "unknown") { return "unknown-session" }
50
70
 
51
71
  $clean = $uuid -replace "-", ""
@@ -55,116 +75,36 @@ function Convert-UuidToWords {
55
75
  $a = [Convert]::ToInt32($clean.Substring(0,4), 16) % $adjectives.Length
56
76
  $n = [Convert]::ToInt32($clean.Substring(4,4), 16) % $nouns.Length
57
77
  $an = [Convert]::ToInt32($clean.Substring(8,4), 16) % $verbs.Length
78
+
58
79
  return "$($adjectives[$a])-$($nouns[$n])-$($verbs[$an])"
59
- } catch { return "unknown-session" }
80
+ } catch {
81
+ return "unknown-session"
82
+ }
60
83
  }
61
84
 
62
85
  # ═══════════════════════════════════════════════════════════════════════════
63
86
  # READ INPUT
64
87
  # ═══════════════════════════════════════════════════════════════════════════
65
88
  $inputJson = [Console]::In.ReadToEnd()
89
+
66
90
  try {
67
91
  $input = $inputJson | ConvertFrom-Json
68
92
  $sessionId = $input.session_id
69
93
  } catch {
70
94
  $sessionId = "unknown"
71
95
  }
72
- if (-not $sessionId) { $sessionId = "unknown" }
73
96
 
74
97
  $sessionName = Convert-UuidToWords $sessionId
75
98
 
76
99
  # ═══════════════════════════════════════════════════════════════════════════
77
- # LOAD AUTH
78
- # ═══════════════════════════════════════════════════════════════════════════
79
- $authToken = ""
80
- $userId = ""
81
- $configFile = Join-Path $EkkosConfigDir "config.json"
82
- if (Test-Path $configFile) {
83
- try {
84
- $config = Get-Content $configFile -Raw | ConvertFrom-Json
85
- $authToken = $config.hookApiKey
86
- if (-not $authToken) { $authToken = $config.apiKey }
87
- $userId = $config.userId
88
- } catch {}
89
- }
90
- if (-not $authToken) { exit 0 }
91
-
92
- # ═══════════════════════════════════════════════════════════════════════════
93
- # TIME MACHINE: Check for pending "Continue from here" requests
100
+ # INITIALIZE STATE DIRECTORY
94
101
  # ═══════════════════════════════════════════════════════════════════════════
95
- $timeMachineSession = ""
96
- $timeMachineFromTurn = ""
97
- $timeMachineToTurn = ""
98
- $restoreRequestId = $env:EKKOS_RESTORE
99
-
100
- if (-not $timeMachineSession -and $userId) {
101
- try {
102
- $headers = @{ "Authorization" = "Bearer $authToken" }
103
- $pendingResponse = Invoke-RestMethod -Uri "$MemoryApiUrl/api/v1/context/restore-request/pending?user_id=$userId" `
104
- -Method GET -Headers $headers -TimeoutSec 3
105
-
106
- if ($pendingResponse.pending -eq $true -and $pendingResponse.request) {
107
- $timeMachineSession = $pendingResponse.request.session_id
108
- $timeMachineFromTurn = $pendingResponse.request.from_turn
109
- $timeMachineToTurn = $pendingResponse.request.to_turn
110
- $restoreRequestId = $pendingResponse.request.request_id
111
-
112
- if ($timeMachineSession) {
113
- $esc = [char]27
114
- [Console]::Error.WriteLine("")
115
- [Console]::Error.WriteLine("${esc}[0;35m------------------------------------------------------------------------${esc}[0m")
116
- [Console]::Error.WriteLine("${esc}[0;35m${esc}[1m TIME MACHINE${esc}[0m ${esc}[2m| Restoring session from web request...${esc}[0m")
117
- [Console]::Error.WriteLine("${esc}[0;35m------------------------------------------------------------------------${esc}[0m")
118
-
119
- $consumeBody = @{ request_id = $restoreRequestId } | ConvertTo-Json -Depth 10
120
- try {
121
- Invoke-RestMethod -Uri "$MemoryApiUrl/api/v1/context/restore-request/consume" `
122
- -Method POST -Headers @{ "Authorization" = "Bearer $authToken"; "Content-Type" = "application/json" } `
123
- -Body ([System.Text.Encoding]::UTF8.GetBytes($consumeBody)) -TimeoutSec 3 | Out-Null
124
- } catch {}
125
- }
126
- }
127
- } catch {}
128
- }
129
-
130
- # ═══════════════════════════════════════════════════════════════════════════
131
- # SESSION PERSISTENCE - PROJECT-LOCAL for isolation
132
- # ═══════════════════════════════════════════════════════════════════════════
133
- $projectRoot = if ($env:PWD) { $env:PWD } else { (Get-Location).Path }
134
102
  $stateDir = Join-Path $env:USERPROFILE ".claude\state"
135
- $projectSessionDir = Join-Path $stateDir "sessions"
136
-
137
- if (-not (Test-Path $stateDir)) { New-Item -ItemType Directory -Path $stateDir -Force | Out-Null }
138
- if (-not (Test-Path $projectSessionDir)) { New-Item -ItemType Directory -Path $projectSessionDir -Force | Out-Null }
139
-
140
- $savedTurnCount = 0
141
- $mostRecentSession = ""
142
-
143
- if ($sessionId -ne "unknown") {
144
- $turnFile = Join-Path $projectSessionDir "$sessionId.turn"
145
- if (Test-Path $turnFile) {
146
- try { $savedTurnCount = [int](Get-Content $turnFile -Raw).Trim() } catch { $savedTurnCount = 0 }
147
- $mostRecentSession = $sessionId
148
- } else {
149
- $mostRecentFile = Get-ChildItem "$projectSessionDir\*.turn" -ErrorAction SilentlyContinue |
150
- Sort-Object LastWriteTime -Descending | Select-Object -First 1
151
- if ($mostRecentFile) {
152
- $mostRecentSession = $mostRecentFile.BaseName
153
- try { $savedTurnCount = [int](Get-Content $mostRecentFile.FullName -Raw).Trim() } catch { $savedTurnCount = 0 }
154
- }
155
- }
103
+ if (-not (Test-Path $stateDir)) {
104
+ New-Item -ItemType Directory -Path $stateDir -Force | Out-Null
156
105
  }
157
106
 
158
- $sessionFile = Join-Path $stateDir "current-session.json"
159
- $sessionData = @{
160
- session_id = $sessionId
161
- session_name = $sessionName
162
- instance_id = $EkkosInstanceId
163
- timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
164
- project_root = $projectRoot
165
- } | ConvertTo-Json -Depth 10
166
- Set-Content -Path $sessionFile -Value $sessionData -Force
167
-
107
+ # Reset turn counter for new session
168
108
  $stateFile = Join-Path $stateDir "hook-state.json"
169
109
  $state = @{
170
110
  turn = 0
@@ -173,137 +113,34 @@ $state = @{
173
113
  instance_id = $EkkosInstanceId
174
114
  started_at = (Get-Date).ToString("o")
175
115
  } | ConvertTo-Json -Depth 10
176
- Set-Content -Path $stateFile -Value $state -Force
177
116
 
178
- # ═══════════════════════════════════════════════════════════════════════════
179
- # GOLDEN LOOP: Initialize session tracking file
180
- # ═══════════════════════════════════════════════════════════════════════════
181
- $ekkosDir = Join-Path $projectRoot ".ekkos"
182
- if (-not (Test-Path $ekkosDir)) { New-Item -ItemType Directory -Path $ekkosDir -Force | Out-Null }
117
+ Set-Content -Path $stateFile -Value $state -Force
183
118
 
184
- $goldenLoopFile = Join-Path $ekkosDir "golden-loop-current.json"
185
- $goldenLoop = @{
186
- phase = "idle"
187
- turn = 0
188
- session = $sessionId
189
- timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
190
- stats = @{ retrieved = 0; applied = 0; forged = 0 }
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
191
125
  } | ConvertTo-Json -Depth 10
192
- Set-Content -Path $goldenLoopFile -Value $goldenLoop -Force
126
+
127
+ Set-Content -Path $sessionFile -Value $sessionData -Force
193
128
 
194
129
  # ═══════════════════════════════════════════════════════════════════════════
195
- # LOCAL CACHE: Initialize session
130
+ # LOCAL CACHE: Initialize session in Tier 0 cache
131
+ # Per v1.2 ADDENDUM: Pass instanceId for namespacing
196
132
  # ═══════════════════════════════════════════════════════════════════════════
197
133
  $captureCmd = Get-Command "ekkos-capture" -ErrorAction SilentlyContinue
198
134
  if ($captureCmd -and $sessionId -ne "unknown") {
199
135
  try {
136
+ # NEW format: ekkos-capture init <instance_id> <session_id> <session_name>
200
137
  Start-Job -ScriptBlock {
201
138
  param($instanceId, $sessId, $sessName)
202
- try { & ekkos-capture init $instanceId $sessId $sessName 2>&1 | Out-Null } catch {}
139
+ try {
140
+ & ekkos-capture init $instanceId $sessId $sessName 2>&1 | Out-Null
141
+ } catch {}
203
142
  } -ArgumentList $EkkosInstanceId, $sessionId, $sessionName | Out-Null
204
143
  } catch {}
205
144
  }
206
145
 
207
- # ═══════════════════════════════════════════════════════════════════════════
208
- # COLORS
209
- # ═══════════════════════════════════════════════════════════════════════════
210
- $esc = [char]27
211
- $CYAN = "${esc}[0;36m"; $GREEN = "${esc}[0;32m"; $MAGENTA = "${esc}[0;35m"
212
- $DIM = "${esc}[2m"; $BOLD = "${esc}[1m"; $RESET = "${esc}[0m"
213
-
214
- # ═══════════════════════════════════════════════════════════════════════════
215
- # TIME MACHINE RESTORATION
216
- # ═══════════════════════════════════════════════════════════════════════════
217
- if ($timeMachineSession) {
218
- [Console]::Error.WriteLine("")
219
- $tmPreview = $timeMachineSession.Substring(0, [Math]::Min(12, $timeMachineSession.Length))
220
- [Console]::Error.WriteLine("${MAGENTA}${BOLD} TIME MACHINE${RESET} ${DIM}| Restoring past session: ${tmPreview}...${RESET}")
221
-
222
- $recallBody = @{ session_id = $timeMachineSession; last_n = 15; format = "summary" }
223
- if ($timeMachineFromTurn -and $timeMachineToTurn) {
224
- $recallBody = @{ session_id = $timeMachineSession; from_turn = [int]$timeMachineFromTurn; to_turn = [int]$timeMachineToTurn; format = "summary" }
225
- } elseif ($timeMachineFromTurn) {
226
- $recallBody = @{ session_id = $timeMachineSession; from_turn = [int]$timeMachineFromTurn; format = "summary" }
227
- }
228
-
229
- $recallJson = $recallBody | ConvertTo-Json -Depth 10
230
- try {
231
- $restoreResponse = Invoke-RestMethod -Uri "$MemoryApiUrl/api/v1/turns/recall" `
232
- -Method POST `
233
- -Headers @{ "Authorization" = "Bearer $authToken"; "Content-Type" = "application/json" } `
234
- -Body ([System.Text.Encoding]::UTF8.GetBytes($recallJson)) -TimeoutSec 5
235
-
236
- $restoredCount = ($restoreResponse.turns | Measure-Object).Count
237
- if ($restoredCount -gt 0) {
238
- [Console]::Error.WriteLine("${MAGENTA} ✓${RESET} Restored $restoredCount turns from past session")
239
- [Console]::Error.WriteLine("")
240
-
241
- $turnsOutput = ""
242
- foreach ($t in $restoreResponse.turns) {
243
- $q = if ($t.user_query) { $t.user_query.Substring(0, [Math]::Min(100, $t.user_query.Length)) } else { "..." }
244
- $r = if ($t.assistant_response) { $t.assistant_response.Substring(0, [Math]::Min(200, $t.assistant_response.Length)) } else { "..." }
245
- $turnLine = "**Turn $($t.turn_number)**: $q..."
246
- [Console]::Error.WriteLine($turnLine)
247
- [Console]::Error.WriteLine("> $r...")
248
- [Console]::Error.WriteLine("")
249
- $turnsOutput += "$turnLine`n> $r...`n`n"
250
- }
251
- Write-Output $turnsOutput
252
-
253
- [Console]::Error.WriteLine("${DIM}You've traveled to a past session. Continue from here!${RESET}")
254
- [Console]::Error.WriteLine("")
255
- }
256
- } catch {}
257
- }
258
-
259
- # ═══════════════════════════════════════════════════════════════════════════
260
- # DIRECTIVE RETRIEVAL: Fetch user's MUST/NEVER/PREFER/AVOID rules
261
- # ═══════════════════════════════════════════════════════════════════════════
262
- if ($authToken) {
263
- try {
264
- $directivesResponse = Invoke-RestMethod -Uri "$MemoryApiUrl/api/v1/memory/directives?limit=20" `
265
- -Method GET -Headers @{ "Authorization" = "Bearer $authToken" } -TimeoutSec 3
266
-
267
- $directiveCount = $directivesResponse.count
268
- if ($directiveCount -gt 0) {
269
- Write-Output "<system-reminder>"
270
- Write-Output "USER DIRECTIVES (FOLLOW THESE):"
271
- Write-Output ""
272
-
273
- foreach ($type in @("MUST", "NEVER", "PREFER", "AVOID")) {
274
- $rules = $directivesResponse.$type
275
- if ($rules -and ($rules | Measure-Object).Count -gt 0) {
276
- Write-Output "${type}:"
277
- $rules | Select-Object -First 5 | ForEach-Object { Write-Output " - $($_.rule)" }
278
- }
279
- }
280
-
281
- Write-Output "</system-reminder>"
282
- [Console]::Error.WriteLine("${GREEN} $directiveCount directives loaded${RESET}")
283
- }
284
- } catch {}
285
- }
286
-
287
- # ═══════════════════════════════════════════════════════════════════════════
288
- # STATUS DISPLAY
289
- # ═══════════════════════════════════════════════════════════════════════════
290
- if ($savedTurnCount -gt 0) {
291
- [Console]::Error.WriteLine("")
292
- [Console]::Error.WriteLine("${CYAN}${BOLD} ekkOS${RESET} ${DIM}|${RESET} Session: $sessionId ${DIM}|${RESET} ${GREEN}$savedTurnCount turns${RESET}")
293
- }
294
-
295
- if ($timeMachineSession) {
296
- $tmP = $timeMachineSession.Substring(0, [Math]::Min(12, $timeMachineSession.Length))
297
- [Console]::Error.WriteLine("${MAGENTA}------------------------------------------------------------------------${RESET}")
298
- [Console]::Error.WriteLine("${MAGENTA} ${RESET} Time Machine active - Restored from session ${tmP}...")
299
- [Console]::Error.WriteLine("${MAGENTA}------------------------------------------------------------------------${RESET}")
300
- } elseif ($savedTurnCount -gt 0) {
301
- [Console]::Error.WriteLine("${GREEN}------------------------------------------------------------------------${RESET}")
302
- [Console]::Error.WriteLine("${GREEN}✓${RESET} Session continued - $savedTurnCount turns preserved - Ready to resume")
303
- [Console]::Error.WriteLine("${GREEN}------------------------------------------------------------------------${RESET}")
304
- } else {
305
- [Console]::Error.WriteLine("${CYAN}✓${RESET} New session started")
306
- }
307
- [Console]::Error.WriteLine("")
308
-
309
- exit 0
146
+ Write-Output "ekkOS session initialized"
@@ -78,7 +78,7 @@ fi
78
78
 
79
79
  [ -z "$AUTH_TOKEN" ] && exit 0
80
80
 
81
- MEMORY_API_URL="https://api.ekkos.dev"
81
+ MEMORY_API_URL="https://mcp.ekkos.dev"
82
82
 
83
83
  # ═══════════════════════════════════════════════════════════════════════════
84
84
  # TIME MACHINE: Check for pending "Continue from here" requests