@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.
Files changed (36) 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/dist/utils/session-words.json +30 -111
  18. package/package.json +1 -1
  19. package/templates/ekkos-manifest.json +223 -0
  20. package/templates/helpers/json-parse.cjs +101 -0
  21. package/templates/hooks/assistant-response.ps1 +256 -0
  22. package/templates/hooks/assistant-response.sh +124 -64
  23. package/templates/hooks/session-start.ps1 +107 -2
  24. package/templates/hooks/session-start.sh +201 -166
  25. package/templates/hooks/stop.ps1 +124 -3
  26. package/templates/hooks/stop.sh +470 -843
  27. package/templates/hooks/user-prompt-submit.ps1 +107 -22
  28. package/templates/hooks/user-prompt-submit.sh +403 -393
  29. package/templates/project-stubs/session-start.ps1 +63 -0
  30. package/templates/project-stubs/session-start.sh +55 -0
  31. package/templates/project-stubs/stop.ps1 +63 -0
  32. package/templates/project-stubs/stop.sh +55 -0
  33. package/templates/project-stubs/user-prompt-submit.ps1 +63 -0
  34. package/templates/project-stubs/user-prompt-submit.sh +55 -0
  35. package/templates/shared/hooks-enabled.json +22 -0
  36. package/templates/shared/session-words.json +45 -0
@@ -1,4 +1,14 @@
1
1
  "use strict";
2
+ /**
3
+ * ekkOS CLI: doctor command
4
+ * Checks system prerequisites for ekkOS
5
+ *
6
+ * Per ekkOS Onboarding Spec v1.2 + Addendum:
7
+ * - Node gate: Node >= 18 required (FAIL if missing)
8
+ * - PTY gate: WARN on Windows if missing (monitor-only mode available)
9
+ * - Hooks gate: Verifies hook installation
10
+ * - NO jq checks (jq dependency eliminated)
11
+ */
2
12
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
13
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
14
  };
@@ -6,8 +16,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
16
  exports.runDiagnostics = runDiagnostics;
7
17
  exports.doctor = doctor;
8
18
  const os_1 = require("os");
19
+ const path_1 = require("path");
20
+ const fs_1 = require("fs");
9
21
  const child_process_1 = require("child_process");
10
22
  const chalk_1 = __importDefault(require("chalk"));
23
+ const hooks_1 = require("./hooks");
11
24
  /**
12
25
  * Check if a command exists in PATH
13
26
  */
@@ -92,18 +105,42 @@ function checkMcpConfig() {
92
105
  }
93
106
  }
94
107
  /**
95
- * Check if bash is available (for hooks on Windows)
108
+ * Check hooks installation status
96
109
  */
97
- function checkBash() {
98
- if ((0, os_1.platform)() !== 'win32')
99
- return true;
100
- try {
101
- (0, child_process_1.execSync)('bash --version', { stdio: 'ignore', timeout: 5000 });
102
- return true;
110
+ function checkHooksInstallation() {
111
+ const isWindows = (0, os_1.platform)() === 'win32';
112
+ const manifestData = (0, hooks_1.loadManifest)();
113
+ if (!manifestData) {
114
+ return { installed: false, details: 'Source manifest not found' };
103
115
  }
104
- catch {
105
- return false;
116
+ const platformConfig = manifestData.manifest.platforms[(0, os_1.platform)()];
117
+ const globalHooksDir = (0, hooks_1.expandPath)(platformConfig.globalHooksDir);
118
+ const configDir = (0, hooks_1.expandPath)(platformConfig.configDir);
119
+ // Check installed manifest
120
+ const installedManifestPath = (0, path_1.join)(globalHooksDir, '.ekkos-manifest.json');
121
+ if (!(0, fs_1.existsSync)(installedManifestPath)) {
122
+ return { installed: false, details: 'No installed manifest found' };
123
+ }
124
+ // Check helper
125
+ const helperPath = (0, path_1.join)(configDir, '.helpers', 'json-parse.cjs');
126
+ if (!(0, fs_1.existsSync)(helperPath)) {
127
+ return { installed: false, details: 'json-parse.cjs helper missing' };
128
+ }
129
+ // Check defaults
130
+ const defaultsPath = (0, path_1.join)(configDir, '.defaults', 'session-words.json');
131
+ if (!(0, fs_1.existsSync)(defaultsPath)) {
132
+ return { installed: false, details: 'Default session words missing' };
106
133
  }
134
+ // Check required hooks
135
+ const hookExt = isWindows ? '.ps1' : '.sh';
136
+ const requiredHooks = ['user-prompt-submit', 'stop', 'session-start'];
137
+ for (const hookName of requiredHooks) {
138
+ const hookPath = (0, path_1.join)(globalHooksDir, `${hookName}${hookExt}`);
139
+ if (!(0, fs_1.existsSync)(hookPath)) {
140
+ return { installed: false, details: `${hookName}${hookExt} missing` };
141
+ }
142
+ }
143
+ return { installed: true, details: 'All hooks and helpers installed' };
107
144
  }
108
145
  /**
109
146
  * Run diagnostic checks and return report
@@ -115,11 +152,38 @@ function runDiagnostics() {
115
152
  const nodeVersion = getVersion('node') || 'Not found';
116
153
  const claudeVersion = getVersion('claude');
117
154
  // ═══════════════════════════════════════════════════════════════════════════
118
- // GATE 1: Interactive Claude Works
155
+ // GATE 1: Node.js Version (>= 18 required per spec)
156
+ // ═══════════════════════════════════════════════════════════════════════════
157
+ const nodeChecks = [];
158
+ let nodeGatePass = true;
159
+ const nodeMatch = nodeVersion.match(/v?(\d+)\./);
160
+ const nodeMajor = nodeMatch ? parseInt(nodeMatch[1], 10) : 0;
161
+ const nodeOk = nodeMajor >= 18;
162
+ nodeChecks.push({
163
+ name: 'Node.js version >= 18',
164
+ passed: nodeOk,
165
+ detail: nodeOk
166
+ ? `${nodeVersion} (OK)`
167
+ : `${nodeVersion} (Need Node.js 18 or higher)`
168
+ });
169
+ if (!nodeOk) {
170
+ nodeGatePass = false;
171
+ }
172
+ gates.push({
173
+ id: 'node',
174
+ title: 'Node.js',
175
+ status: nodeGatePass ? 'PASS' : 'FAIL',
176
+ checks: nodeChecks,
177
+ fix: nodeGatePass ? undefined : isWindows
178
+ ? 'winget install OpenJS.NodeJS.LTS'
179
+ : 'Install Node.js 20 or 22 LTS from nodejs.org'
180
+ });
181
+ // ═══════════════════════════════════════════════════════════════════════════
182
+ // GATE 2: Interactive Claude Works
119
183
  // ═══════════════════════════════════════════════════════════════════════════
120
184
  const claudeChecks = [];
121
185
  let claudeGatePass = true;
122
- // Check 1.1: claude command exists
186
+ // Check 2.1: claude command exists
123
187
  const claudeExists = commandExists('claude');
124
188
  claudeChecks.push({
125
189
  name: 'Claude CLI installed',
@@ -128,7 +192,7 @@ function runDiagnostics() {
128
192
  });
129
193
  if (!claudeExists)
130
194
  claudeGatePass = false;
131
- // Check 1.2: claude --version works
195
+ // Check 2.2: claude --version works
132
196
  if (claudeExists) {
133
197
  const versionWorks = claudeVersion !== null;
134
198
  claudeChecks.push({
@@ -139,15 +203,6 @@ function runDiagnostics() {
139
203
  if (!versionWorks)
140
204
  claudeGatePass = false;
141
205
  }
142
- // Check 1.3: On Windows, PTY is required for interactive mode
143
- // This is checked in Gate 2, but we note it here
144
- if (isWindows) {
145
- claudeChecks.push({
146
- name: 'Interactive mode (requires PTY)',
147
- passed: true, // Will be validated in Gate 2
148
- detail: 'See PTY gate below'
149
- });
150
- }
151
206
  gates.push({
152
207
  id: 'interactive-claude',
153
208
  title: 'Interactive Claude',
@@ -156,52 +211,71 @@ function runDiagnostics() {
156
211
  fix: claudeGatePass ? undefined : 'npm install -g @anthropic-ai/claude-code'
157
212
  });
158
213
  // ═══════════════════════════════════════════════════════════════════════════
159
- // GATE 2: PTY Works (Windows-critical)
214
+ // GATE 3: PTY Works (WARN on Windows if missing per spec v1.2 Addendum)
160
215
  // ═══════════════════════════════════════════════════════════════════════════
161
216
  const ptyChecks = [];
162
- let ptyGatePass = true;
163
- // Check Node version (20.x or 22.x recommended)
164
- const nodeMatch = nodeVersion.match(/v?(\d+)\./);
165
- const nodeMajor = nodeMatch ? parseInt(nodeMatch[1], 10) : 0;
166
- const nodeOk = nodeMajor >= 18 && nodeMajor <= 22;
167
- ptyChecks.push({
168
- name: 'Node.js version',
169
- passed: nodeOk,
170
- detail: nodeOk
171
- ? `${nodeVersion} (OK)`
172
- : `${nodeVersion} (Need 20.x or 22.x LTS for PTY support)`
173
- });
174
- if (!nodeOk && isWindows)
175
- ptyGatePass = false;
217
+ let ptyGateStatus = 'PASS';
176
218
  // Check PTY availability
177
219
  const ptyResult = checkPty();
178
220
  ptyChecks.push({
179
221
  name: 'node-pty loadable',
180
222
  passed: ptyResult.available,
181
- detail: ptyResult.available ? 'ConPTY available' : ptyResult.error
223
+ detail: ptyResult.available
224
+ ? isWindows ? 'ConPTY available' : 'PTY available'
225
+ : ptyResult.error
182
226
  });
183
- if (!ptyResult.available && isWindows)
184
- ptyGatePass = false;
185
- // On macOS/Linux, PTY is typically available, mark as PASS with note
186
- if (!isWindows && !ptyResult.available) {
187
- ptyChecks.push({
188
- name: 'Fallback available',
189
- passed: true,
190
- detail: 'Unix script(1) fallback available'
191
- });
192
- ptyGatePass = true; // Unix has fallback
227
+ if (!ptyResult.available) {
228
+ if (isWindows) {
229
+ // Per spec: Windows without PTY is WARN (monitor-only mode available)
230
+ ptyGateStatus = 'WARN';
231
+ ptyChecks.push({
232
+ name: 'Monitor-only mode',
233
+ passed: true,
234
+ detail: 'Auto-continue disabled; manual /clear + /continue required'
235
+ });
236
+ }
237
+ else {
238
+ // Unix has script(1) fallback
239
+ ptyChecks.push({
240
+ name: 'Unix fallback',
241
+ passed: true,
242
+ detail: 'script(1) fallback available'
243
+ });
244
+ ptyGateStatus = 'PASS';
245
+ }
193
246
  }
194
247
  gates.push({
195
248
  id: 'pty',
196
249
  title: 'PTY (Terminal)',
197
- status: ptyGatePass ? 'PASS' : (isWindows ? 'FAIL' : 'WARN'),
250
+ status: ptyGateStatus,
198
251
  checks: ptyChecks,
199
- fix: ptyGatePass ? undefined : isWindows
200
- ? 'npm install node-pty-prebuilt-multiarch OR install VS Build Tools and run: npm rebuild node-pty'
252
+ fix: ptyGateStatus === 'WARN'
253
+ ? 'npm install node-pty-prebuilt-multiarch (optional for auto-continue)'
201
254
  : undefined
202
255
  });
203
256
  // ═══════════════════════════════════════════════════════════════════════════
204
- // GATE 3: MCP Works
257
+ // GATE 4: Hooks Installation (per spec v1.2)
258
+ // ═══════════════════════════════════════════════════════════════════════════
259
+ const hooksChecks = [];
260
+ let hooksGateStatus = 'PASS';
261
+ const hooksResult = checkHooksInstallation();
262
+ hooksChecks.push({
263
+ name: 'ekkOS hooks installed',
264
+ passed: hooksResult.installed,
265
+ detail: hooksResult.details
266
+ });
267
+ if (!hooksResult.installed) {
268
+ hooksGateStatus = 'WARN';
269
+ }
270
+ gates.push({
271
+ id: 'hooks',
272
+ title: 'ekkOS Hooks',
273
+ status: hooksGateStatus,
274
+ checks: hooksChecks,
275
+ fix: hooksGateStatus === 'WARN' ? 'ekkos hooks install --global' : undefined
276
+ });
277
+ // ═══════════════════════════════════════════════════════════════════════════
278
+ // GATE 5: MCP Works
205
279
  // ═══════════════════════════════════════════════════════════════════════════
206
280
  const mcpChecks = [];
207
281
  let mcpGatePass = true;
@@ -227,16 +301,6 @@ function runDiagnostics() {
227
301
  if (!mcpResult.configured)
228
302
  mcpGatePass = false;
229
303
  }
230
- // Check bash on Windows (for hooks, WARN only)
231
- if (isWindows) {
232
- const bashOk = checkBash();
233
- mcpChecks.push({
234
- name: 'Bash available (for hooks)',
235
- passed: bashOk,
236
- detail: bashOk ? 'Git Bash/WSL detected' : 'PowerShell hooks will be used'
237
- });
238
- // Don't fail gate for missing bash, just note it
239
- }
240
304
  gates.push({
241
305
  id: 'mcp',
242
306
  title: 'MCP Configuration',
@@ -248,7 +312,6 @@ function runDiagnostics() {
248
312
  // ═══════════════════════════════════════════════════════════════════════════
249
313
  // Calculate overall status
250
314
  // ═══════════════════════════════════════════════════════════════════════════
251
- const allPass = gates.every(g => g.status === 'PASS');
252
315
  const hasBlocker = gates.some(g => g.status === 'FAIL');
253
316
  return {
254
317
  platform: isWindows ? 'Windows' : (0, os_1.platform)() === 'darwin' ? 'macOS' : 'Linux',
@@ -263,8 +326,8 @@ function runDiagnostics() {
263
326
  */
264
327
  function displayReport(report) {
265
328
  console.log('');
266
- console.log(chalk_1.default.cyan.bold('🩺 ekkOS Doctor'));
267
- console.log(chalk_1.default.gray(''.repeat(60)));
329
+ console.log(chalk_1.default.cyan.bold('ekkOS Doctor'));
330
+ console.log(chalk_1.default.gray('-'.repeat(60)));
268
331
  console.log('');
269
332
  // Platform info
270
333
  console.log(chalk_1.default.gray(`Platform: ${report.platform}`));
@@ -289,13 +352,13 @@ function displayReport(report) {
289
352
  console.log(chalk_1.default.gray(` ${check.detail}`));
290
353
  }
291
354
  }
292
- if (gate.fix && gate.status === 'FAIL') {
355
+ if (gate.fix && (gate.status === 'FAIL' || gate.status === 'WARN')) {
293
356
  console.log(chalk_1.default.yellow(` → Fix: ${gate.fix}`));
294
357
  }
295
358
  console.log('');
296
359
  }
297
360
  // Summary
298
- console.log(chalk_1.default.gray(''.repeat(60)));
361
+ console.log(chalk_1.default.gray('-'.repeat(60)));
299
362
  const blockers = report.gates.filter(g => g.status === 'FAIL').length;
300
363
  const warnings = report.gates.filter(g => g.status === 'WARN').length;
301
364
  if (blockers > 0) {
@@ -307,7 +370,7 @@ function displayReport(report) {
307
370
  console.log(chalk_1.default.green('ekkOS can run, but some features may be limited.'));
308
371
  }
309
372
  else {
310
- console.log(chalk_1.default.green.bold('All systems operational'));
373
+ console.log(chalk_1.default.green.bold('All systems operational'));
311
374
  console.log(chalk_1.default.green('ekkOS is ready to use.'));
312
375
  }
313
376
  console.log('');
@@ -340,7 +403,6 @@ async function attemptAutoFixes(report) {
340
403
  // Try prebuilt PTY first (safe, non-admin)
341
404
  console.log(chalk_1.default.yellow('\nAttempting to install prebuilt PTY...'));
342
405
  try {
343
- // Try homebridge prebuilt first
344
406
  (0, child_process_1.execSync)('npm install -g @homebridge/node-pty-prebuilt-multiarch', {
345
407
  stdio: 'inherit',
346
408
  timeout: 60000
@@ -352,6 +414,19 @@ async function attemptAutoFixes(report) {
352
414
  manual.push('If that fails: Install VS Build Tools, then: npm rebuild node-pty');
353
415
  }
354
416
  break;
417
+ case 'hooks':
418
+ // Try to install hooks
419
+ console.log(chalk_1.default.yellow('\nAttempting to install hooks...'));
420
+ try {
421
+ // Import and call directly
422
+ const { hooksInstall } = require('./hooks');
423
+ await hooksInstall({ global: true, verbose: false });
424
+ fixed.push('Hooks installed');
425
+ }
426
+ catch {
427
+ manual.push('ekkos hooks install --global');
428
+ }
429
+ break;
355
430
  case 'mcp':
356
431
  // Try to configure MCP (safe)
357
432
  console.log(chalk_1.default.yellow('\nAttempting to configure MCP...'));
@@ -388,26 +463,26 @@ async function doctor(options = {}) {
388
463
  displayReport(report);
389
464
  // Fix mode - attempt safe auto-fixes
390
465
  if (options.fix && !report.canProceed) {
391
- console.log(chalk_1.default.cyan.bold('\n🔧 Attempting auto-fixes...\n'));
466
+ console.log(chalk_1.default.cyan.bold('\nAttempting auto-fixes...\n'));
392
467
  const { fixed, manual } = await attemptAutoFixes(report);
393
468
  if (fixed.length > 0) {
394
469
  console.log(chalk_1.default.green('\n✓ Auto-fixed:'));
395
470
  for (const f of fixed) {
396
- console.log(chalk_1.default.green(` ${f}`));
471
+ console.log(chalk_1.default.green(` - ${f}`));
397
472
  }
398
473
  }
399
474
  if (manual.length > 0) {
400
- console.log(chalk_1.default.yellow('\n⚠ Manual steps required:'));
475
+ console.log(chalk_1.default.yellow('\nManual steps required:'));
401
476
  for (const m of manual) {
402
477
  console.log(chalk_1.default.white(` ${m}`));
403
478
  }
404
479
  }
405
480
  // Re-run diagnostics to check if fixes worked
406
- console.log(chalk_1.default.cyan('\n🔄 Re-checking...'));
481
+ console.log(chalk_1.default.cyan('\nRe-checking...'));
407
482
  const recheck = runDiagnostics();
408
483
  displayReport(recheck);
409
484
  if (recheck.canProceed) {
410
- console.log(chalk_1.default.green.bold('All issues resolved!'));
485
+ console.log(chalk_1.default.green.bold('All issues resolved!'));
411
486
  }
412
487
  process.exit(recheck.canProceed ? 0 : 1);
413
488
  }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * ekkOS CLI: hooks subcommand
3
+ * Implements: ekkos hooks install | verify | status
4
+ *
5
+ * Per ekkOS Onboarding Spec v1.2 + Addendum:
6
+ * - Manifest-driven deployment
7
+ * - SHA256 checksum verification
8
+ * - Installed-state manifest tracking
9
+ */
10
+ interface ManifestFile {
11
+ source: string;
12
+ destination: string;
13
+ description: string;
14
+ checksum: string | null;
15
+ overwrite?: 'always' | 'createOnly';
16
+ executable?: boolean;
17
+ }
18
+ interface Manifest {
19
+ $schema?: string;
20
+ manifestVersion: string;
21
+ generatedAt: string | null;
22
+ platforms: {
23
+ darwin: {
24
+ configDir: string;
25
+ globalHooksDir: string;
26
+ shell: string;
27
+ };
28
+ linux: {
29
+ configDir: string;
30
+ globalHooksDir: string;
31
+ shell: string;
32
+ };
33
+ win32: {
34
+ configDir: string;
35
+ globalHooksDir: string;
36
+ shell: string;
37
+ };
38
+ };
39
+ files: {
40
+ managed: ManifestFile[];
41
+ helpers: ManifestFile[];
42
+ userEditable: ManifestFile[];
43
+ hooks: {
44
+ bash: ManifestFile[];
45
+ powershell: ManifestFile[];
46
+ lib: ManifestFile[];
47
+ };
48
+ };
49
+ projectStubs?: {
50
+ description?: string;
51
+ bash: ManifestFile[];
52
+ powershell: ManifestFile[];
53
+ };
54
+ cli: {
55
+ minVersion: string;
56
+ legacySupported: boolean;
57
+ requiredCommands: Record<string, string[]>;
58
+ };
59
+ validation: {
60
+ requiredFiles: string[];
61
+ checksumAlgorithm: string;
62
+ };
63
+ }
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
+ export declare function findManifest(): {
81
+ path: string;
82
+ templatesDir: string;
83
+ } | null;
84
+ /**
85
+ * Load manifest from disk
86
+ */
87
+ export declare function loadManifest(): {
88
+ manifest: Manifest;
89
+ path: string;
90
+ templatesDir: string;
91
+ } | null;
92
+ declare function findProjectRoot(startDir?: string): string;
93
+ interface InstallOptions {
94
+ global?: boolean;
95
+ project?: boolean;
96
+ verbose?: boolean;
97
+ }
98
+ export declare function hooksInstall(options: InstallOptions): Promise<void>;
99
+ interface VerifyOptions {
100
+ global?: boolean;
101
+ project?: boolean;
102
+ verbose?: boolean;
103
+ }
104
+ export declare function hooksVerify(options: VerifyOptions): Promise<VerifyResult>;
105
+ interface StatusOptions {
106
+ verbose?: boolean;
107
+ }
108
+ export declare function hooksStatus(options: StatusOptions): Promise<void>;
109
+ export { findProjectRoot, expandPath };