@ekkos/cli 0.2.8 → 0.2.10
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/dist/cache/LocalSessionStore.d.ts +34 -21
- package/dist/cache/LocalSessionStore.js +169 -53
- package/dist/cache/capture.d.ts +19 -11
- package/dist/cache/capture.js +243 -76
- package/dist/cache/types.d.ts +14 -1
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.js +148 -73
- package/dist/commands/hooks.d.ts +109 -0
- package/dist/commands/hooks.js +668 -0
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +69 -21
- package/dist/index.js +42 -1
- package/dist/restore/RestoreOrchestrator.d.ts +17 -3
- package/dist/restore/RestoreOrchestrator.js +64 -22
- package/dist/utils/paths.d.ts +125 -0
- package/dist/utils/paths.js +283 -0
- package/dist/utils/session-words.json +30 -111
- package/package.json +1 -1
- package/templates/ekkos-manifest.json +223 -0
- package/templates/helpers/json-parse.cjs +101 -0
- package/templates/hooks/assistant-response.ps1 +256 -0
- package/templates/hooks/assistant-response.sh +124 -64
- package/templates/hooks/session-start.ps1 +107 -2
- package/templates/hooks/session-start.sh +201 -166
- package/templates/hooks/stop.ps1 +124 -3
- package/templates/hooks/stop.sh +470 -843
- package/templates/hooks/user-prompt-submit.ps1 +107 -22
- package/templates/hooks/user-prompt-submit.sh +403 -393
- package/templates/project-stubs/session-start.ps1 +63 -0
- package/templates/project-stubs/session-start.sh +55 -0
- package/templates/project-stubs/stop.ps1 +63 -0
- package/templates/project-stubs/stop.sh +55 -0
- package/templates/project-stubs/user-prompt-submit.ps1 +63 -0
- package/templates/project-stubs/user-prompt-submit.sh +55 -0
- package/templates/shared/hooks-enabled.json +22 -0
- package/templates/shared/session-words.json +45 -0
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
2
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
3
|
+
# ekkOS_ Hook: AssistantResponse - Validates and enforces footer format
|
|
4
|
+
# MANAGED BY ekkos-connect - DO NOT EDIT DIRECTLY
|
|
5
|
+
# EKKOS_MANAGED=1
|
|
6
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
3
7
|
# Runs AFTER assistant response, checks footer compliance
|
|
8
|
+
# Per spec v1.2 Addendum: NO jq, NO hardcoded arrays
|
|
9
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
10
|
+
|
|
11
|
+
set +e
|
|
4
12
|
|
|
5
13
|
RESPONSE_FILE="$1"
|
|
6
14
|
HOOK_ENV="$2"
|
|
@@ -10,57 +18,109 @@ if [[ ! -f "$RESPONSE_FILE" ]]; then
|
|
|
10
18
|
exit 0
|
|
11
19
|
fi
|
|
12
20
|
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
22
|
+
# CONFIG PATHS - No hardcoded word arrays per spec v1.2 Addendum
|
|
23
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
24
|
+
EKKOS_CONFIG_DIR="${EKKOS_CONFIG_DIR:-$HOME/.ekkos}"
|
|
25
|
+
SESSION_WORDS_JSON="$EKKOS_CONFIG_DIR/session-words.json"
|
|
26
|
+
SESSION_WORDS_DEFAULT="$EKKOS_CONFIG_DIR/.defaults/session-words.json"
|
|
27
|
+
JSON_PARSE_HELPER="$EKKOS_CONFIG_DIR/.helpers/json-parse.cjs"
|
|
28
|
+
|
|
29
|
+
# Parse metadata from hook environment using Node (no jq)
|
|
30
|
+
parse_hook_env() {
|
|
31
|
+
local json="$1"
|
|
32
|
+
local path="$2"
|
|
33
|
+
echo "$json" | node -e "
|
|
34
|
+
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8') || '{}');
|
|
35
|
+
const path = '$path'.replace(/^\./,'').split('.').filter(Boolean);
|
|
36
|
+
let result = data;
|
|
37
|
+
for (const p of path) {
|
|
38
|
+
if (result === undefined || result === null) { result = undefined; break; }
|
|
39
|
+
result = result[p];
|
|
40
|
+
}
|
|
41
|
+
if (result !== undefined && result !== null) console.log(result);
|
|
42
|
+
" 2>/dev/null || echo ""
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
SESSION_ID=$(parse_hook_env "$HOOK_ENV" '.sessionId')
|
|
46
|
+
[ -z "$SESSION_ID" ] && SESSION_ID="unknown"
|
|
47
|
+
|
|
48
|
+
TURN=$(parse_hook_env "$HOOK_ENV" '.turn')
|
|
49
|
+
[ -z "$TURN" ] && TURN="0"
|
|
50
|
+
|
|
51
|
+
CONTEXT_PERCENT=$(parse_hook_env "$HOOK_ENV" '.contextUsagePercent')
|
|
52
|
+
[ -z "$CONTEXT_PERCENT" ] && CONTEXT_PERCENT="0"
|
|
53
|
+
|
|
54
|
+
MODEL=$(parse_hook_env "$HOOK_ENV" '.model')
|
|
55
|
+
[ -z "$MODEL" ] && MODEL="Claude Code (Opus 4.5)"
|
|
56
|
+
|
|
57
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
58
|
+
# Session name conversion - Uses external session-words.json (NO hardcoded arrays)
|
|
59
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
60
|
+
declare -a ADJECTIVES
|
|
61
|
+
declare -a NOUNS
|
|
62
|
+
SESSION_WORDS_LOADED=false
|
|
63
|
+
|
|
64
|
+
load_session_words() {
|
|
65
|
+
if [ "$SESSION_WORDS_LOADED" = "true" ]; then
|
|
66
|
+
return 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
local words_file="$SESSION_WORDS_JSON"
|
|
70
|
+
if [ ! -f "$words_file" ]; then
|
|
71
|
+
words_file="$SESSION_WORDS_DEFAULT"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
if [ ! -f "$words_file" ] || [ ! -f "$JSON_PARSE_HELPER" ]; then
|
|
75
|
+
return 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
if command -v node &>/dev/null; then
|
|
79
|
+
if [ "${BASH_VERSINFO[0]}" -ge 4 ]; then
|
|
80
|
+
readarray -t ADJECTIVES < <(node "$JSON_PARSE_HELPER" "$words_file" '.adjectives' 2>/dev/null)
|
|
81
|
+
readarray -t NOUNS < <(node "$JSON_PARSE_HELPER" "$words_file" '.nouns' 2>/dev/null)
|
|
82
|
+
else
|
|
83
|
+
local i=0
|
|
84
|
+
while IFS= read -r line; do
|
|
85
|
+
ADJECTIVES[i]="$line"
|
|
86
|
+
((i++))
|
|
87
|
+
done < <(node "$JSON_PARSE_HELPER" "$words_file" '.adjectives' 2>/dev/null)
|
|
88
|
+
i=0
|
|
89
|
+
while IFS= read -r line; do
|
|
90
|
+
NOUNS[i]="$line"
|
|
91
|
+
((i++))
|
|
92
|
+
done < <(node "$JSON_PARSE_HELPER" "$words_file" '.nouns' 2>/dev/null)
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
if [ ${#ADJECTIVES[@]} -gt 0 ] && [ ${#NOUNS[@]} -gt 0 ]; then
|
|
96
|
+
SESSION_WORDS_LOADED=true
|
|
97
|
+
return 0
|
|
98
|
+
fi
|
|
99
|
+
fi
|
|
100
|
+
return 1
|
|
101
|
+
}
|
|
18
102
|
|
|
19
|
-
# Convert session UUID to word-based name
|
|
20
103
|
convert_uuid_to_name() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"ninja" "pirate" "wizard" "robot" "yeti" "phoenix" "sphinx" "kraken"
|
|
42
|
-
"thunder" "blizzard" "tornado" "avalanche" "mango" "kiwi" "banana" "coconut"
|
|
43
|
-
"donut" "espresso" "falafel" "gyro" "hummus" "icecream" "jambon" "kebab"
|
|
44
|
-
"latte" "mocha" "nachos" "olive" "pasta" "quinoa" "ramen" "sushi"
|
|
45
|
-
"tamale" "udon" "velvet" "wasabi" "xmas" "yogurt" "ziti" "anchor"
|
|
46
|
-
"beacon" "canyon" "drifter" "echo" "falcon" "glacier" "harbor" "island"
|
|
47
|
-
"jetpack" "kayak" "lagoon" "meadow" "orbit" "parrot" "quest"
|
|
48
|
-
"rapids" "summit" "tunnel" "umbrella" "volcano" "whisper" "xylophone" "yacht"
|
|
49
|
-
"zephyr" "acorn" "bobcat" "cactus" "dolphin" "eagle" "ferret" "gopher"
|
|
50
|
-
"hedgehog" "iguana" "jackal" "koala")
|
|
51
|
-
|
|
52
|
-
# Extract first 8 hex chars
|
|
53
|
-
local hex="${uuid:0:8}"
|
|
54
|
-
hex="${hex//-/}"
|
|
55
|
-
|
|
56
|
-
# Convert to number
|
|
57
|
-
local num=$((16#$hex))
|
|
58
|
-
|
|
59
|
-
# Calculate indices
|
|
60
|
-
local adj_idx=$((num % 100))
|
|
61
|
-
local noun_idx=$(((num / 100) % 100))
|
|
62
|
-
|
|
63
|
-
echo "${ADJECTIVES[$adj_idx]}-${NOUNS[$noun_idx]}"
|
|
104
|
+
local uuid="$1"
|
|
105
|
+
|
|
106
|
+
load_session_words || {
|
|
107
|
+
echo "unknown-session"
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
local hex="${uuid:0:8}"
|
|
112
|
+
hex="${hex//-/}"
|
|
113
|
+
|
|
114
|
+
if [[ ! "$hex" =~ ^[0-9a-fA-F]+$ ]]; then
|
|
115
|
+
echo "unknown-session"
|
|
116
|
+
return
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
local num=$((16#$hex))
|
|
120
|
+
local adj_idx=$((num % ${#ADJECTIVES[@]}))
|
|
121
|
+
local noun_idx=$(((num / ${#ADJECTIVES[@]}) % ${#NOUNS[@]}))
|
|
122
|
+
|
|
123
|
+
echo "${ADJECTIVES[$adj_idx]}-${NOUNS[$noun_idx]}"
|
|
64
124
|
}
|
|
65
125
|
|
|
66
126
|
SESSION_NAME=$(convert_uuid_to_name "$SESSION_ID")
|
|
@@ -68,7 +128,7 @@ TIMESTAMP=$(date "+%Y-%m-%d %I:%M %p %Z")
|
|
|
68
128
|
|
|
69
129
|
# Required footer format
|
|
70
130
|
REQUIRED_FOOTER="---
|
|
71
|
-
$MODEL
|
|
131
|
+
$MODEL - $SESSION_NAME - Turn $TURN - ${CONTEXT_PERCENT}% - ekkOS - $TIMESTAMP"
|
|
72
132
|
|
|
73
133
|
# Check if response has correct footer
|
|
74
134
|
RESPONSE_CONTENT=$(cat "$RESPONSE_FILE")
|
|
@@ -76,21 +136,21 @@ LAST_LINE=$(echo "$RESPONSE_CONTENT" | tail -1)
|
|
|
76
136
|
|
|
77
137
|
# Check if footer exists and is correct
|
|
78
138
|
if [[ "$LAST_LINE" == *"ekkOS"* ]] && [[ "$LAST_LINE" == *"Turn"* ]]; then
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
139
|
+
# Footer exists - validate format
|
|
140
|
+
if [[ "$LAST_LINE" == *"Turn $TURN"* ]] && [[ "$LAST_LINE" == *"${CONTEXT_PERCENT}%"* ]] && [[ "$LAST_LINE" == *"$SESSION_NAME"* ]]; then
|
|
141
|
+
# Footer is correct
|
|
142
|
+
exit 0
|
|
143
|
+
else
|
|
144
|
+
# Footer exists but is malformed - replace it
|
|
145
|
+
RESPONSE_WITHOUT_FOOTER=$(echo "$RESPONSE_CONTENT" | head -n -2)
|
|
146
|
+
echo "$RESPONSE_WITHOUT_FOOTER" > "$RESPONSE_FILE"
|
|
147
|
+
echo "" >> "$RESPONSE_FILE"
|
|
148
|
+
echo "$REQUIRED_FOOTER" >> "$RESPONSE_FILE"
|
|
149
|
+
fi
|
|
150
|
+
else
|
|
151
|
+
# Footer missing - append it
|
|
87
152
|
echo "" >> "$RESPONSE_FILE"
|
|
88
153
|
echo "$REQUIRED_FOOTER" >> "$RESPONSE_FILE"
|
|
89
|
-
fi
|
|
90
|
-
else
|
|
91
|
-
# Footer missing - append it
|
|
92
|
-
echo "" >> "$RESPONSE_FILE"
|
|
93
|
-
echo "$REQUIRED_FOOTER" >> "$RESPONSE_FILE"
|
|
94
154
|
fi
|
|
95
155
|
|
|
96
156
|
exit 0
|
|
@@ -1,10 +1,90 @@
|
|
|
1
1
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
2
2
|
# ekkOS_ Hook: SessionStart - Initialize session (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
|
|
3
11
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
4
12
|
|
|
5
13
|
$ErrorActionPreference = "SilentlyContinue"
|
|
6
14
|
|
|
7
|
-
#
|
|
15
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
16
|
+
# CONFIG PATHS - No hardcoded word arrays per spec v1.2 Addendum
|
|
17
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
18
|
+
$EkkosConfigDir = if ($env:EKKOS_CONFIG_DIR) { $env:EKKOS_CONFIG_DIR } else { "$env:USERPROFILE\.ekkos" }
|
|
19
|
+
$SessionWordsJson = "$EkkosConfigDir\session-words.json"
|
|
20
|
+
$SessionWordsDefault = "$EkkosConfigDir\.defaults\session-words.json"
|
|
21
|
+
|
|
22
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
23
|
+
# INSTANCE ID - Multi-session isolation per v1.2 ADDENDUM
|
|
24
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
25
|
+
$EkkosInstanceId = if ($env:EKKOS_INSTANCE_ID) { $env:EKKOS_INSTANCE_ID } else { "default" }
|
|
26
|
+
|
|
27
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
28
|
+
# Load session words from JSON file - NO HARDCODED ARRAYS
|
|
29
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
30
|
+
$script:SessionWords = $null
|
|
31
|
+
|
|
32
|
+
function Load-SessionWords {
|
|
33
|
+
$wordsFile = $SessionWordsJson
|
|
34
|
+
|
|
35
|
+
if (-not (Test-Path $wordsFile)) {
|
|
36
|
+
$wordsFile = $SessionWordsDefault
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (-not (Test-Path $wordsFile)) {
|
|
40
|
+
return $null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
$script:SessionWords = Get-Content $wordsFile -Raw | ConvertFrom-Json
|
|
45
|
+
} catch {
|
|
46
|
+
return $null
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function Convert-UuidToWords {
|
|
51
|
+
param([string]$uuid)
|
|
52
|
+
|
|
53
|
+
if (-not $script:SessionWords) {
|
|
54
|
+
Load-SessionWords
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (-not $script:SessionWords) {
|
|
58
|
+
return "unknown-session"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
$adjectives = $script:SessionWords.adjectives
|
|
62
|
+
$nouns = $script:SessionWords.nouns
|
|
63
|
+
$verbs = $script:SessionWords.verbs
|
|
64
|
+
|
|
65
|
+
if (-not $adjectives -or -not $nouns -or -not $verbs) {
|
|
66
|
+
return "unknown-session"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (-not $uuid -or $uuid -eq "unknown") { return "unknown-session" }
|
|
70
|
+
|
|
71
|
+
$clean = $uuid -replace "-", ""
|
|
72
|
+
if ($clean.Length -lt 6) { return "unknown-session" }
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
$a = [Convert]::ToInt32($clean.Substring(0,2), 16) % $adjectives.Length
|
|
76
|
+
$n = [Convert]::ToInt32($clean.Substring(2,2), 16) % $nouns.Length
|
|
77
|
+
$an = [Convert]::ToInt32($clean.Substring(4,2), 16) % $verbs.Length
|
|
78
|
+
|
|
79
|
+
return "$($adjectives[$a])-$($nouns[$n])-$($verbs[$an])"
|
|
80
|
+
} catch {
|
|
81
|
+
return "unknown-session"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
86
|
+
# READ INPUT
|
|
87
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
8
88
|
$inputJson = [Console]::In.ReadToEnd()
|
|
9
89
|
|
|
10
90
|
try {
|
|
@@ -14,7 +94,11 @@ try {
|
|
|
14
94
|
$sessionId = "unknown"
|
|
15
95
|
}
|
|
16
96
|
|
|
17
|
-
|
|
97
|
+
$sessionName = Convert-UuidToWords $sessionId
|
|
98
|
+
|
|
99
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
100
|
+
# INITIALIZE STATE DIRECTORY
|
|
101
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
18
102
|
$stateDir = Join-Path $env:USERPROFILE ".claude\state"
|
|
19
103
|
if (-not (Test-Path $stateDir)) {
|
|
20
104
|
New-Item -ItemType Directory -Path $stateDir -Force | Out-Null
|
|
@@ -25,6 +109,8 @@ $stateFile = Join-Path $stateDir "hook-state.json"
|
|
|
25
109
|
$state = @{
|
|
26
110
|
turn = 0
|
|
27
111
|
session_id = $sessionId
|
|
112
|
+
session_name = $sessionName
|
|
113
|
+
instance_id = $EkkosInstanceId
|
|
28
114
|
started_at = (Get-Date).ToString("o")
|
|
29
115
|
} | ConvertTo-Json
|
|
30
116
|
|
|
@@ -34,8 +120,27 @@ Set-Content -Path $stateFile -Value $state -Force
|
|
|
34
120
|
$sessionFile = Join-Path $stateDir "current-session.json"
|
|
35
121
|
$sessionData = @{
|
|
36
122
|
session_id = $sessionId
|
|
123
|
+
session_name = $sessionName
|
|
124
|
+
instance_id = $EkkosInstanceId
|
|
37
125
|
} | ConvertTo-Json
|
|
38
126
|
|
|
39
127
|
Set-Content -Path $sessionFile -Value $sessionData -Force
|
|
40
128
|
|
|
129
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
130
|
+
# LOCAL CACHE: Initialize session in Tier 0 cache
|
|
131
|
+
# Per v1.2 ADDENDUM: Pass instanceId for namespacing
|
|
132
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
133
|
+
$captureCmd = Get-Command "ekkos-capture" -ErrorAction SilentlyContinue
|
|
134
|
+
if ($captureCmd -and $sessionId -ne "unknown") {
|
|
135
|
+
try {
|
|
136
|
+
# NEW format: ekkos-capture init <instance_id> <session_id> <session_name>
|
|
137
|
+
Start-Job -ScriptBlock {
|
|
138
|
+
param($instanceId, $sessId, $sessName)
|
|
139
|
+
try {
|
|
140
|
+
& ekkos-capture init $instanceId $sessId $sessName 2>&1 | Out-Null
|
|
141
|
+
} catch {}
|
|
142
|
+
} -ArgumentList $EkkosInstanceId, $sessionId, $sessionName | Out-Null
|
|
143
|
+
} catch {}
|
|
144
|
+
}
|
|
145
|
+
|
|
41
146
|
Write-Output "ekkOS session initialized"
|