@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,419 +0,0 @@
1
- # ═══════════════════════════════════════════════════════════════════════════
2
- # ekkOS_ Hook: UserPromptSubmit - SEAMLESS CONTEXT CONTINUITY (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 - No hardcoded word arrays per spec v1.2 Addendum
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
- # Fallback to managed defaults if user file missing/invalid
44
- if (-not (Test-Path $wordsFile)) {
45
- $wordsFile = $SessionWordsDefault
46
- }
47
-
48
- if (-not (Test-Path $wordsFile)) {
49
- return $null
50
- }
51
-
52
- try {
53
- $script:SessionWords = Get-Content $wordsFile -Raw | ConvertFrom-Json
54
- } catch {
55
- return $null
56
- }
57
- }
58
-
59
- # Read input from stdin
60
- $inputJson = [Console]::In.ReadToEnd()
61
- if (-not $inputJson) { exit 0 }
62
-
63
- try {
64
- $input = $inputJson | ConvertFrom-Json
65
- } catch {
66
- exit 0
67
- }
68
-
69
- $userQuery = $input.query
70
- if (-not $userQuery) { $userQuery = $input.message }
71
- if (-not $userQuery) { $userQuery = $input.prompt }
72
- if (-not $userQuery) { exit 0 }
73
-
74
- $rawSessionId = $input.session_id
75
- if (-not $rawSessionId -or $rawSessionId -eq "null") { $rawSessionId = "unknown" }
76
-
77
- # Fallback: read session_id from saved state
78
- if ($rawSessionId -eq "unknown") {
79
- $stateFile = Join-Path $env:USERPROFILE ".claude\state\current-session.json"
80
- if (Test-Path $stateFile) {
81
- try {
82
- $state = Get-Content $stateFile -Raw | ConvertFrom-Json
83
- $rawSessionId = $state.session_id
84
- } catch {}
85
- }
86
- }
87
-
88
- # ═══════════════════════════════════════════════════════════════════════════
89
- # PROXY MODE: Slim hook path (cosmetic output + local state only)
90
- # ═══════════════════════════════════════════════════════════════════════════
91
- if ($ProxyMode) {
92
- function Get-ProxySessionName {
93
- param([string]$sessionId)
94
- if (-not $sessionId -or $sessionId -eq "unknown") { return "unknown-session" }
95
- if ($sessionId -notmatch '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') {
96
- return $sessionId
97
- }
98
-
99
- if (-not $script:SessionWords) { Load-SessionWords }
100
- if (-not $script:SessionWords) { return $sessionId }
101
-
102
- $adjectives = $script:SessionWords.adjectives
103
- $nouns = $script:SessionWords.nouns
104
- $verbs = $script:SessionWords.verbs
105
- if (-not $adjectives -or -not $nouns -or -not $verbs) { return $sessionId }
106
-
107
- $clean = $sessionId -replace "-", ""
108
- if ($clean.Length -lt 12) { return $sessionId }
109
-
110
- try {
111
- $adj = [Convert]::ToInt32($clean.Substring(0, 4), 16) % $adjectives.Length
112
- $noun = [Convert]::ToInt32($clean.Substring(4, 4), 16) % $nouns.Length
113
- $verb = [Convert]::ToInt32($clean.Substring(8, 4), 16) % $verbs.Length
114
- return "$($adjectives[$adj])-$($nouns[$noun])-$($verbs[$verb])"
115
- } catch {
116
- return $sessionId
117
- }
118
- }
119
-
120
- $sessionName = Get-ProxySessionName $rawSessionId
121
- $timestampUtc = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
122
- $displayTime = (Get-Date).ToString("yyyy-MM-dd hh:mm:ss tt zzz")
123
- $projectPath = if ($env:PWD) { $env:PWD } else { (Get-Location).Path }
124
-
125
- New-Item -ItemType Directory -Path $EkkosConfigDir -Force | Out-Null
126
- New-Item -ItemType Directory -Path (Join-Path $env:USERPROFILE ".claude\state") -Force | Out-Null
127
-
128
- $globalState = @{
129
- session_id = $rawSessionId
130
- session_name = $sessionName
131
- project = $projectPath
132
- timestamp = $timestampUtc
133
- } | ConvertTo-Json -Depth 10 -Compress
134
- Set-Content -Path (Join-Path $EkkosConfigDir "current-session.json") -Value $globalState -Force
135
-
136
- $localState = @{
137
- session_id = $rawSessionId
138
- session_name = $sessionName
139
- timestamp = $timestampUtc
140
- } | ConvertTo-Json -Depth 10 -Compress
141
- Set-Content -Path (Join-Path $env:USERPROFILE ".claude\state\current-session.json") -Value $localState -Force
142
-
143
- $activeSessionsPath = Join-Path $EkkosConfigDir "active-sessions.json"
144
- $sessions = @()
145
- if (Test-Path $activeSessionsPath) {
146
- try { $sessions = @(Get-Content $activeSessionsPath -Raw | ConvertFrom-Json) } catch { $sessions = @() }
147
- }
148
-
149
- $entry = @{
150
- sessionId = $rawSessionId
151
- sessionName = $sessionName
152
- projectPath = $projectPath
153
- startedAt = $timestampUtc
154
- lastHeartbeat = $timestampUtc
155
- pid = 0
156
- }
157
- $idx = -1
158
- for ($i = 0; $i -lt $sessions.Count; $i++) {
159
- if ($sessions[$i].sessionId -eq $rawSessionId) { $idx = $i; break }
160
- }
161
- if ($idx -ge 0) {
162
- $entry.startedAt = if ($sessions[$idx].startedAt) { $sessions[$idx].startedAt } else { $timestampUtc }
163
- $sessions[$idx] = $entry
164
- } else {
165
- $sessions += $entry
166
- }
167
- Set-Content -Path $activeSessionsPath -Value ($sessions | ConvertTo-Json -Depth 10) -Force
168
-
169
- $hint = @{
170
- sessionName = $sessionName
171
- sessionId = $rawSessionId
172
- projectPath = $projectPath
173
- ts = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
174
- } | ConvertTo-Json -Depth 10 -Compress
175
- Set-Content -Path (Join-Path $EkkosConfigDir "hook-session-hint.json") -Value $hint -Force
176
-
177
- $esc = [char]27
178
- Write-Output "${esc}[0;36m${esc}[1m🧠 ekkOS Memory${esc}[0m ${esc}[2m| $sessionName | $displayTime${esc}[0m"
179
- exit 0
180
- }
181
-
182
- # ═══════════════════════════════════════════════════════════════════════════
183
- # INTELLIGENT TOOL ROUTER: Multi-trigger skill detection
184
- # ═══════════════════════════════════════════════════════════════════════════
185
- $skillReminders = @()
186
- $queryLower = $userQuery.ToLower()
187
-
188
- # Memory First - Debug/Error/Problem solving
189
- if ($queryLower -match '(how do i|debug|error|bug|fix|not working|broken|fails|issue|problem|wrong|crash)') {
190
- $skillReminders += "SKILL REQUIRED: Call Skill(skill: `"ekkOS_Memory_First`") FIRST before debugging"
191
- }
192
-
193
- # Recall Triggers - Time-based memory
194
- if ($queryLower -match '(yesterday|last week|last month|remember when|what did we|where did we leave|before|earlier|previous|ago)') {
195
- $skillReminders += "SKILL REQUIRED: Call Skill(skill: `"ekkOS_Deep_Recall`") for time-based memory"
196
- }
197
-
198
- # Directive Triggers - User preferences
199
- if ($queryLower -match '(always |never |i prefer|i like |dont |don.t |avoid |remember that |from now on)') {
200
- $skillReminders += "SKILL REQUIRED: Call Skill(skill: `"ekkOS_Preferences`") to capture directive"
201
- }
202
-
203
- # Safety Triggers - Destructive actions
204
- if ($queryLower -match '(delete|drop |rm -rf|deploy|push.*main|push.*master|production|migrate|rollback)') {
205
- $skillReminders += "SAFETY REQUIRED: Call ekkOS_Conflict before this destructive action"
206
- }
207
-
208
- # Schema Triggers - Database operations
209
- if ($queryLower -match '(sql|query|supabase|prisma|database|table|column|select |insert |update |where )') {
210
- $skillReminders += "SCHEMA REQUIRED: Call ekkOS_GetSchema for correct field names"
211
- }
212
-
213
- # ═══════════════════════════════════════════════════════════════════════════
214
- # SESSION NAME - Resolve early so it's available for all downstream use
215
- # ═══════════════════════════════════════════════════════════════════════════
216
- function Convert-UuidToWords {
217
- param([string]$uuid)
218
-
219
- if (-not $script:SessionWords) { Load-SessionWords }
220
- if (-not $script:SessionWords) { return "unknown-session" }
221
-
222
- $adjectives = $script:SessionWords.adjectives
223
- $nouns = $script:SessionWords.nouns
224
- $verbs = $script:SessionWords.verbs
225
-
226
- if (-not $adjectives -or -not $nouns -or -not $verbs) { return "unknown-session" }
227
- if (-not $uuid -or $uuid -eq "unknown") { return "unknown-session" }
228
-
229
- $clean = $uuid -replace "-", ""
230
- if ($clean.Length -lt 12) { return "unknown-session" }
231
-
232
- try {
233
- $a = [Convert]::ToInt32($clean.Substring(0,4), 16) % $adjectives.Length
234
- $n = [Convert]::ToInt32($clean.Substring(4,4), 16) % $nouns.Length
235
- $an = [Convert]::ToInt32($clean.Substring(8,4), 16) % $verbs.Length
236
- return "$($adjectives[$a])-$($nouns[$n])-$($verbs[$an])"
237
- } catch {
238
- return "unknown-session"
239
- }
240
- }
241
-
242
- $sessionName = Convert-UuidToWords $rawSessionId
243
-
244
- function Get-PendingSessionFromProxyBaseUrl {
245
- param([string]$baseUrl)
246
- if (-not $baseUrl) { return $null }
247
- try {
248
- $uri = [System.Uri]$baseUrl
249
- $segments = $uri.AbsolutePath.Trim('/') -split '/'
250
- if (-not $segments -or $segments.Count -lt 3) { return $null }
251
- $proxyIndex = [Array]::IndexOf($segments, 'proxy')
252
- if ($proxyIndex -lt 0 -or $segments.Count -le ($proxyIndex + 2)) { return $null }
253
- $candidate = [System.Uri]::UnescapeDataString($segments[$proxyIndex + 2])
254
- if ($candidate -eq '_pending' -or $candidate -eq 'pending' -or $candidate.StartsWith('_pending-')) {
255
- return $candidate
256
- }
257
- } catch {}
258
- return $null
259
- }
260
- # Session binding removed — proxy now self-resolves _pending → word-based
261
- # names inline via uuidToSessionName(). No external bind call needed.
262
-
263
- # ═══════════════════════════════════════════════════════════════════════════
264
- # SESSION CURRENT: Update Redis with current session name
265
- # ═══════════════════════════════════════════════════════════════════════════
266
- if ($sessionName -ne "unknown-session" -and $rawSessionId -ne "unknown") {
267
- $configFile2 = Join-Path $EkkosConfigDir "config.json"
268
- if (Test-Path $configFile2) {
269
- try {
270
- $config2 = Get-Content $configFile2 -Raw | ConvertFrom-Json
271
- $sessionToken = $config2.hookApiKey
272
- if (-not $sessionToken) { $sessionToken = $config2.apiKey }
273
- if ($sessionToken) {
274
- $sessionBody = @{ session_name = $sessionName } | ConvertTo-Json -Depth 10
275
- Start-Job -ScriptBlock {
276
- param($body, $token)
277
- Invoke-RestMethod -Uri "https://mcp.ekkos.dev/api/v1/working/session/current" `
278
- -Method POST `
279
- -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
280
- -Body ([System.Text.Encoding]::UTF8.GetBytes($body)) -ErrorAction SilentlyContinue | Out-Null
281
- } -ArgumentList $sessionBody, $sessionToken | Out-Null
282
- }
283
- } catch {}
284
- }
285
- }
286
-
287
- # ═══════════════════════════════════════════════════════════════════════════
288
- # TURN TRACKING & STATE MANAGEMENT
289
- # ═══════════════════════════════════════════════════════════════════════════
290
- $stateDir = Join-Path $env:USERPROFILE ".claude\state"
291
- if (-not (Test-Path $stateDir)) {
292
- New-Item -ItemType Directory -Path $stateDir -Force | Out-Null
293
- }
294
-
295
- $stateFile = Join-Path $stateDir "hook-state.json"
296
- $turn = 0
297
- $contextPercent = ""
298
-
299
- if (Test-Path $stateFile) {
300
- try {
301
- $hookState = Get-Content $stateFile -Raw | ConvertFrom-Json
302
- # Only continue incrementing if this state belongs to the SAME session.
303
- # If session changed, reset turn counter to 0.
304
- if ($hookState.session_id -eq $rawSessionId) {
305
- $turn = [int]$hookState.turn + 1
306
- } else {
307
- $turn = 0
308
- }
309
- } catch {
310
- $turn = 0
311
- }
312
- }
313
-
314
- # Save updated state
315
- $newState = @{
316
- turn = $turn
317
- session_id = $rawSessionId
318
- last_query = $userQuery.Substring(0, [Math]::Min(100, $userQuery.Length))
319
- timestamp = (Get-Date).ToString("o")
320
- } | ConvertTo-Json -Depth 10
321
-
322
- Set-Content -Path $stateFile -Value $newState -Force
323
-
324
- # ═══════════════════════════════════════════════════════════════════════════
325
- # LOCAL CACHE: Tier 0 capture (async, non-blocking)
326
- # Per v1.2 ADDENDUM: Pass instanceId for namespacing
327
- # ═══════════════════════════════════════════════════════════════════════════
328
- $captureCmd = Get-Command "ekkos-capture" -ErrorAction SilentlyContinue
329
- if ($captureCmd -and $rawSessionId -ne "unknown") {
330
- try {
331
- # NEW format: ekkos-capture user <instance_id> <session_id> <session_name> <turn_id> <query> [project_path]
332
- $queryBase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($userQuery))
333
- $projectRoot = if ($env:PWD) { $env:PWD } else { (Get-Location).Path }
334
-
335
- Start-Job -ScriptBlock {
336
- param($instanceId, $sessionId, $sessionName, $turnNum, $queryB64, $projectPath)
337
- try {
338
- $decoded = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($queryB64))
339
- & ekkos-capture user $instanceId $sessionId $sessionName $turnNum $decoded $projectPath 2>&1 | Out-Null
340
- } catch {}
341
- } -ArgumentList $EkkosInstanceId, $rawSessionId, $sessionName, $turn, $queryBase64, $projectRoot | Out-Null
342
- } catch {}
343
- }
344
-
345
- # ═══════════════════════════════════════════════════════════════════════════
346
- # WORKING MEMORY: Fast capture to API (async, non-blocking)
347
- # ═══════════════════════════════════════════════════════════════════════════
348
- $configFile = Join-Path $EkkosConfigDir "config.json"
349
- if (Test-Path $configFile) {
350
- try {
351
- $config = Get-Content $configFile -Raw | ConvertFrom-Json
352
- $captureToken = $config.hookApiKey
353
- if (-not $captureToken) { $captureToken = $config.apiKey }
354
-
355
- if ($captureToken) {
356
- # Async capture using Start-Job (non-blocking)
357
- Start-Job -ScriptBlock {
358
- param($token, $instanceId, $sessionId, $sessionName, $turnNum, $query)
359
- $body = @{
360
- session_id = $sessionId
361
- session_name = $sessionName
362
- instance_id = $instanceId
363
- turn = $turnNum
364
- query = $query
365
- } | ConvertTo-Json -Depth 10
366
-
367
- Invoke-RestMethod -Uri "https://mcp.ekkos.dev/api/v1/working/fast-capture" `
368
- -Method POST `
369
- -Headers @{ Authorization = "Bearer $token" } `
370
- -ContentType "application/json" `
371
- -Body ([System.Text.Encoding]::UTF8.GetBytes($body)) -ErrorAction SilentlyContinue
372
- } -ArgumentList $captureToken, $EkkosInstanceId, $rawSessionId, $sessionName, $turn, $userQuery | Out-Null
373
- }
374
- } catch {}
375
- }
376
-
377
- $timestamp = (Get-Date).ToString("yyyy-MM-dd hh:mm:ss tt") + " EST"
378
-
379
- # ═══════════════════════════════════════════════════════════════════════════
380
- # DASHBOARD HINT FILE: write session info for ekkos dashboard --wait-for-new
381
- # On Windows, active-sessions.json is never populated (hook PIDs are dead).
382
- # The dashboard reads this file instead to locate the JSONL path.
383
- # ═══════════════════════════════════════════════════════════════════════════
384
- if ($sessionName -ne "unknown-session") {
385
- try {
386
- $projectPath = if ($env:PWD) { $env:PWD } else { (Get-Location).Path }
387
- $hintFile = Join-Path $EkkosConfigDir "hook-session-hint.json"
388
- $hint = @{
389
- sessionName = $sessionName
390
- sessionId = $rawSessionId
391
- projectPath = $projectPath
392
- ts = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
393
- } | ConvertTo-Json -Depth 10 -Compress
394
- Set-Content -Path $hintFile -Value $hint -Force
395
- } catch {}
396
- }
397
-
398
- # ═══════════════════════════════════════════════════════════════════════════
399
- # OUTPUT SYSTEM REMINDER
400
- # ═══════════════════════════════════════════════════════════════════════════
401
- $esc = [char]27
402
- $header = "${esc}[0;36m${esc}[1m🧠 ekkOS Memory${esc}[0m ${esc}[2m| $sessionName | $timestamp${esc}[0m"
403
-
404
- $output = @"
405
- $header
406
-
407
- "@
408
-
409
- if ($skillReminders.Count -gt 0) {
410
- $output += "${esc}[0;35m${esc}[1m" + ($skillReminders -join "`n") + "${esc}[0m`n"
411
- }
412
-
413
- $output += @"
414
-
415
- <footer-format>End responses with: Claude Code ({Model}) · 🧠 ekkOS_™ · $sessionName · $timestamp</footer-format>
416
- <footer-note>Do not include a turn counter in the footer.</footer-note>
417
- "@
418
-
419
- Write-Output $output