@ekkos/cli 1.4.2 → 1.5.1
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/commands/dashboard.js +317 -95
- package/dist/commands/init.js +59 -4
- package/dist/commands/living-docs.d.ts +1 -0
- package/dist/commands/living-docs.js +5 -2
- package/dist/commands/logout.d.ts +9 -0
- package/dist/commands/logout.js +104 -0
- package/dist/commands/run.js +56 -23
- package/dist/commands/workspaces.d.ts +4 -0
- package/dist/commands/workspaces.js +153 -0
- package/dist/index.js +82 -83
- package/dist/local/diff-engine.d.ts +19 -0
- package/dist/local/diff-engine.js +81 -0
- package/dist/local/entity-extractor.d.ts +18 -0
- package/dist/local/entity-extractor.js +67 -0
- package/dist/local/git-utils.d.ts +37 -0
- package/dist/local/git-utils.js +169 -0
- package/dist/local/living-docs-manager.d.ts +6 -0
- package/dist/local/living-docs-manager.js +180 -139
- package/dist/utils/notifier.d.ts +15 -0
- package/dist/utils/notifier.js +40 -0
- package/dist/utils/paths.d.ts +4 -0
- package/dist/utils/paths.js +7 -0
- package/dist/utils/state.d.ts +3 -0
- package/dist/utils/stdin-relay.d.ts +37 -0
- package/dist/utils/stdin-relay.js +155 -0
- package/package.json +4 -1
- package/templates/CLAUDE.md +3 -1
- package/dist/commands/setup.d.ts +0 -6
- package/dist/commands/setup.js +0 -389
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* stdin-relay.ts — Universal Session Attach (Phase 1)
|
|
4
|
+
*
|
|
5
|
+
* Lightweight HTTP server bound to 127.0.0.1 that accepts remote input
|
|
6
|
+
* and injects it into the running PTY's stdin. The daemon (or any local
|
|
7
|
+
* process with the auth token) can POST text to be typed into Claude.
|
|
8
|
+
*
|
|
9
|
+
* Security:
|
|
10
|
+
* - Binds to 127.0.0.1 only (no network exposure)
|
|
11
|
+
* - Requires a one-time token generated at startup
|
|
12
|
+
* - Token is stored in active-sessions.json (filesystem-permission scoped)
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.startStdinRelay = startStdinRelay;
|
|
49
|
+
const http = __importStar(require("http"));
|
|
50
|
+
const crypto = __importStar(require("crypto"));
|
|
51
|
+
/**
|
|
52
|
+
* Start a stdin relay HTTP server on a random available port.
|
|
53
|
+
*
|
|
54
|
+
* Endpoints:
|
|
55
|
+
* POST /stdin — inject text into the PTY (body: { text: string })
|
|
56
|
+
* GET /health — check if session is alive
|
|
57
|
+
* GET /status — session metadata (attached clients, uptime)
|
|
58
|
+
*/
|
|
59
|
+
async function startStdinRelay(options) {
|
|
60
|
+
const { write, onAttach, dlog } = options;
|
|
61
|
+
const token = crypto.randomBytes(24).toString('hex');
|
|
62
|
+
let attachedClients = 0;
|
|
63
|
+
const startTime = Date.now();
|
|
64
|
+
const server = http.createServer((req, res) => {
|
|
65
|
+
// CORS headers for local daemon communication
|
|
66
|
+
res.setHeader('Content-Type', 'application/json');
|
|
67
|
+
// Auth check (skip for health)
|
|
68
|
+
if (req.url !== '/health') {
|
|
69
|
+
const authHeader = req.headers.authorization;
|
|
70
|
+
if (authHeader !== `Bearer ${token}`) {
|
|
71
|
+
res.writeHead(401);
|
|
72
|
+
res.end(JSON.stringify({ error: 'Unauthorized' }));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (req.method === 'GET' && req.url === '/health') {
|
|
77
|
+
res.writeHead(200);
|
|
78
|
+
res.end(JSON.stringify({ alive: true, pid: process.pid }));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (req.method === 'GET' && req.url === '/status') {
|
|
82
|
+
res.writeHead(200);
|
|
83
|
+
res.end(JSON.stringify({
|
|
84
|
+
pid: process.pid,
|
|
85
|
+
uptime_ms: Date.now() - startTime,
|
|
86
|
+
attached_clients: attachedClients,
|
|
87
|
+
}));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (req.method === 'POST' && req.url === '/stdin') {
|
|
91
|
+
let body = '';
|
|
92
|
+
req.on('data', chunk => { body += chunk; });
|
|
93
|
+
req.on('end', () => {
|
|
94
|
+
try {
|
|
95
|
+
const { text } = JSON.parse(body);
|
|
96
|
+
if (typeof text !== 'string') {
|
|
97
|
+
res.writeHead(400);
|
|
98
|
+
res.end(JSON.stringify({ error: 'Missing "text" field' }));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
write(text);
|
|
102
|
+
dlog?.(`[stdin-relay] Injected ${text.length} chars from remote`);
|
|
103
|
+
res.writeHead(200);
|
|
104
|
+
res.end(JSON.stringify({ ok: true, length: text.length }));
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
res.writeHead(400);
|
|
108
|
+
res.end(JSON.stringify({ error: 'Invalid JSON body' }));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (req.method === 'POST' && req.url === '/attach') {
|
|
114
|
+
attachedClients++;
|
|
115
|
+
const remoteInfo = req.headers['x-remote-info'] || 'unknown';
|
|
116
|
+
onAttach?.(remoteInfo);
|
|
117
|
+
dlog?.(`[stdin-relay] Remote client attached (${remoteInfo}), total: ${attachedClients}`);
|
|
118
|
+
res.writeHead(200);
|
|
119
|
+
res.end(JSON.stringify({ ok: true, attached_clients: attachedClients }));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (req.method === 'POST' && req.url === '/detach') {
|
|
123
|
+
attachedClients = Math.max(0, attachedClients - 1);
|
|
124
|
+
dlog?.(`[stdin-relay] Remote client detached, total: ${attachedClients}`);
|
|
125
|
+
res.writeHead(200);
|
|
126
|
+
res.end(JSON.stringify({ ok: true, attached_clients: attachedClients }));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
res.writeHead(404);
|
|
130
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
131
|
+
});
|
|
132
|
+
// Bind to localhost only — no network exposure
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
server.listen(0, '127.0.0.1', () => {
|
|
135
|
+
const addr = server.address();
|
|
136
|
+
if (!addr || typeof addr === 'string') {
|
|
137
|
+
reject(new Error('Failed to get server address'));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const port = addr.port;
|
|
141
|
+
dlog?.(`[stdin-relay] Listening on 127.0.0.1:${port}`);
|
|
142
|
+
resolve({
|
|
143
|
+
port,
|
|
144
|
+
token,
|
|
145
|
+
stop: () => new Promise((resolveStop) => {
|
|
146
|
+
server.close(() => {
|
|
147
|
+
dlog?.('[stdin-relay] Server stopped');
|
|
148
|
+
resolveStop();
|
|
149
|
+
});
|
|
150
|
+
}),
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
server.on('error', reject);
|
|
154
|
+
});
|
|
155
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekkos/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "ekkOS memory CLI — persistent memory for AI coding assistants (Claude Code, Gemini, Cursor, Windsurf)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"license": "UNLICENSED",
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@ekkos/agent": "^0.1.0",
|
|
44
|
+
"@ekkos/prometheus": "workspace:*",
|
|
44
45
|
"@ekkos/remote": "^0.14.1",
|
|
45
46
|
"@supabase/supabase-js": "^2.39.8",
|
|
46
47
|
"blessed": "^0.1.81",
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
"chalk": "^5.3.0",
|
|
50
51
|
"commander": "^12.1.0",
|
|
51
52
|
"inquirer": "^9.2.23",
|
|
53
|
+
"node-notifier": "^10.0.1",
|
|
52
54
|
"node-pty": "1.2.0-beta.7",
|
|
53
55
|
"open": "^10.0.0",
|
|
54
56
|
"ora": "^8.0.1",
|
|
@@ -57,6 +59,7 @@
|
|
|
57
59
|
"devDependencies": {
|
|
58
60
|
"@types/blessed": "^0.1.27",
|
|
59
61
|
"@types/node": "^20.11.0",
|
|
62
|
+
"@types/node-notifier": "^8.0.5",
|
|
60
63
|
"typescript": "^5.3.3"
|
|
61
64
|
},
|
|
62
65
|
"engines": {
|
package/templates/CLAUDE.md
CHANGED
|
@@ -97,7 +97,9 @@ Every system directory contains an auto-generated `ekkOS_CONTEXT.md` file. These
|
|
|
97
97
|
|
|
98
98
|
**Read the nearest `ekkOS_CONTEXT.md` when entering any directory you're about to work in.** Walk up the tree for broader context.
|
|
99
99
|
|
|
100
|
-
**What they contain:** system architecture, dependencies, environment variables, file composition, activity status (`active` / `stale` / `dormant`), and recent git changes.
|
|
100
|
+
**What they contain:** system architecture, dependencies, environment variables, file composition, activity status (`active` / `stale` / `dormant`), and recent git changes.
|
|
101
|
+
|
|
102
|
+
**Semantic Analysis:** These files often contain a `## Active Work` or `## Semantic Analysis` section. This is high-fidelity memory of recent intent and architectural impacts (e.g. regressions, state changes). **Always prioritize these insights over your own assumptions about the current code state.**
|
|
101
103
|
|
|
102
104
|
**When to read them:**
|
|
103
105
|
- Before modifying code in a system you haven't touched yet
|
package/dist/commands/setup.d.ts
DELETED
package/dist/commands/setup.js
DELETED
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.setup = setup;
|
|
7
|
-
const os_1 = require("os");
|
|
8
|
-
const path_1 = require("path");
|
|
9
|
-
const fs_1 = require("fs");
|
|
10
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
-
const inquirer_1 = __importDefault(require("inquirer"));
|
|
12
|
-
const ora_1 = __importDefault(require("ora"));
|
|
13
|
-
// DEPRECATED: Hooks removed in hookless architecture migration
|
|
14
|
-
// import { hooksInstall } from './hooks.js';
|
|
15
|
-
const EKKOS_API_URL = 'https://mcp.ekkos.dev';
|
|
16
|
-
const CONFIG_DIR = (0, path_1.join)((0, os_1.homedir)(), '.ekkos');
|
|
17
|
-
const CONFIG_FILE = (0, path_1.join)(CONFIG_DIR, 'config.json');
|
|
18
|
-
/**
|
|
19
|
-
* Auto-detect which IDE the user is running in
|
|
20
|
-
*/
|
|
21
|
-
function detectIDE() {
|
|
22
|
-
// Check TERM_PROGRAM environment variable (set by many terminals/IDEs)
|
|
23
|
-
const termProgram = process.env.TERM_PROGRAM?.toLowerCase() || '';
|
|
24
|
-
const termEmulator = process.env.TERMINAL_EMULATOR?.toLowerCase() || '';
|
|
25
|
-
const colorterm = process.env.COLORTERM?.toLowerCase() || '';
|
|
26
|
-
// Cursor sets TERM_PROGRAM
|
|
27
|
-
if (termProgram.includes('cursor')) {
|
|
28
|
-
return 'cursor';
|
|
29
|
-
}
|
|
30
|
-
// VSCode sets TERM_PROGRAM to "vscode"
|
|
31
|
-
if (termProgram.includes('vscode') || termEmulator.includes('vscode')) {
|
|
32
|
-
return 'vscode';
|
|
33
|
-
}
|
|
34
|
-
// Windsurf/Codeium detection
|
|
35
|
-
if (termProgram.includes('windsurf') || termProgram.includes('codeium')) {
|
|
36
|
-
return 'windsurf';
|
|
37
|
-
}
|
|
38
|
-
// Check for Windsurf by config directory existence (if recently used)
|
|
39
|
-
const windsurfConfig = (0, path_1.join)((0, os_1.homedir)(), '.codeium', 'windsurf');
|
|
40
|
-
if ((0, fs_1.existsSync)(windsurfConfig)) {
|
|
41
|
-
// Also check if there's activity (mcp_config.json exists or was modified recently)
|
|
42
|
-
const mcpConfig = (0, path_1.join)(windsurfConfig, 'mcp_config.json');
|
|
43
|
-
if ((0, fs_1.existsSync)(mcpConfig)) {
|
|
44
|
-
// Windsurf is installed and configured - suggest it
|
|
45
|
-
// But don't return it as definitive without env var
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
// Check for Claude Code by looking at parent process or specific env vars
|
|
49
|
-
// Claude Code typically runs in a terminal context
|
|
50
|
-
if (process.env.CLAUDE_CODE || process.env.ANTHROPIC_API_KEY) {
|
|
51
|
-
return 'claude-code';
|
|
52
|
-
}
|
|
53
|
-
// Check ~/.claude directory for Claude Code usage
|
|
54
|
-
const claudeDir = (0, path_1.join)((0, os_1.homedir)(), '.claude');
|
|
55
|
-
if ((0, fs_1.existsSync)(claudeDir)) {
|
|
56
|
-
const hooksDir = (0, path_1.join)(claudeDir, 'hooks');
|
|
57
|
-
if ((0, fs_1.existsSync)(hooksDir)) {
|
|
58
|
-
// Claude Code has been set up before
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// Check for cursor config directory
|
|
62
|
-
const cursorDir = (0, path_1.join)((0, os_1.homedir)(), '.cursor');
|
|
63
|
-
if ((0, fs_1.existsSync)(cursorDir)) {
|
|
64
|
-
// Cursor is installed
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
async function setup(options) {
|
|
69
|
-
console.log('');
|
|
70
|
-
console.log(chalk_1.default.cyan.bold('🧠 ekkOS Setup'));
|
|
71
|
-
console.log(chalk_1.default.gray('─'.repeat(50)));
|
|
72
|
-
console.log('');
|
|
73
|
-
// Get API key
|
|
74
|
-
let apiKey = options.key || process.env.EKKOS_API_KEY;
|
|
75
|
-
if (!apiKey) {
|
|
76
|
-
console.log(chalk_1.default.yellow('Get your API key from: https://ekkos.dev/dashboard/keys'));
|
|
77
|
-
console.log('');
|
|
78
|
-
const answers = await inquirer_1.default.prompt([
|
|
79
|
-
{
|
|
80
|
-
type: 'password',
|
|
81
|
-
name: 'apiKey',
|
|
82
|
-
message: 'Enter your ekkOS API key:',
|
|
83
|
-
mask: '*',
|
|
84
|
-
validate: (input) => {
|
|
85
|
-
if (!input || input.length < 10) {
|
|
86
|
-
return 'Please enter a valid API key';
|
|
87
|
-
}
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
]);
|
|
92
|
-
apiKey = answers.apiKey;
|
|
93
|
-
}
|
|
94
|
-
// Verify API key
|
|
95
|
-
const spinner = (0, ora_1.default)('Verifying API key...').start();
|
|
96
|
-
try {
|
|
97
|
-
const response = await fetch(`${EKKOS_API_URL}/api/v1/patterns/query`, {
|
|
98
|
-
method: 'POST',
|
|
99
|
-
headers: {
|
|
100
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
101
|
-
'Content-Type': 'application/json'
|
|
102
|
-
},
|
|
103
|
-
body: JSON.stringify({ query: 'test', k: 1 })
|
|
104
|
-
});
|
|
105
|
-
if (!response.ok) {
|
|
106
|
-
spinner.fail('Invalid API key');
|
|
107
|
-
console.log(chalk_1.default.red('Please check your API key and try again.'));
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
spinner.succeed('API key verified');
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
|
-
spinner.fail('Could not connect to ekkOS');
|
|
114
|
-
console.log(chalk_1.default.red('Check your internet connection and try again.'));
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
// Save config
|
|
118
|
-
if (!(0, fs_1.existsSync)(CONFIG_DIR)) {
|
|
119
|
-
(0, fs_1.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
120
|
-
}
|
|
121
|
-
const config = {
|
|
122
|
-
apiKey: apiKey,
|
|
123
|
-
installedIDEs: [],
|
|
124
|
-
installedAt: new Date().toISOString()
|
|
125
|
-
};
|
|
126
|
-
// Determine which IDE to setup
|
|
127
|
-
let ideToSetup;
|
|
128
|
-
if (options.ide === 'all') {
|
|
129
|
-
// Try to auto-detect the IDE
|
|
130
|
-
const detectedIDE = detectIDE();
|
|
131
|
-
if (detectedIDE) {
|
|
132
|
-
console.log(chalk_1.default.green(`✓ Detected IDE: ${detectedIDE}`));
|
|
133
|
-
console.log('');
|
|
134
|
-
}
|
|
135
|
-
const ideAnswers = await inquirer_1.default.prompt([
|
|
136
|
-
{
|
|
137
|
-
type: 'list',
|
|
138
|
-
name: 'ide',
|
|
139
|
-
message: 'Which IDE are you setting up?',
|
|
140
|
-
choices: [
|
|
141
|
-
{ name: 'Claude Code (CLI)', value: 'claude-code' },
|
|
142
|
-
{ name: 'Claude Desktop (MCP)', value: 'claude-desktop' },
|
|
143
|
-
{ name: 'Cursor', value: 'cursor' },
|
|
144
|
-
{ name: 'Windsurf (Cascade)', value: 'windsurf' },
|
|
145
|
-
{ name: 'VSCode (Copilot)', value: 'vscode' }
|
|
146
|
-
],
|
|
147
|
-
default: detectedIDE || 'cursor'
|
|
148
|
-
}
|
|
149
|
-
]);
|
|
150
|
-
ideToSetup = ideAnswers.ide;
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
ideToSetup = options.ide;
|
|
154
|
-
}
|
|
155
|
-
console.log('');
|
|
156
|
-
// Setup the selected IDE
|
|
157
|
-
await setupIDE(ideToSetup, apiKey, config);
|
|
158
|
-
// Save final config
|
|
159
|
-
(0, fs_1.writeFileSync)(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
160
|
-
// Summary
|
|
161
|
-
console.log('');
|
|
162
|
-
console.log(chalk_1.default.green.bold('✅ Setup complete!'));
|
|
163
|
-
console.log('');
|
|
164
|
-
console.log(chalk_1.default.gray('─'.repeat(50)));
|
|
165
|
-
console.log('');
|
|
166
|
-
console.log('Configured IDEs:');
|
|
167
|
-
for (const ide of config.installedIDEs) {
|
|
168
|
-
console.log(chalk_1.default.cyan(` • ${ide}`));
|
|
169
|
-
}
|
|
170
|
-
console.log('');
|
|
171
|
-
console.log('Next steps:');
|
|
172
|
-
console.log(chalk_1.default.gray(' 1. Open your IDE'));
|
|
173
|
-
console.log(chalk_1.default.gray(' 2. Start coding - memory is now active'));
|
|
174
|
-
console.log(chalk_1.default.gray(' 3. Watch patterns form as you work'));
|
|
175
|
-
console.log('');
|
|
176
|
-
console.log(chalk_1.default.cyan('View your memory at: https://ekkos.dev/dashboard'));
|
|
177
|
-
console.log('');
|
|
178
|
-
}
|
|
179
|
-
async function setupIDE(ide, apiKey, config) {
|
|
180
|
-
const spinner = (0, ora_1.default)(`Setting up ${ide}...`).start();
|
|
181
|
-
try {
|
|
182
|
-
switch (ide) {
|
|
183
|
-
case 'claude-code':
|
|
184
|
-
await setupClaudeCode(apiKey);
|
|
185
|
-
break;
|
|
186
|
-
case 'claude-desktop':
|
|
187
|
-
await setupClaudeDesktop(apiKey);
|
|
188
|
-
break;
|
|
189
|
-
case 'cursor':
|
|
190
|
-
await setupCursor(apiKey);
|
|
191
|
-
break;
|
|
192
|
-
case 'windsurf':
|
|
193
|
-
await setupWindsurf(apiKey);
|
|
194
|
-
break;
|
|
195
|
-
case 'vscode':
|
|
196
|
-
await setupVSCode(apiKey);
|
|
197
|
-
break;
|
|
198
|
-
default:
|
|
199
|
-
spinner.warn(`Unknown IDE: ${ide}`);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
config.installedIDEs.push(ide);
|
|
203
|
-
spinner.succeed(`${ide} configured`);
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
spinner.fail(`Failed to setup ${ide}: ${error}`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
async function setupClaudeCode(apiKey) {
|
|
210
|
-
const claudeDir = (0, path_1.join)((0, os_1.homedir)(), '.claude');
|
|
211
|
-
// Create .claude directory (hooks directory is no longer created — hookless architecture)
|
|
212
|
-
(0, fs_1.mkdirSync)(claudeDir, { recursive: true });
|
|
213
|
-
// Save API key to ekkOS config
|
|
214
|
-
const ekkosConfigDir = (0, path_1.join)((0, os_1.homedir)(), '.ekkos');
|
|
215
|
-
(0, fs_1.mkdirSync)(ekkosConfigDir, { recursive: true });
|
|
216
|
-
const configPath = (0, path_1.join)(ekkosConfigDir, 'config.json');
|
|
217
|
-
let existingConfig = {};
|
|
218
|
-
if ((0, fs_1.existsSync)(configPath)) {
|
|
219
|
-
try {
|
|
220
|
-
existingConfig = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf-8'));
|
|
221
|
-
}
|
|
222
|
-
catch { }
|
|
223
|
-
}
|
|
224
|
-
existingConfig.apiKey = apiKey;
|
|
225
|
-
existingConfig.updatedAt = new Date().toISOString();
|
|
226
|
-
(0, fs_1.writeFileSync)(configPath, JSON.stringify(existingConfig, null, 2));
|
|
227
|
-
}
|
|
228
|
-
// DEPRECATED: Hooks removed in hookless architecture migration
|
|
229
|
-
// generateBasicHooks() removed — hook generation is no longer performed.
|
|
230
|
-
async function setupCursor(apiKey) {
|
|
231
|
-
// Cursor uses .cursorrules for system prompt
|
|
232
|
-
// and MCP servers for tools
|
|
233
|
-
const cursorDir = (0, path_1.join)((0, os_1.homedir)(), '.cursor');
|
|
234
|
-
if (!(0, fs_1.existsSync)(cursorDir)) {
|
|
235
|
-
(0, fs_1.mkdirSync)(cursorDir, { recursive: true });
|
|
236
|
-
}
|
|
237
|
-
// Create/update MCP settings
|
|
238
|
-
const mcpConfigPath = (0, path_1.join)(cursorDir, 'mcp.json');
|
|
239
|
-
let mcpConfig = {};
|
|
240
|
-
if ((0, fs_1.existsSync)(mcpConfigPath)) {
|
|
241
|
-
try {
|
|
242
|
-
mcpConfig = JSON.parse((0, fs_1.readFileSync)(mcpConfigPath, 'utf-8'));
|
|
243
|
-
}
|
|
244
|
-
catch { }
|
|
245
|
-
}
|
|
246
|
-
mcpConfig.mcpServers = mcpConfig.mcpServers || {};
|
|
247
|
-
mcpConfig.mcpServers['ekkos-memory'] = {
|
|
248
|
-
command: 'npx',
|
|
249
|
-
args: ['@ekkos/mcp-server'],
|
|
250
|
-
env: {
|
|
251
|
-
EKKOS_API_KEY: apiKey
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
(0, fs_1.writeFileSync)(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
|
|
255
|
-
// Create .cursorrules template in current directory
|
|
256
|
-
const cursorRules = generateCursorRules();
|
|
257
|
-
const cursorRulesPath = (0, path_1.join)(process.cwd(), '.cursorrules');
|
|
258
|
-
if (!(0, fs_1.existsSync)(cursorRulesPath)) {
|
|
259
|
-
(0, fs_1.writeFileSync)(cursorRulesPath, cursorRules);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
async function setupWindsurf(apiKey) {
|
|
263
|
-
// Windsurf (Codeium) supports MCP servers like Cursor
|
|
264
|
-
// Config location: ~/.codeium/windsurf/mcp_config.json
|
|
265
|
-
const codeiumDir = (0, path_1.join)((0, os_1.homedir)(), '.codeium', 'windsurf');
|
|
266
|
-
if (!(0, fs_1.existsSync)(codeiumDir)) {
|
|
267
|
-
(0, fs_1.mkdirSync)(codeiumDir, { recursive: true });
|
|
268
|
-
}
|
|
269
|
-
// Create/update MCP config for Windsurf
|
|
270
|
-
const mcpConfigPath = (0, path_1.join)(codeiumDir, 'mcp_config.json');
|
|
271
|
-
let mcpConfig = { mcpServers: {} };
|
|
272
|
-
if ((0, fs_1.existsSync)(mcpConfigPath)) {
|
|
273
|
-
try {
|
|
274
|
-
mcpConfig = JSON.parse((0, fs_1.readFileSync)(mcpConfigPath, 'utf-8'));
|
|
275
|
-
mcpConfig.mcpServers = mcpConfig.mcpServers || {};
|
|
276
|
-
}
|
|
277
|
-
catch { }
|
|
278
|
-
}
|
|
279
|
-
// Add ekkOS cloud MCP server (HTTP transport)
|
|
280
|
-
mcpConfig.mcpServers['ekkos-memory'] = {
|
|
281
|
-
serverUrl: 'https://mcp.ekkos.dev',
|
|
282
|
-
disabled: false,
|
|
283
|
-
alwaysAllow: [],
|
|
284
|
-
headers: {
|
|
285
|
-
'Authorization': `Bearer ${apiKey}`
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
(0, fs_1.writeFileSync)(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
|
|
289
|
-
// Also save to legacy location for backwards compat
|
|
290
|
-
const windsurfDir = (0, path_1.join)((0, os_1.homedir)(), '.windsurf');
|
|
291
|
-
if (!(0, fs_1.existsSync)(windsurfDir)) {
|
|
292
|
-
(0, fs_1.mkdirSync)(windsurfDir, { recursive: true });
|
|
293
|
-
}
|
|
294
|
-
(0, fs_1.writeFileSync)((0, path_1.join)(windsurfDir, 'ekkos.json'), JSON.stringify({ apiKey }, null, 2));
|
|
295
|
-
// Create project rules template
|
|
296
|
-
const cascadeRules = generateCascadeRules();
|
|
297
|
-
const cascadeRulesPath = (0, path_1.join)(process.cwd(), '.windsurfrules');
|
|
298
|
-
if (!(0, fs_1.existsSync)(cascadeRulesPath)) {
|
|
299
|
-
(0, fs_1.writeFileSync)(cascadeRulesPath, cascadeRules);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
async function setupVSCode(apiKey) {
|
|
303
|
-
// VSCode - create settings for Copilot/Continue
|
|
304
|
-
const vscodeDir = (0, path_1.join)((0, os_1.homedir)(), '.vscode');
|
|
305
|
-
if (!(0, fs_1.existsSync)(vscodeDir)) {
|
|
306
|
-
(0, fs_1.mkdirSync)(vscodeDir, { recursive: true });
|
|
307
|
-
}
|
|
308
|
-
// Save config
|
|
309
|
-
const configPath = (0, path_1.join)(vscodeDir, 'ekkos.json');
|
|
310
|
-
(0, fs_1.writeFileSync)(configPath, JSON.stringify({ apiKey }, null, 2));
|
|
311
|
-
// Note: Full VSCode integration requires extension
|
|
312
|
-
console.log(chalk_1.default.yellow(' Note: VSCode requires the ekkOS extension for full integration'));
|
|
313
|
-
}
|
|
314
|
-
async function setupClaudeDesktop(apiKey) {
|
|
315
|
-
// Claude Desktop - configure MCP server
|
|
316
|
-
const claudeDir = (0, path_1.join)((0, os_1.homedir)(), 'Library', 'Application Support', 'Claude');
|
|
317
|
-
if (!(0, fs_1.existsSync)(claudeDir)) {
|
|
318
|
-
(0, fs_1.mkdirSync)(claudeDir, { recursive: true });
|
|
319
|
-
}
|
|
320
|
-
// Create/update MCP config
|
|
321
|
-
const configPath = (0, path_1.join)(claudeDir, 'claude_desktop_config.json');
|
|
322
|
-
let config = {};
|
|
323
|
-
if ((0, fs_1.existsSync)(configPath)) {
|
|
324
|
-
try {
|
|
325
|
-
config = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf-8'));
|
|
326
|
-
}
|
|
327
|
-
catch { }
|
|
328
|
-
}
|
|
329
|
-
// Preserve existing preferences
|
|
330
|
-
config.mcpServers = config.mcpServers || {};
|
|
331
|
-
config.mcpServers['ekkos-memory'] = {
|
|
332
|
-
command: 'npx',
|
|
333
|
-
args: ['-y', '@ekkos/mcp-server@latest'],
|
|
334
|
-
env: {
|
|
335
|
-
EKKOS_API_KEY: apiKey
|
|
336
|
-
}
|
|
337
|
-
};
|
|
338
|
-
(0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2));
|
|
339
|
-
console.log(chalk_1.default.yellow(' Note: Restart Claude Desktop to load the MCP server'));
|
|
340
|
-
}
|
|
341
|
-
// DEPRECATED: Hooks removed in hookless architecture migration
|
|
342
|
-
// generatePromptSubmitHook() and generateStopHook() removed.
|
|
343
|
-
function generateCursorRules() {
|
|
344
|
-
return `# ekkOS Golden Loop Integration
|
|
345
|
-
|
|
346
|
-
## Memory System Active
|
|
347
|
-
|
|
348
|
-
Before responding, check ekkOS memory for relevant patterns using the MCP tools:
|
|
349
|
-
- \`ekkOS_Search\` - Find patterns that match the current problem
|
|
350
|
-
- \`ekkOS_Forge\` - Save new patterns when you solve something
|
|
351
|
-
|
|
352
|
-
## When to FORGE New Patterns
|
|
353
|
-
|
|
354
|
-
Use \`ekkOS_Forge\` when you:
|
|
355
|
-
- Fixed a non-obvious bug
|
|
356
|
-
- Found a better approach
|
|
357
|
-
- Discovered a pitfall
|
|
358
|
-
- User corrected you
|
|
359
|
-
|
|
360
|
-
## Pattern Application
|
|
361
|
-
|
|
362
|
-
Apply relevant patterns silently in the answer.
|
|
363
|
-
Do not emit control markers such as \`[ekkOS_APPLY]\` or \`[ekkOS_SKIP]\` unless the runtime explicitly asks for them.
|
|
364
|
-
`;
|
|
365
|
-
}
|
|
366
|
-
function generateCascadeRules() {
|
|
367
|
-
return `# ekkOS Memory Integration for Windsurf
|
|
368
|
-
|
|
369
|
-
## Active Learning
|
|
370
|
-
|
|
371
|
-
This project uses ekkOS for persistent AI memory.
|
|
372
|
-
|
|
373
|
-
When solving problems:
|
|
374
|
-
1. Check if similar patterns exist in memory
|
|
375
|
-
2. Apply proven solutions when they match
|
|
376
|
-
3. Create new patterns when you solve something novel
|
|
377
|
-
|
|
378
|
-
## Creating Patterns
|
|
379
|
-
|
|
380
|
-
When you solve a problem that others might encounter:
|
|
381
|
-
- Document the problem clearly
|
|
382
|
-
- Explain the solution
|
|
383
|
-
- Note any anti-patterns to avoid
|
|
384
|
-
|
|
385
|
-
The system learns from every interaction.
|
|
386
|
-
`;
|
|
387
|
-
}
|
|
388
|
-
// DEPRECATED: Hooks removed in hookless architecture migration
|
|
389
|
-
// generatePromptSubmitHookPS() and generateStopHookPS() removed.
|