Skip to content

Commit 3ad4d5b

Browse files
colbymchenryclaude
andcommitted
Download embedding model to ~/.codegraph/models on install
The nomic-ai model is now downloaded during npm install via a postinstall script and stored globally in ~/.codegraph/models (shared across projects) instead of per-project in .codegraph/models. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3c20223 commit 3ad4d5b

4 files changed

Lines changed: 75 additions & 2 deletions

File tree

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
},
1010
"files": [
1111
"dist",
12+
"scripts",
1213
"README.md"
1314
],
1415
"scripts": {
1516
"build": "tsc && npm run copy-assets",
17+
"postinstall": "node scripts/postinstall.js",
1618
"copy-assets": "cp -r src/extraction/queries dist/extraction/ && cp src/db/schema.sql dist/db/",
1719
"dev": "tsc --watch",
1820
"cli": "npm run build && node dist/bin/codegraph.js",

scripts/postinstall.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Postinstall script - downloads the embedding model to ~/.codegraph/models
4+
* This runs after `npm install` or `npx @colbymchenry/codegraph`
5+
*/
6+
const { existsSync, mkdirSync } = require('fs');
7+
const { join } = require('path');
8+
const { homedir } = require('os');
9+
10+
const CODEGRAPH_DIR = join(homedir(), '.codegraph');
11+
const MODELS_DIR = join(CODEGRAPH_DIR, 'models');
12+
const MODEL_ID = 'nomic-ai/nomic-embed-text-v1.5';
13+
14+
async function downloadModel() {
15+
// Ensure directories exist
16+
if (!existsSync(CODEGRAPH_DIR)) {
17+
mkdirSync(CODEGRAPH_DIR, { recursive: true });
18+
}
19+
if (!existsSync(MODELS_DIR)) {
20+
mkdirSync(MODELS_DIR, { recursive: true });
21+
}
22+
23+
// Check if model is already cached
24+
const modelCachePath = join(MODELS_DIR, MODEL_ID.replace('/', '/'));
25+
if (existsSync(modelCachePath)) {
26+
console.log('Embedding model already downloaded.');
27+
return;
28+
}
29+
30+
console.log('Downloading embedding model (~130MB)...');
31+
console.log('This is a one-time download for semantic code search.\n');
32+
33+
try {
34+
// Dynamic import for @xenova/transformers (ESM-only package)
35+
const { pipeline, env } = await import('@xenova/transformers');
36+
37+
// Configure cache directory
38+
env.cacheDir = MODELS_DIR;
39+
40+
// Download with progress
41+
await pipeline('feature-extraction', MODEL_ID, {
42+
progress_callback: (progress) => {
43+
if (progress.status === 'progress' && progress.file && progress.progress !== undefined) {
44+
const fileName = progress.file.split('/').pop();
45+
const percent = Math.round(progress.progress);
46+
process.stdout.write(`\rDownloading ${fileName}... ${percent}% `);
47+
} else if (progress.status === 'done') {
48+
process.stdout.write('\n');
49+
}
50+
},
51+
});
52+
53+
console.log('\nEmbedding model ready!');
54+
} catch (error) {
55+
// Don't fail the install if model download fails
56+
// User can still use codegraph without semantic search
57+
console.log('\nNote: Could not download embedding model.');
58+
console.log('Semantic search will download it on first use.');
59+
if (process.env.DEBUG) {
60+
console.error(error);
61+
}
62+
}
63+
}
64+
65+
downloadModel().catch(() => {
66+
// Silent exit - don't break npm install
67+
process.exit(0);
68+
});

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,6 @@ export class CodeGraph {
760760
if (!this.vectorManager) {
761761
this.vectorManager = createVectorManager(this.db.getDb(), this.queries, {
762762
embedder: {
763-
cacheDir: path.join(this.projectRoot, '.codegraph', 'models'),
764763
showProgress: true,
765764
},
766765
});

src/vectors/embedder.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77

88
import * as path from 'path';
99
import * as fs from 'fs';
10+
import { homedir } from 'os';
11+
12+
// Global model cache directory (shared across all projects)
13+
const GLOBAL_MODELS_DIR = path.join(homedir(), '.codegraph', 'models');
1014

1115
// Dynamic import for @xenova/transformers (ESM-only package)
1216
// We use dynamic import to support CommonJS builds
@@ -89,7 +93,7 @@ export class TextEmbedder {
8993

9094
constructor(options: EmbedderOptions = {}) {
9195
this.modelId = options.modelId || DEFAULT_MODEL;
92-
this.cacheDir = options.cacheDir || '.codegraph/models';
96+
this.cacheDir = options.cacheDir || GLOBAL_MODELS_DIR;
9397
this.showProgress = options.showProgress ?? false;
9498
}
9599

0 commit comments

Comments
 (0)