@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.
@@ -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();
@@ -17,6 +17,8 @@ export interface EkkosState {
17
17
  turnNumber: number;
18
18
  lastUpdated: string;
19
19
  projectPath: string;
20
+ userId?: string;
21
+ userEmail?: string;
20
22
  }
21
23
  export interface EkkosConfig {
22
24
  hookApiKey?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekkos/cli",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Setup ekkOS memory for AI coding assistants (Claude Code, Cursor, Windsurf)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://ekkos.dev/schemas/manifest-v1.json",
3
3
  "manifestVersion": "1.0.0",
4
- "generatedAt": "2026-01-19T16:26:13.076Z",
4
+ "generatedAt": "2026-01-21T03:41:22.207Z",
5
5
  "platforms": {
6
6
  "darwin": {
7
7
  "configDir": "~/.ekkos",