@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.
- package/dist/cache/capture.js +0 -0
- package/dist/commands/dashboard.js +57 -49
- package/dist/commands/hooks.d.ts +25 -36
- package/dist/commands/hooks.js +43 -615
- package/dist/commands/init.js +7 -23
- package/dist/commands/run.js +90 -3
- package/dist/commands/setup.js +10 -352
- package/dist/deploy/hooks.d.ts +8 -5
- package/dist/deploy/hooks.js +12 -105
- package/dist/deploy/settings.d.ts +8 -2
- package/dist/deploy/settings.js +22 -51
- package/dist/index.js +17 -39
- package/dist/utils/state.js +7 -2
- package/package.json +1 -1
- package/templates/CLAUDE.md +82 -292
- package/templates/cursor-rules/ekkos-memory.md +48 -108
- package/templates/windsurf-rules/ekkos-memory.md +62 -64
- package/templates/cursor-hooks/after-agent-response.sh +0 -117
- package/templates/cursor-hooks/before-submit-prompt.sh +0 -419
- package/templates/cursor-hooks/hooks.json +0 -20
- package/templates/cursor-hooks/lib/contract.sh +0 -320
- package/templates/cursor-hooks/stop.sh +0 -75
- package/templates/hooks/assistant-response.ps1 +0 -256
- package/templates/hooks/assistant-response.sh +0 -160
- package/templates/hooks/hooks.json +0 -40
- package/templates/hooks/lib/contract.sh +0 -332
- package/templates/hooks/lib/count-tokens.cjs +0 -86
- package/templates/hooks/lib/ekkos-reminders.sh +0 -98
- package/templates/hooks/lib/state.sh +0 -210
- package/templates/hooks/session-start.ps1 +0 -146
- package/templates/hooks/session-start.sh +0 -353
- package/templates/hooks/stop.ps1 +0 -349
- package/templates/hooks/stop.sh +0 -382
- package/templates/hooks/user-prompt-submit.ps1 +0 -419
- package/templates/hooks/user-prompt-submit.sh +0 -516
- package/templates/project-stubs/session-start.ps1 +0 -63
- package/templates/project-stubs/session-start.sh +0 -55
- package/templates/project-stubs/stop.ps1 +0 -63
- package/templates/project-stubs/stop.sh +0 -55
- package/templates/project-stubs/user-prompt-submit.ps1 +0 -63
- package/templates/project-stubs/user-prompt-submit.sh +0 -55
- package/templates/windsurf-hooks/README.md +0 -212
- package/templates/windsurf-hooks/hooks.json +0 -17
- package/templates/windsurf-hooks/install.sh +0 -148
- package/templates/windsurf-hooks/lib/contract.sh +0 -322
- package/templates/windsurf-hooks/post-cascade-response.sh +0 -251
- package/templates/windsurf-hooks/pre-user-prompt.sh +0 -435
package/dist/cache/capture.js
CHANGED
|
File without changes
|
|
@@ -361,7 +361,7 @@ function findLatestJsonl(projectPath, createdAfterMs) {
|
|
|
361
361
|
projectPath.replace(/\//g, '-'), // macOS: /Users/name → -Users-name
|
|
362
362
|
]);
|
|
363
363
|
const projectsRoot = path.join(os.homedir(), '.claude', 'projects');
|
|
364
|
-
for (const encoded of candidateEncodings) {
|
|
364
|
+
for (const encoded of Array.from(candidateEncodings)) {
|
|
365
365
|
const projectDir = path.join(projectsRoot, encoded);
|
|
366
366
|
if (!fs.existsSync(projectDir))
|
|
367
367
|
continue;
|
|
@@ -392,7 +392,7 @@ function findJsonlBySessionId(projectPath, sessionId) {
|
|
|
392
392
|
projectPath.replace(/\//g, '-'),
|
|
393
393
|
]);
|
|
394
394
|
const projectsRoot = path.join(os.homedir(), '.claude', 'projects');
|
|
395
|
-
for (const encoded of candidateEncodings) {
|
|
395
|
+
for (const encoded of Array.from(candidateEncodings)) {
|
|
396
396
|
const exactPath = path.join(projectsRoot, encoded, `${sessionId}.jsonl`);
|
|
397
397
|
if (fs.existsSync(exactPath))
|
|
398
398
|
return exactPath;
|
|
@@ -425,15 +425,11 @@ async function waitForNewSession() {
|
|
|
425
425
|
catch { }
|
|
426
426
|
console.log(chalk_1.default.gray(' Waiting for new session to start...'));
|
|
427
427
|
const maxWaitMs = 120000; // 2 minutes max
|
|
428
|
-
const pollMs =
|
|
428
|
+
const pollMs = 1000; // 1s poll — faster than before since we exit early
|
|
429
429
|
const startWait = Date.now();
|
|
430
430
|
let candidateName = null;
|
|
431
431
|
while (Date.now() - startWait < maxWaitMs) {
|
|
432
432
|
// ── Windows hook hint file ──────────────────────────────────────────────
|
|
433
|
-
// On Windows, active-sessions.json is never populated because hook processes
|
|
434
|
-
// are short-lived and their PIDs are dead by the time we poll. Instead, the
|
|
435
|
-
// user-prompt-submit.ps1 hook writes ~/.ekkos/hook-session-hint.json with
|
|
436
|
-
// { sessionName, sessionId, projectPath, ts } on every turn. Read it here.
|
|
437
433
|
const hintPath = path.join(state_js_1.EKKOS_DIR, 'hook-session-hint.json');
|
|
438
434
|
try {
|
|
439
435
|
if (fs.existsSync(hintPath)) {
|
|
@@ -443,37 +439,34 @@ async function waitForNewSession() {
|
|
|
443
439
|
const jsonlPath = findJsonlBySessionId(hint.projectPath, hint.sessionId || '')
|
|
444
440
|
|| findLatestJsonl(hint.projectPath, launchTs)
|
|
445
441
|
|| resolveJsonlPath(hint.sessionName, launchTs);
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
442
|
+
// Return immediately — JSONL may be null; dashboard will lazy-resolve
|
|
443
|
+
console.log(chalk_1.default.green(` Found session (hook hint): ${hint.sessionName}`));
|
|
444
|
+
return { sessionName: hint.sessionName, jsonlPath, launchCwd: hint.projectPath || launchCwd, launchTs };
|
|
450
445
|
}
|
|
451
446
|
}
|
|
452
447
|
}
|
|
453
448
|
catch { /* ignore */ }
|
|
454
449
|
// ── Standard: active-sessions.json (works on Mac/Linux) ────────────────
|
|
455
450
|
const sessions = (0, state_js_1.getActiveSessions)();
|
|
456
|
-
// Find sessions that started after our launch
|
|
457
451
|
for (const s of sessions) {
|
|
458
452
|
const startedMs = new Date(s.startedAt).getTime();
|
|
459
453
|
if (startedMs >= launchTs - 2000) {
|
|
460
454
|
candidateName = s.sessionName;
|
|
461
|
-
// Try
|
|
462
|
-
|
|
463
|
-
if (jsonlPath) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
for (const pp of allPaths) {
|
|
471
|
-
const latestJsonl = findLatestJsonl(pp, launchTs);
|
|
472
|
-
if (latestJsonl) {
|
|
473
|
-
console.log(chalk_1.default.green(` Found session: ${s.sessionName}`));
|
|
474
|
-
return { sessionName: s.sessionName, jsonlPath: latestJsonl };
|
|
455
|
+
// Try to resolve JSONL immediately (may succeed if Claude already created it)
|
|
456
|
+
let jsonlPath = resolveJsonlPath(s.sessionName, launchTs);
|
|
457
|
+
if (!jsonlPath) {
|
|
458
|
+
const allPaths = new Set(sessions.filter(x => x.sessionName === s.sessionName && x.projectPath)
|
|
459
|
+
.map(x => x.projectPath));
|
|
460
|
+
for (const pp of Array.from(allPaths)) {
|
|
461
|
+
jsonlPath = findLatestJsonl(pp, launchTs);
|
|
462
|
+
if (jsonlPath)
|
|
463
|
+
break;
|
|
475
464
|
}
|
|
476
465
|
}
|
|
466
|
+
// Return immediately with session name — JSONL may still be null
|
|
467
|
+
// (Claude Code hasn't created it yet). Dashboard will lazy-resolve.
|
|
468
|
+
console.log(chalk_1.default.green(` Found session: ${s.sessionName}`));
|
|
469
|
+
return { sessionName: s.sessionName, jsonlPath, launchCwd: s.projectPath || launchCwd, launchTs };
|
|
477
470
|
}
|
|
478
471
|
}
|
|
479
472
|
// Fallback: use launch CWD to find any new JSONL
|
|
@@ -482,12 +475,10 @@ async function waitForNewSession() {
|
|
|
482
475
|
if (latestJsonl) {
|
|
483
476
|
const name = candidateName || path.basename(latestJsonl, '.jsonl');
|
|
484
477
|
console.log(chalk_1.default.green(` Found session via CWD: ${name}`));
|
|
485
|
-
return { sessionName: name, jsonlPath: latestJsonl };
|
|
478
|
+
return { sessionName: name, jsonlPath: latestJsonl, launchCwd, launchTs };
|
|
486
479
|
}
|
|
487
480
|
}
|
|
488
|
-
// Broad fallback: scan ALL project directories for any new JSONL
|
|
489
|
-
// Claude creates the JSONL immediately when a session starts, before the first message.
|
|
490
|
-
// This catches cases where path encoding doesn't match.
|
|
481
|
+
// Broad fallback: scan ALL project directories for any new JSONL
|
|
491
482
|
{
|
|
492
483
|
const projectsRoot = path.join(os.homedir(), '.claude', 'projects');
|
|
493
484
|
try {
|
|
@@ -512,12 +503,11 @@ async function waitForNewSession() {
|
|
|
512
503
|
.sort((a, b) => b.birthtime - a.birthtime);
|
|
513
504
|
if (allNewJsonl.length > 0) {
|
|
514
505
|
const jsonlPath = allNewJsonl[0].path;
|
|
515
|
-
// Derive name from filename (e.g. abc123.jsonl → use candidateName if set, else basename)
|
|
516
506
|
const baseName = path.basename(jsonlPath, '.jsonl');
|
|
517
507
|
const derivedName = /^[0-9a-f]{8}-/.test(baseName) ? (0, state_js_1.uuidToWords)(baseName) : baseName;
|
|
518
508
|
const name = candidateName || derivedName;
|
|
519
509
|
console.log(chalk_1.default.green(` Found session via scan: ${name}`));
|
|
520
|
-
return { sessionName: name, jsonlPath };
|
|
510
|
+
return { sessionName: name, jsonlPath, launchCwd, launchTs };
|
|
521
511
|
}
|
|
522
512
|
}
|
|
523
513
|
}
|
|
@@ -526,20 +516,18 @@ async function waitForNewSession() {
|
|
|
526
516
|
await sleep(pollMs);
|
|
527
517
|
process.stdout.write(chalk_1.default.gray('.'));
|
|
528
518
|
}
|
|
529
|
-
// Timeout —
|
|
519
|
+
// Timeout — return candidate if we have one (dashboard will lazy-resolve JSONL)
|
|
530
520
|
if (candidateName) {
|
|
531
|
-
console.log(chalk_1.default.yellow(`\n Timeout.
|
|
521
|
+
console.log(chalk_1.default.yellow(`\n Timeout. Launching with ${candidateName}...`));
|
|
532
522
|
const jsonlPath = resolveJsonlPath(candidateName, launchTs);
|
|
533
|
-
|
|
534
|
-
return { sessionName: candidateName, jsonlPath };
|
|
523
|
+
return { sessionName: candidateName, jsonlPath, launchCwd, launchTs };
|
|
535
524
|
}
|
|
536
525
|
// Last resort: latest session overall
|
|
537
526
|
const latestName = getLatestSession();
|
|
538
527
|
if (latestName) {
|
|
539
528
|
console.log(chalk_1.default.yellow(`\n Falling back to latest: ${latestName}`));
|
|
540
529
|
const jsonlPath = resolveJsonlPath(latestName);
|
|
541
|
-
|
|
542
|
-
return { sessionName: latestName, jsonlPath };
|
|
530
|
+
return { sessionName: latestName, jsonlPath, launchCwd, launchTs };
|
|
543
531
|
}
|
|
544
532
|
console.log(chalk_1.default.red(' No session found.'));
|
|
545
533
|
process.exit(1);
|
|
@@ -548,7 +536,8 @@ function sleep(ms) {
|
|
|
548
536
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
549
537
|
}
|
|
550
538
|
// ── TUI Dashboard ──
|
|
551
|
-
async function launchDashboard(initialSessionName,
|
|
539
|
+
async function launchDashboard(initialSessionName, initialJsonlPath, refreshMs, launchCwd, launchTs) {
|
|
540
|
+
let jsonlPath = initialJsonlPath;
|
|
552
541
|
let sessionName = displaySessionName(initialSessionName);
|
|
553
542
|
const blessed = require('blessed');
|
|
554
543
|
const contrib = require('blessed-contrib');
|
|
@@ -958,11 +947,30 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
958
947
|
}
|
|
959
948
|
function updateDashboard() {
|
|
960
949
|
ensureLayoutSynced();
|
|
950
|
+
// ── Lazy JSONL resolution ──────────────────────────────────────────────
|
|
951
|
+
// Dashboard may launch before Claude Code creates the JSONL file.
|
|
952
|
+
// Keep trying to find it on each poll tick.
|
|
953
|
+
if (!jsonlPath || !fs.existsSync(jsonlPath)) {
|
|
954
|
+
const resolved = resolveJsonlPath(initialSessionName, launchTs)
|
|
955
|
+
|| (launchCwd ? findLatestJsonl(launchCwd, launchTs) : null);
|
|
956
|
+
if (resolved) {
|
|
957
|
+
jsonlPath = resolved;
|
|
958
|
+
dlog(`Lazy-resolved JSONL: ${jsonlPath}`);
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
// Still no JSONL — render the header/footer so the dashboard isn't blank
|
|
962
|
+
renderHeader();
|
|
963
|
+
try {
|
|
964
|
+
screen.render();
|
|
965
|
+
}
|
|
966
|
+
catch { }
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
961
970
|
// Resolve "initializing" → real session name from JSONL UUID filename
|
|
962
971
|
if (sessionName === 'initializing' || sessionName === 'session') {
|
|
963
972
|
try {
|
|
964
973
|
const basename = path.basename(jsonlPath, '.jsonl');
|
|
965
|
-
// JSONL filename is the session UUID (e.g., 607bd8e4-0a04-4db2-acf5-3f794be0f956.jsonl)
|
|
966
974
|
if (/^[0-9a-f]{8}-/.test(basename)) {
|
|
967
975
|
sessionName = displaySessionName(basename);
|
|
968
976
|
screen.title = `ekkOS - ${sessionName}`;
|
|
@@ -1057,24 +1065,24 @@ async function launchDashboard(initialSessionName, jsonlPath, refreshMs) {
|
|
|
1057
1065
|
// wrap by one char if this is too tight, which pushes Cost to next line.
|
|
1058
1066
|
const w = Math.max(18, turnBox.width - 4); // usable content width
|
|
1059
1067
|
const div = '│';
|
|
1060
|
-
|
|
1068
|
+
const pad = (s, width) => {
|
|
1061
1069
|
if (s.length >= width)
|
|
1062
1070
|
return s.slice(0, width);
|
|
1063
1071
|
return s + ' '.repeat(width - s.length);
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1072
|
+
};
|
|
1073
|
+
const rpad = (s, width) => {
|
|
1066
1074
|
if (s.length >= width)
|
|
1067
1075
|
return s.slice(0, width);
|
|
1068
1076
|
return ' '.repeat(width - s.length) + s;
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1077
|
+
};
|
|
1078
|
+
const cpad = (s, width) => {
|
|
1071
1079
|
if (s.length >= width)
|
|
1072
1080
|
return s.slice(0, width);
|
|
1073
1081
|
const total = width - s.length;
|
|
1074
1082
|
const left = Math.floor(total / 2);
|
|
1075
1083
|
const right = total - left;
|
|
1076
1084
|
return ' '.repeat(left) + s + ' '.repeat(right);
|
|
1077
|
-
}
|
|
1085
|
+
};
|
|
1078
1086
|
// Data rows — RENDER ALL TURNS for full scrollback history
|
|
1079
1087
|
// Don't slice to visibleRows only — let user scroll through entire session
|
|
1080
1088
|
const turns = data.turns.slice().reverse();
|
|
@@ -1443,10 +1451,10 @@ exports.dashboardCommand = new commander_1.Command('dashboard')
|
|
|
1443
1451
|
.option('--compact', 'Minimal layout for small terminals')
|
|
1444
1452
|
.action(async (sessionNameArg, options) => {
|
|
1445
1453
|
const refreshMs = parseInt(options.refresh) || 2000;
|
|
1446
|
-
// --wait-for-new: poll until
|
|
1454
|
+
// --wait-for-new: poll until session name appears (JSONL may not exist yet)
|
|
1447
1455
|
if (options.waitForNew) {
|
|
1448
|
-
const
|
|
1449
|
-
await launchDashboard(sessionName, jsonlPath, refreshMs);
|
|
1456
|
+
const result = await waitForNewSession();
|
|
1457
|
+
await launchDashboard(result.sessionName, result.jsonlPath, refreshMs, result.launchCwd, result.launchTs);
|
|
1450
1458
|
return;
|
|
1451
1459
|
}
|
|
1452
1460
|
let sessionName = null;
|
package/dist/commands/hooks.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ekkOS CLI: hooks subcommand
|
|
3
|
-
* Implements: ekkos hooks install | verify | status
|
|
2
|
+
* ekkOS CLI: hooks subcommand — DEPRECATED
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Hooks are no longer needed. ekkOS CLI and proxy handle everything.
|
|
5
|
+
* Use `ekkos run` to start.
|
|
6
|
+
*
|
|
7
|
+
* This command is kept for backwards compatibility. The install/verify/status
|
|
8
|
+
* subcommands print a deprecation notice and exit cleanly. Utility functions
|
|
9
|
+
* (findProjectRoot, expandPath, loadManifest) are preserved for internal use
|
|
10
|
+
* by other commands (e.g. doctor).
|
|
9
11
|
*/
|
|
12
|
+
export declare function expandPath(p: string): string;
|
|
13
|
+
export declare function findProjectRoot(startDir?: string): string;
|
|
10
14
|
interface ManifestFile {
|
|
11
15
|
source: string;
|
|
12
16
|
destination: string;
|
|
@@ -61,49 +65,34 @@ interface Manifest {
|
|
|
61
65
|
checksumAlgorithm: string;
|
|
62
66
|
};
|
|
63
67
|
}
|
|
64
|
-
interface VerifyResult {
|
|
65
|
-
status: 'PASS' | 'WARN' | 'FAIL';
|
|
66
|
-
issues: Array<{
|
|
67
|
-
severity: 'error' | 'warning';
|
|
68
|
-
file?: string;
|
|
69
|
-
message: string;
|
|
70
|
-
}>;
|
|
71
|
-
}
|
|
72
|
-
declare function expandPath(p: string): string;
|
|
73
|
-
/**
|
|
74
|
-
* Find the ekkos-manifest.json file
|
|
75
|
-
* Search order per spec:
|
|
76
|
-
* 1. <packageRoot>/templates/ekkos-manifest.json
|
|
77
|
-
* 2. <distRoot>/../templates/ekkos-manifest.json
|
|
78
|
-
* 3. In monorepo dev: <repoRoot>/templates/ekkos-manifest.json
|
|
79
|
-
*/
|
|
80
68
|
export declare function findManifest(): {
|
|
81
69
|
path: string;
|
|
82
70
|
templatesDir: string;
|
|
83
71
|
} | null;
|
|
84
|
-
/**
|
|
85
|
-
* Load manifest from disk
|
|
86
|
-
*/
|
|
87
72
|
export declare function loadManifest(): {
|
|
88
73
|
manifest: Manifest;
|
|
89
74
|
path: string;
|
|
90
75
|
templatesDir: string;
|
|
91
76
|
} | null;
|
|
92
|
-
|
|
93
|
-
|
|
77
|
+
export interface VerifyResult {
|
|
78
|
+
status: 'PASS' | 'WARN' | 'FAIL';
|
|
79
|
+
issues: Array<{
|
|
80
|
+
severity: 'error' | 'warning';
|
|
81
|
+
file?: string;
|
|
82
|
+
message: string;
|
|
83
|
+
}>;
|
|
84
|
+
}
|
|
85
|
+
export declare function hooksInstall(_options: {
|
|
94
86
|
global?: boolean;
|
|
95
87
|
project?: boolean;
|
|
96
88
|
verbose?: boolean;
|
|
97
|
-
}
|
|
98
|
-
export declare function
|
|
99
|
-
interface VerifyOptions {
|
|
89
|
+
}): Promise<void>;
|
|
90
|
+
export declare function hooksVerify(_options: {
|
|
100
91
|
global?: boolean;
|
|
101
92
|
project?: boolean;
|
|
102
93
|
verbose?: boolean;
|
|
103
|
-
}
|
|
104
|
-
export declare function
|
|
105
|
-
interface StatusOptions {
|
|
94
|
+
}): Promise<VerifyResult>;
|
|
95
|
+
export declare function hooksStatus(_options: {
|
|
106
96
|
verbose?: boolean;
|
|
107
|
-
}
|
|
108
|
-
export
|
|
109
|
-
export { findProjectRoot, expandPath };
|
|
97
|
+
}): Promise<void>;
|
|
98
|
+
export {};
|