@ekkos/cli 0.2.9 ā 0.2.11
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/agent/daemon.d.ts +86 -0
- package/dist/agent/daemon.js +297 -0
- package/dist/agent/pty-runner.d.ts +51 -0
- package/dist/agent/pty-runner.js +184 -0
- package/dist/cache/LocalSessionStore.d.ts +34 -21
- package/dist/cache/LocalSessionStore.js +169 -53
- package/dist/cache/capture.d.ts +19 -11
- package/dist/cache/capture.js +243 -76
- package/dist/cache/types.d.ts +14 -1
- package/dist/commands/agent.d.ts +44 -0
- package/dist/commands/agent.js +300 -0
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.js +175 -87
- package/dist/commands/hooks.d.ts +109 -0
- package/dist/commands/hooks.js +668 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +357 -85
- package/dist/commands/setup-remote.d.ts +20 -0
- package/dist/commands/setup-remote.js +467 -0
- package/dist/index.js +116 -1
- package/dist/restore/RestoreOrchestrator.d.ts +17 -3
- package/dist/restore/RestoreOrchestrator.js +64 -22
- package/dist/utils/paths.d.ts +125 -0
- package/dist/utils/paths.js +283 -0
- package/dist/utils/state.d.ts +2 -0
- package/package.json +1 -1
- package/templates/ekkos-manifest.json +223 -0
- package/templates/helpers/json-parse.cjs +101 -0
- package/templates/hooks/assistant-response.ps1 +256 -0
- package/templates/hooks/assistant-response.sh +124 -64
- package/templates/hooks/session-start.ps1 +107 -2
- package/templates/hooks/session-start.sh +201 -166
- package/templates/hooks/stop.ps1 +124 -3
- package/templates/hooks/stop.sh +470 -843
- package/templates/hooks/user-prompt-submit.ps1 +107 -22
- package/templates/hooks/user-prompt-submit.sh +403 -393
- package/templates/project-stubs/session-start.ps1 +63 -0
- package/templates/project-stubs/session-start.sh +55 -0
- package/templates/project-stubs/stop.ps1 +63 -0
- package/templates/project-stubs/stop.sh +55 -0
- package/templates/project-stubs/user-prompt-submit.ps1 +63 -0
- package/templates/project-stubs/user-prompt-submit.sh +55 -0
- package/templates/shared/hooks-enabled.json +22 -0
- package/templates/shared/session-words.json +45 -0
package/dist/commands/doctor.js
CHANGED
|
@@ -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
|
*/
|
|
@@ -65,25 +78,22 @@ function checkPty() {
|
|
|
65
78
|
*/
|
|
66
79
|
function checkMcpConfig() {
|
|
67
80
|
try {
|
|
68
|
-
const output = (0, child_process_1.execSync)('claude mcp list', {
|
|
81
|
+
const output = (0, child_process_1.execSync)('claude mcp list 2>&1', {
|
|
69
82
|
encoding: 'utf-8',
|
|
70
|
-
timeout:
|
|
83
|
+
timeout: 30000, // 30s - health checks can be slow
|
|
71
84
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
72
85
|
});
|
|
73
|
-
|
|
74
|
-
const servers = [];
|
|
75
|
-
const lines = output.split('\n');
|
|
76
|
-
for (const line of lines) {
|
|
77
|
-
// Look for server names (varies by Claude version)
|
|
78
|
-
if (line.includes('ekkos') || line.includes('memory')) {
|
|
79
|
-
servers.push(line.trim());
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const hasEkkos = output.toLowerCase().includes('ekkos') ||
|
|
83
|
-
output.toLowerCase().includes('memory');
|
|
84
|
-
return { configured: hasEkkos, servers };
|
|
86
|
+
return parseMcpOutput(output);
|
|
85
87
|
}
|
|
86
88
|
catch (error) {
|
|
89
|
+
// Even on timeout/error, stdout may contain partial output
|
|
90
|
+
const partialOutput = error?.stdout || error?.output?.[1] || '';
|
|
91
|
+
if (partialOutput) {
|
|
92
|
+
const result = parseMcpOutput(partialOutput);
|
|
93
|
+
if (result.configured) {
|
|
94
|
+
return result; // Got what we needed despite timeout
|
|
95
|
+
}
|
|
96
|
+
}
|
|
87
97
|
return {
|
|
88
98
|
configured: false,
|
|
89
99
|
servers: [],
|
|
@@ -92,18 +102,58 @@ function checkMcpConfig() {
|
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
104
|
/**
|
|
95
|
-
*
|
|
105
|
+
* Parse MCP list output to find ekkOS servers
|
|
96
106
|
*/
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
function parseMcpOutput(output) {
|
|
108
|
+
const servers = [];
|
|
109
|
+
const lines = output.split('\n');
|
|
110
|
+
for (const line of lines) {
|
|
111
|
+
// Look for server names (varies by Claude version)
|
|
112
|
+
if (line.includes('ekkos') || line.includes('memory')) {
|
|
113
|
+
servers.push(line.trim());
|
|
114
|
+
}
|
|
103
115
|
}
|
|
104
|
-
|
|
105
|
-
|
|
116
|
+
const hasEkkos = output.toLowerCase().includes('ekkos') ||
|
|
117
|
+
output.toLowerCase().includes('memory');
|
|
118
|
+
return { configured: hasEkkos, servers };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Check hooks installation status
|
|
122
|
+
*/
|
|
123
|
+
function checkHooksInstallation() {
|
|
124
|
+
const isWindows = (0, os_1.platform)() === 'win32';
|
|
125
|
+
const manifestData = (0, hooks_1.loadManifest)();
|
|
126
|
+
if (!manifestData) {
|
|
127
|
+
return { installed: false, details: 'Source manifest not found' };
|
|
128
|
+
}
|
|
129
|
+
const platformConfig = manifestData.manifest.platforms[(0, os_1.platform)()];
|
|
130
|
+
const globalHooksDir = (0, hooks_1.expandPath)(platformConfig.globalHooksDir);
|
|
131
|
+
const configDir = (0, hooks_1.expandPath)(platformConfig.configDir);
|
|
132
|
+
// Check installed manifest
|
|
133
|
+
const installedManifestPath = (0, path_1.join)(globalHooksDir, '.ekkos-manifest.json');
|
|
134
|
+
if (!(0, fs_1.existsSync)(installedManifestPath)) {
|
|
135
|
+
return { installed: false, details: 'No installed manifest found' };
|
|
136
|
+
}
|
|
137
|
+
// Check helper
|
|
138
|
+
const helperPath = (0, path_1.join)(configDir, '.helpers', 'json-parse.cjs');
|
|
139
|
+
if (!(0, fs_1.existsSync)(helperPath)) {
|
|
140
|
+
return { installed: false, details: 'json-parse.cjs helper missing' };
|
|
106
141
|
}
|
|
142
|
+
// Check defaults
|
|
143
|
+
const defaultsPath = (0, path_1.join)(configDir, '.defaults', 'session-words.json');
|
|
144
|
+
if (!(0, fs_1.existsSync)(defaultsPath)) {
|
|
145
|
+
return { installed: false, details: 'Default session words missing' };
|
|
146
|
+
}
|
|
147
|
+
// Check required hooks
|
|
148
|
+
const hookExt = isWindows ? '.ps1' : '.sh';
|
|
149
|
+
const requiredHooks = ['user-prompt-submit', 'stop', 'session-start'];
|
|
150
|
+
for (const hookName of requiredHooks) {
|
|
151
|
+
const hookPath = (0, path_1.join)(globalHooksDir, `${hookName}${hookExt}`);
|
|
152
|
+
if (!(0, fs_1.existsSync)(hookPath)) {
|
|
153
|
+
return { installed: false, details: `${hookName}${hookExt} missing` };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return { installed: true, details: 'All hooks and helpers installed' };
|
|
107
157
|
}
|
|
108
158
|
/**
|
|
109
159
|
* Run diagnostic checks and return report
|
|
@@ -115,11 +165,38 @@ function runDiagnostics() {
|
|
|
115
165
|
const nodeVersion = getVersion('node') || 'Not found';
|
|
116
166
|
const claudeVersion = getVersion('claude');
|
|
117
167
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
118
|
-
// GATE 1:
|
|
168
|
+
// GATE 1: Node.js Version (>= 18 required per spec)
|
|
169
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
170
|
+
const nodeChecks = [];
|
|
171
|
+
let nodeGatePass = true;
|
|
172
|
+
const nodeMatch = nodeVersion.match(/v?(\d+)\./);
|
|
173
|
+
const nodeMajor = nodeMatch ? parseInt(nodeMatch[1], 10) : 0;
|
|
174
|
+
const nodeOk = nodeMajor >= 18;
|
|
175
|
+
nodeChecks.push({
|
|
176
|
+
name: 'Node.js version >= 18',
|
|
177
|
+
passed: nodeOk,
|
|
178
|
+
detail: nodeOk
|
|
179
|
+
? `${nodeVersion} (OK)`
|
|
180
|
+
: `${nodeVersion} (Need Node.js 18 or higher)`
|
|
181
|
+
});
|
|
182
|
+
if (!nodeOk) {
|
|
183
|
+
nodeGatePass = false;
|
|
184
|
+
}
|
|
185
|
+
gates.push({
|
|
186
|
+
id: 'node',
|
|
187
|
+
title: 'Node.js',
|
|
188
|
+
status: nodeGatePass ? 'PASS' : 'FAIL',
|
|
189
|
+
checks: nodeChecks,
|
|
190
|
+
fix: nodeGatePass ? undefined : isWindows
|
|
191
|
+
? 'winget install OpenJS.NodeJS.LTS'
|
|
192
|
+
: 'Install Node.js 20 or 22 LTS from nodejs.org'
|
|
193
|
+
});
|
|
194
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
195
|
+
// GATE 2: Interactive Claude Works
|
|
119
196
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
120
197
|
const claudeChecks = [];
|
|
121
198
|
let claudeGatePass = true;
|
|
122
|
-
// Check
|
|
199
|
+
// Check 2.1: claude command exists
|
|
123
200
|
const claudeExists = commandExists('claude');
|
|
124
201
|
claudeChecks.push({
|
|
125
202
|
name: 'Claude CLI installed',
|
|
@@ -128,7 +205,7 @@ function runDiagnostics() {
|
|
|
128
205
|
});
|
|
129
206
|
if (!claudeExists)
|
|
130
207
|
claudeGatePass = false;
|
|
131
|
-
// Check
|
|
208
|
+
// Check 2.2: claude --version works
|
|
132
209
|
if (claudeExists) {
|
|
133
210
|
const versionWorks = claudeVersion !== null;
|
|
134
211
|
claudeChecks.push({
|
|
@@ -139,15 +216,6 @@ function runDiagnostics() {
|
|
|
139
216
|
if (!versionWorks)
|
|
140
217
|
claudeGatePass = false;
|
|
141
218
|
}
|
|
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
219
|
gates.push({
|
|
152
220
|
id: 'interactive-claude',
|
|
153
221
|
title: 'Interactive Claude',
|
|
@@ -156,52 +224,71 @@ function runDiagnostics() {
|
|
|
156
224
|
fix: claudeGatePass ? undefined : 'npm install -g @anthropic-ai/claude-code'
|
|
157
225
|
});
|
|
158
226
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
159
|
-
// GATE
|
|
227
|
+
// GATE 3: PTY Works (WARN on Windows if missing per spec v1.2 Addendum)
|
|
160
228
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
161
229
|
const ptyChecks = [];
|
|
162
|
-
let
|
|
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;
|
|
230
|
+
let ptyGateStatus = 'PASS';
|
|
176
231
|
// Check PTY availability
|
|
177
232
|
const ptyResult = checkPty();
|
|
178
233
|
ptyChecks.push({
|
|
179
234
|
name: 'node-pty loadable',
|
|
180
235
|
passed: ptyResult.available,
|
|
181
|
-
detail: ptyResult.available
|
|
236
|
+
detail: ptyResult.available
|
|
237
|
+
? isWindows ? 'ConPTY available' : 'PTY available'
|
|
238
|
+
: ptyResult.error
|
|
182
239
|
});
|
|
183
|
-
if (!ptyResult.available
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
240
|
+
if (!ptyResult.available) {
|
|
241
|
+
if (isWindows) {
|
|
242
|
+
// Per spec: Windows without PTY is WARN (monitor-only mode available)
|
|
243
|
+
ptyGateStatus = 'WARN';
|
|
244
|
+
ptyChecks.push({
|
|
245
|
+
name: 'Monitor-only mode',
|
|
246
|
+
passed: true,
|
|
247
|
+
detail: 'Auto-continue disabled; manual /clear + /continue required'
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
// Unix has script(1) fallback
|
|
252
|
+
ptyChecks.push({
|
|
253
|
+
name: 'Unix fallback',
|
|
254
|
+
passed: true,
|
|
255
|
+
detail: 'script(1) fallback available'
|
|
256
|
+
});
|
|
257
|
+
ptyGateStatus = 'PASS';
|
|
258
|
+
}
|
|
193
259
|
}
|
|
194
260
|
gates.push({
|
|
195
261
|
id: 'pty',
|
|
196
262
|
title: 'PTY (Terminal)',
|
|
197
|
-
status:
|
|
263
|
+
status: ptyGateStatus,
|
|
198
264
|
checks: ptyChecks,
|
|
199
|
-
fix:
|
|
200
|
-
? 'npm install node-pty-prebuilt-multiarch
|
|
265
|
+
fix: ptyGateStatus === 'WARN'
|
|
266
|
+
? 'npm install node-pty-prebuilt-multiarch (optional for auto-continue)'
|
|
201
267
|
: undefined
|
|
202
268
|
});
|
|
203
269
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
204
|
-
// GATE
|
|
270
|
+
// GATE 4: Hooks Installation (per spec v1.2)
|
|
271
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
272
|
+
const hooksChecks = [];
|
|
273
|
+
let hooksGateStatus = 'PASS';
|
|
274
|
+
const hooksResult = checkHooksInstallation();
|
|
275
|
+
hooksChecks.push({
|
|
276
|
+
name: 'ekkOS hooks installed',
|
|
277
|
+
passed: hooksResult.installed,
|
|
278
|
+
detail: hooksResult.details
|
|
279
|
+
});
|
|
280
|
+
if (!hooksResult.installed) {
|
|
281
|
+
hooksGateStatus = 'WARN';
|
|
282
|
+
}
|
|
283
|
+
gates.push({
|
|
284
|
+
id: 'hooks',
|
|
285
|
+
title: 'ekkOS Hooks',
|
|
286
|
+
status: hooksGateStatus,
|
|
287
|
+
checks: hooksChecks,
|
|
288
|
+
fix: hooksGateStatus === 'WARN' ? 'ekkos hooks install --global' : undefined
|
|
289
|
+
});
|
|
290
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
291
|
+
// GATE 5: MCP Works
|
|
205
292
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
206
293
|
const mcpChecks = [];
|
|
207
294
|
let mcpGatePass = true;
|
|
@@ -227,16 +314,6 @@ function runDiagnostics() {
|
|
|
227
314
|
if (!mcpResult.configured)
|
|
228
315
|
mcpGatePass = false;
|
|
229
316
|
}
|
|
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
317
|
gates.push({
|
|
241
318
|
id: 'mcp',
|
|
242
319
|
title: 'MCP Configuration',
|
|
@@ -248,7 +325,6 @@ function runDiagnostics() {
|
|
|
248
325
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
249
326
|
// Calculate overall status
|
|
250
327
|
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
251
|
-
const allPass = gates.every(g => g.status === 'PASS');
|
|
252
328
|
const hasBlocker = gates.some(g => g.status === 'FAIL');
|
|
253
329
|
return {
|
|
254
330
|
platform: isWindows ? 'Windows' : (0, os_1.platform)() === 'darwin' ? 'macOS' : 'Linux',
|
|
@@ -263,8 +339,8 @@ function runDiagnostics() {
|
|
|
263
339
|
*/
|
|
264
340
|
function displayReport(report) {
|
|
265
341
|
console.log('');
|
|
266
|
-
console.log(chalk_1.default.cyan.bold('
|
|
267
|
-
console.log(chalk_1.default.gray('
|
|
342
|
+
console.log(chalk_1.default.cyan.bold('ekkOS Doctor'));
|
|
343
|
+
console.log(chalk_1.default.gray('-'.repeat(60)));
|
|
268
344
|
console.log('');
|
|
269
345
|
// Platform info
|
|
270
346
|
console.log(chalk_1.default.gray(`Platform: ${report.platform}`));
|
|
@@ -289,13 +365,13 @@ function displayReport(report) {
|
|
|
289
365
|
console.log(chalk_1.default.gray(` ${check.detail}`));
|
|
290
366
|
}
|
|
291
367
|
}
|
|
292
|
-
if (gate.fix && gate.status === 'FAIL') {
|
|
368
|
+
if (gate.fix && (gate.status === 'FAIL' || gate.status === 'WARN')) {
|
|
293
369
|
console.log(chalk_1.default.yellow(` ā Fix: ${gate.fix}`));
|
|
294
370
|
}
|
|
295
371
|
console.log('');
|
|
296
372
|
}
|
|
297
373
|
// Summary
|
|
298
|
-
console.log(chalk_1.default.gray('
|
|
374
|
+
console.log(chalk_1.default.gray('-'.repeat(60)));
|
|
299
375
|
const blockers = report.gates.filter(g => g.status === 'FAIL').length;
|
|
300
376
|
const warnings = report.gates.filter(g => g.status === 'WARN').length;
|
|
301
377
|
if (blockers > 0) {
|
|
@@ -307,7 +383,7 @@ function displayReport(report) {
|
|
|
307
383
|
console.log(chalk_1.default.green('ekkOS can run, but some features may be limited.'));
|
|
308
384
|
}
|
|
309
385
|
else {
|
|
310
|
-
console.log(chalk_1.default.green.bold('
|
|
386
|
+
console.log(chalk_1.default.green.bold('All systems operational'));
|
|
311
387
|
console.log(chalk_1.default.green('ekkOS is ready to use.'));
|
|
312
388
|
}
|
|
313
389
|
console.log('');
|
|
@@ -340,7 +416,6 @@ async function attemptAutoFixes(report) {
|
|
|
340
416
|
// Try prebuilt PTY first (safe, non-admin)
|
|
341
417
|
console.log(chalk_1.default.yellow('\nAttempting to install prebuilt PTY...'));
|
|
342
418
|
try {
|
|
343
|
-
// Try homebridge prebuilt first
|
|
344
419
|
(0, child_process_1.execSync)('npm install -g @homebridge/node-pty-prebuilt-multiarch', {
|
|
345
420
|
stdio: 'inherit',
|
|
346
421
|
timeout: 60000
|
|
@@ -352,6 +427,19 @@ async function attemptAutoFixes(report) {
|
|
|
352
427
|
manual.push('If that fails: Install VS Build Tools, then: npm rebuild node-pty');
|
|
353
428
|
}
|
|
354
429
|
break;
|
|
430
|
+
case 'hooks':
|
|
431
|
+
// Try to install hooks
|
|
432
|
+
console.log(chalk_1.default.yellow('\nAttempting to install hooks...'));
|
|
433
|
+
try {
|
|
434
|
+
// Import and call directly
|
|
435
|
+
const { hooksInstall } = require('./hooks');
|
|
436
|
+
await hooksInstall({ global: true, verbose: false });
|
|
437
|
+
fixed.push('Hooks installed');
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
manual.push('ekkos hooks install --global');
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
355
443
|
case 'mcp':
|
|
356
444
|
// Try to configure MCP (safe)
|
|
357
445
|
console.log(chalk_1.default.yellow('\nAttempting to configure MCP...'));
|
|
@@ -388,26 +476,26 @@ async function doctor(options = {}) {
|
|
|
388
476
|
displayReport(report);
|
|
389
477
|
// Fix mode - attempt safe auto-fixes
|
|
390
478
|
if (options.fix && !report.canProceed) {
|
|
391
|
-
console.log(chalk_1.default.cyan.bold('\
|
|
479
|
+
console.log(chalk_1.default.cyan.bold('\nAttempting auto-fixes...\n'));
|
|
392
480
|
const { fixed, manual } = await attemptAutoFixes(report);
|
|
393
481
|
if (fixed.length > 0) {
|
|
394
482
|
console.log(chalk_1.default.green('\nā Auto-fixed:'));
|
|
395
483
|
for (const f of fixed) {
|
|
396
|
-
console.log(chalk_1.default.green(`
|
|
484
|
+
console.log(chalk_1.default.green(` - ${f}`));
|
|
397
485
|
}
|
|
398
486
|
}
|
|
399
487
|
if (manual.length > 0) {
|
|
400
|
-
console.log(chalk_1.default.yellow('\
|
|
488
|
+
console.log(chalk_1.default.yellow('\nManual steps required:'));
|
|
401
489
|
for (const m of manual) {
|
|
402
490
|
console.log(chalk_1.default.white(` ${m}`));
|
|
403
491
|
}
|
|
404
492
|
}
|
|
405
493
|
// Re-run diagnostics to check if fixes worked
|
|
406
|
-
console.log(chalk_1.default.cyan('\
|
|
494
|
+
console.log(chalk_1.default.cyan('\nRe-checking...'));
|
|
407
495
|
const recheck = runDiagnostics();
|
|
408
496
|
displayReport(recheck);
|
|
409
497
|
if (recheck.canProceed) {
|
|
410
|
-
console.log(chalk_1.default.green.bold('
|
|
498
|
+
console.log(chalk_1.default.green.bold('All issues resolved!'));
|
|
411
499
|
}
|
|
412
500
|
process.exit(recheck.canProceed ? 0 : 1);
|
|
413
501
|
}
|
|
@@ -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 };
|