@ekkos/cli 0.2.10 → 0.2.12
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/commands/agent.d.ts +44 -0
- package/dist/commands/agent.js +300 -0
- package/dist/commands/doctor.js +27 -14
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +289 -65
- package/dist/commands/setup-remote.d.ts +20 -0
- package/dist/commands/setup-remote.js +470 -0
- package/dist/index.js +75 -1
- package/dist/utils/state.d.ts +2 -0
- package/package.json +2 -1
- package/templates/ekkos-manifest.json +1 -1
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ekkos agent - Agent management commands for remote terminal
|
|
4
|
+
*
|
|
5
|
+
* Subcommands:
|
|
6
|
+
* - daemon: Run the agent daemon (used by service)
|
|
7
|
+
* - start: Start the agent service
|
|
8
|
+
* - stop: Stop the agent service
|
|
9
|
+
* - restart: Restart the agent service
|
|
10
|
+
* - status: Check agent status
|
|
11
|
+
* - uninstall: Remove agent service
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
47
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
48
|
+
};
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.agentDaemon = agentDaemon;
|
|
51
|
+
exports.agentStart = agentStart;
|
|
52
|
+
exports.agentStop = agentStop;
|
|
53
|
+
exports.agentRestart = agentRestart;
|
|
54
|
+
exports.agentStatus = agentStatus;
|
|
55
|
+
exports.agentUninstall = agentUninstall;
|
|
56
|
+
exports.agentLogs = agentLogs;
|
|
57
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
58
|
+
const os = __importStar(require("os"));
|
|
59
|
+
const fs = __importStar(require("fs"));
|
|
60
|
+
const path = __importStar(require("path"));
|
|
61
|
+
const child_process_1 = require("child_process");
|
|
62
|
+
const state_1 = require("../utils/state");
|
|
63
|
+
const daemon_1 = require("../agent/daemon");
|
|
64
|
+
/**
|
|
65
|
+
* Run the agent daemon (foreground)
|
|
66
|
+
*/
|
|
67
|
+
async function agentDaemon(options = {}) {
|
|
68
|
+
const verbose = options.verbose || false;
|
|
69
|
+
// Load device info
|
|
70
|
+
const deviceFilePath = path.join(state_1.EKKOS_DIR, 'device.json');
|
|
71
|
+
if (!fs.existsSync(deviceFilePath)) {
|
|
72
|
+
console.error(chalk_1.default.red('Device not configured. Run `ekkos setup-remote` first.'));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
const deviceData = JSON.parse(fs.readFileSync(deviceFilePath, 'utf-8'));
|
|
76
|
+
if (!deviceData.deviceToken) {
|
|
77
|
+
console.error(chalk_1.default.red('Device not paired. Run `ekkos setup-remote` first.'));
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
if (verbose) {
|
|
81
|
+
console.log(chalk_1.default.cyan(`Starting ekkOS agent daemon...`));
|
|
82
|
+
console.log(chalk_1.default.gray(` Device: ${deviceData.deviceName}`));
|
|
83
|
+
console.log(chalk_1.default.gray(` Platform: ${deviceData.platform}/${deviceData.arch}`));
|
|
84
|
+
}
|
|
85
|
+
// Start the daemon
|
|
86
|
+
const daemon = new daemon_1.AgentDaemon({
|
|
87
|
+
deviceToken: deviceData.deviceToken,
|
|
88
|
+
deviceId: deviceData.deviceId,
|
|
89
|
+
deviceName: deviceData.deviceName,
|
|
90
|
+
verbose,
|
|
91
|
+
});
|
|
92
|
+
await daemon.start();
|
|
93
|
+
// Handle shutdown signals
|
|
94
|
+
const shutdown = async () => {
|
|
95
|
+
console.log(chalk_1.default.gray('\nShutting down agent...'));
|
|
96
|
+
await daemon.stop();
|
|
97
|
+
process.exit(0);
|
|
98
|
+
};
|
|
99
|
+
process.on('SIGINT', shutdown);
|
|
100
|
+
process.on('SIGTERM', shutdown);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Start the agent service
|
|
104
|
+
*/
|
|
105
|
+
async function agentStart(options = {}) {
|
|
106
|
+
const platform = os.platform();
|
|
107
|
+
try {
|
|
108
|
+
if (platform === 'darwin') {
|
|
109
|
+
const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'dev.ekkos.agent.plist');
|
|
110
|
+
if (!fs.existsSync(plistPath)) {
|
|
111
|
+
console.log(chalk_1.default.yellow('Agent not installed. Run `ekkos setup-remote` first.'));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
(0, child_process_1.execSync)(`launchctl load "${plistPath}" 2>/dev/null || true`, { encoding: 'utf-8' });
|
|
115
|
+
console.log(chalk_1.default.green('Agent started.'));
|
|
116
|
+
}
|
|
117
|
+
else if (platform === 'win32') {
|
|
118
|
+
(0, child_process_1.execSync)('schtasks /run /tn "ekkOS Agent"', { encoding: 'utf-8' });
|
|
119
|
+
console.log(chalk_1.default.green('Agent started.'));
|
|
120
|
+
}
|
|
121
|
+
else if (platform === 'linux') {
|
|
122
|
+
(0, child_process_1.execSync)('systemctl --user start ekkos-agent', { encoding: 'utf-8' });
|
|
123
|
+
console.log(chalk_1.default.green('Agent started.'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.error(chalk_1.default.red(`Failed to start agent: ${err.message}`));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Stop the agent service
|
|
133
|
+
*/
|
|
134
|
+
async function agentStop(options = {}) {
|
|
135
|
+
const platform = os.platform();
|
|
136
|
+
try {
|
|
137
|
+
if (platform === 'darwin') {
|
|
138
|
+
const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'dev.ekkos.agent.plist');
|
|
139
|
+
(0, child_process_1.execSync)(`launchctl unload "${plistPath}" 2>/dev/null || true`, { encoding: 'utf-8' });
|
|
140
|
+
console.log(chalk_1.default.green('Agent stopped.'));
|
|
141
|
+
}
|
|
142
|
+
else if (platform === 'win32') {
|
|
143
|
+
(0, child_process_1.execSync)('taskkill /im "node.exe" /fi "windowtitle eq ekkOS*" /f 2>nul', { encoding: 'utf-8' });
|
|
144
|
+
console.log(chalk_1.default.green('Agent stopped.'));
|
|
145
|
+
}
|
|
146
|
+
else if (platform === 'linux') {
|
|
147
|
+
(0, child_process_1.execSync)('systemctl --user stop ekkos-agent', { encoding: 'utf-8' });
|
|
148
|
+
console.log(chalk_1.default.green('Agent stopped.'));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
// May not be running
|
|
153
|
+
console.log(chalk_1.default.gray('Agent was not running.'));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Restart the agent service
|
|
158
|
+
*/
|
|
159
|
+
async function agentRestart(options = {}) {
|
|
160
|
+
await agentStop(options);
|
|
161
|
+
await agentStart(options);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Check agent status
|
|
165
|
+
*/
|
|
166
|
+
async function agentStatus(options = {}) {
|
|
167
|
+
const platform = os.platform();
|
|
168
|
+
let running = false;
|
|
169
|
+
let statusOutput = '';
|
|
170
|
+
try {
|
|
171
|
+
if (platform === 'darwin') {
|
|
172
|
+
statusOutput = (0, child_process_1.execSync)('launchctl list | grep dev.ekkos.agent || true', { encoding: 'utf-8' });
|
|
173
|
+
running = statusOutput.includes('dev.ekkos.agent');
|
|
174
|
+
}
|
|
175
|
+
else if (platform === 'win32') {
|
|
176
|
+
statusOutput = (0, child_process_1.execSync)('schtasks /query /tn "ekkOS Agent" 2>nul || true', { encoding: 'utf-8' });
|
|
177
|
+
running = statusOutput.includes('Running');
|
|
178
|
+
}
|
|
179
|
+
else if (platform === 'linux') {
|
|
180
|
+
statusOutput = (0, child_process_1.execSync)('systemctl --user is-active ekkos-agent 2>/dev/null || true', { encoding: 'utf-8' });
|
|
181
|
+
running = statusOutput.trim() === 'active';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
running = false;
|
|
186
|
+
}
|
|
187
|
+
console.log('');
|
|
188
|
+
console.log(chalk_1.default.cyan.bold(' ekkOS Agent Status'));
|
|
189
|
+
console.log('');
|
|
190
|
+
// Check device info
|
|
191
|
+
const deviceFilePath = path.join(state_1.EKKOS_DIR, 'device.json');
|
|
192
|
+
if (!fs.existsSync(deviceFilePath)) {
|
|
193
|
+
console.log(chalk_1.default.yellow(' Not configured. Run `ekkos setup-remote` first.'));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const deviceData = JSON.parse(fs.readFileSync(deviceFilePath, 'utf-8'));
|
|
197
|
+
console.log(` Device: ${chalk_1.default.white(deviceData.deviceName)}`);
|
|
198
|
+
console.log(` ID: ${chalk_1.default.gray(deviceData.deviceId.slice(0, 8))}...`);
|
|
199
|
+
console.log(` Paired: ${deviceData.deviceToken ? chalk_1.default.green('Yes') : chalk_1.default.red('No')}`);
|
|
200
|
+
console.log(` Service: ${running ? chalk_1.default.green('Running') : chalk_1.default.red('Stopped')}`);
|
|
201
|
+
console.log('');
|
|
202
|
+
// Check cloud connection
|
|
203
|
+
if (deviceData.deviceToken) {
|
|
204
|
+
console.log(chalk_1.default.gray(' Checking cloud connection...'));
|
|
205
|
+
try {
|
|
206
|
+
const authToken = (0, state_1.getAuthToken)();
|
|
207
|
+
const state = (0, state_1.getState)();
|
|
208
|
+
if (authToken && state?.userId) {
|
|
209
|
+
const MEMORY_API_URL = process.env.MEMORY_API_URL || 'https://mcp.ekkos.dev';
|
|
210
|
+
const response = await fetch(`${MEMORY_API_URL}/api/v1/relay/devices/${state.userId}`, {
|
|
211
|
+
headers: { 'Authorization': `Bearer ${authToken}` },
|
|
212
|
+
});
|
|
213
|
+
if (response.ok) {
|
|
214
|
+
const data = await response.json();
|
|
215
|
+
const device = data.devices?.find((d) => d.deviceId === deviceData.deviceId);
|
|
216
|
+
if (device?.online) {
|
|
217
|
+
console.log(` Cloud: ${chalk_1.default.green('Connected')}`);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
console.log(` Cloud: ${chalk_1.default.yellow('Offline')}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
console.log(` Cloud: ${chalk_1.default.gray('Unable to check')}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
console.log('');
|
|
230
|
+
// Show log location
|
|
231
|
+
console.log(chalk_1.default.gray(` Logs: ${path.join(state_1.EKKOS_DIR, 'agent.out.log')}`));
|
|
232
|
+
console.log('');
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Uninstall the agent service
|
|
236
|
+
*/
|
|
237
|
+
async function agentUninstall(options = {}) {
|
|
238
|
+
const platform = os.platform();
|
|
239
|
+
console.log(chalk_1.default.cyan('Uninstalling ekkOS agent...'));
|
|
240
|
+
try {
|
|
241
|
+
if (platform === 'darwin') {
|
|
242
|
+
const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'dev.ekkos.agent.plist');
|
|
243
|
+
(0, child_process_1.execSync)(`launchctl unload "${plistPath}" 2>/dev/null || true`, { encoding: 'utf-8' });
|
|
244
|
+
if (fs.existsSync(plistPath)) {
|
|
245
|
+
fs.unlinkSync(plistPath);
|
|
246
|
+
}
|
|
247
|
+
console.log(chalk_1.default.green(' ✓ Service removed'));
|
|
248
|
+
}
|
|
249
|
+
else if (platform === 'win32') {
|
|
250
|
+
(0, child_process_1.execSync)('schtasks /delete /tn "ekkOS Agent" /f 2>nul || true', { encoding: 'utf-8' });
|
|
251
|
+
console.log(chalk_1.default.green(' ✓ Scheduled task removed'));
|
|
252
|
+
}
|
|
253
|
+
else if (platform === 'linux') {
|
|
254
|
+
(0, child_process_1.execSync)('systemctl --user disable ekkos-agent 2>/dev/null || true', { encoding: 'utf-8' });
|
|
255
|
+
(0, child_process_1.execSync)('systemctl --user stop ekkos-agent 2>/dev/null || true', { encoding: 'utf-8' });
|
|
256
|
+
const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', 'ekkos-agent.service');
|
|
257
|
+
if (fs.existsSync(servicePath)) {
|
|
258
|
+
fs.unlinkSync(servicePath);
|
|
259
|
+
}
|
|
260
|
+
console.log(chalk_1.default.green(' ✓ Service removed'));
|
|
261
|
+
}
|
|
262
|
+
// Remove device token (but keep device ID for potential re-pairing)
|
|
263
|
+
const deviceFilePath = path.join(state_1.EKKOS_DIR, 'device.json');
|
|
264
|
+
if (fs.existsSync(deviceFilePath)) {
|
|
265
|
+
const deviceData = JSON.parse(fs.readFileSync(deviceFilePath, 'utf-8'));
|
|
266
|
+
delete deviceData.deviceToken;
|
|
267
|
+
delete deviceData.pairedAt;
|
|
268
|
+
fs.writeFileSync(deviceFilePath, JSON.stringify(deviceData, null, 2));
|
|
269
|
+
console.log(chalk_1.default.green(' ✓ Device token removed'));
|
|
270
|
+
}
|
|
271
|
+
console.log('');
|
|
272
|
+
console.log(chalk_1.default.green('Agent uninstalled. Run `ekkos setup-remote` to set up again.'));
|
|
273
|
+
console.log('');
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
console.error(chalk_1.default.red(`Failed to uninstall: ${err.message}`));
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Show agent logs
|
|
282
|
+
*/
|
|
283
|
+
async function agentLogs(options = {}) {
|
|
284
|
+
const logPath = path.join(state_1.EKKOS_DIR, 'agent.out.log');
|
|
285
|
+
if (!fs.existsSync(logPath)) {
|
|
286
|
+
console.log(chalk_1.default.gray('No logs found.'));
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (options.follow) {
|
|
290
|
+
// Use tail -f
|
|
291
|
+
const tail = (0, child_process_1.spawn)('tail', ['-f', logPath], { stdio: 'inherit' });
|
|
292
|
+
tail.on('close', () => process.exit(0));
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
// Show last 50 lines
|
|
296
|
+
const content = fs.readFileSync(logPath, 'utf-8');
|
|
297
|
+
const lines = content.split('\n').slice(-50);
|
|
298
|
+
console.log(lines.join('\n'));
|
|
299
|
+
}
|
|
300
|
+
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -78,25 +78,22 @@ function checkPty() {
|
|
|
78
78
|
*/
|
|
79
79
|
function checkMcpConfig() {
|
|
80
80
|
try {
|
|
81
|
-
const output = (0, child_process_1.execSync)('claude mcp list', {
|
|
81
|
+
const output = (0, child_process_1.execSync)('claude mcp list 2>&1', {
|
|
82
82
|
encoding: 'utf-8',
|
|
83
|
-
timeout:
|
|
83
|
+
timeout: 30000, // 30s - health checks can be slow
|
|
84
84
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
85
85
|
});
|
|
86
|
-
|
|
87
|
-
const servers = [];
|
|
88
|
-
const lines = output.split('\n');
|
|
89
|
-
for (const line of lines) {
|
|
90
|
-
// Look for server names (varies by Claude version)
|
|
91
|
-
if (line.includes('ekkos') || line.includes('memory')) {
|
|
92
|
-
servers.push(line.trim());
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const hasEkkos = output.toLowerCase().includes('ekkos') ||
|
|
96
|
-
output.toLowerCase().includes('memory');
|
|
97
|
-
return { configured: hasEkkos, servers };
|
|
86
|
+
return parseMcpOutput(output);
|
|
98
87
|
}
|
|
99
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
|
+
}
|
|
100
97
|
return {
|
|
101
98
|
configured: false,
|
|
102
99
|
servers: [],
|
|
@@ -104,6 +101,22 @@ function checkMcpConfig() {
|
|
|
104
101
|
};
|
|
105
102
|
}
|
|
106
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Parse MCP list output to find ekkOS servers
|
|
106
|
+
*/
|
|
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
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const hasEkkos = output.toLowerCase().includes('ekkos') ||
|
|
117
|
+
output.toLowerCase().includes('memory');
|
|
118
|
+
return { configured: hasEkkos, servers };
|
|
119
|
+
}
|
|
107
120
|
/**
|
|
108
121
|
* Check hooks installation status
|
|
109
122
|
*/
|