Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/bin/codegraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ try {
} catch { /* cache is best-effort */ }

import { Command } from 'commander';
import { execFileSync } from 'child_process';
import * as path from 'path';
import * as fs from 'fs';
import { getCodeGraphDir, isInitialized, unsafeIndexRootReason, findNearestCodeGraphRoot, planFrontload, hasStructuralKeyword, extractCodeTokens } from '../directory';
Expand Down Expand Up @@ -291,6 +292,15 @@ function formatDuration(ms: number): string {
return `${minutes}m ${remainingSeconds.toFixed(0)}s`;
}

/**
* Format a byte count for CLI output.
*/
function formatBytes(bytes: number): string {
if (bytes < 1024) return `${bytes}B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
}

// Shimmer progress renderer (runs in a worker thread for smooth animation)
// Imported at top of file from '../ui/shimmer-progress'

Expand Down Expand Up @@ -758,6 +768,91 @@ program
}
});

/**
* codegraph clone [target]
*/
program
.command('clone [target]')
.description('Copy a CodeGraph index from another project or worktree')
.option('-s, --source <path>', 'Source project with an existing .codegraph index')
.option('-f, --force', 'Overwrite if target already has an index')
.option('--auto', 'Auto-detect source from git worktree parent')
.action(async (targetArg: string | undefined, options: { source?: string; force?: boolean; auto?: boolean }) => {
const targetPath = path.resolve(targetArg || process.cwd());
let sourcePath: string | undefined;

if (options.source) {
sourcePath = path.resolve(options.source);
} else if (options.auto) {
try {
const output = execFileSync('git', ['worktree', 'list', '--porcelain'], {
cwd: targetPath,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
timeout: 5000,
}).trim();
const mainLine = output.split('\n').find((line) => line.startsWith('worktree '));
if (mainLine) {
const mainWorktree = mainLine.slice('worktree '.length).trim();
const resolvedMainWorktree = path.isAbsolute(mainWorktree)
? mainWorktree
: path.resolve(targetPath, mainWorktree);
if (isInitialized(resolvedMainWorktree)) {
sourcePath = resolvedMainWorktree;
}
}
} catch {
// Report the same actionable error below when git or the worktree is unavailable.
}
} else {
error('A source is required. Pass --source <path> or use --auto.');
info('Usage: codegraph clone --source <path> [target]');
process.exit(1);
}

if (!sourcePath || !isInitialized(sourcePath)) {
error(`No CodeGraph index found in the source${sourcePath ? ` at ${sourcePath}` : ''}.`);
if (options.auto) {
info('Auto-detection looks for an initialized index in the main git worktree.');
}
process.exit(1);
}

if (isInitialized(targetPath) && !options.force) {
warn(`Target already has a CodeGraph index at ${getCodeGraphDir(targetPath)}; skipping.`);
info('Pass --force to overwrite it.');
return;
}

try {
const targetCodeGraphDir = getCodeGraphDir(targetPath);
fs.mkdirSync(targetCodeGraphDir, { recursive: true });

const gitignorePath = path.join(targetCodeGraphDir, '.gitignore');
if (!fs.existsSync(gitignorePath)) {
fs.writeFileSync(
gitignorePath,
'# CodeGraph data files — local to each machine, not for committing.\n' +
'# Ignore everything in .codegraph/ except this file itself, so transient\n' +
'# files (the database, daemon.pid, sockets, logs) never show up in git.\n' +
'*\n' +
'!.gitignore\n',
'utf8'
);
}

const sourceDbPath = path.join(getCodeGraphDir(sourcePath), 'codegraph.db');
const targetDbPath = path.join(targetCodeGraphDir, 'codegraph.db');
fs.copyFileSync(sourceDbPath, targetDbPath);
const size = fs.statSync(targetDbPath).size;

success(`Copied CodeGraph index to ${targetPath} (${formatBytes(size)})`);
} catch (err) {
error(`Failed to clone CodeGraph index: ${err instanceof Error ? err.message : String(err)}`);
process.exit(1);
}
});

/**
* codegraph index [path]
*/
Expand Down