@ekkos/cli 0.2.9 → 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.
Files changed (35) hide show
  1. package/dist/cache/LocalSessionStore.d.ts +34 -21
  2. package/dist/cache/LocalSessionStore.js +169 -53
  3. package/dist/cache/capture.d.ts +19 -11
  4. package/dist/cache/capture.js +243 -76
  5. package/dist/cache/types.d.ts +14 -1
  6. package/dist/commands/doctor.d.ts +10 -0
  7. package/dist/commands/doctor.js +148 -73
  8. package/dist/commands/hooks.d.ts +109 -0
  9. package/dist/commands/hooks.js +668 -0
  10. package/dist/commands/run.d.ts +1 -0
  11. package/dist/commands/run.js +69 -21
  12. package/dist/index.js +42 -1
  13. package/dist/restore/RestoreOrchestrator.d.ts +17 -3
  14. package/dist/restore/RestoreOrchestrator.js +64 -22
  15. package/dist/utils/paths.d.ts +125 -0
  16. package/dist/utils/paths.js +283 -0
  17. package/package.json +1 -1
  18. package/templates/ekkos-manifest.json +223 -0
  19. package/templates/helpers/json-parse.cjs +101 -0
  20. package/templates/hooks/assistant-response.ps1 +256 -0
  21. package/templates/hooks/assistant-response.sh +124 -64
  22. package/templates/hooks/session-start.ps1 +107 -2
  23. package/templates/hooks/session-start.sh +201 -166
  24. package/templates/hooks/stop.ps1 +124 -3
  25. package/templates/hooks/stop.sh +470 -843
  26. package/templates/hooks/user-prompt-submit.ps1 +107 -22
  27. package/templates/hooks/user-prompt-submit.sh +403 -393
  28. package/templates/project-stubs/session-start.ps1 +63 -0
  29. package/templates/project-stubs/session-start.sh +55 -0
  30. package/templates/project-stubs/stop.ps1 +63 -0
  31. package/templates/project-stubs/stop.sh +55 -0
  32. package/templates/project-stubs/user-prompt-submit.ps1 +63 -0
  33. package/templates/project-stubs/user-prompt-submit.sh +55 -0
  34. package/templates/shared/hooks-enabled.json +22 -0
  35. package/templates/shared/session-words.json +45 -0
@@ -0,0 +1,668 @@
1
+ "use strict";
2
+ /**
3
+ * ekkOS CLI: hooks subcommand
4
+ * Implements: ekkos hooks install | verify | status
5
+ *
6
+ * Per ekkOS Onboarding Spec v1.2 + Addendum:
7
+ * - Manifest-driven deployment
8
+ * - SHA256 checksum verification
9
+ * - Installed-state manifest tracking
10
+ */
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.findManifest = findManifest;
16
+ exports.loadManifest = loadManifest;
17
+ exports.hooksInstall = hooksInstall;
18
+ exports.hooksVerify = hooksVerify;
19
+ exports.hooksStatus = hooksStatus;
20
+ exports.findProjectRoot = findProjectRoot;
21
+ exports.expandPath = expandPath;
22
+ const fs_1 = require("fs");
23
+ const path_1 = require("path");
24
+ const os_1 = require("os");
25
+ const crypto_1 = require("crypto");
26
+ const chalk_1 = __importDefault(require("chalk"));
27
+ // ═══════════════════════════════════════════════════════════════════════════
28
+ // CONSTANTS
29
+ // ═══════════════════════════════════════════════════════════════════════════
30
+ const isWindows = (0, os_1.platform)() === 'win32';
31
+ const HOME_DIR = (0, os_1.homedir)();
32
+ // Resolved paths
33
+ function expandPath(p) {
34
+ return p
35
+ .replace(/^~/, HOME_DIR)
36
+ .replace(/%USERPROFILE%/g, HOME_DIR);
37
+ }
38
+ // ═══════════════════════════════════════════════════════════════════════════
39
+ // MANIFEST DISCOVERY
40
+ // ═══════════════════════════════════════════════════════════════════════════
41
+ /**
42
+ * Find the ekkos-manifest.json file
43
+ * Search order per spec:
44
+ * 1. <packageRoot>/templates/ekkos-manifest.json
45
+ * 2. <distRoot>/../templates/ekkos-manifest.json
46
+ * 3. In monorepo dev: <repoRoot>/templates/ekkos-manifest.json
47
+ */
48
+ function findManifest() {
49
+ const candidates = [];
50
+ // 1. Package root (npm install location)
51
+ const packageRoot = (0, path_1.join)((0, path_1.dirname)((0, path_1.dirname)(__dirname)), 'templates', 'ekkos-manifest.json');
52
+ candidates.push(packageRoot);
53
+ // 2. Dist root (compiled location)
54
+ const distRoot = (0, path_1.join)((0, path_1.dirname)((0, path_1.dirname)((0, path_1.dirname)(__dirname))), 'templates', 'ekkos-manifest.json');
55
+ candidates.push(distRoot);
56
+ // 3. Monorepo dev (presence of templates/ at repo root)
57
+ const repoRoot = (0, path_1.join)((0, path_1.dirname)((0, path_1.dirname)((0, path_1.dirname)((0, path_1.dirname)((0, path_1.dirname)(__dirname))))), 'templates', 'ekkos-manifest.json');
58
+ candidates.push(repoRoot);
59
+ // Also check from current __dirname variations
60
+ const fromDirname = (0, path_1.join)(__dirname, '..', '..', 'templates', 'ekkos-manifest.json');
61
+ candidates.push(fromDirname);
62
+ for (const candidate of candidates) {
63
+ if ((0, fs_1.existsSync)(candidate)) {
64
+ return {
65
+ path: candidate,
66
+ templatesDir: (0, path_1.dirname)(candidate)
67
+ };
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+ /**
73
+ * Load manifest from disk
74
+ */
75
+ function loadManifest() {
76
+ const found = findManifest();
77
+ if (!found)
78
+ return null;
79
+ try {
80
+ const content = (0, fs_1.readFileSync)(found.path, 'utf-8');
81
+ const manifest = JSON.parse(content);
82
+ return { manifest, ...found };
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ // ═══════════════════════════════════════════════════════════════════════════
89
+ // CHECKSUM UTILITIES
90
+ // ═══════════════════════════════════════════════════════════════════════════
91
+ function sha256File(filePath) {
92
+ const content = (0, fs_1.readFileSync)(filePath);
93
+ return (0, crypto_1.createHash)('sha256').update(content).digest('hex');
94
+ }
95
+ function sha256String(content) {
96
+ return (0, crypto_1.createHash)('sha256').update(content).digest('hex');
97
+ }
98
+ // ═══════════════════════════════════════════════════════════════════════════
99
+ // PROJECT ROOT DETECTION
100
+ // ═══════════════════════════════════════════════════════════════════════════
101
+ function findProjectRoot(startDir = process.cwd()) {
102
+ let dir = startDir;
103
+ // Walk up looking for .git or package.json
104
+ while (dir !== (0, path_1.dirname)(dir)) {
105
+ if ((0, fs_1.existsSync)((0, path_1.join)(dir, '.git')) || (0, fs_1.existsSync)((0, path_1.join)(dir, 'package.json'))) {
106
+ return dir;
107
+ }
108
+ dir = (0, path_1.dirname)(dir);
109
+ }
110
+ // Fallback to cwd
111
+ return startDir;
112
+ }
113
+ async function hooksInstall(options) {
114
+ const verbose = options.verbose || false;
115
+ const isGlobal = options.global !== false; // Default to global
116
+ const isProject = options.project === true;
117
+ console.log('');
118
+ console.log(chalk_1.default.cyan.bold('ekkOS Hooks Installer'));
119
+ console.log(chalk_1.default.gray('-'.repeat(50)));
120
+ console.log('');
121
+ // Load manifest
122
+ const manifestData = loadManifest();
123
+ if (!manifestData) {
124
+ console.log(chalk_1.default.red('Error: Could not find ekkos-manifest.json'));
125
+ console.log(chalk_1.default.gray('Searched in:'));
126
+ console.log(chalk_1.default.gray(' - <packageRoot>/templates/'));
127
+ console.log(chalk_1.default.gray(' - <distRoot>/../templates/'));
128
+ console.log(chalk_1.default.gray(' - <repoRoot>/templates/ (monorepo dev)'));
129
+ process.exit(1);
130
+ }
131
+ const { manifest, templatesDir } = manifestData;
132
+ const platformConfig = manifest.platforms[(0, os_1.platform)()];
133
+ if (verbose) {
134
+ console.log(chalk_1.default.gray(`Manifest: ${manifestData.path}`));
135
+ console.log(chalk_1.default.gray(`Templates: ${templatesDir}`));
136
+ console.log(chalk_1.default.gray(`Platform: ${(0, os_1.platform)()}`));
137
+ console.log('');
138
+ }
139
+ const installedFiles = [];
140
+ let errorCount = 0;
141
+ // Resolve config and hooks directories
142
+ const configDir = expandPath(platformConfig.configDir);
143
+ const globalHooksDir = expandPath(platformConfig.globalHooksDir);
144
+ // Create directories
145
+ const defaultsDir = (0, path_1.join)(configDir, '.defaults');
146
+ const helpersDir = (0, path_1.join)(configDir, '.helpers');
147
+ (0, fs_1.mkdirSync)(configDir, { recursive: true });
148
+ (0, fs_1.mkdirSync)(defaultsDir, { recursive: true });
149
+ (0, fs_1.mkdirSync)(helpersDir, { recursive: true });
150
+ (0, fs_1.mkdirSync)(globalHooksDir, { recursive: true });
151
+ (0, fs_1.mkdirSync)((0, path_1.join)(globalHooksDir, 'lib'), { recursive: true });
152
+ if (verbose) {
153
+ console.log(chalk_1.default.gray(`Config dir: ${configDir}`));
154
+ console.log(chalk_1.default.gray(`Hooks dir: ${globalHooksDir}`));
155
+ console.log('');
156
+ }
157
+ // Helper to copy a file
158
+ const copyFile = (source, dest, description, options = {}) => {
159
+ const sourcePath = (0, path_1.join)(templatesDir, source);
160
+ if (!(0, fs_1.existsSync)(sourcePath)) {
161
+ console.log(chalk_1.default.yellow(` SKIP ${(0, path_1.basename)(dest)} - source not found`));
162
+ return false;
163
+ }
164
+ // Check overwrite policy
165
+ if (options.overwrite === 'createOnly' && (0, fs_1.existsSync)(dest)) {
166
+ if (verbose) {
167
+ console.log(chalk_1.default.gray(` SKIP ${(0, path_1.basename)(dest)} - already exists (user-editable)`));
168
+ }
169
+ return true; // Not an error
170
+ }
171
+ try {
172
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(dest), { recursive: true });
173
+ (0, fs_1.copyFileSync)(sourcePath, dest);
174
+ // Make executable on Unix
175
+ if (options.executable && !isWindows) {
176
+ (0, fs_1.chmodSync)(dest, '755');
177
+ }
178
+ const checksum = sha256File(dest);
179
+ installedFiles.push({
180
+ path: dest,
181
+ checksum,
182
+ installedAt: new Date().toISOString()
183
+ });
184
+ console.log(chalk_1.default.green(` ✓ ${(0, path_1.basename)(dest)}`));
185
+ if (verbose) {
186
+ console.log(chalk_1.default.gray(` → ${dest}`));
187
+ }
188
+ return true;
189
+ }
190
+ catch (err) {
191
+ console.log(chalk_1.default.red(` ✗ ${(0, path_1.basename)(dest)} - ${err.message}`));
192
+ errorCount++;
193
+ return false;
194
+ }
195
+ };
196
+ // ═══════════════════════════════════════════════════════════════════════════
197
+ // GLOBAL INSTALL
198
+ // ═══════════════════════════════════════════════════════════════════════════
199
+ if (isGlobal) {
200
+ console.log(chalk_1.default.cyan('Installing global hooks...'));
201
+ console.log('');
202
+ // 1. Managed defaults (always overwrite)
203
+ console.log(chalk_1.default.gray('Managed defaults:'));
204
+ for (const file of manifest.files.managed) {
205
+ const dest = (0, path_1.join)(configDir, file.destination);
206
+ copyFile(file.source, dest, file.description, {
207
+ overwrite: 'always'
208
+ });
209
+ }
210
+ console.log('');
211
+ // 2. Helpers (always overwrite, executable)
212
+ console.log(chalk_1.default.gray('Helpers:'));
213
+ for (const file of manifest.files.helpers) {
214
+ const dest = (0, path_1.join)(configDir, file.destination);
215
+ copyFile(file.source, dest, file.description, {
216
+ executable: file.executable,
217
+ overwrite: 'always'
218
+ });
219
+ }
220
+ console.log('');
221
+ // 3. User-editable files (create if missing only)
222
+ console.log(chalk_1.default.gray('User-editable (create if missing):'));
223
+ for (const file of manifest.files.userEditable) {
224
+ const dest = (0, path_1.join)(configDir, file.destination);
225
+ copyFile(file.source, dest, file.description, {
226
+ overwrite: 'createOnly'
227
+ });
228
+ }
229
+ console.log('');
230
+ // 4. Hooks (platform-specific)
231
+ console.log(chalk_1.default.gray('Hooks:'));
232
+ const hookFiles = isWindows ? manifest.files.hooks.powershell : manifest.files.hooks.bash;
233
+ for (const file of hookFiles) {
234
+ const dest = (0, path_1.join)(globalHooksDir, file.destination);
235
+ copyFile(file.source, dest, file.description, {
236
+ executable: file.executable,
237
+ overwrite: 'always'
238
+ });
239
+ }
240
+ console.log('');
241
+ // 5. Lib files (Unix only)
242
+ if (!isWindows) {
243
+ console.log(chalk_1.default.gray('Hook libraries:'));
244
+ for (const file of manifest.files.hooks.lib) {
245
+ const dest = (0, path_1.join)(globalHooksDir, file.destination);
246
+ copyFile(file.source, dest, file.description, {
247
+ executable: file.executable,
248
+ overwrite: 'always'
249
+ });
250
+ }
251
+ console.log('');
252
+ }
253
+ // Write installed-state manifest (only on success)
254
+ if (errorCount === 0) {
255
+ const installedManifest = {
256
+ version: manifest.manifestVersion,
257
+ installedAt: new Date().toISOString(),
258
+ installedBy: `@ekkos/cli@${require('../../package.json').version}`,
259
+ sourceManifestChecksum: sha256File(manifestData.path),
260
+ installedFiles
261
+ };
262
+ const installedManifestPath = (0, path_1.join)(globalHooksDir, '.ekkos-manifest.json');
263
+ (0, fs_1.writeFileSync)(installedManifestPath, JSON.stringify(installedManifest, null, 2));
264
+ if (verbose) {
265
+ console.log(chalk_1.default.gray(`Installed manifest: ${installedManifestPath}`));
266
+ }
267
+ }
268
+ }
269
+ // ═══════════════════════════════════════════════════════════════════════════
270
+ // PROJECT INSTALL
271
+ // ═══════════════════════════════════════════════════════════════════════════
272
+ if (isProject) {
273
+ const projectRoot = findProjectRoot();
274
+ const projectHooksDir = (0, path_1.join)(projectRoot, '.claude', 'hooks');
275
+ const projectEkkosDir = (0, path_1.join)(projectRoot, '.ekkos');
276
+ console.log(chalk_1.default.cyan(`Installing project hooks for: ${projectRoot}`));
277
+ console.log('');
278
+ (0, fs_1.mkdirSync)(projectHooksDir, { recursive: true });
279
+ (0, fs_1.mkdirSync)(projectEkkosDir, { recursive: true });
280
+ // Use manifest.projectStubs if available, otherwise fallback to scanning directory
281
+ console.log(chalk_1.default.gray('Project stubs:'));
282
+ if (manifest.projectStubs) {
283
+ // Use manifest-driven installation
284
+ const stubFiles = isWindows ? manifest.projectStubs.powershell : manifest.projectStubs.bash;
285
+ for (const file of stubFiles) {
286
+ const sourcePath = (0, path_1.join)(templatesDir, file.source);
287
+ const destPath = (0, path_1.join)(projectHooksDir, file.destination);
288
+ if (!(0, fs_1.existsSync)(sourcePath)) {
289
+ console.log(chalk_1.default.yellow(` SKIP ${file.destination} - source not found`));
290
+ continue;
291
+ }
292
+ try {
293
+ (0, fs_1.copyFileSync)(sourcePath, destPath);
294
+ if (file.executable && !isWindows) {
295
+ (0, fs_1.chmodSync)(destPath, '755');
296
+ }
297
+ console.log(chalk_1.default.green(` ✓ ${file.destination}`));
298
+ installedFiles.push({
299
+ path: destPath,
300
+ checksum: sha256File(destPath),
301
+ installedAt: new Date().toISOString()
302
+ });
303
+ }
304
+ catch (err) {
305
+ console.log(chalk_1.default.red(` ✗ ${file.destination} - ${err.message}`));
306
+ errorCount++;
307
+ }
308
+ }
309
+ }
310
+ else {
311
+ // Fallback: scan project-stubs directory
312
+ const stubsDir = (0, path_1.join)(templatesDir, 'project-stubs');
313
+ if ((0, fs_1.existsSync)(stubsDir)) {
314
+ const stubExt = isWindows ? '.ps1' : '.sh';
315
+ const stubFileNames = (0, fs_1.readdirSync)(stubsDir).filter(f => f.endsWith(stubExt));
316
+ for (const stubFile of stubFileNames) {
317
+ const sourcePath = (0, path_1.join)(stubsDir, stubFile);
318
+ const destPath = (0, path_1.join)(projectHooksDir, stubFile);
319
+ try {
320
+ (0, fs_1.copyFileSync)(sourcePath, destPath);
321
+ if (!isWindows) {
322
+ (0, fs_1.chmodSync)(destPath, '755');
323
+ }
324
+ console.log(chalk_1.default.green(` ✓ ${stubFile}`));
325
+ installedFiles.push({
326
+ path: destPath,
327
+ checksum: sha256File(destPath),
328
+ installedAt: new Date().toISOString()
329
+ });
330
+ }
331
+ catch (err) {
332
+ console.log(chalk_1.default.yellow(` SKIP ${stubFile} - ${err.message}`));
333
+ }
334
+ }
335
+ }
336
+ else {
337
+ console.log(chalk_1.default.yellow(' No project stubs found - using global hooks'));
338
+ }
339
+ }
340
+ console.log('');
341
+ // Write project installed manifest
342
+ if (errorCount === 0) {
343
+ const projectInstalledManifest = {
344
+ version: manifest.manifestVersion,
345
+ installedAt: new Date().toISOString(),
346
+ installedBy: `@ekkos/cli@${require('../../package.json').version}`,
347
+ sourceManifestChecksum: sha256File(manifestData.path),
348
+ installedFiles: installedFiles.filter(f => f.path.startsWith(projectRoot))
349
+ };
350
+ const projectManifestPath = (0, path_1.join)(projectEkkosDir, '.ekkos-project-manifest.json');
351
+ (0, fs_1.writeFileSync)(projectManifestPath, JSON.stringify(projectInstalledManifest, null, 2));
352
+ if (verbose) {
353
+ console.log(chalk_1.default.gray(`Project manifest: ${projectManifestPath}`));
354
+ }
355
+ }
356
+ }
357
+ // Summary
358
+ console.log(chalk_1.default.gray('-'.repeat(50)));
359
+ if (errorCount === 0) {
360
+ console.log(chalk_1.default.green.bold(`✓ ${installedFiles.length} files installed successfully`));
361
+ console.log('');
362
+ console.log(chalk_1.default.gray('Run `ekkos hooks verify` to confirm installation.'));
363
+ }
364
+ else {
365
+ console.log(chalk_1.default.red.bold(`✗ ${errorCount} errors during installation`));
366
+ console.log('');
367
+ console.log(chalk_1.default.yellow('Some files may not have been installed correctly.'));
368
+ process.exit(1);
369
+ }
370
+ console.log('');
371
+ }
372
+ async function hooksVerify(options) {
373
+ const verbose = options.verbose || false;
374
+ const checkGlobal = options.global !== false;
375
+ const checkProject = options.project === true;
376
+ console.log('');
377
+ console.log(chalk_1.default.cyan.bold('ekkOS Hooks Verification'));
378
+ console.log(chalk_1.default.gray('-'.repeat(50)));
379
+ console.log('');
380
+ const issues = [];
381
+ // Load source manifest for reference
382
+ const manifestData = loadManifest();
383
+ if (!manifestData) {
384
+ issues.push({
385
+ severity: 'error',
386
+ message: 'Source manifest not found - cannot verify'
387
+ });
388
+ return { status: 'FAIL', issues };
389
+ }
390
+ const { manifest } = manifestData;
391
+ const platformConfig = manifest.platforms[(0, os_1.platform)()];
392
+ const configDir = expandPath(platformConfig.configDir);
393
+ const globalHooksDir = expandPath(platformConfig.globalHooksDir);
394
+ // ═══════════════════════════════════════════════════════════════════════════
395
+ // GLOBAL VERIFICATION
396
+ // ═══════════════════════════════════════════════════════════════════════════
397
+ if (checkGlobal) {
398
+ console.log(chalk_1.default.cyan('Verifying global installation...'));
399
+ console.log('');
400
+ // Check installed-state manifest
401
+ const installedManifestPath = (0, path_1.join)(globalHooksDir, '.ekkos-manifest.json');
402
+ if (!(0, fs_1.existsSync)(installedManifestPath)) {
403
+ issues.push({
404
+ severity: 'error',
405
+ file: installedManifestPath,
406
+ message: 'Installed-state manifest not found. Run: ekkos hooks install --global'
407
+ });
408
+ }
409
+ else {
410
+ try {
411
+ const installedManifest = JSON.parse((0, fs_1.readFileSync)(installedManifestPath, 'utf-8'));
412
+ console.log(chalk_1.default.gray(`Installed version: ${installedManifest.version}`));
413
+ console.log(chalk_1.default.gray(`Installed at: ${installedManifest.installedAt}`));
414
+ console.log(chalk_1.default.gray(`Installed by: ${installedManifest.installedBy}`));
415
+ console.log('');
416
+ // Verify each installed file
417
+ console.log(chalk_1.default.gray('Checking files:'));
418
+ for (const file of installedManifest.installedFiles) {
419
+ if (!(0, fs_1.existsSync)(file.path)) {
420
+ console.log(chalk_1.default.red(` ✗ ${(0, path_1.basename)(file.path)} - missing`));
421
+ issues.push({
422
+ severity: 'error',
423
+ file: file.path,
424
+ message: 'File missing'
425
+ });
426
+ }
427
+ else {
428
+ const currentChecksum = sha256File(file.path);
429
+ if (currentChecksum !== file.checksum) {
430
+ console.log(chalk_1.default.yellow(` ○ ${(0, path_1.basename)(file.path)} - modified`));
431
+ issues.push({
432
+ severity: 'warning',
433
+ file: file.path,
434
+ message: 'File has been modified since installation'
435
+ });
436
+ }
437
+ else {
438
+ console.log(chalk_1.default.green(` ✓ ${(0, path_1.basename)(file.path)}`));
439
+ }
440
+ }
441
+ }
442
+ console.log('');
443
+ }
444
+ catch (err) {
445
+ issues.push({
446
+ severity: 'error',
447
+ file: installedManifestPath,
448
+ message: `Invalid installed manifest: ${err.message}`
449
+ });
450
+ }
451
+ }
452
+ // Check required files
453
+ console.log(chalk_1.default.gray('Required files:'));
454
+ // Helper
455
+ const helperPath = (0, path_1.join)(configDir, '.helpers', 'json-parse.cjs');
456
+ if (!(0, fs_1.existsSync)(helperPath)) {
457
+ console.log(chalk_1.default.red(` ✗ json-parse.cjs - missing`));
458
+ issues.push({
459
+ severity: 'error',
460
+ file: helperPath,
461
+ message: 'JSON parse helper missing. Run: ekkos hooks install --global'
462
+ });
463
+ }
464
+ else {
465
+ console.log(chalk_1.default.green(` ✓ json-parse.cjs`));
466
+ }
467
+ // Defaults
468
+ const defaultsPath = (0, path_1.join)(configDir, '.defaults', 'session-words.json');
469
+ if (!(0, fs_1.existsSync)(defaultsPath)) {
470
+ console.log(chalk_1.default.red(` ✗ session-words.json (defaults) - missing`));
471
+ issues.push({
472
+ severity: 'error',
473
+ file: defaultsPath,
474
+ message: 'Default session words missing. Run: ekkos hooks install --global'
475
+ });
476
+ }
477
+ else {
478
+ console.log(chalk_1.default.green(` ✓ session-words.json (defaults)`));
479
+ }
480
+ // Check EKKOS_MANAGED header in hooks
481
+ console.log('');
482
+ console.log(chalk_1.default.gray('Hook fingerprints:'));
483
+ const hookFiles = isWindows ? manifest.files.hooks.powershell : manifest.files.hooks.bash;
484
+ for (const hookFile of hookFiles) {
485
+ const hookPath = (0, path_1.join)(globalHooksDir, hookFile.destination);
486
+ if ((0, fs_1.existsSync)(hookPath)) {
487
+ const content = (0, fs_1.readFileSync)(hookPath, 'utf-8');
488
+ if (content.includes('EKKOS_MANAGED=1')) {
489
+ console.log(chalk_1.default.green(` ✓ ${(0, path_1.basename)(hookPath)} - managed`));
490
+ }
491
+ else {
492
+ console.log(chalk_1.default.yellow(` ○ ${(0, path_1.basename)(hookPath)} - custom (not managed)`));
493
+ issues.push({
494
+ severity: 'warning',
495
+ file: hookPath,
496
+ message: 'Hook does not have EKKOS_MANAGED=1 fingerprint'
497
+ });
498
+ }
499
+ }
500
+ else {
501
+ console.log(chalk_1.default.red(` ✗ ${(0, path_1.basename)(hookFile.destination)} - missing`));
502
+ issues.push({
503
+ severity: 'error',
504
+ file: hookPath,
505
+ message: 'Hook file missing'
506
+ });
507
+ }
508
+ }
509
+ console.log('');
510
+ }
511
+ // ═══════════════════════════════════════════════════════════════════════════
512
+ // PROJECT VERIFICATION
513
+ // ═══════════════════════════════════════════════════════════════════════════
514
+ if (checkProject) {
515
+ const projectRoot = findProjectRoot();
516
+ const projectManifestPath = (0, path_1.join)(projectRoot, '.ekkos', '.ekkos-project-manifest.json');
517
+ console.log(chalk_1.default.cyan(`Verifying project installation: ${projectRoot}`));
518
+ console.log('');
519
+ if (!(0, fs_1.existsSync)(projectManifestPath)) {
520
+ issues.push({
521
+ severity: 'warning',
522
+ file: projectManifestPath,
523
+ message: 'No project-level hooks installed (using global)'
524
+ });
525
+ console.log(chalk_1.default.yellow(' No project-level hooks installed'));
526
+ console.log(chalk_1.default.gray(' Using global hooks from ~/.claude/hooks/'));
527
+ }
528
+ else {
529
+ const projectManifest = JSON.parse((0, fs_1.readFileSync)(projectManifestPath, 'utf-8'));
530
+ console.log(chalk_1.default.gray(`Project hooks version: ${projectManifest.version}`));
531
+ for (const file of projectManifest.installedFiles) {
532
+ if (!(0, fs_1.existsSync)(file.path)) {
533
+ console.log(chalk_1.default.red(` ✗ ${(0, path_1.basename)(file.path)} - missing`));
534
+ issues.push({ severity: 'error', file: file.path, message: 'Missing' });
535
+ }
536
+ else {
537
+ console.log(chalk_1.default.green(` ✓ ${(0, path_1.basename)(file.path)}`));
538
+ }
539
+ }
540
+ }
541
+ console.log('');
542
+ }
543
+ // Summary
544
+ console.log(chalk_1.default.gray('-'.repeat(50)));
545
+ const errors = issues.filter(i => i.severity === 'error').length;
546
+ const warnings = issues.filter(i => i.severity === 'warning').length;
547
+ let status = 'PASS';
548
+ if (errors > 0) {
549
+ status = 'FAIL';
550
+ console.log(chalk_1.default.red.bold(`FAIL: ${errors} error(s), ${warnings} warning(s)`));
551
+ }
552
+ else if (warnings > 0) {
553
+ status = 'WARN';
554
+ console.log(chalk_1.default.yellow.bold(`WARN: ${warnings} warning(s)`));
555
+ }
556
+ else {
557
+ console.log(chalk_1.default.green.bold('PASS: All checks passed'));
558
+ }
559
+ console.log('');
560
+ return { status, issues };
561
+ }
562
+ async function hooksStatus(options) {
563
+ const verbose = options.verbose || false;
564
+ console.log('');
565
+ console.log(chalk_1.default.cyan.bold('ekkOS Hooks Status'));
566
+ console.log(chalk_1.default.gray('-'.repeat(50)));
567
+ console.log('');
568
+ const manifestData = loadManifest();
569
+ const platformConfig = manifestData?.manifest.platforms[(0, os_1.platform)()];
570
+ const configDir = platformConfig ? expandPath(platformConfig.configDir) : (0, path_1.join)(HOME_DIR, '.ekkos');
571
+ const globalHooksDir = platformConfig ? expandPath(platformConfig.globalHooksDir) : (0, path_1.join)(HOME_DIR, '.claude', 'hooks');
572
+ const projectRoot = findProjectRoot();
573
+ const projectHooksDir = (0, path_1.join)(projectRoot, '.claude', 'hooks');
574
+ // Display directories
575
+ console.log(chalk_1.default.gray('Directories:'));
576
+ console.log(` Config: ${configDir}`);
577
+ console.log(` Global hooks: ${globalHooksDir}`);
578
+ console.log(` Project: ${projectRoot}`);
579
+ console.log('');
580
+ // Check global installation
581
+ console.log(chalk_1.default.cyan('Global Installation:'));
582
+ const globalManifestPath = (0, path_1.join)(globalHooksDir, '.ekkos-manifest.json');
583
+ if ((0, fs_1.existsSync)(globalManifestPath)) {
584
+ const globalManifest = JSON.parse((0, fs_1.readFileSync)(globalManifestPath, 'utf-8'));
585
+ console.log(chalk_1.default.green(` ✓ Installed (v${globalManifest.version})`));
586
+ console.log(chalk_1.default.gray(` Installed: ${new Date(globalManifest.installedAt).toLocaleString()}`));
587
+ console.log(chalk_1.default.gray(` Files: ${globalManifest.installedFiles.length}`));
588
+ }
589
+ else {
590
+ console.log(chalk_1.default.yellow(' ○ Not installed'));
591
+ console.log(chalk_1.default.gray(' Run: ekkos hooks install --global'));
592
+ }
593
+ console.log('');
594
+ // Check hooks enablement
595
+ console.log(chalk_1.default.cyan('Hook Enablement:'));
596
+ const hooksEnabledPath = (0, path_1.join)(configDir, 'hooks-enabled.json');
597
+ if ((0, fs_1.existsSync)(hooksEnabledPath)) {
598
+ try {
599
+ const hooksEnabled = JSON.parse((0, fs_1.readFileSync)(hooksEnabledPath, 'utf-8'));
600
+ const targets = hooksEnabled.targets || {};
601
+ for (const [target, config] of Object.entries(targets)) {
602
+ const hooks = config || {};
603
+ const enabled = Object.entries(hooks)
604
+ .filter(([, v]) => v === true)
605
+ .map(([k]) => k);
606
+ const disabled = Object.entries(hooks)
607
+ .filter(([, v]) => v === false)
608
+ .map(([k]) => k);
609
+ console.log(` ${target}:`);
610
+ if (enabled.length > 0) {
611
+ console.log(chalk_1.default.green(` Enabled: ${enabled.join(', ')}`));
612
+ }
613
+ if (disabled.length > 0) {
614
+ console.log(chalk_1.default.yellow(` Disabled: ${disabled.join(', ')}`));
615
+ }
616
+ }
617
+ }
618
+ catch {
619
+ console.log(chalk_1.default.yellow(' ○ Could not read hooks-enabled.json'));
620
+ }
621
+ }
622
+ else {
623
+ console.log(chalk_1.default.gray(' Using defaults (all enabled)'));
624
+ }
625
+ console.log('');
626
+ // List hooks with modification times
627
+ console.log(chalk_1.default.cyan('Installed Hooks:'));
628
+ const hookExt = isWindows ? '.ps1' : '.sh';
629
+ if ((0, fs_1.existsSync)(globalHooksDir)) {
630
+ const hookFiles = (0, fs_1.readdirSync)(globalHooksDir)
631
+ .filter(f => f.endsWith(hookExt))
632
+ .sort();
633
+ for (const hookFile of hookFiles) {
634
+ const hookPath = (0, path_1.join)(globalHooksDir, hookFile);
635
+ const stat = (0, fs_1.statSync)(hookPath);
636
+ const mtime = stat.mtime.toLocaleString();
637
+ const content = (0, fs_1.readFileSync)(hookPath, 'utf-8');
638
+ const isManaged = content.includes('EKKOS_MANAGED=1');
639
+ const icon = isManaged ? chalk_1.default.green('✓') : chalk_1.default.yellow('○');
640
+ const tag = isManaged ? chalk_1.default.gray('[managed]') : chalk_1.default.yellow('[custom]');
641
+ console.log(` ${icon} ${hookFile} ${tag}`);
642
+ if (verbose) {
643
+ console.log(chalk_1.default.gray(` Modified: ${mtime}`));
644
+ }
645
+ }
646
+ }
647
+ else {
648
+ console.log(chalk_1.default.gray(' No hooks directory found'));
649
+ }
650
+ console.log('');
651
+ // Check project-level hooks
652
+ console.log(chalk_1.default.cyan('Project Hooks:'));
653
+ if ((0, fs_1.existsSync)(projectHooksDir) && projectHooksDir !== globalHooksDir) {
654
+ const projectHookFiles = (0, fs_1.readdirSync)(projectHooksDir).filter(f => f.endsWith(hookExt));
655
+ if (projectHookFiles.length > 0) {
656
+ for (const hookFile of projectHookFiles) {
657
+ console.log(chalk_1.default.green(` ✓ ${hookFile}`));
658
+ }
659
+ }
660
+ else {
661
+ console.log(chalk_1.default.gray(' Using global hooks'));
662
+ }
663
+ }
664
+ else {
665
+ console.log(chalk_1.default.gray(' Using global hooks'));
666
+ }
667
+ console.log('');
668
+ }
@@ -3,6 +3,7 @@ interface RunOptions {
3
3
  verbose?: boolean;
4
4
  bypass?: boolean;
5
5
  doctor?: boolean;
6
+ noInject?: boolean;
6
7
  slashOpenDelayMs?: number;
7
8
  charDelayMs?: number;
8
9
  postEnterDelayMs?: number;