@ekkos/cli 0.3.3 → 1.0.1
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/README.md +57 -0
- package/dist/agent/daemon.d.ts +27 -0
- package/dist/agent/daemon.js +254 -29
- package/dist/agent/health-check.d.ts +35 -0
- package/dist/agent/health-check.js +243 -0
- package/dist/agent/pty-runner.d.ts +1 -0
- package/dist/agent/pty-runner.js +6 -1
- package/dist/capture/transcript-repair.d.ts +1 -0
- package/dist/capture/transcript-repair.js +12 -1
- package/dist/commands/agent.d.ts +6 -0
- package/dist/commands/agent.js +244 -0
- package/dist/commands/dashboard.d.ts +25 -0
- package/dist/commands/dashboard.js +1175 -0
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.js +503 -350
- package/dist/commands/setup-remote.js +146 -37
- package/dist/commands/swarm-dashboard.d.ts +20 -0
- package/dist/commands/swarm-dashboard.js +735 -0
- package/dist/commands/swarm-setup.d.ts +10 -0
- package/dist/commands/swarm-setup.js +956 -0
- package/dist/commands/swarm.d.ts +46 -0
- package/dist/commands/swarm.js +441 -0
- package/dist/commands/test-claude.d.ts +16 -0
- package/dist/commands/test-claude.js +156 -0
- package/dist/commands/usage/blocks.d.ts +8 -0
- package/dist/commands/usage/blocks.js +60 -0
- package/dist/commands/usage/daily.d.ts +9 -0
- package/dist/commands/usage/daily.js +96 -0
- package/dist/commands/usage/dashboard.d.ts +8 -0
- package/dist/commands/usage/dashboard.js +104 -0
- package/dist/commands/usage/formatters.d.ts +41 -0
- package/dist/commands/usage/formatters.js +147 -0
- package/dist/commands/usage/index.d.ts +13 -0
- package/dist/commands/usage/index.js +87 -0
- package/dist/commands/usage/monthly.d.ts +8 -0
- package/dist/commands/usage/monthly.js +66 -0
- package/dist/commands/usage/session.d.ts +11 -0
- package/dist/commands/usage/session.js +193 -0
- package/dist/commands/usage/weekly.d.ts +9 -0
- package/dist/commands/usage/weekly.js +61 -0
- package/dist/deploy/instructions.d.ts +5 -2
- package/dist/deploy/instructions.js +11 -8
- package/dist/index.js +256 -20
- package/dist/lib/tmux-scrollbar.d.ts +14 -0
- package/dist/lib/tmux-scrollbar.js +296 -0
- package/dist/lib/usage-parser.d.ts +95 -5
- package/dist/lib/usage-parser.js +416 -71
- package/dist/utils/log-rotate.d.ts +18 -0
- package/dist/utils/log-rotate.js +74 -0
- package/dist/utils/platform.d.ts +2 -0
- package/dist/utils/platform.js +3 -1
- package/dist/utils/session-binding.d.ts +5 -0
- package/dist/utils/session-binding.js +46 -0
- package/dist/utils/state.js +4 -0
- package/dist/utils/verify-remote-terminal.d.ts +10 -0
- package/dist/utils/verify-remote-terminal.js +415 -0
- package/package.json +16 -11
- package/templates/CLAUDE.md +135 -23
- package/templates/cursor-hooks/after-agent-response.sh +0 -0
- package/templates/cursor-hooks/before-submit-prompt.sh +0 -0
- package/templates/cursor-hooks/stop.sh +0 -0
- package/templates/ekkos-manifest.json +5 -5
- package/templates/hooks/assistant-response.sh +0 -0
- package/templates/hooks/lib/contract.sh +43 -31
- package/templates/hooks/lib/count-tokens.cjs +86 -0
- package/templates/hooks/lib/ekkos-reminders.sh +98 -0
- package/templates/hooks/lib/state.sh +53 -1
- package/templates/hooks/session-start.sh +0 -0
- package/templates/hooks/stop.sh +150 -388
- package/templates/hooks/user-prompt-submit.sh +353 -443
- package/templates/plan-template.md +0 -0
- package/templates/spec-template.md +0 -0
- package/templates/windsurf-hooks/README.md +212 -0
- package/templates/windsurf-hooks/hooks.json +9 -2
- package/templates/windsurf-hooks/install.sh +148 -0
- package/templates/windsurf-hooks/lib/contract.sh +2 -0
- package/templates/windsurf-hooks/post-cascade-response.sh +251 -0
- package/templates/windsurf-hooks/pre-user-prompt.sh +435 -0
- package/templates/windsurf-skills/ekkos-memory/SKILL.md +219 -0
- package/LICENSE +0 -21
- package/templates/windsurf-hooks/before-submit-prompt.sh +0 -238
package/dist/utils/platform.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WINDSURF_MCP = exports.WINDSURF_DIR = exports.CURSOR_MCP = exports.CURSOR_DIR = exports.CLAUDE_MD = exports.CLAUDE_STATE_DIR = exports.CLAUDE_PLUGINS_DIR = exports.CLAUDE_AGENTS_DIR = exports.CLAUDE_SKILLS_DIR = exports.CLAUDE_HOOKS_DIR = exports.CLAUDE_SETTINGS = exports.CLAUDE_CONFIG = exports.CLAUDE_DIR = exports.EKKOS_CONFIG = exports.EKKOS_DIR = exports.HOME_DIR = exports.MCP_API_URL = exports.PLATFORM_URL = exports.isLinux = exports.isMac = exports.isWindows = void 0;
|
|
3
|
+
exports.WINDSURF_MCP = exports.WINDSURF_DIR = exports.CURSOR_MCP = exports.CURSOR_DIR = exports.CLAUDE_EKKOS_RULES = exports.CLAUDE_RULES_DIR = exports.CLAUDE_MD = exports.CLAUDE_STATE_DIR = exports.CLAUDE_PLUGINS_DIR = exports.CLAUDE_AGENTS_DIR = exports.CLAUDE_SKILLS_DIR = exports.CLAUDE_HOOKS_DIR = exports.CLAUDE_SETTINGS = exports.CLAUDE_CONFIG = exports.CLAUDE_DIR = exports.EKKOS_CONFIG = exports.EKKOS_DIR = exports.HOME_DIR = exports.MCP_API_URL = exports.PLATFORM_URL = exports.isLinux = exports.isMac = exports.isWindows = void 0;
|
|
4
4
|
exports.detectInstalledIDEs = detectInstalledIDEs;
|
|
5
5
|
exports.detectCurrentIDE = detectCurrentIDE;
|
|
6
6
|
const os_1 = require("os");
|
|
@@ -24,6 +24,8 @@ exports.CLAUDE_AGENTS_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'agents');
|
|
|
24
24
|
exports.CLAUDE_PLUGINS_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'plugins', 'ekkos');
|
|
25
25
|
exports.CLAUDE_STATE_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'state');
|
|
26
26
|
exports.CLAUDE_MD = (0, path_1.join)(exports.CLAUDE_DIR, 'CLAUDE.md');
|
|
27
|
+
exports.CLAUDE_RULES_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'rules');
|
|
28
|
+
exports.CLAUDE_EKKOS_RULES = (0, path_1.join)(exports.CLAUDE_RULES_DIR, 'ekkos.md');
|
|
27
29
|
exports.CURSOR_DIR = (0, path_1.join)(exports.HOME_DIR, '.cursor');
|
|
28
30
|
exports.CURSOR_MCP = (0, path_1.join)(exports.CURSOR_DIR, 'mcp.json');
|
|
29
31
|
exports.WINDSURF_DIR = (0, path_1.join)(exports.HOME_DIR, '.codeium', 'windsurf');
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bindSession = bindSession;
|
|
4
|
+
const state_1 = require("./state");
|
|
5
|
+
const MEMORY_API_URL = process.env.EKKOS_PROXY_URL || 'https://proxy.ekkos.dev';
|
|
6
|
+
/**
|
|
7
|
+
* Bind the real session name to the proxy
|
|
8
|
+
* This replaces the '_pending' placeholder for this user, enabling proper eviction
|
|
9
|
+
*/
|
|
10
|
+
async function bindSession(realSession, projectPath, pendingSession) {
|
|
11
|
+
try {
|
|
12
|
+
// Get userId same way as run.ts
|
|
13
|
+
const config = (0, state_1.getConfig)();
|
|
14
|
+
let userId = config?.userId;
|
|
15
|
+
if (!userId || userId === 'anonymous') {
|
|
16
|
+
const token = (0, state_1.getAuthToken)();
|
|
17
|
+
if (token?.startsWith('ekk_')) {
|
|
18
|
+
const parts = token.split('_');
|
|
19
|
+
if (parts.length >= 2) {
|
|
20
|
+
userId = parts[1];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (!userId || userId === 'anonymous')
|
|
25
|
+
return false;
|
|
26
|
+
// Use global fetch (Node 18+)
|
|
27
|
+
const response = await fetch(`${MEMORY_API_URL}/proxy/session/bind`, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
signal: AbortSignal.timeout(2500),
|
|
30
|
+
headers: {
|
|
31
|
+
'Content-Type': 'application/json'
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
userId,
|
|
35
|
+
realSession,
|
|
36
|
+
projectPath,
|
|
37
|
+
pendingSession: pendingSession || null,
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
return response.ok;
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
// Fail silently - don't crash CLI
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/utils/state.js
CHANGED
|
@@ -341,6 +341,10 @@ function getActiveSessionByName(sessionName) {
|
|
|
341
341
|
* Check if a process is still alive
|
|
342
342
|
*/
|
|
343
343
|
function isProcessAlive(pid) {
|
|
344
|
+
// pid <= 1 (including 0 placeholders from hooks) are not valid user processes.
|
|
345
|
+
if (!Number.isInteger(pid) || pid <= 1) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
344
348
|
try {
|
|
345
349
|
// Sending signal 0 doesn't kill the process, just checks if it exists
|
|
346
350
|
process.kill(pid, 0);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote Terminal Verification Script
|
|
3
|
+
*
|
|
4
|
+
* Systematically verifies that all remote terminal components are properly configured
|
|
5
|
+
* and working correctly. Run this after setup-remote to validate the installation.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Run all verification tests
|
|
9
|
+
*/
|
|
10
|
+
export declare function verifyRemoteTerminal(): Promise<void>;
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Remote Terminal Verification Script
|
|
4
|
+
*
|
|
5
|
+
* Systematically verifies that all remote terminal components are properly configured
|
|
6
|
+
* and working correctly. Run this after setup-remote to validate the installation.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
42
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.verifyRemoteTerminal = verifyRemoteTerminal;
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const os = __importStar(require("os"));
|
|
49
|
+
const child_process_1 = require("child_process");
|
|
50
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
51
|
+
const EKKOS_DIR = path.join(os.homedir(), '.ekkos');
|
|
52
|
+
const results = [];
|
|
53
|
+
/**
|
|
54
|
+
* Test 1: Device configuration file exists
|
|
55
|
+
*/
|
|
56
|
+
function testDeviceConfig() {
|
|
57
|
+
const devicePath = path.join(EKKOS_DIR, 'device.json');
|
|
58
|
+
if (fs.existsSync(devicePath)) {
|
|
59
|
+
try {
|
|
60
|
+
const data = JSON.parse(fs.readFileSync(devicePath, 'utf-8'));
|
|
61
|
+
if (data.deviceId && data.deviceToken && data.deviceName) {
|
|
62
|
+
results.push({
|
|
63
|
+
name: 'Device Configuration',
|
|
64
|
+
status: 'pass',
|
|
65
|
+
message: 'Device config found and valid',
|
|
66
|
+
details: `Device: ${data.deviceName} (${data.deviceId.slice(0, 8)}...)`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
results.push({
|
|
71
|
+
name: 'Device Configuration',
|
|
72
|
+
status: 'fail',
|
|
73
|
+
message: 'Device config exists but is incomplete',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
results.push({
|
|
79
|
+
name: 'Device Configuration',
|
|
80
|
+
status: 'fail',
|
|
81
|
+
message: 'Device config is invalid JSON',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
results.push({
|
|
87
|
+
name: 'Device Configuration',
|
|
88
|
+
status: 'fail',
|
|
89
|
+
message: 'Device not configured - run ekkos setup-remote',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Test 2: Service is installed
|
|
95
|
+
*/
|
|
96
|
+
function testServiceInstalled() {
|
|
97
|
+
const platform = os.platform();
|
|
98
|
+
try {
|
|
99
|
+
if (platform === 'darwin') {
|
|
100
|
+
// macOS: check launchctl
|
|
101
|
+
const output = (0, child_process_1.execSync)('launchctl list | grep dev.ekkos.agent || true', {
|
|
102
|
+
encoding: 'utf-8',
|
|
103
|
+
});
|
|
104
|
+
if (output.includes('dev.ekkos.agent')) {
|
|
105
|
+
results.push({
|
|
106
|
+
name: 'Service Installation',
|
|
107
|
+
status: 'pass',
|
|
108
|
+
message: 'Service is installed on macOS',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
results.push({
|
|
113
|
+
name: 'Service Installation',
|
|
114
|
+
status: 'fail',
|
|
115
|
+
message: 'Service not found - run ekkos agent start',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (platform === 'win32') {
|
|
120
|
+
// Windows: check scheduled task
|
|
121
|
+
const output = (0, child_process_1.execSync)('schtasks /query /tn "ekkOS Agent" 2>nul || echo NOT_FOUND', {
|
|
122
|
+
encoding: 'utf-8',
|
|
123
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
124
|
+
});
|
|
125
|
+
if (!output.includes('NOT_FOUND')) {
|
|
126
|
+
results.push({
|
|
127
|
+
name: 'Service Installation',
|
|
128
|
+
status: 'pass',
|
|
129
|
+
message: 'Service is installed on Windows',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
results.push({
|
|
134
|
+
name: 'Service Installation',
|
|
135
|
+
status: 'fail',
|
|
136
|
+
message: 'Service not found - run ekkos agent start',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (platform === 'linux') {
|
|
141
|
+
// Linux: check systemd
|
|
142
|
+
const output = (0, child_process_1.execSync)('systemctl --user is-active ekkos-agent 2>/dev/null || echo INACTIVE', {
|
|
143
|
+
encoding: 'utf-8',
|
|
144
|
+
});
|
|
145
|
+
if (output.includes('active')) {
|
|
146
|
+
results.push({
|
|
147
|
+
name: 'Service Installation',
|
|
148
|
+
status: 'pass',
|
|
149
|
+
message: 'Service is installed on Linux',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
results.push({
|
|
154
|
+
name: 'Service Installation',
|
|
155
|
+
status: 'fail',
|
|
156
|
+
message: 'Service not active - run ekkos agent start',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
results.push({
|
|
163
|
+
name: 'Service Installation',
|
|
164
|
+
status: 'fail',
|
|
165
|
+
message: 'Failed to check service status',
|
|
166
|
+
details: String(err).slice(0, 100),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Test 3: Service is running
|
|
172
|
+
*/
|
|
173
|
+
function testServiceRunning() {
|
|
174
|
+
const pidPath = path.join(EKKOS_DIR, 'agent.pid');
|
|
175
|
+
if (!fs.existsSync(pidPath)) {
|
|
176
|
+
results.push({
|
|
177
|
+
name: 'Service Running',
|
|
178
|
+
status: 'fail',
|
|
179
|
+
message: 'No PID file - service may not have started',
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const pid = parseInt(fs.readFileSync(pidPath, 'utf-8').trim());
|
|
185
|
+
(0, child_process_1.execSync)(`kill -0 ${pid} 2>/dev/null`);
|
|
186
|
+
results.push({
|
|
187
|
+
name: 'Service Running',
|
|
188
|
+
status: 'pass',
|
|
189
|
+
message: `Process running (PID ${pid})`,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
results.push({
|
|
194
|
+
name: 'Service Running',
|
|
195
|
+
status: 'fail',
|
|
196
|
+
message: 'PID file exists but process is not running - restart with ekkos agent restart',
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Test 4: Logs are being written
|
|
202
|
+
*/
|
|
203
|
+
function testLogsWriting() {
|
|
204
|
+
const logPath = path.join(EKKOS_DIR, 'agent.log');
|
|
205
|
+
if (!fs.existsSync(logPath)) {
|
|
206
|
+
results.push({
|
|
207
|
+
name: 'Log Output',
|
|
208
|
+
status: 'warn',
|
|
209
|
+
message: 'No logs yet - service may not have started or run long enough',
|
|
210
|
+
});
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const stats = fs.statSync(logPath);
|
|
215
|
+
const lastModified = Date.now() - stats.mtime.getTime();
|
|
216
|
+
const fiveMinutes = 5 * 60 * 1000;
|
|
217
|
+
if (lastModified < fiveMinutes) {
|
|
218
|
+
results.push({
|
|
219
|
+
name: 'Log Output',
|
|
220
|
+
status: 'pass',
|
|
221
|
+
message: `Logs updating (last write ${Math.round(lastModified / 1000)}s ago)`,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
results.push({
|
|
226
|
+
name: 'Log Output',
|
|
227
|
+
status: 'warn',
|
|
228
|
+
message: `No logs written for ${Math.round(lastModified / 1000 / 60)}m - check for errors`,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
results.push({
|
|
234
|
+
name: 'Log Output',
|
|
235
|
+
status: 'fail',
|
|
236
|
+
message: 'Failed to read log file',
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Test 5: Network connectivity to relay
|
|
242
|
+
*/
|
|
243
|
+
async function testNetworkConnectivity() {
|
|
244
|
+
try {
|
|
245
|
+
const RELAY_URL = 'wss://ekkos-relay-production.up.railway.app';
|
|
246
|
+
const controller = new AbortController();
|
|
247
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
248
|
+
const response = await fetch(`${RELAY_URL}/api/v1/health`, {
|
|
249
|
+
signal: controller.signal,
|
|
250
|
+
});
|
|
251
|
+
clearTimeout(timeout);
|
|
252
|
+
if (response.ok) {
|
|
253
|
+
results.push({
|
|
254
|
+
name: 'Network Connectivity',
|
|
255
|
+
status: 'pass',
|
|
256
|
+
message: 'Relay server is reachable',
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
results.push({
|
|
261
|
+
name: 'Network Connectivity',
|
|
262
|
+
status: 'warn',
|
|
263
|
+
message: `Relay server returned ${response.status}`,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
if (err.name === 'AbortError') {
|
|
269
|
+
results.push({
|
|
270
|
+
name: 'Network Connectivity',
|
|
271
|
+
status: 'fail',
|
|
272
|
+
message: 'Timeout connecting to relay (>5s) - check internet connection',
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
results.push({
|
|
277
|
+
name: 'Network Connectivity',
|
|
278
|
+
status: 'fail',
|
|
279
|
+
message: `Cannot reach relay server: ${err.message}`,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Test 6: WebSocket connection from daemon
|
|
286
|
+
*/
|
|
287
|
+
function testDaemonConnection() {
|
|
288
|
+
const logPath = path.join(EKKOS_DIR, 'agent.log');
|
|
289
|
+
if (!fs.existsSync(logPath)) {
|
|
290
|
+
results.push({
|
|
291
|
+
name: 'Daemon WebSocket',
|
|
292
|
+
status: 'warn',
|
|
293
|
+
message: 'Cannot check - no logs yet',
|
|
294
|
+
});
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
const content = fs.readFileSync(logPath, 'utf-8');
|
|
299
|
+
const lines = content.split('\n');
|
|
300
|
+
const lastLines = lines.slice(-50);
|
|
301
|
+
const hasConnected = lastLines.some(l => l.includes('Connected to relay server'));
|
|
302
|
+
const hasRegistered = lastLines.some(l => l.includes('Registered with relay'));
|
|
303
|
+
const hasError = lastLines.some(l => l.includes('WebSocket error'));
|
|
304
|
+
const hasDisconnected = lastLines.some(l => l.includes('Disconnected'));
|
|
305
|
+
if (hasConnected && hasRegistered && !hasError) {
|
|
306
|
+
results.push({
|
|
307
|
+
name: 'Daemon WebSocket',
|
|
308
|
+
status: 'pass',
|
|
309
|
+
message: 'Daemon connected and registered with relay',
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
else if (hasDisconnected && !hasConnected) {
|
|
313
|
+
results.push({
|
|
314
|
+
name: 'Daemon WebSocket',
|
|
315
|
+
status: 'warn',
|
|
316
|
+
message: 'Daemon disconnected - reconnecting, check again in 30s',
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
else if (hasError) {
|
|
320
|
+
results.push({
|
|
321
|
+
name: 'Daemon WebSocket',
|
|
322
|
+
status: 'fail',
|
|
323
|
+
message: 'Daemon has WebSocket errors - check network and firewall',
|
|
324
|
+
details: lastLines
|
|
325
|
+
.filter(l => l.includes('error'))
|
|
326
|
+
.slice(-1)[0],
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
results.push({
|
|
331
|
+
name: 'Daemon WebSocket',
|
|
332
|
+
status: 'warn',
|
|
333
|
+
message: 'Unknown connection state - run ekkos agent logs -f to monitor',
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (err) {
|
|
338
|
+
results.push({
|
|
339
|
+
name: 'Daemon WebSocket',
|
|
340
|
+
status: 'fail',
|
|
341
|
+
message: 'Failed to read logs',
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Print verification results
|
|
347
|
+
*/
|
|
348
|
+
function printResults() {
|
|
349
|
+
console.log('');
|
|
350
|
+
console.log(chalk_1.default.cyan.bold(' Remote Terminal Verification'));
|
|
351
|
+
console.log('');
|
|
352
|
+
let passed = 0;
|
|
353
|
+
let warned = 0;
|
|
354
|
+
let failed = 0;
|
|
355
|
+
for (const result of results) {
|
|
356
|
+
let icon = '';
|
|
357
|
+
let color = chalk_1.default.gray;
|
|
358
|
+
if (result.status === 'pass') {
|
|
359
|
+
icon = chalk_1.default.green('✓');
|
|
360
|
+
color = chalk_1.default.green;
|
|
361
|
+
passed++;
|
|
362
|
+
}
|
|
363
|
+
else if (result.status === 'warn') {
|
|
364
|
+
icon = chalk_1.default.yellow('⚠');
|
|
365
|
+
color = chalk_1.default.yellow;
|
|
366
|
+
warned++;
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
icon = chalk_1.default.red('✗');
|
|
370
|
+
color = chalk_1.default.red;
|
|
371
|
+
failed++;
|
|
372
|
+
}
|
|
373
|
+
console.log(` ${icon} ${color(result.name)}`);
|
|
374
|
+
console.log(` ${result.message}`);
|
|
375
|
+
if (result.details) {
|
|
376
|
+
console.log(` ${chalk_1.default.gray(result.details.slice(0, 80))}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
console.log('');
|
|
380
|
+
console.log(chalk_1.default.gray(` Results: ${chalk_1.default.green(`${passed} pass`)}, ${chalk_1.default.yellow(`${warned} warn`)}, ${chalk_1.default.red(`${failed} fail`)}`));
|
|
381
|
+
console.log('');
|
|
382
|
+
// Overall status
|
|
383
|
+
if (failed === 0) {
|
|
384
|
+
if (warned === 0) {
|
|
385
|
+
console.log(chalk_1.default.green(' ✓ All checks passed!'));
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
console.log(chalk_1.default.yellow(' ⚠ Most checks passed, but review warnings above.'));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
console.log(chalk_1.default.red(' ✗ Some checks failed. See above for fixes.'));
|
|
393
|
+
}
|
|
394
|
+
console.log('');
|
|
395
|
+
// Next steps
|
|
396
|
+
if (failed > 0) {
|
|
397
|
+
console.log(chalk_1.default.cyan(' Next steps:'));
|
|
398
|
+
console.log(' 1. Review failed checks above');
|
|
399
|
+
console.log(' 2. Run suggested commands');
|
|
400
|
+
console.log(' 3. Wait 30s and run this verification again');
|
|
401
|
+
console.log('');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Run all verification tests
|
|
406
|
+
*/
|
|
407
|
+
async function verifyRemoteTerminal() {
|
|
408
|
+
testDeviceConfig();
|
|
409
|
+
testServiceInstalled();
|
|
410
|
+
testServiceRunning();
|
|
411
|
+
testLogsWriting();
|
|
412
|
+
await testNetworkConnectivity();
|
|
413
|
+
testDaemonConnection();
|
|
414
|
+
printResults();
|
|
415
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekkos/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Setup ekkOS memory for AI coding assistants (Claude Code, Cursor, Windsurf)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ekkos": "dist/index.js",
|
|
8
8
|
"cli": "dist/index.js",
|
|
9
|
-
"ekkos-capture": "dist/cache/capture.js"
|
|
10
|
-
|
|
9
|
+
"ekkos-capture": "dist/cache/capture.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "ts-node src/index.ts",
|
|
14
|
+
"prepack": "node scripts/build-templates.js prepack",
|
|
15
|
+
"postpack": "node scripts/build-templates.js postpack",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
11
17
|
},
|
|
12
18
|
"keywords": [
|
|
13
19
|
"ekkos",
|
|
@@ -23,16 +29,19 @@
|
|
|
23
29
|
"license": "MIT",
|
|
24
30
|
"dependencies": {
|
|
25
31
|
"@supabase/supabase-js": "^2.39.8",
|
|
32
|
+
"blessed": "^0.1.81",
|
|
33
|
+
"blessed-contrib": "^4.11.0",
|
|
34
|
+
"ccusage": "^18.0.5",
|
|
26
35
|
"chalk": "^5.3.0",
|
|
27
36
|
"commander": "^12.1.0",
|
|
28
37
|
"inquirer": "^9.2.23",
|
|
29
38
|
"node-pty": "1.2.0-beta.7",
|
|
30
39
|
"open": "^10.0.0",
|
|
31
40
|
"ora": "^8.0.1",
|
|
32
|
-
"ws": "^8.19.0"
|
|
33
|
-
"@ekkos/prometheus": "0.1.0"
|
|
41
|
+
"ws": "^8.19.0"
|
|
34
42
|
},
|
|
35
43
|
"devDependencies": {
|
|
44
|
+
"@types/blessed": "^0.1.27",
|
|
36
45
|
"@types/node": "^20.11.0",
|
|
37
46
|
"typescript": "^5.3.3"
|
|
38
47
|
},
|
|
@@ -42,9 +51,5 @@
|
|
|
42
51
|
"files": [
|
|
43
52
|
"dist",
|
|
44
53
|
"templates"
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
"build": "tsc",
|
|
48
|
-
"dev": "ts-node src/index.ts"
|
|
49
|
-
}
|
|
50
|
-
}
|
|
54
|
+
]
|
|
55
|
+
}
|