@ekkos/cli 1.0.30 → 1.0.31
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/package.json
CHANGED
|
@@ -207,7 +207,7 @@ if ($captureCmd -and $rawSessionId -ne "unknown") {
|
|
|
207
207
|
$toolMatches = [regex]::Matches($assistantResponse, '\[TOOL:\s*([^\]]+)\]')
|
|
208
208
|
if ($toolMatches.Count -gt 0) {
|
|
209
209
|
$tools = $toolMatches | ForEach-Object { $_.Groups[1].Value } | Select-Object -Unique
|
|
210
|
-
$toolsJson = $tools | ConvertTo-Json -Compress
|
|
210
|
+
$toolsJson = $tools | ConvertTo-Json -Depth 10 -Compress
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
Start-Job -ScriptBlock {
|
|
@@ -240,13 +240,13 @@ if (Test-Path $configFile) {
|
|
|
240
240
|
turn = $turnNum
|
|
241
241
|
response = $response.Substring(0, [Math]::Min(5000, $response.Length))
|
|
242
242
|
pattern_ids = $patterns
|
|
243
|
-
} | ConvertTo-Json
|
|
243
|
+
} | ConvertTo-Json -Depth 10
|
|
244
244
|
|
|
245
|
-
Invoke-RestMethod -Uri "https://
|
|
245
|
+
Invoke-RestMethod -Uri "https://mcp.ekkos.dev/api/v1/working/turn" `
|
|
246
246
|
-Method POST `
|
|
247
247
|
-Headers @{ Authorization = "Bearer $token" } `
|
|
248
248
|
-ContentType "application/json" `
|
|
249
|
-
-Body $body -ErrorAction SilentlyContinue
|
|
249
|
+
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) -ErrorAction SilentlyContinue
|
|
250
250
|
} -ArgumentList $captureToken, $EkkosInstanceId, $rawSessionId, $sessionName, $turn, $assistantResponse, $patternIds | Out-Null
|
|
251
251
|
}
|
|
252
252
|
} catch {}
|
|
@@ -112,7 +112,7 @@ $state = @{
|
|
|
112
112
|
session_name = $sessionName
|
|
113
113
|
instance_id = $EkkosInstanceId
|
|
114
114
|
started_at = (Get-Date).ToString("o")
|
|
115
|
-
} | ConvertTo-Json
|
|
115
|
+
} | ConvertTo-Json -Depth 10
|
|
116
116
|
|
|
117
117
|
Set-Content -Path $stateFile -Value $state -Force
|
|
118
118
|
|
|
@@ -122,7 +122,7 @@ $sessionData = @{
|
|
|
122
122
|
session_id = $sessionId
|
|
123
123
|
session_name = $sessionName
|
|
124
124
|
instance_id = $EkkosInstanceId
|
|
125
|
-
} | ConvertTo-Json
|
|
125
|
+
} | ConvertTo-Json -Depth 10
|
|
126
126
|
|
|
127
127
|
Set-Content -Path $sessionFile -Value $sessionData -Force
|
|
128
128
|
|
package/templates/hooks/stop.ps1
CHANGED
|
@@ -127,19 +127,20 @@ if ((Test-Path $configFile) -and $sessionName -ne "unknown-session") {
|
|
|
127
127
|
$projectPath = (Get-Location).Path
|
|
128
128
|
$pendingSession = if ($env:EKKOS_PENDING_SESSION) { $env:EKKOS_PENDING_SESSION } else { "_pending" }
|
|
129
129
|
|
|
130
|
+
$projectPath = $projectPath -replace '\\', '/'
|
|
130
131
|
$bindBody = @{
|
|
131
132
|
userId = $userId
|
|
132
133
|
realSession = $sessionName
|
|
133
134
|
projectPath = $projectPath
|
|
134
135
|
pendingSession = $pendingSession
|
|
135
|
-
} | ConvertTo-Json
|
|
136
|
+
} | ConvertTo-Json -Depth 10
|
|
136
137
|
|
|
137
138
|
Start-Job -ScriptBlock {
|
|
138
139
|
param($body, $token)
|
|
139
|
-
Invoke-RestMethod -Uri "https://
|
|
140
|
+
Invoke-RestMethod -Uri "https://mcp.ekkos.dev/proxy/session/bind" `
|
|
140
141
|
-Method POST `
|
|
141
142
|
-Headers @{ "Content-Type" = "application/json" } `
|
|
142
|
-
-Body $body -ErrorAction SilentlyContinue | Out-Null
|
|
143
|
+
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) -ErrorAction SilentlyContinue | Out-Null
|
|
143
144
|
} -ArgumentList $bindBody, $authToken | Out-Null
|
|
144
145
|
}
|
|
145
146
|
} catch {}
|
|
@@ -152,7 +153,6 @@ if ((Test-Path $configFile) -and $sessionName -ne "unknown-session") {
|
|
|
152
153
|
$captureCmd = Get-Command "ekkos-capture" -ErrorAction SilentlyContinue
|
|
153
154
|
if ($captureCmd -and $rawSessionId -ne "unknown") {
|
|
154
155
|
try {
|
|
155
|
-
# ACK format: ekkos-capture ack <session_id> <turn_id> --instance=ID
|
|
156
156
|
Start-Job -ScriptBlock {
|
|
157
157
|
param($instanceId, $sessionId, $turnNum)
|
|
158
158
|
try {
|
|
@@ -162,6 +162,169 @@ if ($captureCmd -and $rawSessionId -ne "unknown") {
|
|
|
162
162
|
} catch {}
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
166
|
+
# CHECK INTERRUPTION - Skip capture if user cancelled
|
|
167
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
168
|
+
$isInterrupted = $false
|
|
169
|
+
$stopReason = ""
|
|
170
|
+
if ($inputJson) {
|
|
171
|
+
try {
|
|
172
|
+
$stopInput = $inputJson | ConvertFrom-Json
|
|
173
|
+
$isInterrupted = $stopInput.interrupted -eq $true
|
|
174
|
+
$stopReason = $stopInput.stop_reason
|
|
175
|
+
} catch {}
|
|
176
|
+
}
|
|
177
|
+
|
|
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)"
|
|
181
|
+
exit 0
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
185
|
+
# EXTRACT CONVERSATION FROM TRANSCRIPT
|
|
186
|
+
# Mirrors stop.sh: Extract last user query, assistant response, file changes
|
|
187
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
188
|
+
$transcriptPath = ""
|
|
189
|
+
if ($inputJson) {
|
|
190
|
+
try {
|
|
191
|
+
$stopInput2 = $inputJson | ConvertFrom-Json
|
|
192
|
+
$transcriptPath = $stopInput2.transcript_path
|
|
193
|
+
} catch {}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
$lastUser = ""
|
|
197
|
+
$lastAssistant = ""
|
|
198
|
+
$fileChangesJson = "[]"
|
|
199
|
+
|
|
200
|
+
if ($transcriptPath -and (Test-Path $transcriptPath)) {
|
|
201
|
+
try {
|
|
202
|
+
$extraction = node -e @"
|
|
203
|
+
const fs = require('fs');
|
|
204
|
+
const lines = fs.readFileSync(process.argv[1], 'utf8').split('\n').filter(Boolean);
|
|
205
|
+
const entries = lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
206
|
+
|
|
207
|
+
let lastUser = '', lastUserTime = '';
|
|
208
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
209
|
+
const e = entries[i];
|
|
210
|
+
if (e.type === 'user') {
|
|
211
|
+
const content = e.message?.content;
|
|
212
|
+
if (typeof content === 'string' && !content.startsWith('<')) {
|
|
213
|
+
lastUser = content; lastUserTime = e.timestamp || ''; break;
|
|
214
|
+
} else if (Array.isArray(content)) {
|
|
215
|
+
const textPart = content.find(c => c.type === 'text' && !c.text?.startsWith('<'));
|
|
216
|
+
if (textPart) { lastUser = textPart.text; lastUserTime = e.timestamp || ''; break; }
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let lastAssistant = '';
|
|
222
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
223
|
+
const e = entries[i];
|
|
224
|
+
if (e.type === 'assistant' && (!lastUserTime || e.timestamp >= lastUserTime)) {
|
|
225
|
+
const content = e.message?.content;
|
|
226
|
+
if (typeof content === 'string') { lastAssistant = content; break; }
|
|
227
|
+
else if (Array.isArray(content)) {
|
|
228
|
+
const parts = content.map(c => {
|
|
229
|
+
if (c.type === 'text') return c.text;
|
|
230
|
+
if (c.type === 'tool_use') return '[TOOL: ' + c.name + ']';
|
|
231
|
+
return '';
|
|
232
|
+
}).filter(Boolean);
|
|
233
|
+
lastAssistant = parts.join('\n'); break;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const fileChanges = [];
|
|
239
|
+
entries.filter(e => e.type === 'assistant').forEach(e => {
|
|
240
|
+
const content = e.message?.content;
|
|
241
|
+
if (Array.isArray(content)) {
|
|
242
|
+
content.filter(c => c.type === 'tool_use' && ['Edit', 'Write', 'Read'].includes(c.name)).forEach(c => {
|
|
243
|
+
fileChanges.push({tool: c.name, path: (c.input?.file_path || c.input?.path || '').replace(/\\\\/g, '/'), action: c.name.toLowerCase()});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
console.log(JSON.stringify({
|
|
249
|
+
user: lastUser,
|
|
250
|
+
assistant: lastAssistant.substring(0, 50000),
|
|
251
|
+
fileChanges: fileChanges.slice(0, 20)
|
|
252
|
+
}));
|
|
253
|
+
"@ $transcriptPath 2>$null
|
|
254
|
+
|
|
255
|
+
if ($extraction) {
|
|
256
|
+
$parsed = $extraction | ConvertFrom-Json
|
|
257
|
+
$lastUser = $parsed.user
|
|
258
|
+
$lastAssistant = $parsed.assistant
|
|
259
|
+
$fileChangesJson = ($parsed.fileChanges | ConvertTo-Json -Depth 10 -Compress)
|
|
260
|
+
if (-not $fileChangesJson) { $fileChangesJson = "[]" }
|
|
261
|
+
}
|
|
262
|
+
} catch {}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
266
|
+
# CAPTURE TO BOTH Working Sessions (Redis) AND Episodic Memory (Supabase)
|
|
267
|
+
# Mirrors stop.sh dual-write at lines 271-356
|
|
268
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
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
|
+
|
|
278
|
+
$timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
279
|
+
$projectPath = ((Get-Location).Path) -replace '\\', '/'
|
|
280
|
+
|
|
281
|
+
# 1. WORKING SESSIONS (Redis)
|
|
282
|
+
Start-Job -ScriptBlock {
|
|
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
|
|
300
|
+
|
|
301
|
+
# 2. EPISODIC MEMORY (Supabase)
|
|
302
|
+
Start-Job -ScriptBlock {
|
|
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
|
|
326
|
+
}
|
|
327
|
+
|
|
165
328
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
166
329
|
# CLEAN UP STATE FILES
|
|
167
330
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -153,25 +153,50 @@ if ($sessionName -ne "unknown-session" -and $rawSessionId -ne "unknown") {
|
|
|
153
153
|
if ($userId) {
|
|
154
154
|
$projectPath = if ($env:PWD) { $env:PWD } else { (Get-Location).Path }
|
|
155
155
|
$pendingSession = if ($env:EKKOS_PENDING_SESSION) { $env:EKKOS_PENDING_SESSION } else { "_pending" }
|
|
156
|
+
$projectPath = $projectPath -replace '\\', '/'
|
|
156
157
|
$bindBody = @{
|
|
157
158
|
userId = $userId
|
|
158
159
|
realSession = $sessionName
|
|
159
160
|
projectPath = $projectPath
|
|
160
161
|
pendingSession = $pendingSession
|
|
161
|
-
} | ConvertTo-Json -Compress
|
|
162
|
+
} | ConvertTo-Json -Depth 10 -Compress
|
|
162
163
|
|
|
163
164
|
Start-Job -ScriptBlock {
|
|
164
165
|
param($body)
|
|
165
|
-
Invoke-RestMethod -Uri "https://
|
|
166
|
+
Invoke-RestMethod -Uri "https://mcp.ekkos.dev/proxy/session/bind" `
|
|
166
167
|
-Method POST `
|
|
167
168
|
-Headers @{ "Content-Type" = "application/json" } `
|
|
168
|
-
-Body $body -ErrorAction SilentlyContinue | Out-Null
|
|
169
|
+
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) -ErrorAction SilentlyContinue | Out-Null
|
|
169
170
|
} -ArgumentList $bindBody | Out-Null
|
|
170
171
|
}
|
|
171
172
|
} catch {}
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
177
|
+
# SESSION CURRENT: Update Redis with current session name
|
|
178
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
179
|
+
if ($sessionName -ne "unknown-session" -and $rawSessionId -ne "unknown") {
|
|
180
|
+
$configFile2 = Join-Path $EkkosConfigDir "config.json"
|
|
181
|
+
if (Test-Path $configFile2) {
|
|
182
|
+
try {
|
|
183
|
+
$config2 = Get-Content $configFile2 -Raw | ConvertFrom-Json
|
|
184
|
+
$sessionToken = $config2.hookApiKey
|
|
185
|
+
if (-not $sessionToken) { $sessionToken = $config2.apiKey }
|
|
186
|
+
if ($sessionToken) {
|
|
187
|
+
$sessionBody = @{ session_name = $sessionName } | ConvertTo-Json -Depth 10
|
|
188
|
+
Start-Job -ScriptBlock {
|
|
189
|
+
param($body, $token)
|
|
190
|
+
Invoke-RestMethod -Uri "https://mcp.ekkos.dev/api/v1/working/session/current" `
|
|
191
|
+
-Method POST `
|
|
192
|
+
-Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
|
|
193
|
+
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) -ErrorAction SilentlyContinue | Out-Null
|
|
194
|
+
} -ArgumentList $sessionBody, $sessionToken | Out-Null
|
|
195
|
+
}
|
|
196
|
+
} catch {}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
175
200
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
176
201
|
# TURN TRACKING & STATE MANAGEMENT
|
|
177
202
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -205,7 +230,7 @@ $newState = @{
|
|
|
205
230
|
session_id = $rawSessionId
|
|
206
231
|
last_query = $userQuery.Substring(0, [Math]::Min(100, $userQuery.Length))
|
|
207
232
|
timestamp = (Get-Date).ToString("o")
|
|
208
|
-
} | ConvertTo-Json
|
|
233
|
+
} | ConvertTo-Json -Depth 10
|
|
209
234
|
|
|
210
235
|
Set-Content -Path $stateFile -Value $newState -Force
|
|
211
236
|
|
|
@@ -250,13 +275,13 @@ if (Test-Path $configFile) {
|
|
|
250
275
|
instance_id = $instanceId
|
|
251
276
|
turn = $turnNum
|
|
252
277
|
query = $query
|
|
253
|
-
} | ConvertTo-Json
|
|
278
|
+
} | ConvertTo-Json -Depth 10
|
|
254
279
|
|
|
255
|
-
Invoke-RestMethod -Uri "https://
|
|
280
|
+
Invoke-RestMethod -Uri "https://mcp.ekkos.dev/api/v1/working/fast-capture" `
|
|
256
281
|
-Method POST `
|
|
257
282
|
-Headers @{ Authorization = "Bearer $token" } `
|
|
258
283
|
-ContentType "application/json" `
|
|
259
|
-
-Body $body -ErrorAction SilentlyContinue
|
|
284
|
+
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) -ErrorAction SilentlyContinue
|
|
260
285
|
} -ArgumentList $captureToken, $EkkosInstanceId, $rawSessionId, $sessionName, $turn, $userQuery | Out-Null
|
|
261
286
|
}
|
|
262
287
|
} catch {}
|
|
@@ -278,7 +303,7 @@ if ($sessionName -ne "unknown-session") {
|
|
|
278
303
|
sessionId = $rawSessionId
|
|
279
304
|
projectPath = $projectPath
|
|
280
305
|
ts = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
|
281
|
-
} | ConvertTo-Json -Compress
|
|
306
|
+
} | ConvertTo-Json -Depth 10 -Compress
|
|
282
307
|
Set-Content -Path $hintFile -Value $hint -Force
|
|
283
308
|
} catch {}
|
|
284
309
|
}
|