@ekkos/cli 0.2.10 → 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/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 +467 -0
- package/dist/index.js +75 -1
- package/dist/utils/state.d.ts +2 -0
- package/package.json +1 -1
- package/templates/ekkos-manifest.json +1 -1
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ekkos setup-remote - One-command setup for remote terminal access
|
|
4
|
+
*
|
|
5
|
+
* This command does everything automatically:
|
|
6
|
+
* 1. Checks if user is logged in (prompts login if not)
|
|
7
|
+
* 2. Generates unique device ID and fingerprint
|
|
8
|
+
* 3. Opens browser for device pairing approval
|
|
9
|
+
* 4. Installs background agent as system service
|
|
10
|
+
* 5. Starts agent immediately
|
|
11
|
+
* 6. Verifies connection to cloud relay
|
|
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.setupRemote = setupRemote;
|
|
51
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
52
|
+
const os = __importStar(require("os"));
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const crypto = __importStar(require("crypto"));
|
|
56
|
+
const child_process_1 = require("child_process");
|
|
57
|
+
const open_1 = __importDefault(require("open"));
|
|
58
|
+
const state_1 = require("../utils/state");
|
|
59
|
+
const init_1 = require("./init");
|
|
60
|
+
const PLATFORM_URL = process.env.PLATFORM_URL || 'https://platform.ekkos.dev';
|
|
61
|
+
const MEMORY_API_URL = process.env.MEMORY_API_URL || 'https://mcp.ekkos.dev';
|
|
62
|
+
const POLLING_INTERVAL = 2000; // 2 seconds
|
|
63
|
+
const POLLING_TIMEOUT = 600000; // 10 minutes
|
|
64
|
+
/**
|
|
65
|
+
* Get or create device info
|
|
66
|
+
*/
|
|
67
|
+
function getOrCreateDeviceInfo() {
|
|
68
|
+
const deviceFilePath = path.join(state_1.EKKOS_DIR, 'device.json');
|
|
69
|
+
// Check if device already registered
|
|
70
|
+
if (fs.existsSync(deviceFilePath)) {
|
|
71
|
+
const existing = JSON.parse(fs.readFileSync(deviceFilePath, 'utf-8'));
|
|
72
|
+
if (existing.deviceId) {
|
|
73
|
+
return existing;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Generate new device ID
|
|
77
|
+
const deviceId = crypto.randomUUID();
|
|
78
|
+
// Get device name from hostname or system info
|
|
79
|
+
let deviceName = os.hostname();
|
|
80
|
+
if (os.platform() === 'darwin') {
|
|
81
|
+
try {
|
|
82
|
+
const computerName = (0, child_process_1.execSync)('scutil --get ComputerName', { encoding: 'utf-8' }).trim();
|
|
83
|
+
if (computerName)
|
|
84
|
+
deviceName = computerName;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Fall back to hostname
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const deviceInfo = {
|
|
91
|
+
deviceId,
|
|
92
|
+
deviceName,
|
|
93
|
+
hostname: os.hostname(),
|
|
94
|
+
platform: os.platform(),
|
|
95
|
+
arch: os.arch(),
|
|
96
|
+
nodeVersion: process.version,
|
|
97
|
+
cliVersion: '0.2.8', // TODO: Get from package.json
|
|
98
|
+
};
|
|
99
|
+
// Save device info
|
|
100
|
+
if (!fs.existsSync(state_1.EKKOS_DIR)) {
|
|
101
|
+
fs.mkdirSync(state_1.EKKOS_DIR, { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
fs.writeFileSync(deviceFilePath, JSON.stringify(deviceInfo, null, 2));
|
|
104
|
+
return deviceInfo;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Request device pairing code from server
|
|
108
|
+
*/
|
|
109
|
+
async function requestPairingCode(deviceInfo, authToken) {
|
|
110
|
+
const response = await fetch(`${MEMORY_API_URL}/api/v1/relay/pair`, {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
headers: {
|
|
113
|
+
'Authorization': `Bearer ${authToken}`,
|
|
114
|
+
'Content-Type': 'application/json',
|
|
115
|
+
},
|
|
116
|
+
body: JSON.stringify({
|
|
117
|
+
deviceId: deviceInfo.deviceId,
|
|
118
|
+
deviceName: deviceInfo.deviceName,
|
|
119
|
+
hostname: deviceInfo.hostname,
|
|
120
|
+
platform: deviceInfo.platform,
|
|
121
|
+
arch: deviceInfo.arch,
|
|
122
|
+
fingerprint: deviceInfo,
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
127
|
+
throw new Error(error.error || `Failed to request pairing code: ${response.status}`);
|
|
128
|
+
}
|
|
129
|
+
return response.json();
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Poll for pairing approval
|
|
133
|
+
*/
|
|
134
|
+
async function pollForApproval(deviceCode, authToken) {
|
|
135
|
+
const startTime = Date.now();
|
|
136
|
+
while (Date.now() - startTime < POLLING_TIMEOUT) {
|
|
137
|
+
const response = await fetch(`${MEMORY_API_URL}/api/v1/relay/pair/${deviceCode}`, {
|
|
138
|
+
headers: {
|
|
139
|
+
'Authorization': `Bearer ${authToken}`,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
if (response.status === 200) {
|
|
143
|
+
const data = await response.json();
|
|
144
|
+
if (data.status === 'approved') {
|
|
145
|
+
return { approved: true, deviceToken: data.deviceToken };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else if (response.status === 403) {
|
|
149
|
+
// Denied
|
|
150
|
+
return { approved: false };
|
|
151
|
+
}
|
|
152
|
+
else if (response.status === 410) {
|
|
153
|
+
// Expired
|
|
154
|
+
throw new Error('Pairing code expired');
|
|
155
|
+
}
|
|
156
|
+
// Wait before next poll
|
|
157
|
+
await new Promise(resolve => setTimeout(resolve, POLLING_INTERVAL));
|
|
158
|
+
process.stdout.write('.');
|
|
159
|
+
}
|
|
160
|
+
throw new Error('Pairing timeout');
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Install agent as system service
|
|
164
|
+
*/
|
|
165
|
+
async function installService(verbose) {
|
|
166
|
+
const platform = os.platform();
|
|
167
|
+
if (platform === 'darwin') {
|
|
168
|
+
await installMacOSService(verbose);
|
|
169
|
+
}
|
|
170
|
+
else if (platform === 'win32') {
|
|
171
|
+
await installWindowsService(verbose);
|
|
172
|
+
}
|
|
173
|
+
else if (platform === 'linux') {
|
|
174
|
+
await installLinuxService(verbose);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Install macOS launchd service
|
|
182
|
+
*/
|
|
183
|
+
async function installMacOSService(verbose) {
|
|
184
|
+
const plistName = 'dev.ekkos.agent';
|
|
185
|
+
const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${plistName}.plist`);
|
|
186
|
+
// Find node path
|
|
187
|
+
const nodePath = process.execPath;
|
|
188
|
+
// Find ekkos CLI path (assuming it's in node_modules/.bin or globally installed)
|
|
189
|
+
let ekkosPath;
|
|
190
|
+
try {
|
|
191
|
+
ekkosPath = (0, child_process_1.execSync)('which ekkos', { encoding: 'utf-8' }).trim();
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Fall back to npx
|
|
195
|
+
ekkosPath = 'npx @ekkos/cli';
|
|
196
|
+
}
|
|
197
|
+
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
198
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
199
|
+
<plist version="1.0">
|
|
200
|
+
<dict>
|
|
201
|
+
<key>Label</key>
|
|
202
|
+
<string>${plistName}</string>
|
|
203
|
+
<key>ProgramArguments</key>
|
|
204
|
+
<array>
|
|
205
|
+
<string>${nodePath}</string>
|
|
206
|
+
<string>${ekkosPath.includes('npx') ? ekkosPath.split(' ')[0] : ekkosPath}</string>
|
|
207
|
+
${ekkosPath.includes('npx') ? '<string>@ekkos/cli</string>' : ''}
|
|
208
|
+
<string>agent</string>
|
|
209
|
+
<string>daemon</string>
|
|
210
|
+
</array>
|
|
211
|
+
<key>RunAtLoad</key>
|
|
212
|
+
<true/>
|
|
213
|
+
<key>KeepAlive</key>
|
|
214
|
+
<true/>
|
|
215
|
+
<key>StandardErrorPath</key>
|
|
216
|
+
<string>${path.join(state_1.EKKOS_DIR, 'agent.err.log')}</string>
|
|
217
|
+
<key>StandardOutPath</key>
|
|
218
|
+
<string>${path.join(state_1.EKKOS_DIR, 'agent.out.log')}</string>
|
|
219
|
+
<key>EnvironmentVariables</key>
|
|
220
|
+
<dict>
|
|
221
|
+
<key>PATH</key>
|
|
222
|
+
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
223
|
+
</dict>
|
|
224
|
+
</dict>
|
|
225
|
+
</plist>`;
|
|
226
|
+
// Ensure directory exists
|
|
227
|
+
const launchAgentsDir = path.dirname(plistPath);
|
|
228
|
+
if (!fs.existsSync(launchAgentsDir)) {
|
|
229
|
+
fs.mkdirSync(launchAgentsDir, { recursive: true });
|
|
230
|
+
}
|
|
231
|
+
// Write plist
|
|
232
|
+
fs.writeFileSync(plistPath, plistContent);
|
|
233
|
+
if (verbose) {
|
|
234
|
+
console.log(chalk_1.default.gray(` Created: ${plistPath}`));
|
|
235
|
+
}
|
|
236
|
+
// Unload if already loaded
|
|
237
|
+
try {
|
|
238
|
+
(0, child_process_1.execSync)(`launchctl unload "${plistPath}" 2>/dev/null`, { encoding: 'utf-8' });
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Ignore if not loaded
|
|
242
|
+
}
|
|
243
|
+
// Load the service
|
|
244
|
+
(0, child_process_1.execSync)(`launchctl load "${plistPath}"`, { encoding: 'utf-8' });
|
|
245
|
+
if (verbose) {
|
|
246
|
+
console.log(chalk_1.default.gray(` Loaded: ${plistName}`));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Install Windows service
|
|
251
|
+
*/
|
|
252
|
+
async function installWindowsService(verbose) {
|
|
253
|
+
// Windows uses a scheduled task that runs at login
|
|
254
|
+
const taskName = 'ekkOS Agent';
|
|
255
|
+
// Find ekkos CLI path
|
|
256
|
+
let ekkosPath;
|
|
257
|
+
try {
|
|
258
|
+
ekkosPath = (0, child_process_1.execSync)('where ekkos', { encoding: 'utf-8' }).trim().split('\n')[0];
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
// Fall back to npx
|
|
262
|
+
ekkosPath = 'npx';
|
|
263
|
+
}
|
|
264
|
+
// Create scheduled task
|
|
265
|
+
const command = ekkosPath.includes('npx')
|
|
266
|
+
? `npx @ekkos/cli agent daemon`
|
|
267
|
+
: `"${ekkosPath}" agent daemon`;
|
|
268
|
+
try {
|
|
269
|
+
// Delete existing task
|
|
270
|
+
(0, child_process_1.execSync)(`schtasks /delete /tn "${taskName}" /f 2>nul`, { encoding: 'utf-8' });
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// Ignore if doesn't exist
|
|
274
|
+
}
|
|
275
|
+
// Create task that runs at login
|
|
276
|
+
(0, child_process_1.execSync)(`schtasks /create /tn "${taskName}" /tr "${command}" /sc onlogon /rl limited`, { encoding: 'utf-8' });
|
|
277
|
+
// Start the task now
|
|
278
|
+
(0, child_process_1.execSync)(`schtasks /run /tn "${taskName}"`, { encoding: 'utf-8' });
|
|
279
|
+
if (verbose) {
|
|
280
|
+
console.log(chalk_1.default.gray(` Created scheduled task: ${taskName}`));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Install Linux systemd service
|
|
285
|
+
*/
|
|
286
|
+
async function installLinuxService(verbose) {
|
|
287
|
+
const serviceName = 'ekkos-agent';
|
|
288
|
+
const serviceDir = path.join(os.homedir(), '.config', 'systemd', 'user');
|
|
289
|
+
const servicePath = path.join(serviceDir, `${serviceName}.service`);
|
|
290
|
+
// Find ekkos CLI path
|
|
291
|
+
let ekkosPath;
|
|
292
|
+
try {
|
|
293
|
+
ekkosPath = (0, child_process_1.execSync)('which ekkos', { encoding: 'utf-8' }).trim();
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
ekkosPath = 'npx @ekkos/cli';
|
|
297
|
+
}
|
|
298
|
+
const serviceContent = `[Unit]
|
|
299
|
+
Description=ekkOS Remote Terminal Agent
|
|
300
|
+
After=network.target
|
|
301
|
+
|
|
302
|
+
[Service]
|
|
303
|
+
Type=simple
|
|
304
|
+
ExecStart=${process.execPath} ${ekkosPath} agent daemon
|
|
305
|
+
Restart=always
|
|
306
|
+
RestartSec=10
|
|
307
|
+
StandardOutput=append:${path.join(state_1.EKKOS_DIR, 'agent.out.log')}
|
|
308
|
+
StandardError=append:${path.join(state_1.EKKOS_DIR, 'agent.err.log')}
|
|
309
|
+
|
|
310
|
+
[Install]
|
|
311
|
+
WantedBy=default.target
|
|
312
|
+
`;
|
|
313
|
+
// Ensure directory exists
|
|
314
|
+
if (!fs.existsSync(serviceDir)) {
|
|
315
|
+
fs.mkdirSync(serviceDir, { recursive: true });
|
|
316
|
+
}
|
|
317
|
+
// Write service file
|
|
318
|
+
fs.writeFileSync(servicePath, serviceContent);
|
|
319
|
+
// Reload systemd and enable service
|
|
320
|
+
(0, child_process_1.execSync)('systemctl --user daemon-reload', { encoding: 'utf-8' });
|
|
321
|
+
(0, child_process_1.execSync)(`systemctl --user enable ${serviceName}`, { encoding: 'utf-8' });
|
|
322
|
+
(0, child_process_1.execSync)(`systemctl --user restart ${serviceName}`, { encoding: 'utf-8' });
|
|
323
|
+
if (verbose) {
|
|
324
|
+
console.log(chalk_1.default.gray(` Created: ${servicePath}`));
|
|
325
|
+
console.log(chalk_1.default.gray(` Enabled and started: ${serviceName}`));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Verify agent is connected
|
|
330
|
+
*/
|
|
331
|
+
async function verifyConnection(deviceInfo, authToken) {
|
|
332
|
+
// Wait a bit for agent to start
|
|
333
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
334
|
+
// Check device status
|
|
335
|
+
const response = await fetch(`${MEMORY_API_URL}/api/v1/relay/devices/${(await (0, state_1.getState)())?.userId}`, {
|
|
336
|
+
headers: {
|
|
337
|
+
'Authorization': `Bearer ${authToken}`,
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
const data = await response.json();
|
|
344
|
+
const device = data.devices?.find((d) => d.deviceId === deviceInfo.deviceId);
|
|
345
|
+
return device?.online === true;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Main setup command
|
|
349
|
+
*/
|
|
350
|
+
async function setupRemote(options = {}) {
|
|
351
|
+
const verbose = options.verbose || false;
|
|
352
|
+
console.log('');
|
|
353
|
+
console.log(chalk_1.default.cyan.bold(' 🔧 ekkOS Remote Setup'));
|
|
354
|
+
console.log('');
|
|
355
|
+
// Step 1: Check if logged in
|
|
356
|
+
let authToken = (0, state_1.getAuthToken)();
|
|
357
|
+
if (!authToken) {
|
|
358
|
+
console.log(chalk_1.default.yellow(' You need to log in first.'));
|
|
359
|
+
console.log('');
|
|
360
|
+
await (0, init_1.init)({ ide: 'claude' });
|
|
361
|
+
authToken = (0, state_1.getAuthToken)();
|
|
362
|
+
if (!authToken) {
|
|
363
|
+
console.log(chalk_1.default.red(' Failed to authenticate. Please run `ekkos init` first.'));
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const state = (0, state_1.getState)();
|
|
368
|
+
console.log(chalk_1.default.green(` ✓ Logged in as ${state?.userEmail || 'unknown'}`));
|
|
369
|
+
// Step 2: Get or create device info
|
|
370
|
+
const deviceInfo = getOrCreateDeviceInfo();
|
|
371
|
+
console.log(chalk_1.default.green(` ✓ Device: ${deviceInfo.deviceName} (${deviceInfo.arch})`));
|
|
372
|
+
// Step 3: Check if already paired
|
|
373
|
+
const deviceFilePath = path.join(state_1.EKKOS_DIR, 'device.json');
|
|
374
|
+
const deviceData = JSON.parse(fs.readFileSync(deviceFilePath, 'utf-8'));
|
|
375
|
+
if (deviceData.deviceToken && !options.force) {
|
|
376
|
+
console.log(chalk_1.default.green(' ✓ Device already paired'));
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
// Request pairing code
|
|
380
|
+
console.log('');
|
|
381
|
+
console.log(chalk_1.default.cyan(' Requesting pairing code...'));
|
|
382
|
+
let pairingResponse;
|
|
383
|
+
try {
|
|
384
|
+
pairingResponse = await requestPairingCode(deviceInfo, authToken);
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
console.log(chalk_1.default.red(` Error: ${err.message}`));
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
// Open browser for approval
|
|
391
|
+
const verificationUrl = pairingResponse.verificationUrl || `${PLATFORM_URL}/verify/${pairingResponse.userCode}`;
|
|
392
|
+
console.log('');
|
|
393
|
+
console.log(chalk_1.default.white(` Verification code: ${chalk_1.default.bold.yellow(pairingResponse.userCode)}`));
|
|
394
|
+
console.log(chalk_1.default.gray(` Opening browser to approve...`));
|
|
395
|
+
console.log('');
|
|
396
|
+
try {
|
|
397
|
+
await (0, open_1.default)(verificationUrl);
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
console.log(chalk_1.default.yellow(` Could not open browser. Please visit:`));
|
|
401
|
+
console.log(chalk_1.default.cyan(` ${verificationUrl}`));
|
|
402
|
+
}
|
|
403
|
+
// Poll for approval
|
|
404
|
+
console.log(chalk_1.default.gray(' Waiting for approval'));
|
|
405
|
+
process.stdout.write(' ');
|
|
406
|
+
try {
|
|
407
|
+
const result = await pollForApproval(pairingResponse.deviceCode, authToken);
|
|
408
|
+
console.log('');
|
|
409
|
+
if (!result.approved) {
|
|
410
|
+
console.log(chalk_1.default.red(' Pairing was denied.'));
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
// Save device token
|
|
414
|
+
deviceData.deviceToken = result.deviceToken;
|
|
415
|
+
deviceData.pairedAt = new Date().toISOString();
|
|
416
|
+
fs.writeFileSync(deviceFilePath, JSON.stringify(deviceData, null, 2));
|
|
417
|
+
console.log(chalk_1.default.green(' ✓ Device paired'));
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
console.log('');
|
|
421
|
+
console.log(chalk_1.default.red(` Error: ${err.message}`));
|
|
422
|
+
process.exit(1);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// Step 4: Install service (unless skipped)
|
|
426
|
+
if (!options.skipService) {
|
|
427
|
+
console.log(chalk_1.default.cyan(' Installing agent service...'));
|
|
428
|
+
try {
|
|
429
|
+
await installService(verbose);
|
|
430
|
+
console.log(chalk_1.default.green(' ✓ Agent installed and running'));
|
|
431
|
+
}
|
|
432
|
+
catch (err) {
|
|
433
|
+
console.log(chalk_1.default.yellow(` Warning: Could not install service: ${err.message}`));
|
|
434
|
+
console.log(chalk_1.default.gray(' You can start the agent manually with: ekkos agent start'));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// Step 5: Verify connection
|
|
438
|
+
console.log(chalk_1.default.cyan(' Verifying connection...'));
|
|
439
|
+
const connected = await verifyConnection(deviceInfo, authToken);
|
|
440
|
+
if (connected) {
|
|
441
|
+
console.log(chalk_1.default.green(' ✓ Connected to ekkOS cloud'));
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
console.log(chalk_1.default.yellow(' ⚠ Agent not connected yet (may take a moment)'));
|
|
445
|
+
}
|
|
446
|
+
// Success message
|
|
447
|
+
console.log('');
|
|
448
|
+
console.log(chalk_1.default.green.bold(' 🎉 Setup complete!'));
|
|
449
|
+
console.log('');
|
|
450
|
+
console.log(chalk_1.default.white(' Access your terminal from anywhere at:'));
|
|
451
|
+
console.log(chalk_1.default.cyan(` ${PLATFORM_URL}/dashboard/terminal`));
|
|
452
|
+
console.log('');
|
|
453
|
+
console.log(chalk_1.default.gray(` Your device will appear as "${deviceInfo.deviceName}" in the device list.`));
|
|
454
|
+
console.log(chalk_1.default.gray(' The agent runs in background and auto-starts on boot.'));
|
|
455
|
+
console.log('');
|
|
456
|
+
console.log(chalk_1.default.dim(' How it works:'));
|
|
457
|
+
console.log(chalk_1.default.dim(' • Claude Code runs on THIS machine (uses your subscription)'));
|
|
458
|
+
console.log(chalk_1.default.dim(' • ekkOS provides secure relay to access it from any browser'));
|
|
459
|
+
console.log(chalk_1.default.dim(' • Your code/files stay local - only terminal I/O is relayed'));
|
|
460
|
+
console.log(chalk_1.default.dim(' • ekkOS learns from every session - faster, smarter, better over time'));
|
|
461
|
+
console.log('');
|
|
462
|
+
console.log(chalk_1.default.gray(' Commands:'));
|
|
463
|
+
console.log(chalk_1.default.gray(' ekkos agent status - Check agent status'));
|
|
464
|
+
console.log(chalk_1.default.gray(' ekkos agent stop - Stop agent temporarily'));
|
|
465
|
+
console.log(chalk_1.default.gray(' ekkos agent uninstall - Remove agent completely'));
|
|
466
|
+
console.log('');
|
|
467
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ const run_1 = require("./commands/run");
|
|
|
12
12
|
const doctor_1 = require("./commands/doctor");
|
|
13
13
|
const stream_1 = require("./commands/stream");
|
|
14
14
|
const hooks_1 = require("./commands/hooks");
|
|
15
|
+
const setup_remote_1 = require("./commands/setup-remote");
|
|
16
|
+
const agent_1 = require("./commands/agent");
|
|
15
17
|
const state_1 = require("./utils/state");
|
|
16
18
|
const chalk_1 = __importDefault(require("chalk"));
|
|
17
19
|
commander_1.program
|
|
@@ -47,6 +49,7 @@ commander_1.program
|
|
|
47
49
|
.option('-b, --bypass', 'Enable bypass permissions mode (dangerously skip all permission checks)')
|
|
48
50
|
.option('-v, --verbose', 'Show debug output')
|
|
49
51
|
.option('-d, --doctor', 'Run diagnostics before starting')
|
|
52
|
+
.option('-r, --research', 'Auto-run research agent on startup (scans arXiv for new AI papers)')
|
|
50
53
|
.option('--no-inject', 'Monitor-only mode (detect context wall but print instructions instead of auto-inject)')
|
|
51
54
|
.action((options) => {
|
|
52
55
|
(0, run_1.run)({
|
|
@@ -54,7 +57,8 @@ commander_1.program
|
|
|
54
57
|
bypass: options.bypass,
|
|
55
58
|
verbose: options.verbose,
|
|
56
59
|
doctor: options.doctor,
|
|
57
|
-
noInject: options.noInject
|
|
60
|
+
noInject: options.noInject,
|
|
61
|
+
research: options.research
|
|
58
62
|
});
|
|
59
63
|
});
|
|
60
64
|
// Doctor command - check system prerequisites
|
|
@@ -178,4 +182,74 @@ commander_1.program
|
|
|
178
182
|
key: options.key
|
|
179
183
|
});
|
|
180
184
|
});
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// REMOTE TERMINAL COMMANDS
|
|
187
|
+
// ============================================================================
|
|
188
|
+
// Setup remote - one-command setup for remote terminal access
|
|
189
|
+
commander_1.program
|
|
190
|
+
.command('setup-remote')
|
|
191
|
+
.description('Set up remote terminal access (run Claude on your PC from anywhere)')
|
|
192
|
+
.option('-f, --force', 'Force re-pairing even if already paired')
|
|
193
|
+
.option('--skip-service', 'Skip installing background service')
|
|
194
|
+
.option('-v, --verbose', 'Show detailed output')
|
|
195
|
+
.action((options) => {
|
|
196
|
+
(0, setup_remote_1.setupRemote)({
|
|
197
|
+
force: options.force,
|
|
198
|
+
skipService: options.skipService,
|
|
199
|
+
verbose: options.verbose,
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
// Agent command - manage the background agent
|
|
203
|
+
const agentCmd = commander_1.program
|
|
204
|
+
.command('agent')
|
|
205
|
+
.description('Manage the remote terminal agent');
|
|
206
|
+
agentCmd
|
|
207
|
+
.command('daemon')
|
|
208
|
+
.description('Run the agent daemon (used by system service)')
|
|
209
|
+
.option('-v, --verbose', 'Show verbose output')
|
|
210
|
+
.action((options) => {
|
|
211
|
+
(0, agent_1.agentDaemon)({ verbose: options.verbose });
|
|
212
|
+
});
|
|
213
|
+
agentCmd
|
|
214
|
+
.command('start')
|
|
215
|
+
.description('Start the agent service')
|
|
216
|
+
.option('-v, --verbose', 'Show verbose output')
|
|
217
|
+
.action((options) => {
|
|
218
|
+
(0, agent_1.agentStart)({ verbose: options.verbose });
|
|
219
|
+
});
|
|
220
|
+
agentCmd
|
|
221
|
+
.command('stop')
|
|
222
|
+
.description('Stop the agent service')
|
|
223
|
+
.option('-v, --verbose', 'Show verbose output')
|
|
224
|
+
.action((options) => {
|
|
225
|
+
(0, agent_1.agentStop)({ verbose: options.verbose });
|
|
226
|
+
});
|
|
227
|
+
agentCmd
|
|
228
|
+
.command('restart')
|
|
229
|
+
.description('Restart the agent service')
|
|
230
|
+
.option('-v, --verbose', 'Show verbose output')
|
|
231
|
+
.action((options) => {
|
|
232
|
+
(0, agent_1.agentRestart)({ verbose: options.verbose });
|
|
233
|
+
});
|
|
234
|
+
agentCmd
|
|
235
|
+
.command('status')
|
|
236
|
+
.description('Check agent status and connection')
|
|
237
|
+
.option('-v, --verbose', 'Show verbose output')
|
|
238
|
+
.action((options) => {
|
|
239
|
+
(0, agent_1.agentStatus)({ verbose: options.verbose });
|
|
240
|
+
});
|
|
241
|
+
agentCmd
|
|
242
|
+
.command('uninstall')
|
|
243
|
+
.description('Remove the agent service and unpair device')
|
|
244
|
+
.option('-v, --verbose', 'Show verbose output')
|
|
245
|
+
.action((options) => {
|
|
246
|
+
(0, agent_1.agentUninstall)({ verbose: options.verbose });
|
|
247
|
+
});
|
|
248
|
+
agentCmd
|
|
249
|
+
.command('logs')
|
|
250
|
+
.description('Show agent logs')
|
|
251
|
+
.option('-f, --follow', 'Follow log output (like tail -f)')
|
|
252
|
+
.action((options) => {
|
|
253
|
+
(0, agent_1.agentLogs)({ follow: options.follow });
|
|
254
|
+
});
|
|
181
255
|
commander_1.program.parse();
|
package/dist/utils/state.d.ts
CHANGED
package/package.json
CHANGED