@ekkos/cli 0.2.18 → 0.3.3
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/LICENSE +21 -0
- package/dist/capture/eviction-client.d.ts +139 -0
- package/dist/capture/eviction-client.js +454 -0
- package/dist/capture/index.d.ts +2 -0
- package/dist/capture/index.js +2 -0
- package/dist/capture/jsonl-rewriter.d.ts +96 -0
- package/dist/capture/jsonl-rewriter.js +1369 -0
- package/dist/capture/transcript-repair.d.ts +50 -0
- package/dist/capture/transcript-repair.js +308 -0
- package/dist/commands/doctor.js +23 -1
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +1229 -293
- package/dist/commands/usage.d.ts +7 -0
- package/dist/commands/usage.js +214 -0
- package/dist/cron/index.d.ts +7 -0
- package/dist/cron/index.js +13 -0
- package/dist/cron/promoter.d.ts +70 -0
- package/dist/cron/promoter.js +403 -0
- package/dist/index.js +24 -3
- package/dist/lib/usage-monitor.d.ts +47 -0
- package/dist/lib/usage-monitor.js +124 -0
- package/dist/lib/usage-parser.d.ts +72 -0
- package/dist/lib/usage-parser.js +238 -0
- package/dist/restore/RestoreOrchestrator.d.ts +4 -0
- package/dist/restore/RestoreOrchestrator.js +118 -30
- package/package.json +12 -12
- package/templates/cursor-hooks/after-agent-response.sh +0 -0
- package/templates/cursor-hooks/before-submit-prompt.sh +0 -0
- package/templates/cursor-hooks/stop.sh +0 -0
- package/templates/ekkos-manifest.json +2 -2
- package/templates/hooks/assistant-response.sh +0 -0
- package/templates/hooks/session-start.sh +0 -0
- package/templates/plan-template.md +0 -0
- package/templates/spec-template.md +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 -215
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export interface TurnMetrics {
|
|
2
|
+
turn_number: number;
|
|
3
|
+
timestamp: string;
|
|
4
|
+
input_tokens: number;
|
|
5
|
+
output_tokens: number;
|
|
6
|
+
cache_read_tokens: number;
|
|
7
|
+
cache_creation_tokens: number;
|
|
8
|
+
total_tokens: number;
|
|
9
|
+
context_percentage: number;
|
|
10
|
+
model: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SessionUsage {
|
|
13
|
+
session_id: string;
|
|
14
|
+
instance_id: string;
|
|
15
|
+
session_name: string;
|
|
16
|
+
turn_count: number;
|
|
17
|
+
total_input_tokens: number;
|
|
18
|
+
total_output_tokens: number;
|
|
19
|
+
total_cache_read_tokens: number;
|
|
20
|
+
total_cache_creation_tokens: number;
|
|
21
|
+
total_tokens: number;
|
|
22
|
+
total_cost: number;
|
|
23
|
+
avg_context_percentage: number;
|
|
24
|
+
max_context_percentage: number;
|
|
25
|
+
started_at: string;
|
|
26
|
+
last_activity: string;
|
|
27
|
+
models_used: string[];
|
|
28
|
+
turns: TurnMetrics[];
|
|
29
|
+
patterns_retrieved?: number;
|
|
30
|
+
patterns_applied?: number;
|
|
31
|
+
patterns_learned?: number;
|
|
32
|
+
confidence_gain?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface CcusageSession {
|
|
35
|
+
sessionId: string;
|
|
36
|
+
inputTokens: number;
|
|
37
|
+
outputTokens: number;
|
|
38
|
+
cacheCreationTokens: number;
|
|
39
|
+
cacheReadTokens: number;
|
|
40
|
+
totalTokens: number;
|
|
41
|
+
totalCost: number;
|
|
42
|
+
lastActivity: string;
|
|
43
|
+
modelsUsed: string[];
|
|
44
|
+
modelBreakdowns: {
|
|
45
|
+
modelName: string;
|
|
46
|
+
inputTokens: number;
|
|
47
|
+
outputTokens: number;
|
|
48
|
+
cacheCreationTokens: number;
|
|
49
|
+
cacheReadTokens: number;
|
|
50
|
+
cost: number;
|
|
51
|
+
}[];
|
|
52
|
+
projectPath: string;
|
|
53
|
+
}
|
|
54
|
+
export interface CcusageOutput {
|
|
55
|
+
sessions: CcusageSession[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get all sessions using ccusage
|
|
59
|
+
*/
|
|
60
|
+
export declare function getAllSessions(instanceId?: string): Promise<SessionUsage[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Get specific session usage using ccusage
|
|
63
|
+
*/
|
|
64
|
+
export declare function getSessionUsage(sessionId: string, instanceId?: string): Promise<SessionUsage | null>;
|
|
65
|
+
/**
|
|
66
|
+
* Get human-readable session name
|
|
67
|
+
*/
|
|
68
|
+
export declare function getSessionName(sessionId: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Get current session ID from active Claude Code process
|
|
71
|
+
*/
|
|
72
|
+
export declare function getCurrentSessionId(): string | null;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getAllSessions = getAllSessions;
|
|
37
|
+
exports.getSessionUsage = getSessionUsage;
|
|
38
|
+
exports.getSessionName = getSessionName;
|
|
39
|
+
exports.getCurrentSessionId = getCurrentSessionId;
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
/**
|
|
45
|
+
* Get all sessions using ccusage
|
|
46
|
+
*/
|
|
47
|
+
async function getAllSessions(instanceId) {
|
|
48
|
+
try {
|
|
49
|
+
// Run ccusage to get all sessions
|
|
50
|
+
const result = (0, child_process_1.execSync)('npx -y ccusage@latest session --json --breakdown', {
|
|
51
|
+
encoding: 'utf-8',
|
|
52
|
+
stdio: ['pipe', 'pipe', 'ignore'], // Suppress npm warnings
|
|
53
|
+
});
|
|
54
|
+
const data = JSON.parse(result);
|
|
55
|
+
// Convert ccusage format to our format
|
|
56
|
+
const sessions = await Promise.all(data.sessions.map(async (session) => convertCcusageSession(session, instanceId)));
|
|
57
|
+
return sessions.filter((s) => s !== null);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error('Failed to fetch sessions from ccusage:', error);
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get specific session usage using ccusage
|
|
66
|
+
*/
|
|
67
|
+
async function getSessionUsage(sessionId, instanceId) {
|
|
68
|
+
try {
|
|
69
|
+
// Run ccusage to get all sessions (--id flag doesn't work for IDs with dashes)
|
|
70
|
+
const result = (0, child_process_1.execSync)('npx -y ccusage@latest session --json --breakdown', {
|
|
71
|
+
encoding: 'utf-8',
|
|
72
|
+
stdio: ['pipe', 'pipe', 'ignore'], // Suppress npm warnings
|
|
73
|
+
});
|
|
74
|
+
const data = JSON.parse(result);
|
|
75
|
+
// Find the specific session
|
|
76
|
+
const session = data.sessions.find(s => s.sessionId === sessionId);
|
|
77
|
+
if (!session) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return convertCcusageSession(session, instanceId);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
console.error('Failed to fetch session from ccusage:', error);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Convert ccusage session to our SessionUsage format
|
|
89
|
+
*/
|
|
90
|
+
async function convertCcusageSession(session, instanceId) {
|
|
91
|
+
// Try to get turn-by-turn data from Claude Code's JSONL files
|
|
92
|
+
const turns = await getTurnMetrics(session.sessionId, instanceId);
|
|
93
|
+
// Calculate context percentages if we have turn data
|
|
94
|
+
let avgContextPercentage = 0;
|
|
95
|
+
let maxContextPercentage = 0;
|
|
96
|
+
if (turns.length > 0) {
|
|
97
|
+
avgContextPercentage =
|
|
98
|
+
turns.reduce((sum, t) => sum + t.context_percentage, 0) / turns.length;
|
|
99
|
+
maxContextPercentage = Math.max(...turns.map((t) => t.context_percentage));
|
|
100
|
+
}
|
|
101
|
+
// Try to fetch ekkOS pattern metrics from memory API
|
|
102
|
+
let patternMetrics;
|
|
103
|
+
try {
|
|
104
|
+
patternMetrics = await fetchPatternMetrics(session.sessionId);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Fail silently if memory API not available
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
session_id: session.sessionId,
|
|
111
|
+
instance_id: instanceId || 'unknown',
|
|
112
|
+
session_name: getSessionName(session.sessionId),
|
|
113
|
+
turn_count: turns.length || 0,
|
|
114
|
+
total_input_tokens: session.inputTokens,
|
|
115
|
+
total_output_tokens: session.outputTokens,
|
|
116
|
+
total_cache_read_tokens: session.cacheReadTokens,
|
|
117
|
+
total_cache_creation_tokens: session.cacheCreationTokens,
|
|
118
|
+
total_tokens: session.totalTokens,
|
|
119
|
+
total_cost: session.totalCost,
|
|
120
|
+
avg_context_percentage: avgContextPercentage,
|
|
121
|
+
max_context_percentage: maxContextPercentage,
|
|
122
|
+
started_at: turns[0]?.timestamp || 'unknown',
|
|
123
|
+
last_activity: session.lastActivity,
|
|
124
|
+
models_used: session.modelsUsed,
|
|
125
|
+
turns,
|
|
126
|
+
...patternMetrics,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get turn-by-turn metrics from Claude Code JSONL files
|
|
131
|
+
*/
|
|
132
|
+
async function getTurnMetrics(sessionId, instanceId) {
|
|
133
|
+
// Try multiple possible locations for Claude Code JSONL files
|
|
134
|
+
const possiblePaths = [
|
|
135
|
+
path.join(os.homedir(), '.claude', 'projects', instanceId || '', `${sessionId}.jsonl`),
|
|
136
|
+
path.join(os.homedir(), '.config', 'claude', 'projects', instanceId || '', `${sessionId}.jsonl`),
|
|
137
|
+
path.join(os.homedir(), '.codex', instanceId || '', `${sessionId}.jsonl`),
|
|
138
|
+
];
|
|
139
|
+
for (const filePath of possiblePaths) {
|
|
140
|
+
if (fs.existsSync(filePath)) {
|
|
141
|
+
try {
|
|
142
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
143
|
+
const lines = content.trim().split('\n');
|
|
144
|
+
const turns = [];
|
|
145
|
+
for (const line of lines) {
|
|
146
|
+
try {
|
|
147
|
+
const entry = JSON.parse(line);
|
|
148
|
+
// Look for usage events
|
|
149
|
+
if (entry.type === 'usage' || entry.usage) {
|
|
150
|
+
const usage = entry.usage || entry;
|
|
151
|
+
turns.push({
|
|
152
|
+
turn_number: turns.length,
|
|
153
|
+
timestamp: entry.timestamp || new Date().toISOString(),
|
|
154
|
+
input_tokens: usage.input_tokens || 0,
|
|
155
|
+
output_tokens: usage.output_tokens || 0,
|
|
156
|
+
cache_read_tokens: usage.cache_read_input_tokens || 0,
|
|
157
|
+
cache_creation_tokens: usage.cache_creation_input_tokens || 0,
|
|
158
|
+
total_tokens: (usage.input_tokens || 0) +
|
|
159
|
+
(usage.output_tokens || 0) +
|
|
160
|
+
(usage.cache_read_input_tokens || 0) +
|
|
161
|
+
(usage.cache_creation_input_tokens || 0),
|
|
162
|
+
context_percentage: calculateContextPercentage(usage),
|
|
163
|
+
model: entry.model || 'unknown',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Skip invalid JSON lines
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return turns;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Continue to next path
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Calculate context percentage from usage data
|
|
182
|
+
*/
|
|
183
|
+
function calculateContextPercentage(usage) {
|
|
184
|
+
const totalTokens = (usage.input_tokens || 0) +
|
|
185
|
+
(usage.cache_read_input_tokens || 0) +
|
|
186
|
+
(usage.cache_creation_input_tokens || 0);
|
|
187
|
+
// Claude Code context window is typically 200k tokens
|
|
188
|
+
const contextWindow = 200000;
|
|
189
|
+
return (totalTokens / contextWindow) * 100;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Fetch pattern metrics from ekkOS memory API
|
|
193
|
+
*/
|
|
194
|
+
async function fetchPatternMetrics(sessionId) {
|
|
195
|
+
try {
|
|
196
|
+
const response = await fetch(`http://localhost:3001/api/v1/session/stats?session_id=${sessionId}`, {
|
|
197
|
+
method: 'GET',
|
|
198
|
+
headers: {
|
|
199
|
+
'Content-Type': 'application/json',
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const data = await response.json();
|
|
206
|
+
return {
|
|
207
|
+
patterns_retrieved: data.patterns_retrieved || 0,
|
|
208
|
+
patterns_applied: data.patterns_applied || 0,
|
|
209
|
+
patterns_learned: data.patterns_learned || 0,
|
|
210
|
+
confidence_gain: data.confidence_gain || 0,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Get human-readable session name
|
|
219
|
+
*/
|
|
220
|
+
function getSessionName(sessionId) {
|
|
221
|
+
// Extract last part of path-based session IDs
|
|
222
|
+
const parts = sessionId.split('/');
|
|
223
|
+
const lastPart = parts[parts.length - 1];
|
|
224
|
+
// If it's a UUID-like string, try to map to session name
|
|
225
|
+
// For now, just return a shortened version
|
|
226
|
+
if (lastPart.length > 20) {
|
|
227
|
+
return lastPart.substring(0, 8) + '...' + lastPart.substring(lastPart.length - 8);
|
|
228
|
+
}
|
|
229
|
+
return lastPart;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get current session ID from active Claude Code process
|
|
233
|
+
*/
|
|
234
|
+
function getCurrentSessionId() {
|
|
235
|
+
// TODO: Extract from process environment or Claude Code state
|
|
236
|
+
// For now, require user to pass session ID
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
@@ -30,6 +30,10 @@ export declare class RestoreOrchestrator {
|
|
|
30
30
|
* Main restore function - attempts tiers in order
|
|
31
31
|
*/
|
|
32
32
|
restore(options?: RestoreOptions): Promise<CacheResult<RestorePayload>>;
|
|
33
|
+
/**
|
|
34
|
+
* Scan payload for [ekkOS:page-out:...] stubs and rehydrate from Proxy
|
|
35
|
+
*/
|
|
36
|
+
private rehydratePayload;
|
|
33
37
|
/**
|
|
34
38
|
* Tier -1: Restore from stream log (has mid-turn content)
|
|
35
39
|
* This is checked FIRST because stream logs have the most recent data,
|
|
@@ -58,8 +58,10 @@ const types_js_1 = require("../cache/types.js");
|
|
|
58
58
|
const stream_tailer_js_1 = require("../capture/stream-tailer.js");
|
|
59
59
|
const paths_js_1 = require("../utils/paths.js");
|
|
60
60
|
// API configuration
|
|
61
|
-
const MEMORY_API_URL = process.env.EKKOS_API_URL || 'https://
|
|
61
|
+
const MEMORY_API_URL = process.env.EKKOS_API_URL || 'https://mcp.ekkos.dev';
|
|
62
62
|
const CONFIG_PATH = path.join(os.homedir(), '.ekkos', 'config.json');
|
|
63
|
+
// Rehydration patterns
|
|
64
|
+
const STUB_REGEX = /\[ekkOS:page-out:r2:\/\/([^/]+)\/([^/]+)\/([^/]+)\/([^\]?]+)(?:\?range=(\d+)-(\d+))?\]/g;
|
|
63
65
|
/**
|
|
64
66
|
* Load auth token from config
|
|
65
67
|
*/
|
|
@@ -122,45 +124,131 @@ class RestoreOrchestrator {
|
|
|
122
124
|
latency_ms: Date.now() - startTime,
|
|
123
125
|
};
|
|
124
126
|
}
|
|
127
|
+
let result;
|
|
125
128
|
// Try Tier -1: Stream log (has mid-turn content, most recent data)
|
|
126
129
|
const streamResult = await this.restoreFromStreamLog(instanceId, sessionId, sessionName || '', lastN);
|
|
127
130
|
if (streamResult.success && streamResult.data) {
|
|
128
|
-
|
|
129
|
-
...streamResult,
|
|
130
|
-
latency_ms: Date.now() - startTime,
|
|
131
|
-
};
|
|
131
|
+
result = streamResult;
|
|
132
132
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
133
|
+
else {
|
|
134
|
+
// Try Tier 0: Local cache
|
|
135
|
+
const localResult = await this.restoreFromLocal(instanceId, sessionId, sessionName || '', lastN);
|
|
136
|
+
if (localResult.success && localResult.data) {
|
|
137
|
+
result = localResult;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
// Try Tier 1: Redis
|
|
141
|
+
const redisResult = await this.restoreFromRedis(sessionName || sessionId, lastN, instanceId);
|
|
142
|
+
if (redisResult.success && redisResult.data) {
|
|
143
|
+
result = redisResult;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Try Tier 2: Supabase
|
|
147
|
+
const supabaseResult = await this.restoreFromSupabase(sessionId, lastN, instanceId);
|
|
148
|
+
if (supabaseResult.success && supabaseResult.data) {
|
|
149
|
+
result = supabaseResult;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// All tiers failed
|
|
153
|
+
return {
|
|
154
|
+
success: false,
|
|
155
|
+
error: `All restore tiers failed. Local: ${localResult.error}, Redis: ${redisResult.error}, Supabase: ${supabaseResult.error}`,
|
|
156
|
+
latency_ms: Date.now() - startTime,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
148
161
|
}
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
...supabaseResult,
|
|
154
|
-
latency_ms: Date.now() - startTime,
|
|
155
|
-
};
|
|
162
|
+
// Rehydrate stubs if any found in restored payload
|
|
163
|
+
if (result.success && result.data) {
|
|
164
|
+
await this.rehydratePayload(result.data);
|
|
156
165
|
}
|
|
157
|
-
// All tiers failed
|
|
158
166
|
return {
|
|
159
|
-
|
|
160
|
-
error: `All restore tiers failed. Local: ${localResult.error}, Redis: ${redisResult.error}, Supabase: ${supabaseResult.error}`,
|
|
167
|
+
...result,
|
|
161
168
|
latency_ms: Date.now() - startTime,
|
|
162
169
|
};
|
|
163
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Scan payload for [ekkOS:page-out:...] stubs and rehydrate from Proxy
|
|
173
|
+
*/
|
|
174
|
+
async rehydratePayload(payload) {
|
|
175
|
+
const stubs = new Set();
|
|
176
|
+
// Scan all restored turns (both user queries and assistant responses)
|
|
177
|
+
// Stubs are injected as user messages, so check user_query too
|
|
178
|
+
for (const turn of payload.restored_turns) {
|
|
179
|
+
for (const text of [turn.user_query, turn.assistant_response]) {
|
|
180
|
+
if (!text)
|
|
181
|
+
continue;
|
|
182
|
+
STUB_REGEX.lastIndex = 0;
|
|
183
|
+
const matches = text.match(STUB_REGEX);
|
|
184
|
+
if (matches) {
|
|
185
|
+
for (const match of matches)
|
|
186
|
+
stubs.add(match);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Also scan latest (both user and assistant)
|
|
191
|
+
for (const text of [payload.latest.user_query, payload.latest.assistant_response]) {
|
|
192
|
+
if (!text)
|
|
193
|
+
continue;
|
|
194
|
+
STUB_REGEX.lastIndex = 0;
|
|
195
|
+
const matches = text.match(STUB_REGEX);
|
|
196
|
+
if (matches) {
|
|
197
|
+
for (const match of matches)
|
|
198
|
+
stubs.add(match);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (stubs.size === 0)
|
|
202
|
+
return;
|
|
203
|
+
console.log(`[Restore] Detected ${stubs.size} stubs, rehydrating...`);
|
|
204
|
+
try {
|
|
205
|
+
const url = `${MEMORY_API_URL}/api/v1/context/rehydrate`;
|
|
206
|
+
const response = await fetch(url, {
|
|
207
|
+
method: 'POST',
|
|
208
|
+
headers: {
|
|
209
|
+
'Authorization': `Bearer ${this.authToken}`,
|
|
210
|
+
'Content-Type': 'application/json',
|
|
211
|
+
},
|
|
212
|
+
body: JSON.stringify({ stubs: Array.from(stubs) }),
|
|
213
|
+
signal: AbortSignal.timeout(10000),
|
|
214
|
+
});
|
|
215
|
+
if (!response.ok) {
|
|
216
|
+
console.warn(`[Restore] Rehydration API failed: ${response.status}`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const { rehydrated } = await response.json();
|
|
220
|
+
if (!rehydrated || !Array.isArray(rehydrated))
|
|
221
|
+
return;
|
|
222
|
+
// Create mapping of stub -> content
|
|
223
|
+
const stubMap = new Map();
|
|
224
|
+
for (const entry of rehydrated) {
|
|
225
|
+
const content = entry.messages.map((m) => {
|
|
226
|
+
const role = m.role === 'user' ? 'U' : 'A';
|
|
227
|
+
const text = typeof m.content === 'string'
|
|
228
|
+
? m.content
|
|
229
|
+
: Array.isArray(m.content)
|
|
230
|
+
? m.content.map((b) => b.text || '').join('\n')
|
|
231
|
+
: '';
|
|
232
|
+
return `${role}: ${text}`;
|
|
233
|
+
}).join('\n\n');
|
|
234
|
+
stubMap.set(entry.stub, content);
|
|
235
|
+
}
|
|
236
|
+
// Replace stubs in turns
|
|
237
|
+
for (const turn of payload.restored_turns) {
|
|
238
|
+
for (const [stub, content] of stubMap.entries()) {
|
|
239
|
+
turn.assistant_response = turn.assistant_response.replace(stub, `[REHYDRATED CONTENT]\n${content}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Replace stubs in latest
|
|
243
|
+
for (const [stub, content] of stubMap.entries()) {
|
|
244
|
+
payload.latest.assistant_response = payload.latest.assistant_response.replace(stub, `[REHYDRATED CONTENT]\n${content}`);
|
|
245
|
+
}
|
|
246
|
+
console.log(`[Restore] Successfully rehydrated ${stubMap.size} stubs`);
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
console.warn(`[Restore] Rehydration error: ${err instanceof Error ? err.message : String(err)}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
164
252
|
/**
|
|
165
253
|
* Tier -1: Restore from stream log (has mid-turn content)
|
|
166
254
|
* This is checked FIRST because stream logs have the most recent data,
|
package/package.json
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekkos/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Setup ekkOS memory for AI coding assistants (Claude Code, Cursor, Windsurf)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ekkos": "dist/index.js",
|
|
8
8
|
"cli": "dist/index.js",
|
|
9
|
-
"ekkos-capture": "dist/cache/capture.js"
|
|
10
|
-
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "tsc",
|
|
13
|
-
"dev": "ts-node src/index.ts",
|
|
14
|
-
"prepack": "node scripts/build-templates.js prepack",
|
|
15
|
-
"postpack": "node scripts/build-templates.js postpack",
|
|
16
|
-
"prepublishOnly": "npm run build"
|
|
9
|
+
"ekkos-capture": "dist/cache/capture.js",
|
|
10
|
+
"ekkos-promote": "dist/cron/promoter.js"
|
|
17
11
|
},
|
|
18
12
|
"keywords": [
|
|
19
13
|
"ekkos",
|
|
@@ -28,13 +22,15 @@
|
|
|
28
22
|
"author": "ekkOS",
|
|
29
23
|
"license": "MIT",
|
|
30
24
|
"dependencies": {
|
|
25
|
+
"@supabase/supabase-js": "^2.39.8",
|
|
31
26
|
"chalk": "^5.3.0",
|
|
32
27
|
"commander": "^12.1.0",
|
|
33
28
|
"inquirer": "^9.2.23",
|
|
34
29
|
"node-pty": "1.2.0-beta.7",
|
|
35
30
|
"open": "^10.0.0",
|
|
36
31
|
"ora": "^8.0.1",
|
|
37
|
-
"ws": "^8.19.0"
|
|
32
|
+
"ws": "^8.19.0",
|
|
33
|
+
"@ekkos/prometheus": "0.1.0"
|
|
38
34
|
},
|
|
39
35
|
"devDependencies": {
|
|
40
36
|
"@types/node": "^20.11.0",
|
|
@@ -46,5 +42,9 @@
|
|
|
46
42
|
"files": [
|
|
47
43
|
"dist",
|
|
48
44
|
"templates"
|
|
49
|
-
]
|
|
50
|
-
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc",
|
|
48
|
+
"dev": "ts-node src/index.ts"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://ekkos.dev/schemas/manifest-v1.json",
|
|
3
3
|
"manifestVersion": "1.0.0",
|
|
4
|
-
"generatedAt": "2026-
|
|
4
|
+
"generatedAt": "2026-02-04T04:30:49.260Z",
|
|
5
5
|
"platforms": {
|
|
6
6
|
"darwin": {
|
|
7
7
|
"configDir": "~/.ekkos",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"source": "hooks/user-prompt-submit.sh",
|
|
69
69
|
"destination": "user-prompt-submit.sh",
|
|
70
70
|
"description": "User prompt submit hook (Unix)",
|
|
71
|
-
"checksum": "
|
|
71
|
+
"checksum": "38aa190d08e2c24129e7f653732a860693fbd2a7015e59226aec57e8b3659114",
|
|
72
72
|
"executable": true
|
|
73
73
|
},
|
|
74
74
|
{
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|