@ekkos/cli 1.2.18 → 1.3.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 (47) hide show
  1. package/dist/cache/capture.js +0 -0
  2. package/dist/commands/dashboard.js +57 -49
  3. package/dist/commands/hooks.d.ts +25 -36
  4. package/dist/commands/hooks.js +43 -615
  5. package/dist/commands/init.js +7 -23
  6. package/dist/commands/run.js +90 -3
  7. package/dist/commands/setup.js +10 -352
  8. package/dist/deploy/hooks.d.ts +8 -5
  9. package/dist/deploy/hooks.js +12 -105
  10. package/dist/deploy/settings.d.ts +8 -2
  11. package/dist/deploy/settings.js +22 -51
  12. package/dist/index.js +17 -39
  13. package/dist/utils/state.js +7 -2
  14. package/package.json +1 -1
  15. package/templates/CLAUDE.md +82 -292
  16. package/templates/cursor-rules/ekkos-memory.md +48 -108
  17. package/templates/windsurf-rules/ekkos-memory.md +62 -64
  18. package/templates/cursor-hooks/after-agent-response.sh +0 -117
  19. package/templates/cursor-hooks/before-submit-prompt.sh +0 -419
  20. package/templates/cursor-hooks/hooks.json +0 -20
  21. package/templates/cursor-hooks/lib/contract.sh +0 -320
  22. package/templates/cursor-hooks/stop.sh +0 -75
  23. package/templates/hooks/assistant-response.ps1 +0 -256
  24. package/templates/hooks/assistant-response.sh +0 -160
  25. package/templates/hooks/hooks.json +0 -40
  26. package/templates/hooks/lib/contract.sh +0 -332
  27. package/templates/hooks/lib/count-tokens.cjs +0 -86
  28. package/templates/hooks/lib/ekkos-reminders.sh +0 -98
  29. package/templates/hooks/lib/state.sh +0 -210
  30. package/templates/hooks/session-start.ps1 +0 -146
  31. package/templates/hooks/session-start.sh +0 -353
  32. package/templates/hooks/stop.ps1 +0 -349
  33. package/templates/hooks/stop.sh +0 -382
  34. package/templates/hooks/user-prompt-submit.ps1 +0 -419
  35. package/templates/hooks/user-prompt-submit.sh +0 -516
  36. package/templates/project-stubs/session-start.ps1 +0 -63
  37. package/templates/project-stubs/session-start.sh +0 -55
  38. package/templates/project-stubs/stop.ps1 +0 -63
  39. package/templates/project-stubs/stop.sh +0 -55
  40. package/templates/project-stubs/user-prompt-submit.ps1 +0 -63
  41. package/templates/project-stubs/user-prompt-submit.sh +0 -55
  42. package/templates/windsurf-hooks/README.md +0 -212
  43. package/templates/windsurf-hooks/hooks.json +0 -17
  44. package/templates/windsurf-hooks/install.sh +0 -148
  45. package/templates/windsurf-hooks/lib/contract.sh +0 -322
  46. package/templates/windsurf-hooks/post-cascade-response.sh +0 -251
  47. package/templates/windsurf-hooks/pre-user-prompt.sh +0 -435
@@ -1,349 +0,0 @@
1
- # ═══════════════════════════════════════════════════════════════════════════
2
- # ekkOS_ Hook: Stop - Session cleanup and capture finalization (Windows)
3
- # MANAGED BY ekkos-connect - DO NOT EDIT DIRECTLY
4
- # EKKOS_MANAGED=1
5
- # EKKOS_MANIFEST_SHA256=<computed-at-build>
6
- # EKKOS_TEMPLATE_VERSION=1.0.0
7
- #
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
11
- # ═══════════════════════════════════════════════════════════════════════════
12
-
13
- $ErrorActionPreference = "SilentlyContinue"
14
-
15
- # ═══════════════════════════════════════════════════════════════════════════
16
- # PROXY MODE DETECTION
17
- # ═══════════════════════════════════════════════════════════════════════════
18
- $ProxyMode = $false
19
- if ($env:ANTHROPIC_BASE_URL -and $env:ANTHROPIC_BASE_URL -like "*proxy.ekkos.dev*") {
20
- $ProxyMode = $true
21
- }
22
-
23
- # ═══════════════════════════════════════════════════════════════════════════
24
- # CONFIG PATHS
25
- # ═══════════════════════════════════════════════════════════════════════════
26
- $EkkosConfigDir = if ($env:EKKOS_CONFIG_DIR) { $env:EKKOS_CONFIG_DIR } else { "$env:USERPROFILE\.ekkos" }
27
- $SessionWordsJson = "$EkkosConfigDir\session-words.json"
28
- $SessionWordsDefault = "$EkkosConfigDir\.defaults\session-words.json"
29
-
30
- # ═══════════════════════════════════════════════════════════════════════════
31
- # INSTANCE ID - Multi-session isolation per v1.2 ADDENDUM
32
- # ═══════════════════════════════════════════════════════════════════════════
33
- $EkkosInstanceId = if ($env:EKKOS_INSTANCE_ID) { $env:EKKOS_INSTANCE_ID } else { "default" }
34
-
35
- # ═══════════════════════════════════════════════════════════════════════════
36
- # Load session words from JSON file - NO HARDCODED ARRAYS
37
- # ═══════════════════════════════════════════════════════════════════════════
38
- $script:SessionWords = $null
39
-
40
- function Load-SessionWords {
41
- $wordsFile = $SessionWordsJson
42
-
43
- if (-not (Test-Path $wordsFile)) {
44
- $wordsFile = $SessionWordsDefault
45
- }
46
-
47
- if (-not (Test-Path $wordsFile)) {
48
- return $null
49
- }
50
-
51
- try {
52
- $script:SessionWords = Get-Content $wordsFile -Raw | ConvertFrom-Json
53
- } catch {
54
- return $null
55
- }
56
- }
57
-
58
- function Convert-UuidToWords {
59
- param([string]$uuid)
60
-
61
- if (-not $script:SessionWords) {
62
- Load-SessionWords
63
- }
64
-
65
- if (-not $script:SessionWords) {
66
- return "unknown-session"
67
- }
68
-
69
- $adjectives = $script:SessionWords.adjectives
70
- $nouns = $script:SessionWords.nouns
71
- $verbs = $script:SessionWords.verbs
72
-
73
- if (-not $adjectives -or -not $nouns -or -not $verbs) {
74
- return "unknown-session"
75
- }
76
-
77
- if (-not $uuid -or $uuid -eq "unknown") { return "unknown-session" }
78
-
79
- $clean = $uuid -replace "-", ""
80
- if ($clean.Length -lt 12) { return "unknown-session" }
81
-
82
- try {
83
- $a = [Convert]::ToInt32($clean.Substring(0,4), 16) % $adjectives.Length
84
- $n = [Convert]::ToInt32($clean.Substring(4,4), 16) % $nouns.Length
85
- $an = [Convert]::ToInt32($clean.Substring(8,4), 16) % $verbs.Length
86
-
87
- return "$($adjectives[$a])-$($nouns[$n])-$($verbs[$an])"
88
- } catch {
89
- return "unknown-session"
90
- }
91
- }
92
-
93
- # ═══════════════════════════════════════════════════════════════════════════
94
- # READ INPUT
95
- # ═══════════════════════════════════════════════════════════════════════════
96
- $inputJson = [Console]::In.ReadToEnd()
97
-
98
- if ($ProxyMode) {
99
- $projectState = Join-Path (Join-Path ((Get-Location).Path) ".claude\state") "hook-state.json"
100
- $globalState = Join-Path $env:USERPROFILE ".claude\state\hook-state.json"
101
- if (Test-Path $projectState) { Remove-Item $projectState -Force }
102
- if (Test-Path $globalState) { Remove-Item $globalState -Force }
103
- exit 0
104
- }
105
-
106
- # Get session ID from state
107
- $stateFile = Join-Path $env:USERPROFILE ".claude\state\hook-state.json"
108
- $sessionFile = Join-Path $env:USERPROFILE ".claude\state\current-session.json"
109
- $rawSessionId = "unknown"
110
- $turn = 0
111
-
112
- if (Test-Path $stateFile) {
113
- try {
114
- $hookState = Get-Content $stateFile -Raw | ConvertFrom-Json
115
- $rawSessionId = $hookState.session_id
116
- $turn = [int]$hookState.turn
117
- } catch {}
118
- }
119
-
120
- if ($rawSessionId -eq "unknown" -and (Test-Path $sessionFile)) {
121
- try {
122
- $sessionData = Get-Content $sessionFile -Raw | ConvertFrom-Json
123
- $rawSessionId = $sessionData.session_id
124
- } catch {}
125
- }
126
-
127
- $sessionName = Convert-UuidToWords $rawSessionId
128
-
129
- function Get-PendingSessionFromProxyBaseUrl {
130
- param([string]$baseUrl)
131
- if (-not $baseUrl) { return $null }
132
- try {
133
- $uri = [System.Uri]$baseUrl
134
- $segments = $uri.AbsolutePath.Trim('/') -split '/'
135
- if (-not $segments -or $segments.Count -lt 3) { return $null }
136
- $proxyIndex = [Array]::IndexOf($segments, 'proxy')
137
- if ($proxyIndex -lt 0 -or $segments.Count -le ($proxyIndex + 2)) { return $null }
138
- $candidate = [System.Uri]::UnescapeDataString($segments[$proxyIndex + 2])
139
- if ($candidate -eq '_pending' -or $candidate -eq 'pending' -or $candidate.StartsWith('_pending-')) {
140
- return $candidate
141
- }
142
- } catch {}
143
- return $null
144
- }
145
- # Session binding removed — proxy now self-resolves _pending → word-based
146
- # names inline via uuidToSessionName(). No external bind call needed.
147
-
148
- # ═══════════════════════════════════════════════════════════════════════════
149
- # AUTH LOADING (direct mode only)
150
- # ═══════════════════════════════════════════════════════════════════════════
151
- $authToken = $null
152
- $userId = $null
153
- $configFile = Join-Path $EkkosConfigDir "config.json"
154
- if (Test-Path $configFile) {
155
- try {
156
- $cfg = Get-Content $configFile -Raw | ConvertFrom-Json
157
- $authToken = $cfg.hookApiKey
158
- if (-not $authToken) { $authToken = $cfg.apiKey }
159
- $userId = $cfg.userId
160
- } catch {}
161
- }
162
-
163
- # ═══════════════════════════════════════════════════════════════════════════
164
- # LOCAL CACHE: ACK turn to mark as synced
165
- # Per v1.2 ADDENDUM: Pass instanceId for namespacing
166
- # ═══════════════════════════════════════════════════════════════════════════
167
- $captureCmd = Get-Command "ekkos-capture" -ErrorAction SilentlyContinue
168
- if ($captureCmd -and $rawSessionId -ne "unknown") {
169
- try {
170
- Start-Job -ScriptBlock {
171
- param($instanceId, $sessionId, $turnNum)
172
- try {
173
- & ekkos-capture ack $sessionId $turnNum "--instance=$instanceId" 2>&1 | Out-Null
174
- } catch {}
175
- } -ArgumentList $EkkosInstanceId, $rawSessionId, $turn | Out-Null
176
- } catch {}
177
- }
178
-
179
- # ═══════════════════════════════════════════════════════════════════════════
180
- # CHECK INTERRUPTION - Skip capture if user cancelled
181
- # ═══════════════════════════════════════════════════════════════════════════
182
- $isInterrupted = $false
183
- $stopReason = ""
184
- if ($inputJson) {
185
- try {
186
- $stopInput = $inputJson | ConvertFrom-Json
187
- $isInterrupted = $stopInput.interrupted -eq $true
188
- $stopReason = $stopInput.stop_reason
189
- } catch {}
190
- }
191
-
192
- if ($isInterrupted -or $stopReason -eq "user_cancelled" -or $stopReason -eq "interrupted") {
193
- if (Test-Path $stateFile) { Remove-Item $stateFile -Force }
194
- Write-Output "ekkOS session ended (interrupted)"
195
- exit 0
196
- }
197
-
198
- # ═══════════════════════════════════════════════════════════════════════════
199
- # EXTRACT CONVERSATION FROM TRANSCRIPT
200
- # Mirrors stop.sh: Extract last user query, assistant response, file changes
201
- # ═══════════════════════════════════════════════════════════════════════════
202
- $transcriptPath = ""
203
- if ($inputJson) {
204
- try {
205
- $stopInput2 = $inputJson | ConvertFrom-Json
206
- $transcriptPath = $stopInput2.transcript_path
207
- } catch {}
208
- }
209
-
210
- $lastUser = ""
211
- $lastAssistant = ""
212
- $fileChangesJson = "[]"
213
-
214
- if ($transcriptPath -and (Test-Path $transcriptPath)) {
215
- try {
216
- $extraction = node -e @"
217
- const fs = require('fs');
218
- const lines = fs.readFileSync(process.argv[1], 'utf8').split('\n').filter(Boolean);
219
- const entries = lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
220
-
221
- let lastUser = '', lastUserTime = '';
222
- for (let i = entries.length - 1; i >= 0; i--) {
223
- const e = entries[i];
224
- if (e.type === 'user') {
225
- const content = e.message?.content;
226
- if (typeof content === 'string' && !content.startsWith('<')) {
227
- lastUser = content; lastUserTime = e.timestamp || ''; break;
228
- } else if (Array.isArray(content)) {
229
- const textPart = content.find(c => c.type === 'text' && !c.text?.startsWith('<'));
230
- if (textPart) { lastUser = textPart.text; lastUserTime = e.timestamp || ''; break; }
231
- }
232
- }
233
- }
234
-
235
- let lastAssistant = '';
236
- for (let i = entries.length - 1; i >= 0; i--) {
237
- const e = entries[i];
238
- if (e.type === 'assistant' && (!lastUserTime || e.timestamp >= lastUserTime)) {
239
- const content = e.message?.content;
240
- if (typeof content === 'string') { lastAssistant = content; break; }
241
- else if (Array.isArray(content)) {
242
- const parts = content.map(c => {
243
- if (c.type === 'text') return c.text;
244
- if (c.type === 'tool_use') return '[TOOL: ' + c.name + ']';
245
- return '';
246
- }).filter(Boolean);
247
- lastAssistant = parts.join('\n'); break;
248
- }
249
- }
250
- }
251
-
252
- const fileChanges = [];
253
- entries.filter(e => e.type === 'assistant').forEach(e => {
254
- const content = e.message?.content;
255
- if (Array.isArray(content)) {
256
- content.filter(c => c.type === 'tool_use' && ['Edit', 'Write', 'Read'].includes(c.name)).forEach(c => {
257
- fileChanges.push({tool: c.name, path: (c.input?.file_path || c.input?.path || '').replace(/\\\\/g, '/'), action: c.name.toLowerCase()});
258
- });
259
- }
260
- });
261
-
262
- console.log(JSON.stringify({
263
- user: lastUser,
264
- assistant: lastAssistant.substring(0, 50000),
265
- fileChanges: fileChanges.slice(0, 20)
266
- }));
267
- "@ $transcriptPath 2>$null
268
-
269
- if ($extraction) {
270
- $parsed = $extraction | ConvertFrom-Json
271
- $lastUser = $parsed.user
272
- $lastAssistant = $parsed.assistant
273
- $fileChangesJson = ($parsed.fileChanges | ConvertTo-Json -Depth 10 -Compress)
274
- if (-not $fileChangesJson) { $fileChangesJson = "[]" }
275
- }
276
- } catch {}
277
- }
278
-
279
- # ═══════════════════════════════════════════════════════════════════════════
280
- # CAPTURE TO BOTH Working Sessions (Redis) AND Episodic Memory (Supabase)
281
- # Mirrors stop.sh dual-write at lines 271-356
282
- # ═══════════════════════════════════════════════════════════════════════════
283
- if ($lastUser -and $lastAssistant -and $authToken) {
284
- $modelUsed = "claude-sonnet-4-5"
285
- if ($inputJson) {
286
- try {
287
- $stopInput3 = $inputJson | ConvertFrom-Json
288
- if ($stopInput3.model) { $modelUsed = $stopInput3.model }
289
- } catch {}
290
- }
291
-
292
- $timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
293
- $projectPath = ((Get-Location).Path) -replace '\\', '/'
294
-
295
- # 1. WORKING SESSIONS (Redis)
296
- Start-Job -ScriptBlock {
297
- param($token, $sessionName, $turnNum, $userQuery, $agentResponse, $model)
298
- $body = @{
299
- session_name = $sessionName
300
- turn_number = $turnNum
301
- user_query = $userQuery
302
- agent_response = $agentResponse.Substring(0, [Math]::Min(50000, $agentResponse.Length))
303
- model = $model
304
- tools_used = @()
305
- files_referenced = @()
306
- } | ConvertTo-Json -Depth 10
307
-
308
- Invoke-RestMethod -Uri "https://mcp.ekkos.dev/api/v1/working/turn" `
309
- -Method POST `
310
- -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
311
- -Body ([System.Text.Encoding]::UTF8.GetBytes($body)) `
312
- -ErrorAction SilentlyContinue | Out-Null
313
- } -ArgumentList $authToken, $sessionName, $turn, $lastUser, $lastAssistant, $modelUsed | Out-Null
314
-
315
- # 2. EPISODIC MEMORY (Supabase)
316
- Start-Job -ScriptBlock {
317
- param($token, $userQuery, $agentResponse, $sessionId, $userId, $fileChanges, $model, $ts, $turnNum, $sessionName)
318
- $body = @{
319
- user_query = $userQuery
320
- assistant_response = $agentResponse
321
- session_id = $sessionId
322
- user_id = if ($userId) { $userId } else { "system" }
323
- file_changes = @()
324
- metadata = @{
325
- source = "claude-code"
326
- model_used = $model
327
- captured_at = $ts
328
- turn_number = $turnNum
329
- session_name = $sessionName
330
- minimal_hook = $true
331
- }
332
- } | ConvertTo-Json -Depth 10
333
-
334
- Invoke-RestMethod -Uri "https://mcp.ekkos.dev/api/v1/memory/capture" `
335
- -Method POST `
336
- -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
337
- -Body ([System.Text.Encoding]::UTF8.GetBytes($body)) `
338
- -ErrorAction SilentlyContinue | Out-Null
339
- } -ArgumentList $authToken, $lastUser, $lastAssistant, $rawSessionId, $userId, $fileChangesJson, $modelUsed, $timestamp, $turn, $sessionName | Out-Null
340
- }
341
-
342
- # ═══════════════════════════════════════════════════════════════════════════
343
- # CLEAN UP STATE FILES
344
- # ═══════════════════════════════════════════════════════════════════════════
345
- if (Test-Path $stateFile) {
346
- Remove-Item $stateFile -Force
347
- }
348
-
349
- Write-Output "ekkOS session ended"