@ekkos/cli 0.2.15 → 0.2.17
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 +18 -1
- package/dist/agent/daemon.js +91 -7
- package/dist/agent/pty-runner.d.ts +1 -0
- package/dist/agent/pty-runner.js +4 -3
- package/dist/commands/run.js +94 -4
- package/dist/commands/setup-remote.js +4 -4
- package/dist/commands/setup.js +31 -0
- package/package.json +2 -1
- package/templates/agents/railway-manager.md +38 -2
- package/templates/ekkos-manifest.json +1 -1
package/dist/agent/daemon.d.ts
CHANGED
|
@@ -21,6 +21,11 @@ export declare class AgentDaemon {
|
|
|
21
21
|
private ptyRunner;
|
|
22
22
|
private currentSessionId;
|
|
23
23
|
private running;
|
|
24
|
+
private outputBuffer;
|
|
25
|
+
private currentSessionName;
|
|
26
|
+
private isAutoClearInProgress;
|
|
27
|
+
private lastContextWallTime;
|
|
28
|
+
private readonly CONTEXT_WALL_COOLDOWN;
|
|
24
29
|
constructor(config: DaemonConfig);
|
|
25
30
|
/**
|
|
26
31
|
* Start the daemon
|
|
@@ -63,9 +68,21 @@ export declare class AgentDaemon {
|
|
|
63
68
|
*/
|
|
64
69
|
private handlePTYExit;
|
|
65
70
|
/**
|
|
66
|
-
* Send PTY output to server
|
|
71
|
+
* Send PTY output to server (with auto-continue detection)
|
|
67
72
|
*/
|
|
68
73
|
private sendOutput;
|
|
74
|
+
/**
|
|
75
|
+
* Trigger auto /clear + /continue when context wall is hit
|
|
76
|
+
*/
|
|
77
|
+
private triggerAutoContinue;
|
|
78
|
+
/**
|
|
79
|
+
* Wait for Claude's idle prompt ("> ")
|
|
80
|
+
*/
|
|
81
|
+
private waitForIdlePrompt;
|
|
82
|
+
/**
|
|
83
|
+
* Sleep helper
|
|
84
|
+
*/
|
|
85
|
+
private sleep;
|
|
69
86
|
/**
|
|
70
87
|
* Handle WebSocket close
|
|
71
88
|
*/
|
package/dist/agent/daemon.js
CHANGED
|
@@ -51,9 +51,15 @@ const os = __importStar(require("os"));
|
|
|
51
51
|
const fs = __importStar(require("fs"));
|
|
52
52
|
const path = __importStar(require("path"));
|
|
53
53
|
const pty_runner_1 = require("./pty-runner");
|
|
54
|
-
const RELAY_URL = process.env.RELAY_WS_URL || 'wss://
|
|
54
|
+
const RELAY_URL = process.env.RELAY_WS_URL || 'wss://ekkos-relay-production.up.railway.app';
|
|
55
55
|
const HEARTBEAT_INTERVAL = 30000; // 30 seconds
|
|
56
56
|
const RECONNECT_DELAYS = [1000, 2000, 4000, 8000, 16000, 32000, 60000]; // Exponential backoff
|
|
57
|
+
// Auto-continue: Context wall detection pattern
|
|
58
|
+
const CONTEXT_WALL_REGEX = /context limit reached.*\/(compact|clear)\b.*to continue/i;
|
|
59
|
+
// Session name detection pattern (3-word slug: word-word-word)
|
|
60
|
+
const SESSION_NAME_REGEX = /\b([a-z]+-[a-z]+-[a-z]+)\b/i;
|
|
61
|
+
// Idle prompt detection - Claude shows "> " when ready for input
|
|
62
|
+
const IDLE_PROMPT_REGEX = />\s*$/;
|
|
57
63
|
class AgentDaemon {
|
|
58
64
|
constructor(config) {
|
|
59
65
|
this.ws = null;
|
|
@@ -62,6 +68,12 @@ class AgentDaemon {
|
|
|
62
68
|
this.ptyRunner = null;
|
|
63
69
|
this.currentSessionId = null;
|
|
64
70
|
this.running = false;
|
|
71
|
+
// Auto-continue state
|
|
72
|
+
this.outputBuffer = '';
|
|
73
|
+
this.currentSessionName = null;
|
|
74
|
+
this.isAutoClearInProgress = false;
|
|
75
|
+
this.lastContextWallTime = 0;
|
|
76
|
+
this.CONTEXT_WALL_COOLDOWN = 30000; // 30 seconds between auto-clears
|
|
65
77
|
this.config = config;
|
|
66
78
|
}
|
|
67
79
|
/**
|
|
@@ -140,7 +152,7 @@ class AgentDaemon {
|
|
|
140
152
|
this.log('Registered with relay');
|
|
141
153
|
break;
|
|
142
154
|
case 'session_start':
|
|
143
|
-
this.handleSessionStart(message.sessionId);
|
|
155
|
+
this.handleSessionStart(message.sessionId, message.cwd);
|
|
144
156
|
break;
|
|
145
157
|
case 'session_end':
|
|
146
158
|
this.handleSessionEnd(message.sessionId);
|
|
@@ -159,20 +171,26 @@ class AgentDaemon {
|
|
|
159
171
|
/**
|
|
160
172
|
* Handle session start request
|
|
161
173
|
*/
|
|
162
|
-
handleSessionStart(sessionId) {
|
|
163
|
-
this.log(`Session start request: ${sessionId}`);
|
|
174
|
+
handleSessionStart(sessionId, cwd) {
|
|
175
|
+
this.log(`Session start request: ${sessionId}${cwd ? ` (cwd: ${cwd})` : ''}`);
|
|
164
176
|
// Kill existing session if any
|
|
165
177
|
if (this.ptyRunner) {
|
|
166
178
|
this.ptyRunner.kill();
|
|
167
179
|
this.ptyRunner = null;
|
|
168
180
|
}
|
|
169
181
|
this.currentSessionId = sessionId;
|
|
170
|
-
//
|
|
182
|
+
// Reset auto-continue state
|
|
183
|
+
this.outputBuffer = '';
|
|
184
|
+
this.currentSessionName = null;
|
|
185
|
+
this.isAutoClearInProgress = false;
|
|
186
|
+
this.lastContextWallTime = 0;
|
|
187
|
+
// Start PTY with ekkos run (skip -d flag to avoid double init)
|
|
171
188
|
this.ptyRunner = new pty_runner_1.PTYRunner({
|
|
172
189
|
command: 'ekkos',
|
|
173
|
-
args: ['run', '-
|
|
190
|
+
args: ['run', '-b'], // -b for bypass permissions (skip -d to avoid double spawn)
|
|
174
191
|
onData: (data) => this.sendOutput(data),
|
|
175
192
|
onExit: (code) => this.handlePTYExit(code),
|
|
193
|
+
cwd: cwd || process.env.HOME, // Use specified cwd or fall back to home
|
|
176
194
|
verbose: this.config.verbose,
|
|
177
195
|
});
|
|
178
196
|
this.ptyRunner.start();
|
|
@@ -225,14 +243,80 @@ class AgentDaemon {
|
|
|
225
243
|
this.currentSessionId = null;
|
|
226
244
|
}
|
|
227
245
|
/**
|
|
228
|
-
* Send PTY output to server
|
|
246
|
+
* Send PTY output to server (with auto-continue detection)
|
|
229
247
|
*/
|
|
230
248
|
sendOutput(data) {
|
|
249
|
+
// Always forward output to server
|
|
231
250
|
this.sendMessage({
|
|
232
251
|
type: 'output',
|
|
233
252
|
sessionId: this.currentSessionId || undefined,
|
|
234
253
|
data,
|
|
235
254
|
});
|
|
255
|
+
// Auto-continue: Buffer output and detect context wall
|
|
256
|
+
this.outputBuffer += data;
|
|
257
|
+
// Keep buffer manageable (last 2KB)
|
|
258
|
+
if (this.outputBuffer.length > 2048) {
|
|
259
|
+
this.outputBuffer = this.outputBuffer.slice(-2048);
|
|
260
|
+
}
|
|
261
|
+
// Extract session name from output (e.g., "qix-fox-use" in hook output)
|
|
262
|
+
const sessionMatch = this.outputBuffer.match(SESSION_NAME_REGEX);
|
|
263
|
+
if (sessionMatch) {
|
|
264
|
+
this.currentSessionName = sessionMatch[1];
|
|
265
|
+
}
|
|
266
|
+
// Detect context wall
|
|
267
|
+
if (CONTEXT_WALL_REGEX.test(this.outputBuffer) && !this.isAutoClearInProgress) {
|
|
268
|
+
const now = Date.now();
|
|
269
|
+
if (now - this.lastContextWallTime > this.CONTEXT_WALL_COOLDOWN) {
|
|
270
|
+
this.lastContextWallTime = now;
|
|
271
|
+
this.triggerAutoContinue();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Trigger auto /clear + /continue when context wall is hit
|
|
277
|
+
*/
|
|
278
|
+
async triggerAutoContinue() {
|
|
279
|
+
if (this.isAutoClearInProgress || !this.ptyRunner)
|
|
280
|
+
return;
|
|
281
|
+
this.isAutoClearInProgress = true;
|
|
282
|
+
this.log('Auto-continue: Context wall detected, initiating /clear + /continue');
|
|
283
|
+
// Wait for idle prompt
|
|
284
|
+
await this.waitForIdlePrompt();
|
|
285
|
+
// Type /clear
|
|
286
|
+
this.log('Auto-continue: Sending /clear');
|
|
287
|
+
this.ptyRunner.write('/clear\n');
|
|
288
|
+
// Wait for clear to complete
|
|
289
|
+
await this.sleep(2000);
|
|
290
|
+
await this.waitForIdlePrompt();
|
|
291
|
+
// Type /continue with session name
|
|
292
|
+
const continueCmd = this.currentSessionName
|
|
293
|
+
? `/continue ${this.currentSessionName}\n`
|
|
294
|
+
: '/continue\n';
|
|
295
|
+
this.log(`Auto-continue: Sending ${continueCmd.trim()}`);
|
|
296
|
+
this.ptyRunner.write(continueCmd);
|
|
297
|
+
// Reset state
|
|
298
|
+
this.outputBuffer = '';
|
|
299
|
+
this.isAutoClearInProgress = false;
|
|
300
|
+
this.log('Auto-continue: Complete');
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Wait for Claude's idle prompt ("> ")
|
|
304
|
+
*/
|
|
305
|
+
async waitForIdlePrompt(timeout = 10000) {
|
|
306
|
+
const startTime = Date.now();
|
|
307
|
+
while (Date.now() - startTime < timeout) {
|
|
308
|
+
if (IDLE_PROMPT_REGEX.test(this.outputBuffer)) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
await this.sleep(100);
|
|
312
|
+
}
|
|
313
|
+
this.log('Auto-continue: Timeout waiting for idle prompt');
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Sleep helper
|
|
317
|
+
*/
|
|
318
|
+
sleep(ms) {
|
|
319
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
236
320
|
}
|
|
237
321
|
/**
|
|
238
322
|
* Handle WebSocket close
|
package/dist/agent/pty-runner.js
CHANGED
|
@@ -82,7 +82,7 @@ class PTYRunner {
|
|
|
82
82
|
name: 'xterm-256color',
|
|
83
83
|
cols: this.config.cols || 80,
|
|
84
84
|
rows: this.config.rows || 24,
|
|
85
|
-
cwd: process.cwd(),
|
|
85
|
+
cwd: this.config.cwd || process.cwd(),
|
|
86
86
|
env: {
|
|
87
87
|
...process.env,
|
|
88
88
|
TERM: 'xterm-256color',
|
|
@@ -101,11 +101,12 @@ class PTYRunner {
|
|
|
101
101
|
*/
|
|
102
102
|
startWithSpawn() {
|
|
103
103
|
const isWindows = os.platform() === 'win32';
|
|
104
|
+
const cwd = this.config.cwd || process.cwd();
|
|
104
105
|
if (isWindows) {
|
|
105
106
|
// Windows: Use cmd.exe
|
|
106
107
|
this.spawnProcess = (0, child_process_1.spawn)('cmd.exe', ['/c', this.config.command, ...this.config.args], {
|
|
107
108
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
108
|
-
cwd
|
|
109
|
+
cwd,
|
|
109
110
|
env: process.env,
|
|
110
111
|
});
|
|
111
112
|
}
|
|
@@ -116,7 +117,7 @@ class PTYRunner {
|
|
|
116
117
|
: ['-q', '-c', `${this.config.command} ${this.config.args.join(' ')}`, '/dev/null'];
|
|
117
118
|
this.spawnProcess = (0, child_process_1.spawn)('script', scriptArgs, {
|
|
118
119
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
119
|
-
cwd
|
|
120
|
+
cwd,
|
|
120
121
|
env: {
|
|
121
122
|
...process.env,
|
|
122
123
|
TERM: 'xterm-256color',
|
package/dist/commands/run.js
CHANGED
|
@@ -256,15 +256,105 @@ const MEMORY_API_URL = 'https://mcp.ekkos.dev';
|
|
|
256
256
|
const isWindows = os.platform() === 'win32';
|
|
257
257
|
// Pinned Claude Code version for ekkos run
|
|
258
258
|
// 2.1.6 has the old context calculation (95% of full 200K, not effective window)
|
|
259
|
+
// NOTE: Homebrew global installs may be broken, but npm installs work fine
|
|
259
260
|
const PINNED_CLAUDE_VERSION = '2.1.6';
|
|
261
|
+
// ekkOS-managed Claude installation path
|
|
262
|
+
const EKKOS_CLAUDE_DIR = path.join(os.homedir(), '.ekkos', 'claude-code');
|
|
263
|
+
const EKKOS_CLAUDE_BIN = path.join(EKKOS_CLAUDE_DIR, 'node_modules', '.bin', 'claude');
|
|
264
|
+
/**
|
|
265
|
+
* Check if a Claude installation matches our required version
|
|
266
|
+
*/
|
|
267
|
+
function checkClaudeVersion(claudePath) {
|
|
268
|
+
try {
|
|
269
|
+
const version = (0, child_process_1.execSync)(`"${claudePath}" --version 2>/dev/null`, { encoding: 'utf-8' }).trim();
|
|
270
|
+
// Version output is like "2.1.6 (Claude Code)" - extract the version number
|
|
271
|
+
const match = version.match(/^(\d+\.\d+\.\d+)/);
|
|
272
|
+
if (match) {
|
|
273
|
+
return match[1] === PINNED_CLAUDE_VERSION;
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Install Claude Code to ekkOS-managed directory
|
|
283
|
+
* This gives us full control over the version without npx auto-update messages
|
|
284
|
+
*/
|
|
285
|
+
function installEkkosClaudeVersion() {
|
|
286
|
+
console.log(chalk_1.default.cyan(`\n📦 Installing Claude Code v${PINNED_CLAUDE_VERSION} to ~/.ekkos/claude-code...`));
|
|
287
|
+
console.log(chalk_1.default.gray(' (This is a one-time setup for optimal context window behavior)\n'));
|
|
288
|
+
try {
|
|
289
|
+
// Create directory if needed
|
|
290
|
+
if (!fs.existsSync(EKKOS_CLAUDE_DIR)) {
|
|
291
|
+
fs.mkdirSync(EKKOS_CLAUDE_DIR, { recursive: true });
|
|
292
|
+
}
|
|
293
|
+
// Initialize package.json if needed
|
|
294
|
+
const packageJsonPath = path.join(EKKOS_CLAUDE_DIR, 'package.json');
|
|
295
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
296
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify({
|
|
297
|
+
name: 'ekkos-claude-code',
|
|
298
|
+
version: '1.0.0',
|
|
299
|
+
private: true,
|
|
300
|
+
description: 'ekkOS-managed Claude Code installation'
|
|
301
|
+
}, null, 2));
|
|
302
|
+
}
|
|
303
|
+
// Install specific version
|
|
304
|
+
(0, child_process_1.execSync)(`npm install @anthropic-ai/claude-code@${PINNED_CLAUDE_VERSION}`, {
|
|
305
|
+
cwd: EKKOS_CLAUDE_DIR,
|
|
306
|
+
stdio: 'inherit'
|
|
307
|
+
});
|
|
308
|
+
console.log(chalk_1.default.green(`\n✓ Claude Code v${PINNED_CLAUDE_VERSION} installed successfully!`));
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
console.error(chalk_1.default.red(`\n✗ Failed to install Claude Code: ${err.message}`));
|
|
313
|
+
console.log(chalk_1.default.yellow(' Falling back to npx (will show update message)\n'));
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
260
317
|
/**
|
|
261
318
|
* Resolve full path to claude executable
|
|
262
|
-
* Returns
|
|
319
|
+
* Returns direct path if found with correct version, otherwise 'npx:VERSION'
|
|
320
|
+
*
|
|
321
|
+
* IMPORTANT: We MUST use version 2.1.6 specifically because Anthropic changed
|
|
322
|
+
* context window calculation after this version. 2.1.6 uses 95% of full 200K,
|
|
323
|
+
* newer versions use a different (more restrictive) effective window.
|
|
324
|
+
*
|
|
325
|
+
* Priority:
|
|
326
|
+
* 1. ekkOS-managed installation (~/.ekkos/claude-code) - CLEANEST, auto-installed
|
|
327
|
+
* 2. Homebrew/global install IF version matches 2.1.6
|
|
328
|
+
* 3. npx with pinned version (fallback, shows update message)
|
|
263
329
|
*/
|
|
264
330
|
function resolveClaudePath() {
|
|
265
|
-
// PRIORITY 1:
|
|
266
|
-
|
|
267
|
-
|
|
331
|
+
// PRIORITY 1: ekkOS-managed installation (cleanest - no update messages)
|
|
332
|
+
if (fs.existsSync(EKKOS_CLAUDE_BIN) && checkClaudeVersion(EKKOS_CLAUDE_BIN)) {
|
|
333
|
+
return EKKOS_CLAUDE_BIN;
|
|
334
|
+
}
|
|
335
|
+
// PRIORITY 2: Check Homebrew and global installations - only use if version matches
|
|
336
|
+
const candidatePaths = [
|
|
337
|
+
// Homebrew
|
|
338
|
+
'/opt/homebrew/bin/claude', // macOS Apple Silicon
|
|
339
|
+
'/usr/local/bin/claude', // macOS Intel
|
|
340
|
+
'/home/linuxbrew/.linuxbrew/bin/claude', // Linux (system)
|
|
341
|
+
path.join(os.homedir(), '.linuxbrew/bin/claude'), // Linux (user)
|
|
342
|
+
// Global npm install
|
|
343
|
+
path.join(os.homedir(), '.npm-global/bin/claude'),
|
|
344
|
+
path.join(os.homedir(), '.local/bin/claude'),
|
|
345
|
+
];
|
|
346
|
+
for (const p of candidatePaths) {
|
|
347
|
+
if (fs.existsSync(p) && checkClaudeVersion(p)) {
|
|
348
|
+
return p; // Direct path with correct version
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// PRIORITY 3: Auto-install to ekkOS-managed directory
|
|
352
|
+
if (installEkkosClaudeVersion()) {
|
|
353
|
+
if (fs.existsSync(EKKOS_CLAUDE_BIN)) {
|
|
354
|
+
return EKKOS_CLAUDE_BIN;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// PRIORITY 4: Fall back to npx with pinned version (shows update message)
|
|
268
358
|
return `npx:${PINNED_CLAUDE_VERSION}`;
|
|
269
359
|
}
|
|
270
360
|
/**
|
|
@@ -61,7 +61,7 @@ const init_1 = require("./init");
|
|
|
61
61
|
const pkgPath = path.resolve(__dirname, '../../package.json');
|
|
62
62
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
63
63
|
const PLATFORM_URL = process.env.PLATFORM_URL || 'https://platform.ekkos.dev';
|
|
64
|
-
const
|
|
64
|
+
const RELAY_API_URL = process.env.RELAY_URL || 'https://ekkos-relay-production.up.railway.app';
|
|
65
65
|
const POLLING_INTERVAL = 2000; // 2 seconds
|
|
66
66
|
const POLLING_TIMEOUT = 600000; // 10 minutes
|
|
67
67
|
/**
|
|
@@ -110,7 +110,7 @@ function getOrCreateDeviceInfo() {
|
|
|
110
110
|
* Request device pairing code from server
|
|
111
111
|
*/
|
|
112
112
|
async function requestPairingCode(deviceInfo, authToken) {
|
|
113
|
-
const response = await fetch(`${
|
|
113
|
+
const response = await fetch(`${RELAY_API_URL}/api/v1/relay/pair`, {
|
|
114
114
|
method: 'POST',
|
|
115
115
|
headers: {
|
|
116
116
|
'Authorization': `Bearer ${authToken}`,
|
|
@@ -140,7 +140,7 @@ async function requestPairingCode(deviceInfo, authToken) {
|
|
|
140
140
|
async function pollForApproval(deviceCode, authToken) {
|
|
141
141
|
const startTime = Date.now();
|
|
142
142
|
while (Date.now() - startTime < POLLING_TIMEOUT) {
|
|
143
|
-
const response = await fetch(`${
|
|
143
|
+
const response = await fetch(`${RELAY_API_URL}/api/v1/relay/pair/${deviceCode}`, {
|
|
144
144
|
headers: {
|
|
145
145
|
'Authorization': `Bearer ${authToken}`,
|
|
146
146
|
},
|
|
@@ -338,7 +338,7 @@ async function verifyConnection(deviceInfo, authToken) {
|
|
|
338
338
|
// Wait a bit for agent to start
|
|
339
339
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
340
340
|
// Check device status
|
|
341
|
-
const response = await fetch(`${
|
|
341
|
+
const response = await fetch(`${RELAY_API_URL}/api/v1/relay/devices/${(await (0, state_1.getState)())?.userId}`, {
|
|
342
342
|
headers: {
|
|
343
343
|
'Authorization': `Bearer ${authToken}`,
|
|
344
344
|
},
|
package/dist/commands/setup.js
CHANGED
|
@@ -137,6 +137,7 @@ async function setup(options) {
|
|
|
137
137
|
message: 'Which IDE are you setting up?',
|
|
138
138
|
choices: [
|
|
139
139
|
{ name: 'Claude Code (CLI)', value: 'claude-code' },
|
|
140
|
+
{ name: 'Claude Desktop (MCP)', value: 'claude-desktop' },
|
|
140
141
|
{ name: 'Cursor', value: 'cursor' },
|
|
141
142
|
{ name: 'Windsurf (Cascade)', value: 'windsurf' },
|
|
142
143
|
{ name: 'VSCode (Copilot)', value: 'vscode' }
|
|
@@ -180,6 +181,9 @@ async function setupIDE(ide, apiKey, config) {
|
|
|
180
181
|
case 'claude-code':
|
|
181
182
|
await setupClaudeCode(apiKey);
|
|
182
183
|
break;
|
|
184
|
+
case 'claude-desktop':
|
|
185
|
+
await setupClaudeDesktop(apiKey);
|
|
186
|
+
break;
|
|
183
187
|
case 'cursor':
|
|
184
188
|
await setupCursor(apiKey);
|
|
185
189
|
break;
|
|
@@ -320,6 +324,33 @@ async function setupVSCode(apiKey) {
|
|
|
320
324
|
// Note: Full VSCode integration requires extension
|
|
321
325
|
console.log(chalk_1.default.yellow(' Note: VSCode requires the ekkOS extension for full integration'));
|
|
322
326
|
}
|
|
327
|
+
async function setupClaudeDesktop(apiKey) {
|
|
328
|
+
// Claude Desktop - configure MCP server
|
|
329
|
+
const claudeDir = (0, path_1.join)((0, os_1.homedir)(), 'Library', 'Application Support', 'Claude');
|
|
330
|
+
if (!(0, fs_1.existsSync)(claudeDir)) {
|
|
331
|
+
(0, fs_1.mkdirSync)(claudeDir, { recursive: true });
|
|
332
|
+
}
|
|
333
|
+
// Create/update MCP config
|
|
334
|
+
const configPath = (0, path_1.join)(claudeDir, 'claude_desktop_config.json');
|
|
335
|
+
let config = {};
|
|
336
|
+
if ((0, fs_1.existsSync)(configPath)) {
|
|
337
|
+
try {
|
|
338
|
+
config = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf-8'));
|
|
339
|
+
}
|
|
340
|
+
catch { }
|
|
341
|
+
}
|
|
342
|
+
// Preserve existing preferences
|
|
343
|
+
config.mcpServers = config.mcpServers || {};
|
|
344
|
+
config.mcpServers['ekkos-memory'] = {
|
|
345
|
+
command: 'npx',
|
|
346
|
+
args: ['-y', '@ekkos/mcp-server@latest'],
|
|
347
|
+
env: {
|
|
348
|
+
EKKOS_API_KEY: apiKey
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
(0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2));
|
|
352
|
+
console.log(chalk_1.default.yellow(' Note: Restart Claude Desktop to load the MCP server'));
|
|
353
|
+
}
|
|
323
354
|
function generatePromptSubmitHook(apiKey) {
|
|
324
355
|
return `#!/bin/bash
|
|
325
356
|
# ekkOS Hook: UserPromptSubmit
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekkos/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
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": {
|
|
@@ -68,10 +68,30 @@ railway variables set KEY=value -s pm2-workers
|
|
|
68
68
|
|
|
69
69
|
## EKKOS SERVICES
|
|
70
70
|
|
|
71
|
-
### Railway
|
|
72
|
-
**Project**: imaginative-vision
|
|
71
|
+
### Railway Project: `imaginative-vision`
|
|
73
72
|
**Environment**: production
|
|
74
73
|
|
|
74
|
+
There are **3 services** in this Railway project:
|
|
75
|
+
|
|
76
|
+
| Service | Purpose | Domain |
|
|
77
|
+
|---------|---------|--------|
|
|
78
|
+
| `ekkos-relay` | WebSocket relay for remote terminal | relay.ekkos.dev |
|
|
79
|
+
| `pm2-workers` | PM2-managed background workers | pm2.ekkos.dev |
|
|
80
|
+
| `cloud-terminal` | Cloud PTY service for browser terminals | (internal) |
|
|
81
|
+
|
|
82
|
+
### Service: `ekkos-relay`
|
|
83
|
+
WebSocket relay server connecting browsers to devices/cloud terminals.
|
|
84
|
+
- Source: `apps/relay/`
|
|
85
|
+
- Health: `/health`
|
|
86
|
+
- Endpoints: `/api/v1/relay/device`, `/api/v1/relay/browser`, `/api/v1/relay/cloud`
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
railway logs -s ekkos-relay --lines 50
|
|
90
|
+
railway up -s ekkos-relay
|
|
91
|
+
railway redeploy -s ekkos-relay
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Service: `pm2-workers`
|
|
75
95
|
PM2-managed workers:
|
|
76
96
|
| Worker | Purpose |
|
|
77
97
|
|--------|---------|
|
|
@@ -79,6 +99,22 @@ PM2-managed workers:
|
|
|
79
99
|
| `working-memory-processor` | WM → DB batch sync |
|
|
80
100
|
| `slow-loop-processor` | Pattern extraction (if enabled) |
|
|
81
101
|
|
|
102
|
+
```bash
|
|
103
|
+
railway logs -s pm2-workers --lines 50
|
|
104
|
+
railway run -s pm2-workers -- pm2 status
|
|
105
|
+
railway run -s pm2-workers -- pm2 restart all
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Service: `cloud-terminal`
|
|
109
|
+
Cloud-based PTY service for browser terminal access.
|
|
110
|
+
- Source: `apps/cloud-terminal/`
|
|
111
|
+
- Connects to relay via WebSocket
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
railway logs -s cloud-terminal --lines 50
|
|
115
|
+
railway redeploy -s cloud-terminal
|
|
116
|
+
```
|
|
117
|
+
|
|
82
118
|
### Vercel Services (NOT on Railway)
|
|
83
119
|
| Service | URL |
|
|
84
120
|
|---------|-----|
|