@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/dist/deploy/settings.js
CHANGED
|
@@ -4,42 +4,29 @@ exports.deployClaudeSettings = deployClaudeSettings;
|
|
|
4
4
|
exports.areHooksConfigured = areHooksConfigured;
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
6
|
const platform_1 = require("../utils/platform");
|
|
7
|
+
function makeHook(command) {
|
|
8
|
+
return [{ matcher: '*', hooks: [{ type: 'command', command, timeout: 5000 }] }];
|
|
9
|
+
}
|
|
7
10
|
/**
|
|
8
11
|
* Generate the hooks configuration for Claude Code settings.json
|
|
12
|
+
* Uses the correct Claude Code format: [{ matcher, hooks: [{ type, command, timeout }] }]
|
|
9
13
|
*/
|
|
10
14
|
function generateHooksConfig() {
|
|
11
|
-
const hooksDir =
|
|
15
|
+
const hooksDir = platform_1.CLAUDE_HOOKS_DIR;
|
|
12
16
|
if (platform_1.isWindows) {
|
|
13
|
-
|
|
17
|
+
const ps = (name) => `powershell -ExecutionPolicy Bypass -File "${hooksDir}\\${name}.ps1"`;
|
|
14
18
|
return {
|
|
15
|
-
SessionStart:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
UserPromptSubmit: [
|
|
19
|
-
{ type: 'command', command: `powershell -ExecutionPolicy Bypass -File "${hooksDir}/user-prompt-submit.ps1"` }
|
|
20
|
-
],
|
|
21
|
-
Stop: [
|
|
22
|
-
{ type: 'command', command: `powershell -ExecutionPolicy Bypass -File "${hooksDir}/stop.ps1"` }
|
|
23
|
-
],
|
|
24
|
-
AssistantResponse: [
|
|
25
|
-
{ type: 'command', command: `powershell -ExecutionPolicy Bypass -File "${hooksDir}/assistant-response.ps1"` }
|
|
26
|
-
]
|
|
19
|
+
SessionStart: makeHook(ps('session-start')),
|
|
20
|
+
UserPromptSubmit: makeHook(ps('user-prompt-submit')),
|
|
21
|
+
Stop: makeHook(ps('stop')),
|
|
27
22
|
};
|
|
28
23
|
}
|
|
29
24
|
// Unix uses bash
|
|
25
|
+
const sh = (name) => `bash "${hooksDir}/${name}.sh"`;
|
|
30
26
|
return {
|
|
31
|
-
SessionStart:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
UserPromptSubmit: [
|
|
35
|
-
{ type: 'command', command: `bash ${hooksDir}/user-prompt-submit.sh` }
|
|
36
|
-
],
|
|
37
|
-
Stop: [
|
|
38
|
-
{ type: 'command', command: `bash ${hooksDir}/stop.sh` }
|
|
39
|
-
],
|
|
40
|
-
AssistantResponse: [
|
|
41
|
-
{ type: 'command', command: `bash ${hooksDir}/assistant-response.sh` }
|
|
42
|
-
]
|
|
27
|
+
SessionStart: makeHook(sh('session-start')),
|
|
28
|
+
UserPromptSubmit: makeHook(sh('user-prompt-submit')),
|
|
29
|
+
Stop: makeHook(sh('stop')),
|
|
43
30
|
};
|
|
44
31
|
}
|
|
45
32
|
/**
|
package/package.json
CHANGED
|
@@ -2,66 +2,117 @@
|
|
|
2
2
|
# ekkOS_ Hook: AssistantResponse - Process Claude's response (Windows)
|
|
3
3
|
# MANAGED BY ekkos-connect - DO NOT EDIT DIRECTLY
|
|
4
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
|
|
5
11
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
6
12
|
|
|
7
13
|
$ErrorActionPreference = "SilentlyContinue"
|
|
8
14
|
|
|
9
15
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
10
|
-
# CONFIG PATHS
|
|
16
|
+
# CONFIG PATHS - No hardcoded word arrays per spec v1.2 Addendum
|
|
11
17
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
12
18
|
$EkkosConfigDir = if ($env:EKKOS_CONFIG_DIR) { $env:EKKOS_CONFIG_DIR } else { "$env:USERPROFILE\.ekkos" }
|
|
13
19
|
$HooksEnabledJson = "$EkkosConfigDir\hooks-enabled.json"
|
|
14
20
|
$HooksEnabledDefault = "$EkkosConfigDir\.defaults\hooks-enabled.json"
|
|
15
21
|
$SessionWordsJson = "$EkkosConfigDir\session-words.json"
|
|
16
22
|
$SessionWordsDefault = "$EkkosConfigDir\.defaults\session-words.json"
|
|
17
|
-
$EkkosInstanceId = if ($env:EKKOS_INSTANCE_ID) { $env:EKKOS_INSTANCE_ID } else { "default" }
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
25
|
+
# INSTANCE ID - Multi-session isolation per v1.2 ADDENDUM
|
|
26
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
27
|
+
$EkkosInstanceId = if ($env:EKKOS_INSTANCE_ID) { $env:EKKOS_INSTANCE_ID } else { "default" }
|
|
20
28
|
|
|
21
29
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
22
|
-
# CHECK ENABLEMENT
|
|
30
|
+
# CHECK ENABLEMENT - Respect hooks-enabled.json
|
|
23
31
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
24
32
|
function Test-HookEnabled {
|
|
25
33
|
param([string]$HookName)
|
|
34
|
+
|
|
26
35
|
$enabledFile = $HooksEnabledJson
|
|
27
|
-
if (-not (Test-Path $enabledFile)) {
|
|
28
|
-
|
|
36
|
+
if (-not (Test-Path $enabledFile)) {
|
|
37
|
+
$enabledFile = $HooksEnabledDefault
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (-not (Test-Path $enabledFile)) {
|
|
41
|
+
# No enablement file = all enabled by default
|
|
42
|
+
return $true
|
|
43
|
+
}
|
|
44
|
+
|
|
29
45
|
try {
|
|
30
46
|
$config = Get-Content $enabledFile -Raw | ConvertFrom-Json
|
|
47
|
+
|
|
48
|
+
# Check claude.enabled array
|
|
31
49
|
if ($config.claude -and $config.claude.enabled) {
|
|
32
50
|
$enabledHooks = $config.claude.enabled
|
|
33
|
-
if ($enabledHooks -contains $HookName -or $enabledHooks -contains "assistant-response") {
|
|
51
|
+
if ($enabledHooks -contains $HookName -or $enabledHooks -contains "assistant-response") {
|
|
52
|
+
return $true
|
|
53
|
+
}
|
|
34
54
|
return $false
|
|
35
55
|
}
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
|
|
57
|
+
return $true # Default to enabled if no config
|
|
58
|
+
} catch {
|
|
59
|
+
return $true # Default to enabled on error
|
|
60
|
+
}
|
|
38
61
|
}
|
|
39
62
|
|
|
40
|
-
|
|
63
|
+
# Check if this hook is enabled
|
|
64
|
+
if (-not (Test-HookEnabled "assistant-response")) {
|
|
65
|
+
exit 0
|
|
66
|
+
}
|
|
41
67
|
|
|
42
68
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
43
|
-
# Load session words - NO HARDCODED ARRAYS
|
|
69
|
+
# Load session words from JSON file - NO HARDCODED ARRAYS
|
|
44
70
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
45
71
|
$script:SessionWords = $null
|
|
46
72
|
|
|
47
73
|
function Load-SessionWords {
|
|
48
74
|
$wordsFile = $SessionWordsJson
|
|
49
|
-
|
|
50
|
-
|
|
75
|
+
|
|
76
|
+
# Fallback to managed defaults if user file missing/invalid
|
|
77
|
+
if (-not (Test-Path $wordsFile)) {
|
|
78
|
+
$wordsFile = $SessionWordsDefault
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (-not (Test-Path $wordsFile)) {
|
|
82
|
+
return $null
|
|
83
|
+
}
|
|
84
|
+
|
|
51
85
|
try {
|
|
52
86
|
$script:SessionWords = Get-Content $wordsFile -Raw | ConvertFrom-Json
|
|
53
|
-
} catch {
|
|
87
|
+
} catch {
|
|
88
|
+
return $null
|
|
89
|
+
}
|
|
54
90
|
}
|
|
55
91
|
|
|
92
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
93
|
+
# SESSION NAME (UUID to words) - Uses external session-words.json
|
|
94
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
56
95
|
function Convert-UuidToWords {
|
|
57
96
|
param([string]$uuid)
|
|
58
|
-
|
|
59
|
-
|
|
97
|
+
|
|
98
|
+
# Load session words if not already loaded
|
|
99
|
+
if (-not $script:SessionWords) {
|
|
100
|
+
Load-SessionWords
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Handle missing session words gracefully
|
|
104
|
+
if (-not $script:SessionWords) {
|
|
105
|
+
return "unknown-session"
|
|
106
|
+
}
|
|
60
107
|
|
|
61
108
|
$adjectives = $script:SessionWords.adjectives
|
|
62
109
|
$nouns = $script:SessionWords.nouns
|
|
63
110
|
$verbs = $script:SessionWords.verbs
|
|
64
|
-
|
|
111
|
+
|
|
112
|
+
if (-not $adjectives -or -not $nouns -or -not $verbs) {
|
|
113
|
+
return "unknown-session"
|
|
114
|
+
}
|
|
115
|
+
|
|
65
116
|
if (-not $uuid -or $uuid -eq "unknown") { return "unknown-session" }
|
|
66
117
|
|
|
67
118
|
$clean = $uuid -replace "-", ""
|
|
@@ -71,8 +122,11 @@ function Convert-UuidToWords {
|
|
|
71
122
|
$a = [Convert]::ToInt32($clean.Substring(0,4), 16) % $adjectives.Length
|
|
72
123
|
$n = [Convert]::ToInt32($clean.Substring(4,4), 16) % $nouns.Length
|
|
73
124
|
$an = [Convert]::ToInt32($clean.Substring(8,4), 16) % $verbs.Length
|
|
125
|
+
|
|
74
126
|
return "$($adjectives[$a])-$($nouns[$n])-$($verbs[$an])"
|
|
75
|
-
} catch {
|
|
127
|
+
} catch {
|
|
128
|
+
return "unknown-session"
|
|
129
|
+
}
|
|
76
130
|
}
|
|
77
131
|
|
|
78
132
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -81,16 +135,23 @@ function Convert-UuidToWords {
|
|
|
81
135
|
$inputJson = [Console]::In.ReadToEnd()
|
|
82
136
|
if (-not $inputJson) { exit 0 }
|
|
83
137
|
|
|
84
|
-
try {
|
|
138
|
+
try {
|
|
139
|
+
$input = $inputJson | ConvertFrom-Json
|
|
140
|
+
} catch {
|
|
141
|
+
exit 0
|
|
142
|
+
}
|
|
85
143
|
|
|
144
|
+
# Extract response content
|
|
86
145
|
$assistantResponse = $input.response
|
|
87
146
|
if (-not $assistantResponse) { $assistantResponse = $input.message }
|
|
88
147
|
if (-not $assistantResponse) { $assistantResponse = $input.content }
|
|
89
148
|
if (-not $assistantResponse) { exit 0 }
|
|
90
149
|
|
|
150
|
+
# Get session ID
|
|
91
151
|
$rawSessionId = $input.session_id
|
|
92
152
|
if (-not $rawSessionId -or $rawSessionId -eq "null") { $rawSessionId = "unknown" }
|
|
93
153
|
|
|
154
|
+
# Fallback: read session_id from saved state
|
|
94
155
|
if ($rawSessionId -eq "unknown") {
|
|
95
156
|
$stateFile = Join-Path $env:USERPROFILE ".claude\state\current-session.json"
|
|
96
157
|
if (Test-Path $stateFile) {
|
|
@@ -106,13 +167,16 @@ $sessionName = Convert-UuidToWords $rawSessionId
|
|
|
106
167
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
107
168
|
# READ TURN STATE
|
|
108
169
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
109
|
-
$
|
|
170
|
+
$stateFile = Join-Path $env:USERPROFILE ".claude\state\hook-state.json"
|
|
110
171
|
$turn = 0
|
|
111
|
-
|
|
172
|
+
|
|
173
|
+
if (Test-Path $stateFile) {
|
|
112
174
|
try {
|
|
113
|
-
$hookState = Get-Content $
|
|
175
|
+
$hookState = Get-Content $stateFile -Raw | ConvertFrom-Json
|
|
114
176
|
$turn = [int]$hookState.turn
|
|
115
|
-
} catch {
|
|
177
|
+
} catch {
|
|
178
|
+
$turn = 0
|
|
179
|
+
}
|
|
116
180
|
}
|
|
117
181
|
|
|
118
182
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -120,6 +184,7 @@ if (Test-Path $hookStateFile) {
|
|
|
120
184
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
121
185
|
$patternIds = @()
|
|
122
186
|
if ($assistantResponse -match '\[ekkOS_SELECT\]') {
|
|
187
|
+
# Extract pattern IDs from SELECT blocks
|
|
123
188
|
$selectMatches = [regex]::Matches($assistantResponse, 'id:\s*([a-zA-Z0-9\-_]+)')
|
|
124
189
|
foreach ($match in $selectMatches) {
|
|
125
190
|
$patternIds += $match.Groups[1].Value
|
|
@@ -127,15 +192,18 @@ if ($assistantResponse -match '\[ekkOS_SELECT\]') {
|
|
|
127
192
|
}
|
|
128
193
|
|
|
129
194
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
130
|
-
# LOCAL CACHE: Tier 0 capture (async)
|
|
195
|
+
# LOCAL CACHE: Tier 0 capture (async, non-blocking)
|
|
196
|
+
# Per v1.2 ADDENDUM: Pass instanceId for namespacing
|
|
131
197
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
132
198
|
$captureCmd = Get-Command "ekkos-capture" -ErrorAction SilentlyContinue
|
|
133
199
|
if ($captureCmd -and $rawSessionId -ne "unknown") {
|
|
134
200
|
try {
|
|
201
|
+
# NEW format: ekkos-capture response <instance_id> <session_id> <turn_id> <response> [tools] [files]
|
|
135
202
|
$responseBase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($assistantResponse))
|
|
136
203
|
$toolsJson = "[]"
|
|
137
204
|
$filesJson = "[]"
|
|
138
205
|
|
|
206
|
+
# Extract tools used from response
|
|
139
207
|
$toolMatches = [regex]::Matches($assistantResponse, '\[TOOL:\s*([^\]]+)\]')
|
|
140
208
|
if ($toolMatches.Count -gt 0) {
|
|
141
209
|
$tools = $toolMatches | ForEach-Object { $_.Groups[1].Value } | Select-Object -Unique
|
|
@@ -153,7 +221,7 @@ if ($captureCmd -and $rawSessionId -ne "unknown") {
|
|
|
153
221
|
}
|
|
154
222
|
|
|
155
223
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
156
|
-
# WORKING MEMORY: Fast capture to API (async)
|
|
224
|
+
# WORKING MEMORY: Fast capture to API (async, non-blocking)
|
|
157
225
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
158
226
|
$configFile = Join-Path $EkkosConfigDir "config.json"
|
|
159
227
|
if (Test-Path $configFile) {
|
|
@@ -174,7 +242,7 @@ if (Test-Path $configFile) {
|
|
|
174
242
|
pattern_ids = $patterns
|
|
175
243
|
} | ConvertTo-Json -Depth 10
|
|
176
244
|
|
|
177
|
-
Invoke-RestMethod -Uri "https://
|
|
245
|
+
Invoke-RestMethod -Uri "https://mcp.ekkos.dev/api/v1/working/turn" `
|
|
178
246
|
-Method POST `
|
|
179
247
|
-Headers @{ Authorization = "Bearer $token" } `
|
|
180
248
|
-ContentType "application/json" `
|
|
@@ -2,26 +2,38 @@
|
|
|
2
2
|
"hooks": {
|
|
3
3
|
"SessionStart": [
|
|
4
4
|
{
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
"matcher": "*",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "bash .claude/hooks/session-start.sh",
|
|
10
|
+
"timeout": 5000
|
|
11
|
+
}
|
|
12
|
+
]
|
|
7
13
|
}
|
|
8
14
|
],
|
|
9
15
|
"UserPromptSubmit": [
|
|
10
16
|
{
|
|
11
|
-
"
|
|
12
|
-
"
|
|
17
|
+
"matcher": "*",
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "bash .claude/hooks/user-prompt-submit.sh",
|
|
22
|
+
"timeout": 5000
|
|
23
|
+
}
|
|
24
|
+
]
|
|
13
25
|
}
|
|
14
26
|
],
|
|
15
27
|
"Stop": [
|
|
16
28
|
{
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
"matcher": "*",
|
|
30
|
+
"hooks": [
|
|
31
|
+
{
|
|
32
|
+
"type": "command",
|
|
33
|
+
"command": "bash .claude/hooks/stop.sh",
|
|
34
|
+
"timeout": 5000
|
|
35
|
+
}
|
|
36
|
+
]
|
|
25
37
|
}
|
|
26
38
|
]
|
|
27
39
|
}
|
|
File without changes
|
|
File without changes
|