Add interactive CLI installer for Claude Code integration
- New `codegraph install` command and auto-run when invoked with no args - Beautiful ASCII banner using figlet - Interactive prompts for global (~/.claude) or local (./.claude) installation - Writes MCP server config to claude.json - Writes auto-allow permissions to settings.json - For local installs: auto-initializes project, indexes, and installs git hooks Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
0a1b78854a
commit
9baf59e5f0
Generated
+28
-4
@@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "codegraph",
|
"name": "@colbymchenry/codegraph",
|
||||||
"version": "0.1.5",
|
"version": "0.1.9",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "codegraph",
|
"name": "@colbymchenry/codegraph",
|
||||||
"version": "0.1.5",
|
"version": "0.1.9",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xenova/transformers": "^2.17.0",
|
"@xenova/transformers": "^2.17.0",
|
||||||
"better-sqlite3": "^11.0.0",
|
"better-sqlite3": "^11.0.0",
|
||||||
"commander": "^14.0.2",
|
"commander": "^14.0.2",
|
||||||
|
"figlet": "^1.8.0",
|
||||||
"sqlite-vss": "^0.1.2",
|
"sqlite-vss": "^0.1.2",
|
||||||
"tree-sitter": "^0.22.4",
|
"tree-sitter": "^0.22.4",
|
||||||
"tree-sitter-c": "^0.23.4",
|
"tree-sitter-c": "^0.23.4",
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.0",
|
"@types/better-sqlite3": "^7.6.0",
|
||||||
|
"@types/figlet": "^1.5.8",
|
||||||
"@types/node": "^20.19.30",
|
"@types/node": "^20.19.30",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
@@ -879,6 +881,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/figlet": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/figlet/-/figlet-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-KwrT7p/8Eo3Op/HBSIwGXOsTZKYiM9NpWRBJ5sVjWP/SmlS+oxxRvJht/FNAtliJvja44N3ul1yATgohnVBV0Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/long": {
|
"node_modules/@types/long": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
|
||||||
@@ -1464,6 +1473,21 @@
|
|||||||
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
|
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/figlet": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-uN6QE+TrzTAHC1IWTyrc4FfGo2KH/82J8Jl1tyKB7+z5DBit/m3D++Iu5lg91qJMnQQ3vpJrj5gxcK/pk4R9tQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^14.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"figlet": "bin/index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/file-uri-to-path": {
|
"node_modules/file-uri-to-path": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@colbymchenry/codegraph",
|
"name": "@colbymchenry/codegraph",
|
||||||
"version": "0.1.9",
|
"version": "0.2.0",
|
||||||
"description": "A local-first code intelligence system that builds a semantic knowledge graph from any codebase",
|
"description": "A local-first code intelligence system that builds a semantic knowledge graph from any codebase",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xenova/transformers": "^2.17.0",
|
"@xenova/transformers": "^2.17.0",
|
||||||
|
"figlet": "^1.8.0",
|
||||||
"better-sqlite3": "^11.0.0",
|
"better-sqlite3": "^11.0.0",
|
||||||
"commander": "^14.0.2",
|
"commander": "^14.0.2",
|
||||||
"sqlite-vss": "^0.1.2",
|
"sqlite-vss": "^0.1.2",
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.0",
|
"@types/better-sqlite3": "^7.6.0",
|
||||||
|
"@types/figlet": "^1.5.8",
|
||||||
"@types/node": "^20.19.30",
|
"@types/node": "^20.19.30",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
* Command-line interface for CodeGraph code intelligence.
|
* Command-line interface for CodeGraph code intelligence.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
|
* codegraph Run interactive installer (when no args)
|
||||||
|
* codegraph install Run interactive installer
|
||||||
* codegraph init [path] Initialize CodeGraph in a project
|
* codegraph init [path] Initialize CodeGraph in a project
|
||||||
* codegraph index [path] Index all files in the project
|
* codegraph index [path] Index all files in the project
|
||||||
* codegraph sync [path] Sync changes since last index
|
* codegraph sync [path] Sync changes since last index
|
||||||
@@ -20,6 +22,20 @@ import * as path from 'path';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import CodeGraph from '../index';
|
import CodeGraph from '../index';
|
||||||
import type { IndexProgress } from '../index';
|
import type { IndexProgress } from '../index';
|
||||||
|
import { runInstaller } from '../installer';
|
||||||
|
|
||||||
|
// Check if running with no arguments - run installer
|
||||||
|
if (process.argv.length === 2) {
|
||||||
|
runInstaller().catch((err) => {
|
||||||
|
console.error('Installation failed:', err.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Normal CLI flow
|
||||||
|
main();
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
@@ -721,5 +737,17 @@ program
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* codegraph install
|
||||||
|
*/
|
||||||
|
program
|
||||||
|
.command('install')
|
||||||
|
.description('Run interactive installer for Claude Code integration')
|
||||||
|
.action(async () => {
|
||||||
|
await runInstaller();
|
||||||
|
});
|
||||||
|
|
||||||
// Parse and run
|
// Parse and run
|
||||||
program.parse();
|
program.parse();
|
||||||
|
|
||||||
|
} // end main()
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/**
|
||||||
|
* Banner and branding for the CodeGraph installer
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as figlet from 'figlet';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// ANSI Color Helpers (same pattern as CLI to avoid chalk ESM issues)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
reset: '\x1b[0m',
|
||||||
|
bold: '\x1b[1m',
|
||||||
|
dim: '\x1b[2m',
|
||||||
|
red: '\x1b[31m',
|
||||||
|
green: '\x1b[32m',
|
||||||
|
yellow: '\x1b[33m',
|
||||||
|
blue: '\x1b[34m',
|
||||||
|
magenta: '\x1b[35m',
|
||||||
|
cyan: '\x1b[36m',
|
||||||
|
white: '\x1b[37m',
|
||||||
|
gray: '\x1b[90m',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const chalk = {
|
||||||
|
bold: (s: string) => `${colors.bold}${s}${colors.reset}`,
|
||||||
|
dim: (s: string) => `${colors.dim}${s}${colors.reset}`,
|
||||||
|
red: (s: string) => `${colors.red}${s}${colors.reset}`,
|
||||||
|
green: (s: string) => `${colors.green}${s}${colors.reset}`,
|
||||||
|
yellow: (s: string) => `${colors.yellow}${s}${colors.reset}`,
|
||||||
|
blue: (s: string) => `${colors.blue}${s}${colors.reset}`,
|
||||||
|
magenta: (s: string) => `${colors.magenta}${s}${colors.reset}`,
|
||||||
|
cyan: (s: string) => `${colors.cyan}${s}${colors.reset}`,
|
||||||
|
white: (s: string) => `${colors.white}${s}${colors.reset}`,
|
||||||
|
gray: (s: string) => `${colors.gray}${s}${colors.reset}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the package version
|
||||||
|
*/
|
||||||
|
function getVersion(): string {
|
||||||
|
try {
|
||||||
|
const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
||||||
|
return packageJson.version;
|
||||||
|
} catch {
|
||||||
|
return '0.0.0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the CodeGraph banner
|
||||||
|
*/
|
||||||
|
export function showBanner(): void {
|
||||||
|
// Generate ASCII art using figlet
|
||||||
|
let banner: string;
|
||||||
|
try {
|
||||||
|
banner = figlet.textSync('CODEGRAPH', {
|
||||||
|
font: 'ANSI Shadow',
|
||||||
|
horizontalLayout: 'default',
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// Fallback if figlet fails
|
||||||
|
banner = `
|
||||||
|
██████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗
|
||||||
|
██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██║ ██║
|
||||||
|
██║ ██║ ██║██║ ██║█████╗ ██║ ███╗██████╔╝███████║██████╔╝███████║
|
||||||
|
██║ ██║ ██║██║ ██║██╔══╝ ██║ ██║██╔══██╗██╔══██║██╔═══╝ ██╔══██║
|
||||||
|
╚██████╗╚██████╔╝██████╔╝███████╗╚██████╔╝██║ ██║██║ ██║██║ ██║ ██║
|
||||||
|
╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
console.log(chalk.cyan(banner));
|
||||||
|
console.log();
|
||||||
|
console.log(` ${chalk.bold('CodeGraph')} v${getVersion()}`);
|
||||||
|
console.log(' Semantic code intelligence for Claude Code');
|
||||||
|
console.log(chalk.dim(' Created by: Colby McHenry'));
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show success checkmark
|
||||||
|
*/
|
||||||
|
export function success(message: string): void {
|
||||||
|
console.log(chalk.green(' ✓') + ' ' + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show error message
|
||||||
|
*/
|
||||||
|
export function error(message: string): void {
|
||||||
|
console.log(chalk.red(' ✗') + ' ' + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show info message
|
||||||
|
*/
|
||||||
|
export function info(message: string): void {
|
||||||
|
console.log(chalk.blue(' ℹ') + ' ' + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show warning message
|
||||||
|
*/
|
||||||
|
export function warn(message: string): void {
|
||||||
|
console.log(chalk.yellow(' ⚠') + ' ' + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the "next steps" section after installation
|
||||||
|
*/
|
||||||
|
export function showNextSteps(location: 'global' | 'local'): void {
|
||||||
|
console.log();
|
||||||
|
console.log(chalk.bold(' Done!') + ' Restart Claude Code to use CodeGraph.');
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
if (location === 'global') {
|
||||||
|
console.log(chalk.dim(' Quick start:'));
|
||||||
|
console.log(chalk.dim(' cd your-project'));
|
||||||
|
console.log(chalk.cyan(' codegraph init -i'));
|
||||||
|
} else {
|
||||||
|
console.log(chalk.dim(' CodeGraph is ready to use in this project!'));
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
/**
|
||||||
|
* Config file writing for the CodeGraph installer
|
||||||
|
* Writes to claude.json and settings.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as os from 'os';
|
||||||
|
import { InstallLocation } from './prompts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path to the Claude config directory
|
||||||
|
*/
|
||||||
|
function getClaudeConfigDir(location: InstallLocation): string {
|
||||||
|
if (location === 'global') {
|
||||||
|
return path.join(os.homedir(), '.claude');
|
||||||
|
}
|
||||||
|
return path.join(process.cwd(), '.claude');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path to the claude.json file
|
||||||
|
* - Global: ~/.claude.json (root level)
|
||||||
|
* - Local: ./.claude.json (project root)
|
||||||
|
*/
|
||||||
|
function getClaudeJsonPath(location: InstallLocation): string {
|
||||||
|
if (location === 'global') {
|
||||||
|
return path.join(os.homedir(), '.claude.json');
|
||||||
|
}
|
||||||
|
return path.join(process.cwd(), '.claude.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path to the settings.json file
|
||||||
|
* - Global: ~/.claude/settings.json
|
||||||
|
* - Local: ./.claude/settings.json
|
||||||
|
*/
|
||||||
|
function getSettingsJsonPath(location: InstallLocation): string {
|
||||||
|
const configDir = getClaudeConfigDir(location);
|
||||||
|
return path.join(configDir, 'settings.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a JSON file, returning an empty object if it doesn't exist
|
||||||
|
*/
|
||||||
|
function readJsonFile(filePath: string): Record<string, any> {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
return JSON.parse(content);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore parse errors, return empty object
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a JSON file, creating parent directories if needed
|
||||||
|
*/
|
||||||
|
function writeJsonFile(filePath: string, data: Record<string, any>): void {
|
||||||
|
const dir = path.dirname(filePath);
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the MCP server configuration for the given location
|
||||||
|
*/
|
||||||
|
function getMcpServerConfig(location: InstallLocation): Record<string, any> {
|
||||||
|
if (location === 'global') {
|
||||||
|
// Global: use 'codegraph' command directly (assumes globally installed)
|
||||||
|
return {
|
||||||
|
type: 'stdio',
|
||||||
|
command: 'codegraph',
|
||||||
|
args: ['serve', '--mcp'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Local: use npx to run the package
|
||||||
|
return {
|
||||||
|
type: 'stdio',
|
||||||
|
command: 'npx',
|
||||||
|
args: ['@colbymchenry/codegraph', 'serve', '--mcp'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the MCP server configuration to claude.json
|
||||||
|
*/
|
||||||
|
export function writeMcpConfig(location: InstallLocation): void {
|
||||||
|
const claudeJsonPath = getClaudeJsonPath(location);
|
||||||
|
const config = readJsonFile(claudeJsonPath);
|
||||||
|
|
||||||
|
// Ensure mcpServers object exists
|
||||||
|
if (!config.mcpServers) {
|
||||||
|
config.mcpServers = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add or update codegraph server
|
||||||
|
config.mcpServers.codegraph = getMcpServerConfig(location);
|
||||||
|
|
||||||
|
writeJsonFile(claudeJsonPath, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of permissions for CodeGraph tools
|
||||||
|
*/
|
||||||
|
function getCodeGraphPermissions(): string[] {
|
||||||
|
return [
|
||||||
|
'mcp__codegraph__codegraph_search',
|
||||||
|
'mcp__codegraph__codegraph_context',
|
||||||
|
'mcp__codegraph__codegraph_callers',
|
||||||
|
'mcp__codegraph__codegraph_callees',
|
||||||
|
'mcp__codegraph__codegraph_impact',
|
||||||
|
'mcp__codegraph__codegraph_node',
|
||||||
|
'mcp__codegraph__codegraph_status',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write permissions to settings.json
|
||||||
|
*/
|
||||||
|
export function writePermissions(location: InstallLocation): void {
|
||||||
|
const settingsPath = getSettingsJsonPath(location);
|
||||||
|
const settings = readJsonFile(settingsPath);
|
||||||
|
|
||||||
|
// Ensure permissions object exists
|
||||||
|
if (!settings.permissions) {
|
||||||
|
settings.permissions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure allow array exists
|
||||||
|
if (!Array.isArray(settings.permissions.allow)) {
|
||||||
|
settings.permissions.allow = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add CodeGraph permissions (avoiding duplicates)
|
||||||
|
const codegraphPermissions = getCodeGraphPermissions();
|
||||||
|
for (const permission of codegraphPermissions) {
|
||||||
|
if (!settings.permissions.allow.includes(permission)) {
|
||||||
|
settings.permissions.allow.push(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJsonFile(settingsPath, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if MCP config already exists for CodeGraph
|
||||||
|
*/
|
||||||
|
export function hasMcpConfig(location: InstallLocation): boolean {
|
||||||
|
const claudeJsonPath = getClaudeJsonPath(location);
|
||||||
|
const config = readJsonFile(claudeJsonPath);
|
||||||
|
return !!config.mcpServers?.codegraph;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if permissions already exist for CodeGraph
|
||||||
|
*/
|
||||||
|
export function hasPermissions(location: InstallLocation): boolean {
|
||||||
|
const settingsPath = getSettingsJsonPath(location);
|
||||||
|
const settings = readJsonFile(settingsPath);
|
||||||
|
const permissions = settings.permissions?.allow;
|
||||||
|
if (!Array.isArray(permissions)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Check if at least one CodeGraph permission exists
|
||||||
|
return permissions.some((p: string) => p.startsWith('mcp__codegraph__'));
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
* CodeGraph Interactive Installer
|
||||||
|
*
|
||||||
|
* Provides a beautiful interactive CLI experience for setting up CodeGraph
|
||||||
|
* with Claude Code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { showBanner, showNextSteps, success, error, info, chalk } from './banner';
|
||||||
|
import { promptInstallLocation, promptAutoAllow, InstallLocation } from './prompts';
|
||||||
|
import { writeMcpConfig, writePermissions, hasMcpConfig, hasPermissions } from './config-writer';
|
||||||
|
import CodeGraph from '../index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a number with commas
|
||||||
|
*/
|
||||||
|
function formatNumber(n: number): string {
|
||||||
|
return n.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the interactive installer
|
||||||
|
*/
|
||||||
|
export async function runInstaller(): Promise<void> {
|
||||||
|
// Show the banner
|
||||||
|
showBanner();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Step 1: Ask for installation location
|
||||||
|
const location = await promptInstallLocation();
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// Step 2: Write MCP configuration
|
||||||
|
const alreadyHasMcp = hasMcpConfig(location);
|
||||||
|
writeMcpConfig(location);
|
||||||
|
|
||||||
|
if (alreadyHasMcp) {
|
||||||
|
success(`Updated MCP server in ${location === 'global' ? '~/.claude.json' : './.claude.json'}`);
|
||||||
|
} else {
|
||||||
|
success(`Added MCP server to ${location === 'global' ? '~/.claude.json' : './.claude.json'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Ask about auto-allow permissions
|
||||||
|
const autoAllow = await promptAutoAllow();
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
if (autoAllow) {
|
||||||
|
const alreadyHasPerms = hasPermissions(location);
|
||||||
|
writePermissions(location);
|
||||||
|
|
||||||
|
if (alreadyHasPerms) {
|
||||||
|
success(`Updated permissions in ${location === 'global' ? '~/.claude/settings.json' : './.claude/settings.json'}`);
|
||||||
|
} else {
|
||||||
|
success(`Added permissions to ${location === 'global' ? '~/.claude/settings.json' : './.claude/settings.json'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: For local install, initialize the project
|
||||||
|
if (location === 'local') {
|
||||||
|
await initializeLocalProject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show next steps
|
||||||
|
showNextSteps(location);
|
||||||
|
} catch (err) {
|
||||||
|
console.log();
|
||||||
|
if (err instanceof Error && err.message.includes('readline was closed')) {
|
||||||
|
// User cancelled with Ctrl+C
|
||||||
|
console.log(chalk.dim(' Installation cancelled.'));
|
||||||
|
} else {
|
||||||
|
error(`Installation failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize CodeGraph in the current project (for local installs)
|
||||||
|
*/
|
||||||
|
async function initializeLocalProject(): Promise<void> {
|
||||||
|
const projectPath = process.cwd();
|
||||||
|
|
||||||
|
// Check if already initialized
|
||||||
|
if (CodeGraph.isInitialized(projectPath)) {
|
||||||
|
info('CodeGraph already initialized in this project');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
console.log(chalk.dim(' Initializing CodeGraph in current project...'));
|
||||||
|
|
||||||
|
// Initialize CodeGraph
|
||||||
|
const cg = await CodeGraph.init(projectPath);
|
||||||
|
success('Created .codegraph/ directory');
|
||||||
|
|
||||||
|
// Index the project
|
||||||
|
const result = await cg.indexAll({
|
||||||
|
onProgress: (progress) => {
|
||||||
|
// Simple progress indicator
|
||||||
|
const phaseNames: Record<string, string> = {
|
||||||
|
scanning: 'Scanning files',
|
||||||
|
parsing: 'Parsing code',
|
||||||
|
storing: 'Storing data',
|
||||||
|
resolving: 'Resolving refs',
|
||||||
|
};
|
||||||
|
const phaseName = phaseNames[progress.phase] || progress.phase;
|
||||||
|
const percent = progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0;
|
||||||
|
process.stdout.write(`\r ${chalk.dim(phaseName)}... ${percent}% `);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear progress line
|
||||||
|
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
success(`Indexed ${formatNumber(result.filesIndexed)} files (${formatNumber(result.nodesCreated)} symbols)`);
|
||||||
|
} else {
|
||||||
|
success(`Indexed ${formatNumber(result.filesIndexed)} files with ${result.errors.length} warnings`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install git hooks if this is a git repository
|
||||||
|
if (cg.isGitRepository()) {
|
||||||
|
const hookResult = cg.installGitHooks();
|
||||||
|
if (hookResult.success) {
|
||||||
|
success('Installed git post-commit hook');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cg.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for use in CLI
|
||||||
|
export { InstallLocation };
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/**
|
||||||
|
* User prompts for the CodeGraph installer
|
||||||
|
* Uses built-in readline to avoid ESM issues with inquirer
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as readline from 'readline';
|
||||||
|
import { chalk } from './banner';
|
||||||
|
|
||||||
|
export type InstallLocation = 'global' | 'local';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a readline interface for prompts
|
||||||
|
*/
|
||||||
|
function createInterface(): readline.Interface {
|
||||||
|
return readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt the user with a question and return their answer
|
||||||
|
*/
|
||||||
|
function prompt(rl: readline.Interface, question: string): Promise<string> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
rl.question(question, (answer) => {
|
||||||
|
resolve(answer.trim());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt for installation location (global or local)
|
||||||
|
*/
|
||||||
|
export async function promptInstallLocation(): Promise<InstallLocation> {
|
||||||
|
const rl = createInterface();
|
||||||
|
|
||||||
|
console.log(chalk.bold(' Where would you like to install?'));
|
||||||
|
console.log();
|
||||||
|
console.log(' 1) Global (~/.claude) - available in all projects');
|
||||||
|
console.log(' 2) Local (./.claude) - this project only');
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
const answer = await prompt(rl, ' Choice [1]: ');
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
// Default to '1' if empty, parse the answer
|
||||||
|
const choice = answer === '' ? '1' : answer;
|
||||||
|
|
||||||
|
if (choice === '2') {
|
||||||
|
return 'local';
|
||||||
|
}
|
||||||
|
return 'global';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt for auto-allow permissions
|
||||||
|
*/
|
||||||
|
export async function promptAutoAllow(): Promise<boolean> {
|
||||||
|
const rl = createInterface();
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
console.log(chalk.bold(' Auto-allow CodeGraph commands?') + chalk.dim(' (Skips permission prompts)'));
|
||||||
|
console.log();
|
||||||
|
console.log(' 1) Yes - auto-approve all codegraph_* tools');
|
||||||
|
console.log(' 2) No - ask for permission each time');
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
const answer = await prompt(rl, ' Choice [1]: ');
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
// Default to '1' if empty
|
||||||
|
const choice = answer === '' ? '1' : answer;
|
||||||
|
|
||||||
|
return choice !== '2';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt for confirmation (yes/no)
|
||||||
|
*/
|
||||||
|
export async function promptConfirm(message: string, defaultYes: boolean = true): Promise<boolean> {
|
||||||
|
const rl = createInterface();
|
||||||
|
|
||||||
|
const defaultStr = defaultYes ? 'Y/n' : 'y/N';
|
||||||
|
const answer = await prompt(rl, ` ${message} [${defaultStr}]: `);
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
if (answer === '') {
|
||||||
|
return defaultYes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer.toLowerCase().startsWith('y');
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user