@optave/codegraph 2.3.1-dev.1aeea34 → 2.4.0
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/README.md +2 -0
- package/package.json +4 -5
- package/src/cli.js +12 -0
- package/src/update-check.js +159 -0
package/README.md
CHANGED
|
@@ -173,6 +173,8 @@ Full agent setup: [AI Agent Guide](docs/guides/ai-agent-guide.md) · [CLAU
|
|
|
173
173
|
| 🤖 | **MCP server** | 19-tool MCP server for AI assistants; single-repo by default, opt-in multi-repo |
|
|
174
174
|
| ⚡ | **Always fresh** | Three-tier incremental detection — sub-second rebuilds even on large codebases |
|
|
175
175
|
|
|
176
|
+
See [docs/examples](docs/examples) for real-world CLI and MCP usage examples.
|
|
177
|
+
|
|
176
178
|
## 📦 Commands
|
|
177
179
|
|
|
178
180
|
### Build & Watch
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optave/codegraph",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Local code graph CLI — parse codebases with tree-sitter, build dependency graphs, query them",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -62,10 +62,9 @@
|
|
|
62
62
|
"optionalDependencies": {
|
|
63
63
|
"@huggingface/transformers": "^3.8.1",
|
|
64
64
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
65
|
-
"@optave/codegraph-darwin-arm64": "2.
|
|
66
|
-
"@optave/codegraph-darwin-x64": "2.
|
|
67
|
-
"@optave/codegraph-linux-x64-gnu": "2.
|
|
68
|
-
"@optave/codegraph-win32-x64-msvc": "2.3.1-dev.1aeea34"
|
|
65
|
+
"@optave/codegraph-darwin-arm64": "2.4.0",
|
|
66
|
+
"@optave/codegraph-darwin-x64": "2.4.0",
|
|
67
|
+
"@optave/codegraph-linux-x64-gnu": "2.4.0"
|
|
69
68
|
},
|
|
70
69
|
"devDependencies": {
|
|
71
70
|
"@biomejs/biome": "^2.4.4",
|
package/src/cli.js
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
registerRepo,
|
|
40
40
|
unregisterRepo,
|
|
41
41
|
} from './registry.js';
|
|
42
|
+
import { checkForUpdates, printUpdateNotification } from './update-check.js';
|
|
42
43
|
import { watchProject } from './watcher.js';
|
|
43
44
|
|
|
44
45
|
const __cliDir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/i, '$1'));
|
|
@@ -56,6 +57,17 @@ program
|
|
|
56
57
|
.hook('preAction', (thisCommand) => {
|
|
57
58
|
const opts = thisCommand.opts();
|
|
58
59
|
if (opts.verbose) setVerbose(true);
|
|
60
|
+
})
|
|
61
|
+
.hook('postAction', async (_thisCommand, actionCommand) => {
|
|
62
|
+
const name = actionCommand.name();
|
|
63
|
+
if (name === 'mcp' || name === 'watch') return;
|
|
64
|
+
if (actionCommand.opts().json) return;
|
|
65
|
+
try {
|
|
66
|
+
const result = await checkForUpdates(pkg.version);
|
|
67
|
+
if (result) printUpdateNotification(result.current, result.latest);
|
|
68
|
+
} catch {
|
|
69
|
+
/* never break CLI */
|
|
70
|
+
}
|
|
59
71
|
});
|
|
60
72
|
|
|
61
73
|
/**
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const CACHE_PATH =
|
|
7
|
+
process.env.CODEGRAPH_UPDATE_CACHE_PATH ||
|
|
8
|
+
path.join(os.homedir(), '.codegraph', 'update-check.json');
|
|
9
|
+
|
|
10
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
11
|
+
const FETCH_TIMEOUT_MS = 3000;
|
|
12
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/@optave/codegraph/latest';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Minimal semver comparison. Returns -1, 0, or 1.
|
|
16
|
+
* Only handles numeric x.y.z (no pre-release tags).
|
|
17
|
+
*/
|
|
18
|
+
export function semverCompare(a, b) {
|
|
19
|
+
const pa = a.split('.').map(Number);
|
|
20
|
+
const pb = b.split('.').map(Number);
|
|
21
|
+
for (let i = 0; i < 3; i++) {
|
|
22
|
+
const na = pa[i] || 0;
|
|
23
|
+
const nb = pb[i] || 0;
|
|
24
|
+
if (na < nb) return -1;
|
|
25
|
+
if (na > nb) return 1;
|
|
26
|
+
}
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load the cached update-check result from disk.
|
|
32
|
+
* Returns null on missing or corrupt file.
|
|
33
|
+
*/
|
|
34
|
+
function loadCache(cachePath = CACHE_PATH) {
|
|
35
|
+
try {
|
|
36
|
+
const raw = fs.readFileSync(cachePath, 'utf-8');
|
|
37
|
+
const data = JSON.parse(raw);
|
|
38
|
+
if (!data || typeof data.lastCheckedAt !== 'number' || typeof data.latestVersion !== 'string') {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return data;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Persist the cache to disk (atomic write via temp + rename).
|
|
49
|
+
*/
|
|
50
|
+
function saveCache(cache, cachePath = CACHE_PATH) {
|
|
51
|
+
const dir = path.dirname(cachePath);
|
|
52
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
53
|
+
|
|
54
|
+
const tmp = `${cachePath}.tmp.${process.pid}`;
|
|
55
|
+
fs.writeFileSync(tmp, JSON.stringify(cache), 'utf-8');
|
|
56
|
+
fs.renameSync(tmp, cachePath);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Fetch the latest version string from the npm registry.
|
|
61
|
+
* Returns the version string or null on failure.
|
|
62
|
+
*/
|
|
63
|
+
function fetchLatestVersion() {
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
const req = https.get(
|
|
66
|
+
REGISTRY_URL,
|
|
67
|
+
{ timeout: FETCH_TIMEOUT_MS, headers: { Accept: 'application/json' } },
|
|
68
|
+
(res) => {
|
|
69
|
+
if (res.statusCode !== 200) {
|
|
70
|
+
res.resume();
|
|
71
|
+
resolve(null);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
let body = '';
|
|
75
|
+
res.setEncoding('utf-8');
|
|
76
|
+
res.on('data', (chunk) => {
|
|
77
|
+
body += chunk;
|
|
78
|
+
});
|
|
79
|
+
res.on('end', () => {
|
|
80
|
+
try {
|
|
81
|
+
const data = JSON.parse(body);
|
|
82
|
+
resolve(typeof data.version === 'string' ? data.version : null);
|
|
83
|
+
} catch {
|
|
84
|
+
resolve(null);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
req.on('error', () => resolve(null));
|
|
90
|
+
req.on('timeout', () => {
|
|
91
|
+
req.destroy();
|
|
92
|
+
resolve(null);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check whether a newer version of codegraph is available.
|
|
99
|
+
*
|
|
100
|
+
* Returns `{ current, latest }` if an update is available, `null` otherwise.
|
|
101
|
+
* Silently returns null on any error — never affects CLI operation.
|
|
102
|
+
*
|
|
103
|
+
* Options:
|
|
104
|
+
* cachePath — override cache file location (for testing)
|
|
105
|
+
* _fetchLatest — override the fetch function (for testing)
|
|
106
|
+
*/
|
|
107
|
+
export async function checkForUpdates(currentVersion, options = {}) {
|
|
108
|
+
// Suppress in non-interactive / CI contexts
|
|
109
|
+
if (process.env.CI) return null;
|
|
110
|
+
if (process.env.NO_UPDATE_CHECK) return null;
|
|
111
|
+
if (!process.stderr.isTTY) return null;
|
|
112
|
+
|
|
113
|
+
const cachePath = options.cachePath || CACHE_PATH;
|
|
114
|
+
const fetchFn = options._fetchLatest || fetchLatestVersion;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const cache = loadCache(cachePath);
|
|
118
|
+
|
|
119
|
+
// Cache is fresh — use it
|
|
120
|
+
if (cache && Date.now() - cache.lastCheckedAt < CACHE_TTL_MS) {
|
|
121
|
+
if (semverCompare(currentVersion, cache.latestVersion) < 0) {
|
|
122
|
+
return { current: currentVersion, latest: cache.latestVersion };
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Cache is stale or missing — fetch
|
|
128
|
+
const latest = await fetchFn();
|
|
129
|
+
if (!latest) return null;
|
|
130
|
+
|
|
131
|
+
// Update cache regardless of result
|
|
132
|
+
saveCache({ lastCheckedAt: Date.now(), latestVersion: latest }, cachePath);
|
|
133
|
+
|
|
134
|
+
if (semverCompare(currentVersion, latest) < 0) {
|
|
135
|
+
return { current: currentVersion, latest };
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Print a visible update notification box to stderr.
|
|
145
|
+
*/
|
|
146
|
+
export function printUpdateNotification(current, latest) {
|
|
147
|
+
const msg1 = `Update available: ${current} → ${latest}`;
|
|
148
|
+
const msg2 = 'Run `npm i -g @optave/codegraph` to update';
|
|
149
|
+
const width = Math.max(msg1.length, msg2.length) + 4;
|
|
150
|
+
|
|
151
|
+
const top = `┌${'─'.repeat(width)}┐`;
|
|
152
|
+
const bot = `└${'─'.repeat(width)}┘`;
|
|
153
|
+
const pad1 = ' '.repeat(width - msg1.length - 2);
|
|
154
|
+
const pad2 = ' '.repeat(width - msg2.length - 2);
|
|
155
|
+
const line1 = `│ ${msg1}${pad1}│`;
|
|
156
|
+
const line2 = `│ ${msg2}${pad2}│`;
|
|
157
|
+
|
|
158
|
+
process.stderr.write(`\n${top}\n${line1}\n${line2}\n${bot}\n\n`);
|
|
159
|
+
}
|