@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.
- package/README.md +57 -0
- package/dist/commands/dashboard.js +561 -186
- package/dist/deploy/settings.js +13 -26
- package/package.json +1 -1
- package/templates/hooks/assistant-response.ps1 +94 -26
- package/templates/hooks/hooks.json +24 -12
- package/templates/hooks/lib/count-tokens.cjs +0 -0
- package/templates/hooks/lib/ekkos-reminders.sh +0 -0
- package/templates/hooks/session-start.ps1 +61 -224
- package/templates/hooks/session-start.sh +1 -1
- package/templates/hooks/stop.ps1 +103 -249
- package/templates/hooks/stop.sh +1 -1
- package/templates/hooks/user-prompt-submit.ps1 +129 -519
- package/templates/hooks/user-prompt-submit.sh +2 -2
- package/templates/plan-template.md +0 -0
- package/templates/spec-template.md +0 -0
- package/templates/windsurf-hooks/install.sh +0 -0
- package/templates/windsurf-hooks/lib/contract.sh +0 -0
- package/templates/windsurf-hooks/post-cascade-response.sh +0 -0
- package/templates/windsurf-hooks/pre-user-prompt.sh +0 -0
- package/templates/agents/README.md +0 -182
- package/templates/agents/code-reviewer.md +0 -166
- package/templates/agents/debug-detective.md +0 -169
- package/templates/agents/ekkOS_Vercel.md +0 -99
- package/templates/agents/extension-manager.md +0 -229
- package/templates/agents/git-companion.md +0 -185
- package/templates/agents/github-test-agent.md +0 -321
- package/templates/agents/railway-manager.md +0 -179
- package/templates/windsurf-hooks/before-submit-prompt.sh +0 -238
- package/templates/windsurf-skills/ekkos-memory/SKILL.md +0 -219
package/templates/hooks/stop.ps1
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
-
# ekkOS_ Hook: Stop -
|
|
3
|
-
# NO jq dependency - uses Node.js for all JSON parsing
|
|
2
|
+
# ekkOS_ Hook: Stop - Session cleanup and capture finalization (Windows)
|
|
4
3
|
# MANAGED BY ekkos-connect - DO NOT EDIT DIRECTLY
|
|
5
4
|
# EKKOS_MANAGED=1
|
|
6
5
|
# EKKOS_MANIFEST_SHA256=<computed-at-build>
|
|
@@ -10,32 +9,15 @@
|
|
|
10
9
|
# - All persisted records MUST include: instanceId, sessionId, sessionName
|
|
11
10
|
# - Uses EKKOS_INSTANCE_ID env var for multi-session isolation
|
|
12
11
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
13
|
-
# This hook captures every turn to:
|
|
14
|
-
# 1. Working Sessions (Redis) - Fast hot cache for /continue
|
|
15
|
-
# 2. Episodic Memory (Supabase) - Permanent cold storage
|
|
16
|
-
#
|
|
17
|
-
# NO compliance checking - skills handle that
|
|
18
|
-
# NO PatternGuard validation - skills handle that
|
|
19
|
-
# NO verbose output - just capture silently
|
|
20
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
21
12
|
|
|
22
13
|
$ErrorActionPreference = "SilentlyContinue"
|
|
23
14
|
|
|
24
15
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
25
16
|
# CONFIG PATHS
|
|
26
17
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
27
|
-
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
28
|
-
$ProjectRoot = Split-Path -Parent (Split-Path -Parent $ScriptDir)
|
|
29
|
-
$StateDir = Join-Path $ProjectRoot ".claude\state"
|
|
30
|
-
|
|
31
|
-
if (-not (Test-Path $StateDir)) {
|
|
32
|
-
New-Item -ItemType Directory -Path $StateDir -Force | Out-Null
|
|
33
|
-
}
|
|
34
|
-
|
|
35
18
|
$EkkosConfigDir = if ($env:EKKOS_CONFIG_DIR) { $env:EKKOS_CONFIG_DIR } else { "$env:USERPROFILE\.ekkos" }
|
|
36
19
|
$SessionWordsJson = "$EkkosConfigDir\session-words.json"
|
|
37
20
|
$SessionWordsDefault = "$EkkosConfigDir\.defaults\session-words.json"
|
|
38
|
-
$JsonParseHelper = "$EkkosConfigDir\.helpers\json-parse.cjs"
|
|
39
21
|
|
|
40
22
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
41
23
|
# INSTANCE ID - Multi-session isolation per v1.2 ADDENDUM
|
|
@@ -105,131 +87,65 @@ function Convert-UuidToWords {
|
|
|
105
87
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
106
88
|
$inputJson = [Console]::In.ReadToEnd()
|
|
107
89
|
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
90
|
+
# Get session ID from state
|
|
91
|
+
$stateFile = Join-Path $env:USERPROFILE ".claude\state\hook-state.json"
|
|
92
|
+
$sessionFile = Join-Path $env:USERPROFILE ".claude\state\current-session.json"
|
|
111
93
|
$rawSessionId = "unknown"
|
|
112
|
-
$
|
|
113
|
-
$modelUsed = "claude-sonnet-4-5"
|
|
94
|
+
$turn = 0
|
|
114
95
|
|
|
115
|
-
if ($
|
|
96
|
+
if (Test-Path $stateFile) {
|
|
116
97
|
try {
|
|
117
|
-
$
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if ($stopInput.model) { $modelUsed = $stopInput.model }
|
|
98
|
+
$hookState = Get-Content $stateFile -Raw | ConvertFrom-Json
|
|
99
|
+
$rawSessionId = $hookState.session_id
|
|
100
|
+
$turn = [int]$hookState.turn
|
|
121
101
|
} catch {}
|
|
122
102
|
}
|
|
123
103
|
|
|
124
|
-
|
|
125
|
-
# Session ID validation
|
|
126
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
127
|
-
if (-not $rawSessionId -or $rawSessionId -eq "unknown" -or $rawSessionId -eq "null") {
|
|
128
|
-
exit 0
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
$isUuid = $rawSessionId -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
|
|
132
|
-
|
|
133
|
-
if ($isUuid) {
|
|
134
|
-
$sessionName = Convert-UuidToWords $rawSessionId
|
|
135
|
-
} else {
|
|
136
|
-
$sessionName = $rawSessionId
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
140
|
-
# Turn Number - read from turn file written by user-prompt-submit.ps1
|
|
141
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
142
|
-
$sessionsDir = Join-Path $StateDir "sessions"
|
|
143
|
-
if (-not (Test-Path $sessionsDir)) {
|
|
144
|
-
New-Item -ItemType Directory -Path $sessionsDir -Force | Out-Null
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
$turnFile = Join-Path $sessionsDir "$rawSessionId.turn"
|
|
148
|
-
$turn = 1
|
|
149
|
-
if (Test-Path $turnFile) {
|
|
150
|
-
try {
|
|
151
|
-
$turnContent = Get-Content $turnFile -Raw
|
|
152
|
-
$turn = [int]$turnContent.Trim()
|
|
153
|
-
} catch {
|
|
154
|
-
$turn = 1
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
159
|
-
# Load auth - No jq
|
|
160
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
161
|
-
$configFile = Join-Path $EkkosConfigDir "config.json"
|
|
162
|
-
$authToken = ""
|
|
163
|
-
$userId = ""
|
|
164
|
-
|
|
165
|
-
if ((Test-Path $configFile) -and (Test-Path $JsonParseHelper)) {
|
|
166
|
-
try {
|
|
167
|
-
$authToken = & node $JsonParseHelper $configFile '.hookApiKey' 2>$null
|
|
168
|
-
if (-not $authToken) {
|
|
169
|
-
$authToken = & node $JsonParseHelper $configFile '.apiKey' 2>$null
|
|
170
|
-
}
|
|
171
|
-
$userId = & node $JsonParseHelper $configFile '.userId' 2>$null
|
|
172
|
-
} catch {}
|
|
173
|
-
} elseif (Test-Path $configFile) {
|
|
104
|
+
if ($rawSessionId -eq "unknown" -and (Test-Path $sessionFile)) {
|
|
174
105
|
try {
|
|
175
|
-
$
|
|
176
|
-
$
|
|
177
|
-
$userId = $config.userId
|
|
106
|
+
$sessionData = Get-Content $sessionFile -Raw | ConvertFrom-Json
|
|
107
|
+
$rawSessionId = $sessionData.session_id
|
|
178
108
|
} catch {}
|
|
179
109
|
}
|
|
180
110
|
|
|
181
|
-
|
|
182
|
-
$envLocal = Join-Path $ProjectRoot ".env.local"
|
|
183
|
-
if (Test-Path $envLocal) {
|
|
184
|
-
try {
|
|
185
|
-
$envContent = Get-Content $envLocal -Raw
|
|
186
|
-
if ($envContent -match 'SUPABASE_SECRET_KEY=["'']?([^"''\r\n]+)') {
|
|
187
|
-
$authToken = $Matches[1]
|
|
188
|
-
}
|
|
189
|
-
} catch {}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (-not $authToken) { exit 0 }
|
|
194
|
-
|
|
195
|
-
$memoryApiUrl = "https://api.ekkos.dev"
|
|
111
|
+
$sessionName = Convert-UuidToWords $rawSessionId
|
|
196
112
|
|
|
197
113
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
198
114
|
# SESSION BINDING: Bridge _pending → real session name for proxy eviction
|
|
199
115
|
# Windows has no PTY so run.ts can't detect the session name. The stop hook
|
|
200
116
|
# is the first place we have a confirmed session name, so we bind here.
|
|
117
|
+
# Mac does this in stop.sh (lines 171-179). Logic is identical.
|
|
201
118
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
202
|
-
|
|
119
|
+
$configFile = Join-Path $EkkosConfigDir "config.json"
|
|
120
|
+
if ((Test-Path $configFile) -and $sessionName -ne "unknown-session") {
|
|
203
121
|
try {
|
|
204
|
-
$
|
|
205
|
-
$
|
|
206
|
-
|
|
207
|
-
$bindBody = @{
|
|
208
|
-
userId = $userId
|
|
209
|
-
realSession = $sessionName
|
|
210
|
-
projectPath = $projectPath
|
|
211
|
-
pendingSession = $pendingSession
|
|
212
|
-
} | ConvertTo-Json -Depth 10 -Compress
|
|
122
|
+
$config = Get-Content $configFile -Raw | ConvertFrom-Json
|
|
123
|
+
$userId = $config.userId
|
|
124
|
+
$authToken = if ($config.hookApiKey) { $config.hookApiKey } else { $config.apiKey }
|
|
213
125
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
126
|
+
if ($userId -and $authToken) {
|
|
127
|
+
$projectPath = (Get-Location).Path
|
|
128
|
+
$pendingSession = if ($env:EKKOS_PENDING_SESSION) { $env:EKKOS_PENDING_SESSION } else { "_pending" }
|
|
129
|
+
|
|
130
|
+
$projectPath = $projectPath -replace '\\', '/'
|
|
131
|
+
$bindBody = @{
|
|
132
|
+
userId = $userId
|
|
133
|
+
realSession = $sessionName
|
|
134
|
+
projectPath = $projectPath
|
|
135
|
+
pendingSession = $pendingSession
|
|
136
|
+
} | ConvertTo-Json -Depth 10
|
|
137
|
+
|
|
138
|
+
Start-Job -ScriptBlock {
|
|
139
|
+
param($body, $token)
|
|
140
|
+
Invoke-RestMethod -Uri "https://mcp.ekkos.dev/proxy/session/bind" `
|
|
218
141
|
-Method POST `
|
|
219
142
|
-Headers @{ "Content-Type" = "application/json" } `
|
|
220
|
-
-Body ([System.Text.Encoding]::UTF8.GetBytes($body))
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
} catch {}
|
|
224
|
-
} -ArgumentList $bindBody, $memoryApiUrl | Out-Null
|
|
143
|
+
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) -ErrorAction SilentlyContinue | Out-Null
|
|
144
|
+
} -ArgumentList $bindBody, $authToken | Out-Null
|
|
145
|
+
}
|
|
225
146
|
} catch {}
|
|
226
147
|
}
|
|
227
148
|
|
|
228
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
229
|
-
# EVICTION: Handled by IPC (In-Place Progressive Compression) in the proxy.
|
|
230
|
-
# No hook-side eviction needed - passthrough is default for cache stability.
|
|
231
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
232
|
-
|
|
233
149
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
234
150
|
# LOCAL CACHE: ACK turn to mark as synced
|
|
235
151
|
# Per v1.2 ADDENDUM: Pass instanceId for namespacing
|
|
@@ -253,13 +169,15 @@ $isInterrupted = $false
|
|
|
253
169
|
$stopReason = ""
|
|
254
170
|
if ($inputJson) {
|
|
255
171
|
try {
|
|
256
|
-
$
|
|
257
|
-
$isInterrupted = $
|
|
258
|
-
$stopReason = $
|
|
172
|
+
$stopInput = $inputJson | ConvertFrom-Json
|
|
173
|
+
$isInterrupted = $stopInput.interrupted -eq $true
|
|
174
|
+
$stopReason = $stopInput.stop_reason
|
|
259
175
|
} catch {}
|
|
260
176
|
}
|
|
261
177
|
|
|
262
178
|
if ($isInterrupted -or $stopReason -eq "user_cancelled" -or $stopReason -eq "interrupted") {
|
|
179
|
+
if (Test-Path $stateFile) { Remove-Item $stateFile -Force }
|
|
180
|
+
Write-Output "ekkOS session ended (interrupted)"
|
|
263
181
|
exit 0
|
|
264
182
|
}
|
|
265
183
|
|
|
@@ -267,6 +185,14 @@ if ($isInterrupted -or $stopReason -eq "user_cancelled" -or $stopReason -eq "int
|
|
|
267
185
|
# EXTRACT CONVERSATION FROM TRANSCRIPT
|
|
268
186
|
# Mirrors stop.sh: Extract last user query, assistant response, file changes
|
|
269
187
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
188
|
+
$transcriptPath = ""
|
|
189
|
+
if ($inputJson) {
|
|
190
|
+
try {
|
|
191
|
+
$stopInput2 = $inputJson | ConvertFrom-Json
|
|
192
|
+
$transcriptPath = $stopInput2.transcript_path
|
|
193
|
+
} catch {}
|
|
194
|
+
}
|
|
195
|
+
|
|
270
196
|
$lastUser = ""
|
|
271
197
|
$lastAssistant = ""
|
|
272
198
|
$fileChangesJson = "[]"
|
|
@@ -302,7 +228,6 @@ for (let i = entries.length - 1; i >= 0; i--) {
|
|
|
302
228
|
const parts = content.map(c => {
|
|
303
229
|
if (c.type === 'text') return c.text;
|
|
304
230
|
if (c.type === 'tool_use') return '[TOOL: ' + c.name + ']';
|
|
305
|
-
if (c.type === 'thinking') return '[THINKING]' + (c.thinking || c.text || '') + '[/THINKING]';
|
|
306
231
|
return '';
|
|
307
232
|
}).filter(Boolean);
|
|
308
233
|
lastAssistant = parts.join('\n'); break;
|
|
@@ -332,49 +257,7 @@ console.log(JSON.stringify({
|
|
|
332
257
|
$lastUser = $parsed.user
|
|
333
258
|
$lastAssistant = $parsed.assistant
|
|
334
259
|
$fileChangesJson = ($parsed.fileChanges | ConvertTo-Json -Depth 10 -Compress)
|
|
335
|
-
if (-not $fileChangesJson
|
|
336
|
-
}
|
|
337
|
-
} catch {}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (-not $lastUser -or $lastUser -match '\[Request interrupted') {
|
|
341
|
-
exit 0
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
345
|
-
# Extract tools_used and files_referenced from file changes
|
|
346
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
347
|
-
$toolsUsed = @()
|
|
348
|
-
$filesReferenced = @()
|
|
349
|
-
|
|
350
|
-
if ($parsed -and $parsed.fileChanges) {
|
|
351
|
-
try {
|
|
352
|
-
$toolsUsed = @($parsed.fileChanges | ForEach-Object { $_.tool } | Where-Object { $_ } | Select-Object -Unique)
|
|
353
|
-
$filesReferenced = @($parsed.fileChanges | ForEach-Object { $_.path } | Where-Object { $_ } | Select-Object -Unique)
|
|
354
|
-
} catch {
|
|
355
|
-
$toolsUsed = @()
|
|
356
|
-
$filesReferenced = @()
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
361
|
-
# Token breakdown from tokenizer script
|
|
362
|
-
# ═══════════════════════════════════════════════════════════════════════════
|
|
363
|
-
$totalTokens = 0
|
|
364
|
-
$inputTokens = 0
|
|
365
|
-
$cacheReadTokens = 0
|
|
366
|
-
$cacheCreationTokens = 0
|
|
367
|
-
|
|
368
|
-
$tokenizerScript = Join-Path $ScriptDir "lib\count-tokens.cjs"
|
|
369
|
-
if ($transcriptPath -and (Test-Path $transcriptPath) -and (Test-Path $tokenizerScript)) {
|
|
370
|
-
try {
|
|
371
|
-
$tokenJson = & node $tokenizerScript $transcriptPath --json 2>$null
|
|
372
|
-
if ($tokenJson) {
|
|
373
|
-
$tokenData = $tokenJson | ConvertFrom-Json
|
|
374
|
-
if ($tokenData.total_tokens) { $totalTokens = [int]$tokenData.total_tokens }
|
|
375
|
-
if ($tokenData.input_tokens) { $inputTokens = [int]$tokenData.input_tokens }
|
|
376
|
-
if ($tokenData.cache_read_tokens) { $cacheReadTokens = [int]$tokenData.cache_read_tokens }
|
|
377
|
-
if ($tokenData.cache_creation_tokens) { $cacheCreationTokens = [int]$tokenData.cache_creation_tokens }
|
|
260
|
+
if (-not $fileChangesJson) { $fileChangesJson = "[]" }
|
|
378
261
|
}
|
|
379
262
|
} catch {}
|
|
380
263
|
}
|
|
@@ -384,98 +267,69 @@ if ($transcriptPath -and (Test-Path $transcriptPath) -and (Test-Path $tokenizerS
|
|
|
384
267
|
# Mirrors stop.sh dual-write at lines 271-356
|
|
385
268
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
386
269
|
if ($lastUser -and $lastAssistant -and $authToken) {
|
|
270
|
+
$modelUsed = "claude-sonnet-4-5"
|
|
271
|
+
if ($inputJson) {
|
|
272
|
+
try {
|
|
273
|
+
$stopInput3 = $inputJson | ConvertFrom-Json
|
|
274
|
+
if ($stopInput3.model) { $modelUsed = $stopInput3.model }
|
|
275
|
+
} catch {}
|
|
276
|
+
}
|
|
277
|
+
|
|
387
278
|
$timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
388
279
|
$projectPath = ((Get-Location).Path) -replace '\\', '/'
|
|
389
280
|
|
|
390
|
-
# Prepare tools_used and files_referenced as JSON strings for passing into jobs
|
|
391
|
-
$toolsUsedJson = ($toolsUsed | ConvertTo-Json -Depth 5 -Compress)
|
|
392
|
-
if (-not $toolsUsedJson -or $toolsUsedJson -eq "null") { $toolsUsedJson = "[]" }
|
|
393
|
-
# Single-element arrays lose their array wrapper in PowerShell ConvertTo-Json
|
|
394
|
-
if ($toolsUsed.Count -eq 1) { $toolsUsedJson = "[$toolsUsedJson]" }
|
|
395
|
-
|
|
396
|
-
$filesRefJson = ($filesReferenced | ConvertTo-Json -Depth 5 -Compress)
|
|
397
|
-
if (-not $filesRefJson -or $filesRefJson -eq "null") { $filesRefJson = "[]" }
|
|
398
|
-
if ($filesReferenced.Count -eq 1) { $filesRefJson = "[$filesRefJson]" }
|
|
399
|
-
|
|
400
281
|
# 1. WORKING SESSIONS (Redis)
|
|
401
282
|
Start-Job -ScriptBlock {
|
|
402
|
-
param($token, $
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
cache_read_tokens = [int]$cacheReadTok
|
|
420
|
-
cache_creation_tokens = [int]$cacheCreateTok
|
|
421
|
-
}
|
|
422
|
-
} | ConvertTo-Json -Depth 10 -Compress
|
|
423
|
-
|
|
424
|
-
Invoke-RestMethod -Uri "$apiUrl/api/v1/working/turn" `
|
|
425
|
-
-Method POST `
|
|
426
|
-
-Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
|
|
427
|
-
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) `
|
|
428
|
-
-TimeoutSec 5 `
|
|
429
|
-
-ErrorAction SilentlyContinue | Out-Null
|
|
430
|
-
} catch {}
|
|
431
|
-
} -ArgumentList $authToken, $memoryApiUrl, $sessionName, $turn, $lastUser, $lastAssistant, $modelUsed, $toolsUsedJson, $filesRefJson, $totalTokens, $inputTokens, $cacheReadTokens, $cacheCreationTokens | Out-Null
|
|
283
|
+
param($token, $sessionName, $turnNum, $userQuery, $agentResponse, $model)
|
|
284
|
+
$body = @{
|
|
285
|
+
session_name = $sessionName
|
|
286
|
+
turn_number = $turnNum
|
|
287
|
+
user_query = $userQuery
|
|
288
|
+
agent_response = $agentResponse.Substring(0, [Math]::Min(50000, $agentResponse.Length))
|
|
289
|
+
model = $model
|
|
290
|
+
tools_used = @()
|
|
291
|
+
files_referenced = @()
|
|
292
|
+
} | ConvertTo-Json -Depth 10
|
|
293
|
+
|
|
294
|
+
Invoke-RestMethod -Uri "https://mcp.ekkos.dev/api/v1/working/turn" `
|
|
295
|
+
-Method POST `
|
|
296
|
+
-Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
|
|
297
|
+
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) `
|
|
298
|
+
-ErrorAction SilentlyContinue | Out-Null
|
|
299
|
+
} -ArgumentList $authToken, $sessionName, $turn, $lastUser, $lastAssistant, $modelUsed | Out-Null
|
|
432
300
|
|
|
433
301
|
# 2. EPISODIC MEMORY (Supabase)
|
|
434
302
|
Start-Job -ScriptBlock {
|
|
435
|
-
param($token, $
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
-Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
|
|
459
|
-
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) `
|
|
460
|
-
-TimeoutSec 5 `
|
|
461
|
-
-ErrorAction SilentlyContinue | Out-Null
|
|
462
|
-
} catch {}
|
|
463
|
-
} -ArgumentList $authToken, $memoryApiUrl, $lastUser, $lastAssistant, $rawSessionId, $userId, $fileChangesJson, $modelUsed, $timestamp, $turn, $sessionName | Out-Null
|
|
303
|
+
param($token, $userQuery, $agentResponse, $sessionId, $userId, $fileChanges, $model, $ts, $turnNum, $sessionName)
|
|
304
|
+
$body = @{
|
|
305
|
+
user_query = $userQuery
|
|
306
|
+
assistant_response = $agentResponse
|
|
307
|
+
session_id = $sessionId
|
|
308
|
+
user_id = if ($userId) { $userId } else { "system" }
|
|
309
|
+
file_changes = @()
|
|
310
|
+
metadata = @{
|
|
311
|
+
source = "claude-code"
|
|
312
|
+
model_used = $model
|
|
313
|
+
captured_at = $ts
|
|
314
|
+
turn_number = $turnNum
|
|
315
|
+
session_name = $sessionName
|
|
316
|
+
minimal_hook = $true
|
|
317
|
+
}
|
|
318
|
+
} | ConvertTo-Json -Depth 10
|
|
319
|
+
|
|
320
|
+
Invoke-RestMethod -Uri "https://mcp.ekkos.dev/api/v1/memory/capture" `
|
|
321
|
+
-Method POST `
|
|
322
|
+
-Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
|
|
323
|
+
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) `
|
|
324
|
+
-ErrorAction SilentlyContinue | Out-Null
|
|
325
|
+
} -ArgumentList $authToken, $lastUser, $lastAssistant, $rawSessionId, $userId, $fileChangesJson, $modelUsed, $timestamp, $turn, $sessionName | Out-Null
|
|
464
326
|
}
|
|
465
327
|
|
|
466
328
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
467
|
-
#
|
|
329
|
+
# CLEAN UP STATE FILES
|
|
468
330
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
try {
|
|
472
|
-
$focusFile = Join-Path $ekkosDir "current-focus.md"
|
|
473
|
-
$taskSummary = $lastUser.Substring(0, [Math]::Min(100, $lastUser.Length))
|
|
474
|
-
if ($lastUser.Length -gt 100) { $taskSummary += "..." }
|
|
475
|
-
$focusTimestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
476
|
-
$focusContent = "---`nlast_updated: $focusTimestamp`nsession_id: $rawSessionId`n---`n`n# Current Focus`n$taskSummary"
|
|
477
|
-
Set-Content -Path $focusFile -Value $focusContent -Force
|
|
478
|
-
} catch {}
|
|
331
|
+
if (Test-Path $stateFile) {
|
|
332
|
+
Remove-Item $stateFile -Force
|
|
479
333
|
}
|
|
480
334
|
|
|
481
|
-
|
|
335
|
+
Write-Output "ekkOS session ended"
|
package/templates/hooks/stop.sh
CHANGED
|
@@ -161,7 +161,7 @@ fi
|
|
|
161
161
|
|
|
162
162
|
[ -z "$AUTH_TOKEN" ] && exit 0
|
|
163
163
|
|
|
164
|
-
MEMORY_API_URL="https://
|
|
164
|
+
MEMORY_API_URL="https://mcp.ekkos.dev"
|
|
165
165
|
|
|
166
166
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
167
167
|
# SESSION BINDING: Bridge _pending → real session name for proxy eviction
|