@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.
@@ -0,0 +1,470 @@
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 errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
127
+ const errorMessage = typeof errorData === 'string'
128
+ ? errorData
129
+ : errorData.error || errorData.message || JSON.stringify(errorData);
130
+ throw new Error(errorMessage || `Failed to request pairing code: ${response.status}`);
131
+ }
132
+ return response.json();
133
+ }
134
+ /**
135
+ * Poll for pairing approval
136
+ */
137
+ async function pollForApproval(deviceCode, authToken) {
138
+ const startTime = Date.now();
139
+ while (Date.now() - startTime < POLLING_TIMEOUT) {
140
+ const response = await fetch(`${MEMORY_API_URL}/api/v1/relay/pair/${deviceCode}`, {
141
+ headers: {
142
+ 'Authorization': `Bearer ${authToken}`,
143
+ },
144
+ });
145
+ if (response.status === 200) {
146
+ const data = await response.json();
147
+ if (data.status === 'approved') {
148
+ return { approved: true, deviceToken: data.deviceToken };
149
+ }
150
+ }
151
+ else if (response.status === 403) {
152
+ // Denied
153
+ return { approved: false };
154
+ }
155
+ else if (response.status === 410) {
156
+ // Expired
157
+ throw new Error('Pairing code expired');
158
+ }
159
+ // Wait before next poll
160
+ await new Promise(resolve => setTimeout(resolve, POLLING_INTERVAL));
161
+ process.stdout.write('.');
162
+ }
163
+ throw new Error('Pairing timeout');
164
+ }
165
+ /**
166
+ * Install agent as system service
167
+ */
168
+ async function installService(verbose) {
169
+ const platform = os.platform();
170
+ if (platform === 'darwin') {
171
+ await installMacOSService(verbose);
172
+ }
173
+ else if (platform === 'win32') {
174
+ await installWindowsService(verbose);
175
+ }
176
+ else if (platform === 'linux') {
177
+ await installLinuxService(verbose);
178
+ }
179
+ else {
180
+ throw new Error(`Unsupported platform: ${platform}`);
181
+ }
182
+ }
183
+ /**
184
+ * Install macOS launchd service
185
+ */
186
+ async function installMacOSService(verbose) {
187
+ const plistName = 'dev.ekkos.agent';
188
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${plistName}.plist`);
189
+ // Find node path
190
+ const nodePath = process.execPath;
191
+ // Find ekkos CLI path (assuming it's in node_modules/.bin or globally installed)
192
+ let ekkosPath;
193
+ try {
194
+ ekkosPath = (0, child_process_1.execSync)('which ekkos', { encoding: 'utf-8' }).trim();
195
+ }
196
+ catch {
197
+ // Fall back to npx
198
+ ekkosPath = 'npx @ekkos/cli';
199
+ }
200
+ const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
201
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
202
+ <plist version="1.0">
203
+ <dict>
204
+ <key>Label</key>
205
+ <string>${plistName}</string>
206
+ <key>ProgramArguments</key>
207
+ <array>
208
+ <string>${nodePath}</string>
209
+ <string>${ekkosPath.includes('npx') ? ekkosPath.split(' ')[0] : ekkosPath}</string>
210
+ ${ekkosPath.includes('npx') ? '<string>@ekkos/cli</string>' : ''}
211
+ <string>agent</string>
212
+ <string>daemon</string>
213
+ </array>
214
+ <key>RunAtLoad</key>
215
+ <true/>
216
+ <key>KeepAlive</key>
217
+ <true/>
218
+ <key>StandardErrorPath</key>
219
+ <string>${path.join(state_1.EKKOS_DIR, 'agent.err.log')}</string>
220
+ <key>StandardOutPath</key>
221
+ <string>${path.join(state_1.EKKOS_DIR, 'agent.out.log')}</string>
222
+ <key>EnvironmentVariables</key>
223
+ <dict>
224
+ <key>PATH</key>
225
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
226
+ </dict>
227
+ </dict>
228
+ </plist>`;
229
+ // Ensure directory exists
230
+ const launchAgentsDir = path.dirname(plistPath);
231
+ if (!fs.existsSync(launchAgentsDir)) {
232
+ fs.mkdirSync(launchAgentsDir, { recursive: true });
233
+ }
234
+ // Write plist
235
+ fs.writeFileSync(plistPath, plistContent);
236
+ if (verbose) {
237
+ console.log(chalk_1.default.gray(` Created: ${plistPath}`));
238
+ }
239
+ // Unload if already loaded
240
+ try {
241
+ (0, child_process_1.execSync)(`launchctl unload "${plistPath}" 2>/dev/null`, { encoding: 'utf-8' });
242
+ }
243
+ catch {
244
+ // Ignore if not loaded
245
+ }
246
+ // Load the service
247
+ (0, child_process_1.execSync)(`launchctl load "${plistPath}"`, { encoding: 'utf-8' });
248
+ if (verbose) {
249
+ console.log(chalk_1.default.gray(` Loaded: ${plistName}`));
250
+ }
251
+ }
252
+ /**
253
+ * Install Windows service
254
+ */
255
+ async function installWindowsService(verbose) {
256
+ // Windows uses a scheduled task that runs at login
257
+ const taskName = 'ekkOS Agent';
258
+ // Find ekkos CLI path
259
+ let ekkosPath;
260
+ try {
261
+ ekkosPath = (0, child_process_1.execSync)('where ekkos', { encoding: 'utf-8' }).trim().split('\n')[0];
262
+ }
263
+ catch {
264
+ // Fall back to npx
265
+ ekkosPath = 'npx';
266
+ }
267
+ // Create scheduled task
268
+ const command = ekkosPath.includes('npx')
269
+ ? `npx @ekkos/cli agent daemon`
270
+ : `"${ekkosPath}" agent daemon`;
271
+ try {
272
+ // Delete existing task
273
+ (0, child_process_1.execSync)(`schtasks /delete /tn "${taskName}" /f 2>nul`, { encoding: 'utf-8' });
274
+ }
275
+ catch {
276
+ // Ignore if doesn't exist
277
+ }
278
+ // Create task that runs at login
279
+ (0, child_process_1.execSync)(`schtasks /create /tn "${taskName}" /tr "${command}" /sc onlogon /rl limited`, { encoding: 'utf-8' });
280
+ // Start the task now
281
+ (0, child_process_1.execSync)(`schtasks /run /tn "${taskName}"`, { encoding: 'utf-8' });
282
+ if (verbose) {
283
+ console.log(chalk_1.default.gray(` Created scheduled task: ${taskName}`));
284
+ }
285
+ }
286
+ /**
287
+ * Install Linux systemd service
288
+ */
289
+ async function installLinuxService(verbose) {
290
+ const serviceName = 'ekkos-agent';
291
+ const serviceDir = path.join(os.homedir(), '.config', 'systemd', 'user');
292
+ const servicePath = path.join(serviceDir, `${serviceName}.service`);
293
+ // Find ekkos CLI path
294
+ let ekkosPath;
295
+ try {
296
+ ekkosPath = (0, child_process_1.execSync)('which ekkos', { encoding: 'utf-8' }).trim();
297
+ }
298
+ catch {
299
+ ekkosPath = 'npx @ekkos/cli';
300
+ }
301
+ const serviceContent = `[Unit]
302
+ Description=ekkOS Remote Terminal Agent
303
+ After=network.target
304
+
305
+ [Service]
306
+ Type=simple
307
+ ExecStart=${process.execPath} ${ekkosPath} agent daemon
308
+ Restart=always
309
+ RestartSec=10
310
+ StandardOutput=append:${path.join(state_1.EKKOS_DIR, 'agent.out.log')}
311
+ StandardError=append:${path.join(state_1.EKKOS_DIR, 'agent.err.log')}
312
+
313
+ [Install]
314
+ WantedBy=default.target
315
+ `;
316
+ // Ensure directory exists
317
+ if (!fs.existsSync(serviceDir)) {
318
+ fs.mkdirSync(serviceDir, { recursive: true });
319
+ }
320
+ // Write service file
321
+ fs.writeFileSync(servicePath, serviceContent);
322
+ // Reload systemd and enable service
323
+ (0, child_process_1.execSync)('systemctl --user daemon-reload', { encoding: 'utf-8' });
324
+ (0, child_process_1.execSync)(`systemctl --user enable ${serviceName}`, { encoding: 'utf-8' });
325
+ (0, child_process_1.execSync)(`systemctl --user restart ${serviceName}`, { encoding: 'utf-8' });
326
+ if (verbose) {
327
+ console.log(chalk_1.default.gray(` Created: ${servicePath}`));
328
+ console.log(chalk_1.default.gray(` Enabled and started: ${serviceName}`));
329
+ }
330
+ }
331
+ /**
332
+ * Verify agent is connected
333
+ */
334
+ async function verifyConnection(deviceInfo, authToken) {
335
+ // Wait a bit for agent to start
336
+ await new Promise(resolve => setTimeout(resolve, 3000));
337
+ // Check device status
338
+ const response = await fetch(`${MEMORY_API_URL}/api/v1/relay/devices/${(await (0, state_1.getState)())?.userId}`, {
339
+ headers: {
340
+ 'Authorization': `Bearer ${authToken}`,
341
+ },
342
+ });
343
+ if (!response.ok) {
344
+ return false;
345
+ }
346
+ const data = await response.json();
347
+ const device = data.devices?.find((d) => d.deviceId === deviceInfo.deviceId);
348
+ return device?.online === true;
349
+ }
350
+ /**
351
+ * Main setup command
352
+ */
353
+ async function setupRemote(options = {}) {
354
+ const verbose = options.verbose || false;
355
+ console.log('');
356
+ console.log(chalk_1.default.cyan.bold(' 🔧 ekkOS Remote Setup'));
357
+ console.log('');
358
+ // Step 1: Check if logged in
359
+ let authToken = (0, state_1.getAuthToken)();
360
+ if (!authToken) {
361
+ console.log(chalk_1.default.yellow(' You need to log in first.'));
362
+ console.log('');
363
+ await (0, init_1.init)({ ide: 'claude' });
364
+ authToken = (0, state_1.getAuthToken)();
365
+ if (!authToken) {
366
+ console.log(chalk_1.default.red(' Failed to authenticate. Please run `ekkos init` first.'));
367
+ process.exit(1);
368
+ }
369
+ }
370
+ const state = (0, state_1.getState)();
371
+ console.log(chalk_1.default.green(` ✓ Logged in as ${state?.userEmail || 'unknown'}`));
372
+ // Step 2: Get or create device info
373
+ const deviceInfo = getOrCreateDeviceInfo();
374
+ console.log(chalk_1.default.green(` ✓ Device: ${deviceInfo.deviceName} (${deviceInfo.arch})`));
375
+ // Step 3: Check if already paired
376
+ const deviceFilePath = path.join(state_1.EKKOS_DIR, 'device.json');
377
+ const deviceData = JSON.parse(fs.readFileSync(deviceFilePath, 'utf-8'));
378
+ if (deviceData.deviceToken && !options.force) {
379
+ console.log(chalk_1.default.green(' ✓ Device already paired'));
380
+ }
381
+ else {
382
+ // Request pairing code
383
+ console.log('');
384
+ console.log(chalk_1.default.cyan(' Requesting pairing code...'));
385
+ let pairingResponse;
386
+ try {
387
+ pairingResponse = await requestPairingCode(deviceInfo, authToken);
388
+ }
389
+ catch (err) {
390
+ console.log(chalk_1.default.red(` Error: ${err.message}`));
391
+ process.exit(1);
392
+ }
393
+ // Open browser for approval
394
+ const verificationUrl = pairingResponse.verificationUrl || `${PLATFORM_URL}/verify/${pairingResponse.userCode}`;
395
+ console.log('');
396
+ console.log(chalk_1.default.white(` Verification code: ${chalk_1.default.bold.yellow(pairingResponse.userCode)}`));
397
+ console.log(chalk_1.default.gray(` Opening browser to approve...`));
398
+ console.log('');
399
+ try {
400
+ await (0, open_1.default)(verificationUrl);
401
+ }
402
+ catch {
403
+ console.log(chalk_1.default.yellow(` Could not open browser. Please visit:`));
404
+ console.log(chalk_1.default.cyan(` ${verificationUrl}`));
405
+ }
406
+ // Poll for approval
407
+ console.log(chalk_1.default.gray(' Waiting for approval'));
408
+ process.stdout.write(' ');
409
+ try {
410
+ const result = await pollForApproval(pairingResponse.deviceCode, authToken);
411
+ console.log('');
412
+ if (!result.approved) {
413
+ console.log(chalk_1.default.red(' Pairing was denied.'));
414
+ process.exit(1);
415
+ }
416
+ // Save device token
417
+ deviceData.deviceToken = result.deviceToken;
418
+ deviceData.pairedAt = new Date().toISOString();
419
+ fs.writeFileSync(deviceFilePath, JSON.stringify(deviceData, null, 2));
420
+ console.log(chalk_1.default.green(' ✓ Device paired'));
421
+ }
422
+ catch (err) {
423
+ console.log('');
424
+ console.log(chalk_1.default.red(` Error: ${err.message}`));
425
+ process.exit(1);
426
+ }
427
+ }
428
+ // Step 4: Install service (unless skipped)
429
+ if (!options.skipService) {
430
+ console.log(chalk_1.default.cyan(' Installing agent service...'));
431
+ try {
432
+ await installService(verbose);
433
+ console.log(chalk_1.default.green(' ✓ Agent installed and running'));
434
+ }
435
+ catch (err) {
436
+ console.log(chalk_1.default.yellow(` Warning: Could not install service: ${err.message}`));
437
+ console.log(chalk_1.default.gray(' You can start the agent manually with: ekkos agent start'));
438
+ }
439
+ }
440
+ // Step 5: Verify connection
441
+ console.log(chalk_1.default.cyan(' Verifying connection...'));
442
+ const connected = await verifyConnection(deviceInfo, authToken);
443
+ if (connected) {
444
+ console.log(chalk_1.default.green(' ✓ Connected to ekkOS cloud'));
445
+ }
446
+ else {
447
+ console.log(chalk_1.default.yellow(' ⚠ Agent not connected yet (may take a moment)'));
448
+ }
449
+ // Success message
450
+ console.log('');
451
+ console.log(chalk_1.default.green.bold(' 🎉 Setup complete!'));
452
+ console.log('');
453
+ console.log(chalk_1.default.white(' Access your terminal from anywhere at:'));
454
+ console.log(chalk_1.default.cyan(` ${PLATFORM_URL}/dashboard/terminal`));
455
+ console.log('');
456
+ console.log(chalk_1.default.gray(` Your device will appear as "${deviceInfo.deviceName}" in the device list.`));
457
+ console.log(chalk_1.default.gray(' The agent runs in background and auto-starts on boot.'));
458
+ console.log('');
459
+ console.log(chalk_1.default.dim(' How it works:'));
460
+ console.log(chalk_1.default.dim(' • Claude Code runs on THIS machine (uses your subscription)'));
461
+ console.log(chalk_1.default.dim(' • ekkOS provides secure relay to access it from any browser'));
462
+ console.log(chalk_1.default.dim(' • Your code/files stay local - only terminal I/O is relayed'));
463
+ console.log(chalk_1.default.dim(' • ekkOS learns from every session - faster, smarter, better over time'));
464
+ console.log('');
465
+ console.log(chalk_1.default.gray(' Commands:'));
466
+ console.log(chalk_1.default.gray(' ekkos agent status - Check agent status'));
467
+ console.log(chalk_1.default.gray(' ekkos agent stop - Stop agent temporarily'));
468
+ console.log(chalk_1.default.gray(' ekkos agent uninstall - Remove agent completely'));
469
+ console.log('');
470
+ }
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,10 +1,11 @@
1
1
  {
2
2
  "name": "@ekkos/cli",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
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
+ "cli": "dist/index.js",
8
9
  "ekkos-capture": "dist/cache/capture.js"
9
10
  },
10
11
  "scripts": {
@@ -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-21T08:07:39.857Z",
5
5
  "platforms": {
6
6
  "darwin": {
7
7
  "configDir": "~/.ekkos",