ragcode-context-engine 0.1.0 ā 0.1.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/scripts/init-config.d.ts +15 -0
- package/dist/scripts/init-config.js +163 -0
- package/dist/scripts/setup-mcp.d.ts +53 -0
- package/dist/scripts/setup-mcp.js +345 -0
- package/dist/src/cli/index.js +2 -1
- package/dist/src/cli/update.d.ts +2 -1
- package/dist/src/cli/update.js +3 -22
- package/dist/src/config/package-info.d.ts +2 -0
- package/dist/src/config/package-info.js +16 -0
- package/dist/src/mcp/server.js +2 -1
- package/package.json +2 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Interactive configuration wizard for RagCode
|
|
4
|
+
*/
|
|
5
|
+
import { type RuntimeConfigFile } from '../src/config/runtime-config.js';
|
|
6
|
+
export interface InitConfigOptions {
|
|
7
|
+
targetDir?: string;
|
|
8
|
+
defaults?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface InitConfigResult {
|
|
11
|
+
targetDir: string;
|
|
12
|
+
configPath: string;
|
|
13
|
+
config: RuntimeConfigFile;
|
|
14
|
+
}
|
|
15
|
+
export declare function runInitConfig(options?: InitConfigOptions): Promise<InitConfigResult>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Interactive configuration wizard for RagCode
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import readline from 'node:readline';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { DEFAULT_RUNTIME_CONFIG, writeRuntimeConfigFile } from '../src/config/runtime-config.js';
|
|
10
|
+
const DEFAULT_INIT_CONFIG = {
|
|
11
|
+
graphStore: DEFAULT_RUNTIME_CONFIG.graphStore,
|
|
12
|
+
sqlitePath: DEFAULT_RUNTIME_CONFIG.sqlitePath,
|
|
13
|
+
semanticStore: DEFAULT_RUNTIME_CONFIG.semanticStore,
|
|
14
|
+
lancedbUri: DEFAULT_RUNTIME_CONFIG.lancedbUri,
|
|
15
|
+
embeddingProvider: DEFAULT_RUNTIME_CONFIG.embeddingProvider
|
|
16
|
+
};
|
|
17
|
+
function createInterface() {
|
|
18
|
+
return readline.createInterface({
|
|
19
|
+
input: process.stdin,
|
|
20
|
+
output: process.stdout
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function question(rl, prompt) {
|
|
24
|
+
return new Promise(resolve => {
|
|
25
|
+
rl.question(prompt, answer => resolve(answer.trim()));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async function runWizard() {
|
|
29
|
+
const rl = createInterface();
|
|
30
|
+
const config = {
|
|
31
|
+
graphStore: 'sqlite',
|
|
32
|
+
semanticStore: 'lancedb',
|
|
33
|
+
embeddingProvider: 'deterministic'
|
|
34
|
+
};
|
|
35
|
+
console.log('š§ RagCode Configuration Wizard\n');
|
|
36
|
+
// Graph store
|
|
37
|
+
console.log('š Graph Store (structural code graph)');
|
|
38
|
+
const graphStore = await question(rl, ' Choose: [1] SQLite (persistent), [2] Memory (testing only) [1]: ');
|
|
39
|
+
config.graphStore = graphStore === '2' ? 'memory' : 'sqlite';
|
|
40
|
+
if (config.graphStore === 'sqlite') {
|
|
41
|
+
const sqlitePath = await question(rl, ' SQLite database path [.ragcode/graph.sqlite]: ');
|
|
42
|
+
config.sqlitePath = sqlitePath || '.ragcode/graph.sqlite';
|
|
43
|
+
}
|
|
44
|
+
// Semantic store
|
|
45
|
+
console.log('\nš Semantic Store (vector embeddings)');
|
|
46
|
+
const semanticStore = await question(rl, ' Choose: [1] LanceDB (persistent), [2] Memory (testing only) [1]: ');
|
|
47
|
+
config.semanticStore = semanticStore === '2' ? 'memory' : 'lancedb';
|
|
48
|
+
if (config.semanticStore === 'lancedb') {
|
|
49
|
+
const lancedbUri = await question(rl, ' LanceDB directory [.ragcode/lancedb]: ');
|
|
50
|
+
config.lancedbUri = lancedbUri || '.ragcode/lancedb';
|
|
51
|
+
}
|
|
52
|
+
// Embedding provider
|
|
53
|
+
console.log('\nš¤ Embedding Provider');
|
|
54
|
+
const embeddingProvider = await question(rl, ' Choose: [1] Deterministic (offline), [2] OpenAI-compatible (requires API key) [1]: ');
|
|
55
|
+
config.embeddingProvider = embeddingProvider === '2' ? 'openai-compatible' : 'deterministic';
|
|
56
|
+
if (config.embeddingProvider === 'openai-compatible') {
|
|
57
|
+
console.log(' š” Set OPENAI_API_KEY environment variable or provide it now.');
|
|
58
|
+
const apiKey = await question(rl, ' OpenAI API Key (leave empty to use env var): ');
|
|
59
|
+
if (apiKey) {
|
|
60
|
+
config.openaiApiKey = apiKey;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
rl.close();
|
|
64
|
+
return config;
|
|
65
|
+
}
|
|
66
|
+
function toRuntimeConfigFile(config) {
|
|
67
|
+
const configObj = {
|
|
68
|
+
graphStore: config.graphStore,
|
|
69
|
+
semanticStore: config.semanticStore,
|
|
70
|
+
embeddingProvider: config.embeddingProvider
|
|
71
|
+
};
|
|
72
|
+
if (config.sqlitePath)
|
|
73
|
+
configObj.sqlitePath = config.sqlitePath;
|
|
74
|
+
if (config.lancedbUri)
|
|
75
|
+
configObj.lancedbUri = config.lancedbUri;
|
|
76
|
+
if (config.openaiApiKey)
|
|
77
|
+
configObj.embeddingApiKey = config.openaiApiKey;
|
|
78
|
+
return configObj;
|
|
79
|
+
}
|
|
80
|
+
function writeConfigFile(targetDir, config) {
|
|
81
|
+
const ragcodeDir = path.join(targetDir, '.ragcode');
|
|
82
|
+
if (!fs.existsSync(ragcodeDir)) {
|
|
83
|
+
fs.mkdirSync(ragcodeDir, { recursive: true });
|
|
84
|
+
console.log(`\nš Created directory: ${ragcodeDir}`);
|
|
85
|
+
}
|
|
86
|
+
const configPath = writeRuntimeConfigFile(targetDir, toRuntimeConfigFile(config));
|
|
87
|
+
console.log(`ā
Configuration saved to: ${configPath}\n`);
|
|
88
|
+
return configPath;
|
|
89
|
+
}
|
|
90
|
+
function writeEnvExample(targetDir, config) {
|
|
91
|
+
const envPath = path.join(targetDir, '.ragcode', '.env.example');
|
|
92
|
+
let envContent = '# RagCode Environment Variables\n\n';
|
|
93
|
+
envContent += `RAGCODE_GRAPH_STORE=${config.graphStore}\n`;
|
|
94
|
+
if (config.sqlitePath) {
|
|
95
|
+
envContent += `RAGCODE_SQLITE_PATH=${config.sqlitePath}\n`;
|
|
96
|
+
}
|
|
97
|
+
envContent += `\nRAGCODE_SEMANTIC_STORE=${config.semanticStore}\n`;
|
|
98
|
+
if (config.lancedbUri) {
|
|
99
|
+
envContent += `RAGCODE_LANCEDB_URI=${config.lancedbUri}\n`;
|
|
100
|
+
}
|
|
101
|
+
envContent += `\nRAGCODE_EMBEDDING_PROVIDER=${config.embeddingProvider}\n`;
|
|
102
|
+
if (config.embeddingProvider === 'openai-compatible') {
|
|
103
|
+
envContent += 'OPENAI_API_KEY=your-api-key-here\n';
|
|
104
|
+
}
|
|
105
|
+
fs.writeFileSync(envPath, envContent, 'utf-8');
|
|
106
|
+
console.log(`š Environment template saved to: ${envPath}`);
|
|
107
|
+
}
|
|
108
|
+
function printNextSteps(config) {
|
|
109
|
+
console.log('\nš Setup complete!\n');
|
|
110
|
+
console.log('Next steps:');
|
|
111
|
+
console.log(' 1. Index your codebase:');
|
|
112
|
+
console.log(' ragcode index .\n');
|
|
113
|
+
console.log(' 2. Search code:');
|
|
114
|
+
console.log(' ragcode search . "your query"\n');
|
|
115
|
+
if (config.embeddingProvider === 'openai-compatible' && !config.openaiApiKey) {
|
|
116
|
+
console.log(' ā ļø Remember to set OPENAI_API_KEY:');
|
|
117
|
+
console.log(' export OPENAI_API_KEY=your-key\n');
|
|
118
|
+
}
|
|
119
|
+
console.log(' 3. Start the MCP server:');
|
|
120
|
+
console.log(' ragcode setup-mcp\n');
|
|
121
|
+
console.log(' 4. Upgrade embedding provider if needed:');
|
|
122
|
+
console.log(' ragcode configure\n');
|
|
123
|
+
console.log(' 5. Launch the web dashboard for observation:');
|
|
124
|
+
console.log(' ragcode dashboard\n');
|
|
125
|
+
}
|
|
126
|
+
export async function runInitConfig(options = {}) {
|
|
127
|
+
const targetDir = path.resolve(options.targetDir || process.cwd());
|
|
128
|
+
console.log(`š Target directory: ${targetDir}\n`);
|
|
129
|
+
const config = options.defaults ? { ...DEFAULT_INIT_CONFIG } : await runWizard();
|
|
130
|
+
const configPath = writeConfigFile(targetDir, config);
|
|
131
|
+
writeEnvExample(targetDir, config);
|
|
132
|
+
printNextSteps(config);
|
|
133
|
+
return { targetDir, configPath, config: toRuntimeConfigFile(config) };
|
|
134
|
+
}
|
|
135
|
+
async function main() {
|
|
136
|
+
const args = process.argv.slice(2);
|
|
137
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
138
|
+
console.log(`
|
|
139
|
+
RagCode Configuration Wizard
|
|
140
|
+
|
|
141
|
+
Usage:
|
|
142
|
+
ragcode init [directory] [--defaults]
|
|
143
|
+
|
|
144
|
+
Examples:
|
|
145
|
+
ragcode init # Initialize in current directory
|
|
146
|
+
ragcode init /path/to/project
|
|
147
|
+
ragcode init --defaults # Write offline-first defaults without prompts
|
|
148
|
+
`);
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
const positional = args.filter((arg) => !arg.startsWith('-'));
|
|
152
|
+
await runInitConfig({
|
|
153
|
+
targetDir: positional[0],
|
|
154
|
+
defaults: args.includes('--defaults') || args.includes('--yes') || args.includes('-y')
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : undefined;
|
|
158
|
+
if (invokedPath && invokedPath === fileURLToPath(import.meta.url)) {
|
|
159
|
+
main().catch(err => {
|
|
160
|
+
console.error('ā Setup failed:', err);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Auto-configure RagCode as an MCP server for AI coding clients.
|
|
4
|
+
*
|
|
5
|
+
* Supported clients:
|
|
6
|
+
* - claude Claude Desktop JSON ~/.../claude_desktop_config.json (mcpServers.ragcode)
|
|
7
|
+
* - claude-code Claude Code (repo) JSON <cwd>/.mcp.json (mcpServers.ragcode)
|
|
8
|
+
* - codex Codex CLI TOML ~/.codex/config.toml (mcp_servers.ragcode)
|
|
9
|
+
* - generic print only JSON (paste into any MCP client)
|
|
10
|
+
*
|
|
11
|
+
* Merge strategy: existing config is parsed, the `ragcode` entry is upserted, and the
|
|
12
|
+
* file is rewritten. Other servers and unrelated keys are preserved. The previous file
|
|
13
|
+
* is backed up alongside the original before any overwrite.
|
|
14
|
+
*/
|
|
15
|
+
export type McpClient = 'claude' | 'claude-code' | 'codex' | 'generic';
|
|
16
|
+
interface MCPServerConfig {
|
|
17
|
+
command: string;
|
|
18
|
+
args: string[];
|
|
19
|
+
cwd?: string;
|
|
20
|
+
env?: Record<string, string>;
|
|
21
|
+
}
|
|
22
|
+
interface MCPConfig {
|
|
23
|
+
mcpServers: Record<string, MCPServerConfig>;
|
|
24
|
+
}
|
|
25
|
+
export interface SetupMcpOptions {
|
|
26
|
+
configPath?: string;
|
|
27
|
+
print?: boolean;
|
|
28
|
+
includeSecrets?: boolean;
|
|
29
|
+
client?: McpClient;
|
|
30
|
+
/** Skip the interactive overwrite prompt and replace any existing ragcode entry. */
|
|
31
|
+
force?: boolean;
|
|
32
|
+
cwd?: string;
|
|
33
|
+
env?: NodeJS.ProcessEnv;
|
|
34
|
+
}
|
|
35
|
+
export declare function getClaudeMCPConfigPath(): string;
|
|
36
|
+
/** Claude Code reads project-scoped MCP servers from `<repoRoot>/.mcp.json`. */
|
|
37
|
+
export declare function getClaudeCodeMCPConfigPath(cwd: string): string;
|
|
38
|
+
/** Codex CLI reads MCP servers from `~/.codex/config.toml` (CODEX_HOME overrides ~/.codex). */
|
|
39
|
+
export declare function getCodexConfigPath(env?: NodeJS.ProcessEnv): string;
|
|
40
|
+
export declare function buildMcpServerConfig(options?: SetupMcpOptions): MCPServerConfig;
|
|
41
|
+
/**
|
|
42
|
+
* Upsert the ragcode server into a JSON MCP config object (Claude Desktop / Claude Code).
|
|
43
|
+
* Unknown top-level keys and other servers are preserved. Returns a new object.
|
|
44
|
+
*/
|
|
45
|
+
export declare function mergeMcpServersJson(existing: unknown, server: MCPServerConfig): MCPConfig;
|
|
46
|
+
/**
|
|
47
|
+
* Upsert the ragcode server into a Codex config.toml string.
|
|
48
|
+
* Parses existing TOML, sets `mcp_servers.ragcode`, and re-stringifies. Other tables and
|
|
49
|
+
* keys are preserved; TOML comments are not (smol-toml limitation ā callers back up first).
|
|
50
|
+
*/
|
|
51
|
+
export declare function mergeCodexToml(existingToml: string, server: MCPServerConfig): string;
|
|
52
|
+
export declare function setupMCP(options?: SetupMcpOptions): void;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Auto-configure RagCode as an MCP server for AI coding clients.
|
|
4
|
+
*
|
|
5
|
+
* Supported clients:
|
|
6
|
+
* - claude Claude Desktop JSON ~/.../claude_desktop_config.json (mcpServers.ragcode)
|
|
7
|
+
* - claude-code Claude Code (repo) JSON <cwd>/.mcp.json (mcpServers.ragcode)
|
|
8
|
+
* - codex Codex CLI TOML ~/.codex/config.toml (mcp_servers.ragcode)
|
|
9
|
+
* - generic print only JSON (paste into any MCP client)
|
|
10
|
+
*
|
|
11
|
+
* Merge strategy: existing config is parsed, the `ragcode` entry is upserted, and the
|
|
12
|
+
* file is rewritten. Other servers and unrelated keys are preserved. The previous file
|
|
13
|
+
* is backed up alongside the original before any overwrite.
|
|
14
|
+
*/
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import os from 'node:os';
|
|
18
|
+
import { execFileSync } from 'node:child_process';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { parse as parseToml, stringify as stringifyToml } from 'smol-toml';
|
|
21
|
+
import { loadRuntimeConfig, runtimeConfigToEnv } from '../src/config/runtime-config.js';
|
|
22
|
+
const SERVER_KEY = 'ragcode';
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Config-path resolution (one per client)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
export function getClaudeMCPConfigPath() {
|
|
27
|
+
const platform = os.platform();
|
|
28
|
+
if (platform === 'win32') {
|
|
29
|
+
return path.join(os.homedir(), 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
|
|
30
|
+
}
|
|
31
|
+
else if (platform === 'darwin') {
|
|
32
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Linux
|
|
36
|
+
return path.join(os.homedir(), '.config', 'claude', 'claude_desktop_config.json');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Claude Code reads project-scoped MCP servers from `<repoRoot>/.mcp.json`. */
|
|
40
|
+
export function getClaudeCodeMCPConfigPath(cwd) {
|
|
41
|
+
return path.join(cwd, '.mcp.json');
|
|
42
|
+
}
|
|
43
|
+
/** Codex CLI reads MCP servers from `~/.codex/config.toml` (CODEX_HOME overrides ~/.codex). */
|
|
44
|
+
export function getCodexConfigPath(env = process.env) {
|
|
45
|
+
const home = env.CODEX_HOME && env.CODEX_HOME.trim().length > 0
|
|
46
|
+
? env.CODEX_HOME
|
|
47
|
+
: path.join(os.homedir(), '.codex');
|
|
48
|
+
return path.join(home, 'config.toml');
|
|
49
|
+
}
|
|
50
|
+
function resolveConfigPath(client, options) {
|
|
51
|
+
if (options.configPath)
|
|
52
|
+
return options.configPath;
|
|
53
|
+
const cwd = options.cwd ?? process.cwd();
|
|
54
|
+
switch (client) {
|
|
55
|
+
case 'codex':
|
|
56
|
+
return getCodexConfigPath(options.env ?? process.env);
|
|
57
|
+
case 'claude-code':
|
|
58
|
+
return getClaudeCodeMCPConfigPath(cwd);
|
|
59
|
+
case 'claude':
|
|
60
|
+
default:
|
|
61
|
+
return getClaudeMCPConfigPath();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Server config (shared core ā unchanged contract, tests depend on this)
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
function getRagCodeCommand() {
|
|
68
|
+
// Check if ragcode is globally installed
|
|
69
|
+
try {
|
|
70
|
+
execFileSync('ragcode', ['--version'], { stdio: 'ignore' });
|
|
71
|
+
return 'ragcode';
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Fallback to npx
|
|
75
|
+
return 'npx';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export function buildMcpServerConfig(options = {}) {
|
|
79
|
+
const command = getRagCodeCommand();
|
|
80
|
+
const args = command === 'npx' ? ['ragcode-context-engine', 'mcp'] : ['mcp'];
|
|
81
|
+
const runtime = loadRuntimeConfig({ cwd: options.cwd ?? process.cwd(), env: options.env ?? process.env });
|
|
82
|
+
return {
|
|
83
|
+
command,
|
|
84
|
+
args,
|
|
85
|
+
cwd: runtime.repoRoot,
|
|
86
|
+
env: runtimeConfigToEnv(runtime, { includeSecrets: options.includeSecrets })
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Pure merge functions (no IO ā unit-testable)
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
/**
|
|
93
|
+
* Upsert the ragcode server into a JSON MCP config object (Claude Desktop / Claude Code).
|
|
94
|
+
* Unknown top-level keys and other servers are preserved. Returns a new object.
|
|
95
|
+
*/
|
|
96
|
+
export function mergeMcpServersJson(existing, server) {
|
|
97
|
+
const base = existing && typeof existing === 'object' ? { ...existing } : {};
|
|
98
|
+
const servers = base.mcpServers && typeof base.mcpServers === 'object'
|
|
99
|
+
? { ...base.mcpServers }
|
|
100
|
+
: {};
|
|
101
|
+
servers[SERVER_KEY] = server;
|
|
102
|
+
return { ...base, mcpServers: servers };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Upsert the ragcode server into a Codex config.toml string.
|
|
106
|
+
* Parses existing TOML, sets `mcp_servers.ragcode`, and re-stringifies. Other tables and
|
|
107
|
+
* keys are preserved; TOML comments are not (smol-toml limitation ā callers back up first).
|
|
108
|
+
*/
|
|
109
|
+
export function mergeCodexToml(existingToml, server) {
|
|
110
|
+
let root = {};
|
|
111
|
+
const trimmed = existingToml.trim();
|
|
112
|
+
if (trimmed.length > 0) {
|
|
113
|
+
const parsed = parseToml(existingToml);
|
|
114
|
+
if (parsed && typeof parsed === 'object') {
|
|
115
|
+
root = parsed;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const mcpServers = root.mcp_servers && typeof root.mcp_servers === 'object'
|
|
119
|
+
? { ...root.mcp_servers }
|
|
120
|
+
: {};
|
|
121
|
+
// Codex stdio server schema: command, args, env. cwd is honored by recent Codex builds and
|
|
122
|
+
// ignored by older ones; the env values already carry absolute paths so behavior is correct
|
|
123
|
+
// either way. Drop undefined keys so the emitted TOML stays clean.
|
|
124
|
+
const entry = {
|
|
125
|
+
command: server.command,
|
|
126
|
+
args: server.args
|
|
127
|
+
};
|
|
128
|
+
if (server.cwd)
|
|
129
|
+
entry.cwd = server.cwd;
|
|
130
|
+
if (server.env && Object.keys(server.env).length > 0)
|
|
131
|
+
entry.env = server.env;
|
|
132
|
+
mcpServers[SERVER_KEY] = entry;
|
|
133
|
+
root.mcp_servers = mcpServers;
|
|
134
|
+
return stringifyToml(root);
|
|
135
|
+
}
|
|
136
|
+
function isJsonClient(client) {
|
|
137
|
+
return client === 'claude' || client === 'claude-code';
|
|
138
|
+
}
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// IO helpers
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
function ensureDir(filePath) {
|
|
143
|
+
const dir = path.dirname(filePath);
|
|
144
|
+
if (!fs.existsSync(dir)) {
|
|
145
|
+
console.log(`š Creating config directory: ${dir}`);
|
|
146
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function backupExisting(filePath) {
|
|
150
|
+
if (!fs.existsSync(filePath))
|
|
151
|
+
return;
|
|
152
|
+
const backup = `${filePath}.ragcode-backup`;
|
|
153
|
+
fs.copyFileSync(filePath, backup);
|
|
154
|
+
console.log(`šļø Backed up existing config to: ${backup}`);
|
|
155
|
+
}
|
|
156
|
+
function readFileSafe(filePath) {
|
|
157
|
+
try {
|
|
158
|
+
if (fs.existsSync(filePath))
|
|
159
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
console.warn(`ā ļø Failed to read existing config: ${err}`);
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
/** Detect whether a ragcode entry already exists in the on-disk config. */
|
|
167
|
+
function hasExistingEntry(client, raw) {
|
|
168
|
+
if (!raw)
|
|
169
|
+
return false;
|
|
170
|
+
try {
|
|
171
|
+
if (isJsonClient(client)) {
|
|
172
|
+
const parsed = JSON.parse(raw);
|
|
173
|
+
return Boolean(parsed.mcpServers && SERVER_KEY in parsed.mcpServers);
|
|
174
|
+
}
|
|
175
|
+
const parsed = parseToml(raw);
|
|
176
|
+
return Boolean(parsed.mcp_servers && SERVER_KEY in parsed.mcp_servers);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Public entry point
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
export function setupMCP(options = {}) {
|
|
186
|
+
const client = options.client ?? 'claude';
|
|
187
|
+
validateClient(client);
|
|
188
|
+
if (options.print || client === 'generic') {
|
|
189
|
+
printConfig(options);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const configPath = resolveConfigPath(client, options);
|
|
193
|
+
const server = buildMcpServerConfig(options);
|
|
194
|
+
const raw = readFileSafe(configPath);
|
|
195
|
+
console.log(`š ${clientLabel(client)} config path: ${configPath}`);
|
|
196
|
+
if (hasExistingEntry(client, raw) && !options.force) {
|
|
197
|
+
if (!process.stdin.isTTY) {
|
|
198
|
+
console.log('ā ļø RagCode is already configured here. Re-run with --force to overwrite, or remove the existing entry first.');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
void import('node:readline').then((readline) => {
|
|
202
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
203
|
+
rl.question('\nā ļø RagCode is already configured. Overwrite existing entry? (y/N): ', (answer) => {
|
|
204
|
+
rl.close();
|
|
205
|
+
if (answer.trim().toLowerCase() !== 'y') {
|
|
206
|
+
console.log('ā Setup cancelled.');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
writeClientConfig(client, configPath, raw, server);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
writeClientConfig(client, configPath, raw, server);
|
|
215
|
+
}
|
|
216
|
+
function writeClientConfig(client, configPath, raw, server) {
|
|
217
|
+
ensureDir(configPath);
|
|
218
|
+
backupExisting(configPath);
|
|
219
|
+
let contents;
|
|
220
|
+
if (isJsonClient(client)) {
|
|
221
|
+
let existing = {};
|
|
222
|
+
if (raw) {
|
|
223
|
+
try {
|
|
224
|
+
existing = JSON.parse(raw);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
console.warn(`ā ļø Existing config is not valid JSON; starting fresh. (${err})`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
contents = `${JSON.stringify(mergeMcpServersJson(existing, server), null, 2)}\n`;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
// codex
|
|
234
|
+
contents = mergeCodexToml(raw ?? '', server);
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
fs.writeFileSync(configPath, contents, 'utf-8');
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
console.error(`ā Failed to write config: ${err}`);
|
|
241
|
+
process.exitCode = 1;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
console.log(`\nā
RagCode MCP server configured for ${clientLabel(client)}.`);
|
|
245
|
+
console.log('\nš Server entry:');
|
|
246
|
+
console.log(JSON.stringify(server, null, 2));
|
|
247
|
+
if (server.env?.RAGCODE_EMBEDDING_API_KEY === '<redacted>') {
|
|
248
|
+
console.log('\nā ļø API key was redacted. Re-run with --include-secrets to embed it, or set the key in your environment.');
|
|
249
|
+
}
|
|
250
|
+
console.log(`\nš ${restartHint(client)}`);
|
|
251
|
+
}
|
|
252
|
+
function printConfig(options = {}) {
|
|
253
|
+
const client = options.client ?? 'generic';
|
|
254
|
+
const server = buildMcpServerConfig(options);
|
|
255
|
+
if (client === 'codex') {
|
|
256
|
+
console.log('Add this to your Codex config (~/.codex/config.toml):\n');
|
|
257
|
+
console.log(mergeCodexToml('', server));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
console.log('Add this to your MCP client config:\n');
|
|
261
|
+
console.log(JSON.stringify({ mcpServers: { [SERVER_KEY]: server } }, null, 2));
|
|
262
|
+
}
|
|
263
|
+
function clientLabel(client) {
|
|
264
|
+
switch (client) {
|
|
265
|
+
case 'claude':
|
|
266
|
+
return 'Claude Desktop';
|
|
267
|
+
case 'claude-code':
|
|
268
|
+
return 'Claude Code';
|
|
269
|
+
case 'codex':
|
|
270
|
+
return 'Codex';
|
|
271
|
+
case 'generic':
|
|
272
|
+
return 'generic MCP client';
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function restartHint(client) {
|
|
276
|
+
switch (client) {
|
|
277
|
+
case 'claude':
|
|
278
|
+
return 'Restart Claude Desktop to activate the MCP server.';
|
|
279
|
+
case 'claude-code':
|
|
280
|
+
return 'Reopen the project in Claude Code (it loads .mcp.json on startup) to activate the server.';
|
|
281
|
+
case 'codex':
|
|
282
|
+
return 'Restart your Codex session to activate the MCP server.';
|
|
283
|
+
default:
|
|
284
|
+
return 'Restart your client to activate the MCP server.';
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// CLI
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
function printHelp() {
|
|
291
|
+
console.log(`
|
|
292
|
+
RagCode MCP Setup
|
|
293
|
+
|
|
294
|
+
Usage:
|
|
295
|
+
ragcode setup-mcp [options]
|
|
296
|
+
|
|
297
|
+
Options:
|
|
298
|
+
--config <path> Custom config path (overrides the client default)
|
|
299
|
+
--print Print config without writing
|
|
300
|
+
--include-secrets Include real secrets instead of redacted placeholders
|
|
301
|
+
--client <client> Target client: claude, claude-code, codex, or generic (default: claude)
|
|
302
|
+
--force Overwrite an existing ragcode entry without prompting
|
|
303
|
+
--help, -h Show this help
|
|
304
|
+
|
|
305
|
+
Examples:
|
|
306
|
+
ragcode setup-mcp # Claude Desktop (default)
|
|
307
|
+
ragcode setup-mcp --client claude-code # project .mcp.json for Claude Code
|
|
308
|
+
ragcode setup-mcp --client codex # ~/.codex/config.toml
|
|
309
|
+
ragcode setup-mcp --client codex --print # print TOML, write nothing
|
|
310
|
+
ragcode setup-mcp --config ~/custom.json # custom path
|
|
311
|
+
`);
|
|
312
|
+
}
|
|
313
|
+
function parseOptions(args) {
|
|
314
|
+
const configIndex = args.indexOf('--config');
|
|
315
|
+
const clientIndex = args.indexOf('--client');
|
|
316
|
+
const client = clientIndex >= 0 ? args[clientIndex + 1] : undefined;
|
|
317
|
+
validateClient(client);
|
|
318
|
+
return {
|
|
319
|
+
print: args.includes('--print'),
|
|
320
|
+
includeSecrets: args.includes('--include-secrets'),
|
|
321
|
+
force: args.includes('--force'),
|
|
322
|
+
configPath: configIndex >= 0 ? args[configIndex + 1] : undefined,
|
|
323
|
+
client: client
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function validateClient(client) {
|
|
327
|
+
if (client && !['claude', 'claude-code', 'codex', 'generic'].includes(client)) {
|
|
328
|
+
throw new Error(`Unsupported MCP client: ${client} (expected claude, claude-code, codex, or generic)`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : undefined;
|
|
332
|
+
if (invokedPath && invokedPath === fileURLToPath(import.meta.url)) {
|
|
333
|
+
const args = process.argv.slice(2);
|
|
334
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
335
|
+
printHelp();
|
|
336
|
+
process.exit(0);
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
setupMCP(parseOptions(args));
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
console.error(`ā MCP setup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
}
|
package/dist/src/cli/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { loadDotEnv } from "../config/dotenv.js";
|
|
5
|
+
import { getPackageVersion } from "../config/package-info.js";
|
|
5
6
|
import { createRuntimeComponentsForRepo } from "../config/runtime-config.js";
|
|
6
7
|
import { RagCodeEngine } from "../core/engine.js";
|
|
7
8
|
import { runConfigureCommand } from "./configure.js";
|
|
@@ -17,7 +18,7 @@ import { setupMCP } from "../../scripts/setup-mcp.js";
|
|
|
17
18
|
import { runUpdate } from "./update.js";
|
|
18
19
|
loadDotEnv();
|
|
19
20
|
const program = new Command();
|
|
20
|
-
program.name("ragcode").description("Local code intelligence context engine").version(
|
|
21
|
+
program.name("ragcode").description("Local code intelligence context engine").version(getPackageVersion());
|
|
21
22
|
program
|
|
22
23
|
.command("index")
|
|
23
24
|
.argument("<repoRoot>")
|
package/dist/src/cli/update.d.ts
CHANGED
package/dist/src/cli/update.js
CHANGED
|
@@ -1,27 +1,8 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
|
-
import {
|
|
3
|
+
import { getPackageVersion, PACKAGE_NAME } from "../config/package-info.js";
|
|
4
4
|
const execFileAsync = promisify(execFile);
|
|
5
|
-
|
|
6
|
-
// we shell out to the package manager that owns the global install. The published package name is
|
|
7
|
-
// `ragcode-context-engine` (see package.json), which is what npm/pnpm/yarn install globally.
|
|
8
|
-
export const PACKAGE_NAME = "ragcode-context-engine";
|
|
9
|
-
function currentVersion() {
|
|
10
|
-
// The package.json depth differs between source (src/cli/update.ts -> ../../package.json) and the
|
|
11
|
-
// built layout (dist/src/cli/update.js -> ../../../package.json), so try both rather than guessing.
|
|
12
|
-
const require = createRequire(import.meta.url);
|
|
13
|
-
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
14
|
-
try {
|
|
15
|
-
const pkg = require(candidate);
|
|
16
|
-
if (pkg.name === PACKAGE_NAME && pkg.version)
|
|
17
|
-
return pkg.version;
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
// try the next candidate
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return "unknown";
|
|
24
|
-
}
|
|
5
|
+
export { PACKAGE_NAME };
|
|
25
6
|
// Auto-detect the package manager from the npm_config_user_agent the parent PM exposes, falling
|
|
26
7
|
// back to npm. This keeps a pnpm-global install from being "updated" by npm into a broken state.
|
|
27
8
|
function detectPackageManager(env = process.env) {
|
|
@@ -55,7 +36,7 @@ function installArgv(pm, spec) {
|
|
|
55
36
|
}
|
|
56
37
|
}
|
|
57
38
|
export async function runUpdate(options = {}) {
|
|
58
|
-
const current =
|
|
39
|
+
const current = getPackageVersion();
|
|
59
40
|
const pm = options.packageManager ?? detectPackageManager();
|
|
60
41
|
const target = options.version ?? "latest";
|
|
61
42
|
const latest = target === "latest" ? await fetchLatestVersion() : target;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
export const PACKAGE_NAME = "ragcode-context-engine";
|
|
3
|
+
export function getPackageVersion() {
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
6
|
+
try {
|
|
7
|
+
const pkg = require(candidate);
|
|
8
|
+
if (pkg.name === PACKAGE_NAME && pkg.version)
|
|
9
|
+
return pkg.version;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
// Source and built layouts have different depths; try the next candidate.
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return "unknown";
|
|
16
|
+
}
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { getPackageVersion } from "../config/package-info.js";
|
|
3
4
|
import { createRuntimeComponentsForRepo } from "../config/runtime-config.js";
|
|
4
5
|
import { RagCodeEngine } from "../core/engine.js";
|
|
5
6
|
import { callTool, listRuntimeToolDefinitions } from "./tools.js";
|
|
6
7
|
export function createMcpServer(engine, options = {}) {
|
|
7
8
|
const server = new McpServer({
|
|
8
9
|
name: options.name ?? "ragcode-context-engine",
|
|
9
|
-
version: options.version ??
|
|
10
|
+
version: options.version ?? getPackageVersion()
|
|
10
11
|
});
|
|
11
12
|
for (const tool of listRuntimeToolDefinitions()) {
|
|
12
13
|
server.registerTool(tool.name, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ragcode-context-engine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Local code intelligence foundation: structural code graph, LanceDB semantic layer, retrieval, context packing, and MCP tools.",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"dist/src",
|
|
34
|
+
"dist/scripts",
|
|
34
35
|
"README.md",
|
|
35
36
|
"LICENSE"
|
|
36
37
|
],
|