@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekkos/cli",
3
- "version": "1.0.30",
3
+ "version": "1.0.31",
4
4
  "description": "Setup ekkOS memory for AI coding assistants (Claude Code, Cursor, Windsurf)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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://api.ekkos.dev/api/v1/working/turn" `
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
 
@@ -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://proxy.ekkos.dev/proxy/session/bind" `
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://proxy.ekkos.dev/proxy/session/bind" `
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://api.ekkos.dev/api/v1/working/fast-capture" `
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
  }