@ekkos/cli 0.2.7 → 0.2.8
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/types.d.ts +11 -2
- package/dist/capture/index.d.ts +8 -0
- package/dist/capture/index.js +24 -0
- package/dist/capture/stream-tailer.d.ts +138 -0
- package/dist/capture/stream-tailer.js +658 -0
- package/dist/capture/types.d.ts +227 -0
- package/dist/capture/types.js +5 -0
- package/dist/commands/run.js +117 -7
- package/dist/commands/stream.d.ts +19 -0
- package/dist/commands/stream.js +340 -0
- package/dist/index.js +58 -1
- package/dist/restore/RestoreOrchestrator.d.ts +6 -0
- package/dist/restore/RestoreOrchestrator.js +174 -22
- package/dist/utils/state.d.ts +39 -1
- package/dist/utils/state.js +152 -2
- package/package.json +1 -1
package/dist/utils/state.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
export declare const EKKOS_DIR: string;
|
|
2
2
|
export declare const STATE_FILE: string;
|
|
3
|
+
export declare const ACTIVE_SESSIONS_FILE: string;
|
|
3
4
|
export declare const AUTO_CLEAR_FLAG: string;
|
|
4
5
|
export declare const CONFIG_FILE: string;
|
|
6
|
+
export interface ActiveSession {
|
|
7
|
+
sessionId: string;
|
|
8
|
+
sessionName: string;
|
|
9
|
+
pid: number;
|
|
10
|
+
startedAt: string;
|
|
11
|
+
projectPath: string;
|
|
12
|
+
lastHeartbeat: string;
|
|
13
|
+
}
|
|
5
14
|
export interface EkkosState {
|
|
6
15
|
sessionName: string;
|
|
7
16
|
sessionId: string;
|
|
@@ -20,7 +29,7 @@ export interface EkkosConfig {
|
|
|
20
29
|
*/
|
|
21
30
|
export declare function uuidToWords(uuid: string): string;
|
|
22
31
|
/**
|
|
23
|
-
* Ensure .ekkos directory exists
|
|
32
|
+
* Ensure .ekkos directory exists and has required files
|
|
24
33
|
*/
|
|
25
34
|
export declare function ensureEkkosDir(): void;
|
|
26
35
|
/**
|
|
@@ -64,3 +73,32 @@ export declare function getMostRecentSession(): {
|
|
|
64
73
|
sessionId: string;
|
|
65
74
|
lastActive: string;
|
|
66
75
|
} | null;
|
|
76
|
+
/**
|
|
77
|
+
* Get all active sessions (filters out stale ones with dead PIDs)
|
|
78
|
+
*/
|
|
79
|
+
export declare function getActiveSessions(): ActiveSession[];
|
|
80
|
+
/**
|
|
81
|
+
* Register a new active session (for swarm tracking)
|
|
82
|
+
*/
|
|
83
|
+
export declare function registerActiveSession(sessionId: string, sessionName: string, projectPath: string): ActiveSession;
|
|
84
|
+
/**
|
|
85
|
+
* Update heartbeat for current process's session
|
|
86
|
+
*/
|
|
87
|
+
export declare function heartbeatActiveSession(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Unregister current process's session (on clean exit)
|
|
90
|
+
*/
|
|
91
|
+
export declare function unregisterActiveSession(): void;
|
|
92
|
+
/**
|
|
93
|
+
* Get the active session for current PID (if any)
|
|
94
|
+
*/
|
|
95
|
+
export declare function getCurrentProcessSession(): ActiveSession | null;
|
|
96
|
+
/**
|
|
97
|
+
* Get active session by session name
|
|
98
|
+
*/
|
|
99
|
+
export declare function getActiveSessionByName(sessionName: string): ActiveSession | null;
|
|
100
|
+
/**
|
|
101
|
+
* Update the session for the current PID (when session name is detected)
|
|
102
|
+
* This does NOT write to the global state.json - only to active-sessions.json
|
|
103
|
+
*/
|
|
104
|
+
export declare function updateCurrentProcessSession(sessionId: string, sessionName: string): void;
|
package/dist/utils/state.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.CONFIG_FILE = exports.AUTO_CLEAR_FLAG = exports.STATE_FILE = exports.EKKOS_DIR = void 0;
|
|
39
|
+
exports.CONFIG_FILE = exports.AUTO_CLEAR_FLAG = exports.ACTIVE_SESSIONS_FILE = exports.STATE_FILE = exports.EKKOS_DIR = void 0;
|
|
40
40
|
exports.uuidToWords = uuidToWords;
|
|
41
41
|
exports.ensureEkkosDir = ensureEkkosDir;
|
|
42
42
|
exports.getState = getState;
|
|
@@ -47,12 +47,20 @@ exports.getAuthToken = getAuthToken;
|
|
|
47
47
|
exports.clearAutoClearFlag = clearAutoClearFlag;
|
|
48
48
|
exports.parseAutoClearFlag = parseAutoClearFlag;
|
|
49
49
|
exports.getMostRecentSession = getMostRecentSession;
|
|
50
|
+
exports.getActiveSessions = getActiveSessions;
|
|
51
|
+
exports.registerActiveSession = registerActiveSession;
|
|
52
|
+
exports.heartbeatActiveSession = heartbeatActiveSession;
|
|
53
|
+
exports.unregisterActiveSession = unregisterActiveSession;
|
|
54
|
+
exports.getCurrentProcessSession = getCurrentProcessSession;
|
|
55
|
+
exports.getActiveSessionByName = getActiveSessionByName;
|
|
56
|
+
exports.updateCurrentProcessSession = updateCurrentProcessSession;
|
|
50
57
|
const fs = __importStar(require("fs"));
|
|
51
58
|
const path = __importStar(require("path"));
|
|
52
59
|
const os = __importStar(require("os"));
|
|
53
60
|
// State file paths
|
|
54
61
|
exports.EKKOS_DIR = path.join(os.homedir(), '.ekkos');
|
|
55
62
|
exports.STATE_FILE = path.join(exports.EKKOS_DIR, 'state.json');
|
|
63
|
+
exports.ACTIVE_SESSIONS_FILE = path.join(exports.EKKOS_DIR, 'active-sessions.json');
|
|
56
64
|
exports.AUTO_CLEAR_FLAG = path.join(exports.EKKOS_DIR, 'auto-clear.flag');
|
|
57
65
|
exports.CONFIG_FILE = path.join(exports.EKKOS_DIR, 'config.json');
|
|
58
66
|
// SINGLE SOURCE OF TRUTH: Import word lists from shared JSON
|
|
@@ -81,12 +89,27 @@ function uuidToWords(uuid) {
|
|
|
81
89
|
return `${ADJECTIVES[adjIdx]}-${NOUNS[nounIdx]}-${VERBS[verbIdx]}`;
|
|
82
90
|
}
|
|
83
91
|
/**
|
|
84
|
-
* Ensure .ekkos directory exists
|
|
92
|
+
* Ensure .ekkos directory exists and has required files
|
|
85
93
|
*/
|
|
86
94
|
function ensureEkkosDir() {
|
|
87
95
|
if (!fs.existsSync(exports.EKKOS_DIR)) {
|
|
88
96
|
fs.mkdirSync(exports.EKKOS_DIR, { recursive: true });
|
|
89
97
|
}
|
|
98
|
+
// Copy session-words.json to ~/.ekkos/ if not present
|
|
99
|
+
// This is the SINGLE SOURCE OF TRUTH for session name generation
|
|
100
|
+
// Used by: CLI, hooks, Time Machine, extension
|
|
101
|
+
const destWords = path.join(exports.EKKOS_DIR, 'session-words.json');
|
|
102
|
+
if (!fs.existsSync(destWords)) {
|
|
103
|
+
try {
|
|
104
|
+
const srcWords = path.join(__dirname, 'session-words.json');
|
|
105
|
+
if (fs.existsSync(srcWords)) {
|
|
106
|
+
fs.copyFileSync(srcWords, destWords);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Ignore - file will be created manually or by another component
|
|
111
|
+
}
|
|
112
|
+
}
|
|
90
113
|
}
|
|
91
114
|
/**
|
|
92
115
|
* Get current session state
|
|
@@ -220,3 +243,130 @@ function getMostRecentSession() {
|
|
|
220
243
|
return null;
|
|
221
244
|
}
|
|
222
245
|
}
|
|
246
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
247
|
+
// MULTI-SESSION/SWARM SUPPORT
|
|
248
|
+
// Track multiple concurrent Claude Code sessions without state collision
|
|
249
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
250
|
+
/**
|
|
251
|
+
* Get all active sessions (filters out stale ones with dead PIDs)
|
|
252
|
+
*/
|
|
253
|
+
function getActiveSessions() {
|
|
254
|
+
ensureEkkosDir();
|
|
255
|
+
try {
|
|
256
|
+
if (!fs.existsSync(exports.ACTIVE_SESSIONS_FILE)) {
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
const content = fs.readFileSync(exports.ACTIVE_SESSIONS_FILE, 'utf-8');
|
|
260
|
+
const sessions = JSON.parse(content);
|
|
261
|
+
// Filter out sessions with dead PIDs
|
|
262
|
+
const alive = sessions.filter(s => isProcessAlive(s.pid));
|
|
263
|
+
// If we filtered any out, update the file
|
|
264
|
+
if (alive.length !== sessions.length) {
|
|
265
|
+
fs.writeFileSync(exports.ACTIVE_SESSIONS_FILE, JSON.stringify(alive, null, 2));
|
|
266
|
+
}
|
|
267
|
+
return alive;
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Register a new active session (for swarm tracking)
|
|
275
|
+
*/
|
|
276
|
+
function registerActiveSession(sessionId, sessionName, projectPath) {
|
|
277
|
+
ensureEkkosDir();
|
|
278
|
+
const sessions = getActiveSessions();
|
|
279
|
+
const now = new Date().toISOString();
|
|
280
|
+
const pid = process.pid;
|
|
281
|
+
// Remove any existing entry for this PID (process restart)
|
|
282
|
+
const filtered = sessions.filter(s => s.pid !== pid);
|
|
283
|
+
const newSession = {
|
|
284
|
+
sessionId,
|
|
285
|
+
sessionName,
|
|
286
|
+
pid,
|
|
287
|
+
startedAt: now,
|
|
288
|
+
projectPath,
|
|
289
|
+
lastHeartbeat: now
|
|
290
|
+
};
|
|
291
|
+
filtered.push(newSession);
|
|
292
|
+
fs.writeFileSync(exports.ACTIVE_SESSIONS_FILE, JSON.stringify(filtered, null, 2));
|
|
293
|
+
return newSession;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Update heartbeat for current process's session
|
|
297
|
+
*/
|
|
298
|
+
function heartbeatActiveSession() {
|
|
299
|
+
try {
|
|
300
|
+
const sessions = getActiveSessions();
|
|
301
|
+
const pid = process.pid;
|
|
302
|
+
const idx = sessions.findIndex(s => s.pid === pid);
|
|
303
|
+
if (idx !== -1) {
|
|
304
|
+
sessions[idx].lastHeartbeat = new Date().toISOString();
|
|
305
|
+
fs.writeFileSync(exports.ACTIVE_SESSIONS_FILE, JSON.stringify(sessions, null, 2));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// Ignore heartbeat errors
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Unregister current process's session (on clean exit)
|
|
314
|
+
*/
|
|
315
|
+
function unregisterActiveSession() {
|
|
316
|
+
try {
|
|
317
|
+
const sessions = getActiveSessions();
|
|
318
|
+
const pid = process.pid;
|
|
319
|
+
const filtered = sessions.filter(s => s.pid !== pid);
|
|
320
|
+
fs.writeFileSync(exports.ACTIVE_SESSIONS_FILE, JSON.stringify(filtered, null, 2));
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
// Ignore unregister errors on exit
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Get the active session for current PID (if any)
|
|
328
|
+
*/
|
|
329
|
+
function getCurrentProcessSession() {
|
|
330
|
+
const sessions = getActiveSessions();
|
|
331
|
+
return sessions.find(s => s.pid === process.pid) || null;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Get active session by session name
|
|
335
|
+
*/
|
|
336
|
+
function getActiveSessionByName(sessionName) {
|
|
337
|
+
const sessions = getActiveSessions();
|
|
338
|
+
return sessions.find(s => s.sessionName === sessionName) || null;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Check if a process is still alive
|
|
342
|
+
*/
|
|
343
|
+
function isProcessAlive(pid) {
|
|
344
|
+
try {
|
|
345
|
+
// Sending signal 0 doesn't kill the process, just checks if it exists
|
|
346
|
+
process.kill(pid, 0);
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Update the session for the current PID (when session name is detected)
|
|
355
|
+
* This does NOT write to the global state.json - only to active-sessions.json
|
|
356
|
+
*/
|
|
357
|
+
function updateCurrentProcessSession(sessionId, sessionName) {
|
|
358
|
+
try {
|
|
359
|
+
const sessions = getActiveSessions();
|
|
360
|
+
const pid = process.pid;
|
|
361
|
+
const idx = sessions.findIndex(s => s.pid === pid);
|
|
362
|
+
if (idx !== -1) {
|
|
363
|
+
sessions[idx].sessionId = sessionId;
|
|
364
|
+
sessions[idx].sessionName = sessionName;
|
|
365
|
+
sessions[idx].lastHeartbeat = new Date().toISOString();
|
|
366
|
+
fs.writeFileSync(exports.ACTIVE_SESSIONS_FILE, JSON.stringify(sessions, null, 2));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
// Ignore errors
|
|
371
|
+
}
|
|
372
|
+
}
|