@altikva/cgh 0.12.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 +65 -0
- package/bin/cgh.js +138 -0
- package/lib/resolve.js +71 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @altikva/cgh
|
|
2
|
+
|
|
3
|
+
Run [**cgh**](https://github.com/altikva/cgh) — a local code graph + memory +
|
|
4
|
+
plans + knowledge layer for AI coding agents — with no Python installed. This
|
|
5
|
+
package is a thin launcher: on first use it downloads the standalone `cgh`
|
|
6
|
+
binary for your OS from the matching GitHub Release, verifies its SHA-256,
|
|
7
|
+
caches it, and runs it.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# one-off, no install
|
|
11
|
+
npx @altikva/cgh --version
|
|
12
|
+
npx @altikva/cgh serve --root . # start the MCP server for this repo
|
|
13
|
+
|
|
14
|
+
# or install the `cgh` command globally
|
|
15
|
+
npm install -g @altikva/cgh
|
|
16
|
+
cgh serve --root .
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Sealed vs egress
|
|
20
|
+
|
|
21
|
+
The binary ships in two variants, and this launcher picks between them:
|
|
22
|
+
|
|
23
|
+
- **Sealed (default).** The core graph, the MCP tools, memory / plans /
|
|
24
|
+
knowledge, and the local-only plugins (PII scrubbing, classification). It
|
|
25
|
+
contains no code that can reach the network, so it cannot phone home.
|
|
26
|
+
- **Egress.** Adds the plugins that can call an external model (code
|
|
27
|
+
generation, summarization, bug reports). They stay behind cgh's egress gate
|
|
28
|
+
and do nothing until configured, and can target a local model too.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx @altikva/cgh --egress serve # fetch and run the egress build
|
|
32
|
+
CGH_EGRESS=1 npx @altikva/cgh serve # same, via env
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A leading `--egress` is consumed by the launcher; everything after it is passed
|
|
36
|
+
straight to `cgh`.
|
|
37
|
+
|
|
38
|
+
## What you get vs pip / uvx
|
|
39
|
+
|
|
40
|
+
This launcher runs the **light SQLite build**. If you want DuckDB's analytical
|
|
41
|
+
speed, or the heavy `docs` / `vision` plugins, install with Python instead:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uvx cgh serve # bundles DuckDB
|
|
45
|
+
pip install "cgh[full]" # DuckDB + every first-party plugin
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Environment
|
|
49
|
+
|
|
50
|
+
| Variable | Effect |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `CGH_EGRESS=1` / `CGH_VARIANT=egress` | Use the egress build. |
|
|
53
|
+
| `CGH_CACHE_DIR` | Where the binary is cached (default: `~/.cache/cgh/bin`). |
|
|
54
|
+
| `CGH_DOWNLOAD_BASE` | Override the release download base (private mirrors, testing). |
|
|
55
|
+
|
|
56
|
+
## Supported platforms
|
|
57
|
+
|
|
58
|
+
macOS (Apple Silicon), Linux (x64, arm64), Windows (x64). Intel Macs and any
|
|
59
|
+
other platform have no prebuilt binary, so the launcher tells you to `uvx cgh`
|
|
60
|
+
instead.
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT AND CC-BY-NC-SA-4.0, the same as cgh itself. See the
|
|
65
|
+
[main repository](https://github.com/altikva/cgh).
|
package/bin/cgh.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
|
|
3
|
+
// __creation__ = 2026-09-17
|
|
4
|
+
// __author__ = "jndjama (Joy Ndjama)"
|
|
5
|
+
// __copyright__ = "Copyright 2026 ALTIKVA."
|
|
6
|
+
// __licence__ = "MIT & CC BY-NC-SA (https://www.altikva.com/licenses/LICENSE-1.0)"
|
|
7
|
+
// -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
|
|
8
|
+
// Description: The npx launcher. Resolves the standalone cgh binary for this
|
|
9
|
+
// OS/arch and variant (sealed by default, egress with a leading
|
|
10
|
+
// --egress), downloading it from the matching GitHub Release on
|
|
11
|
+
// first use, verifying its SHA-256 against the published .sha256,
|
|
12
|
+
// caching it under the user's cache dir, then exec-ing it with the
|
|
13
|
+
// remaining args and forwarding its exit code. No binary is bundled
|
|
14
|
+
// in the npm package: it is fetched lazily so a single small
|
|
15
|
+
// package serves every platform, and the variant is a runtime
|
|
16
|
+
// choice the wrapper cannot know at install time. The download base
|
|
17
|
+
// and cache dir are overridable (CGH_DOWNLOAD_BASE, CGH_CACHE_DIR)
|
|
18
|
+
// for private mirrors and for tests.
|
|
19
|
+
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const os = require('os');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const http = require('http');
|
|
26
|
+
const https = require('https');
|
|
27
|
+
const crypto = require('crypto');
|
|
28
|
+
const { spawnSync } = require('child_process');
|
|
29
|
+
|
|
30
|
+
const { targetFor, assetName, selectVariant, downloadUrl } = require('../lib/resolve');
|
|
31
|
+
const pkg = require('../package.json');
|
|
32
|
+
|
|
33
|
+
const DEFAULT_BASE = 'https://github.com/altikva/cgh/releases/download';
|
|
34
|
+
|
|
35
|
+
function log(msg) {
|
|
36
|
+
process.stderr.write(`[cgh] ${msg}\n`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function httpGet(url, redirects) {
|
|
40
|
+
redirects = redirects || 0;
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
if (redirects > 6) {
|
|
43
|
+
reject(new Error(`too many redirects fetching ${url}`));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const client = url.startsWith('http://') ? http : https;
|
|
47
|
+
client
|
|
48
|
+
.get(url, { headers: { 'user-agent': `cgh-npm/${pkg.version}` } }, (res) => {
|
|
49
|
+
const { statusCode, headers } = res;
|
|
50
|
+
if (statusCode >= 300 && statusCode < 400 && headers.location) {
|
|
51
|
+
res.resume();
|
|
52
|
+
resolve(httpGet(new URL(headers.location, url).toString(), redirects + 1));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (statusCode !== 200) {
|
|
56
|
+
res.resume();
|
|
57
|
+
reject(new Error(`GET ${url} -> HTTP ${statusCode}`));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const chunks = [];
|
|
61
|
+
res.on('data', (c) => chunks.push(c));
|
|
62
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
63
|
+
res.on('error', reject);
|
|
64
|
+
})
|
|
65
|
+
.on('error', reject);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// sha256sum / shasum -a 256 write "<64 hex> <filename>"; take the digest.
|
|
70
|
+
function expectedSha(shaText) {
|
|
71
|
+
const m = /^([0-9a-fA-F]{64})\b/.exec(shaText.trim());
|
|
72
|
+
if (!m) {
|
|
73
|
+
throw new Error('could not parse the .sha256 checksum file');
|
|
74
|
+
}
|
|
75
|
+
return m[1].toLowerCase();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function sha256(buf) {
|
|
79
|
+
return crypto.createHash('sha256').update(buf).digest('hex');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function cacheDir(version) {
|
|
83
|
+
const base =
|
|
84
|
+
process.env.CGH_CACHE_DIR || path.join(os.homedir() || os.tmpdir(), '.cache', 'cgh', 'bin');
|
|
85
|
+
return path.join(base, `v${version}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function ensureBinary(variant) {
|
|
89
|
+
const version = pkg.version;
|
|
90
|
+
const target = targetFor(process.platform, process.arch);
|
|
91
|
+
const asset = assetName(target, variant);
|
|
92
|
+
const dir = cacheDir(version);
|
|
93
|
+
const dest = path.join(dir, asset);
|
|
94
|
+
if (fs.existsSync(dest)) {
|
|
95
|
+
return dest;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const base = process.env.CGH_DOWNLOAD_BASE || DEFAULT_BASE;
|
|
99
|
+
const url = downloadUrl(base, version, asset);
|
|
100
|
+
log(`downloading ${asset} (v${version})...`);
|
|
101
|
+
const [bin, shaText] = await Promise.all([httpGet(url), httpGet(`${url}.sha256`)]);
|
|
102
|
+
const want = expectedSha(shaText.toString('utf8'));
|
|
103
|
+
const got = sha256(bin);
|
|
104
|
+
if (got !== want) {
|
|
105
|
+
throw new Error(`checksum mismatch for ${asset}: expected ${want}, got ${got}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
109
|
+
const tmp = `${dest}.${process.pid}.tmp`;
|
|
110
|
+
fs.writeFileSync(tmp, bin);
|
|
111
|
+
fs.chmodSync(tmp, 0o755);
|
|
112
|
+
fs.renameSync(tmp, dest);
|
|
113
|
+
log(`cached at ${dest}`);
|
|
114
|
+
return dest;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function main() {
|
|
118
|
+
const { variant, rest } = selectVariant(process.argv.slice(2), process.env);
|
|
119
|
+
let bin;
|
|
120
|
+
try {
|
|
121
|
+
bin = await ensureBinary(variant);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
log(`error: ${err.message}`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
const result = spawnSync(bin, rest, { stdio: 'inherit' });
|
|
127
|
+
if (result.error) {
|
|
128
|
+
log(`failed to run the binary: ${result.error.message}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (require.main === module) {
|
|
135
|
+
main();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = { ensureBinary, cacheDir, expectedSha, sha256 };
|
package/lib/resolve.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
|
|
2
|
+
// __creation__ = 2026-09-17
|
|
3
|
+
// __author__ = "jndjama (Joy Ndjama)"
|
|
4
|
+
// __copyright__ = "Copyright 2026 ALTIKVA."
|
|
5
|
+
// __licence__ = "MIT & CC BY-NC-SA (https://www.altikva.com/licenses/LICENSE-1.0)"
|
|
6
|
+
// -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
|
|
7
|
+
// Description: Pure, side-effect-free resolution helpers for the npx wrapper.
|
|
8
|
+
// targetFor maps a Node platform/arch to the release asset target;
|
|
9
|
+
// assetName builds the file name for a target and variant (sealed
|
|
10
|
+
// -> cgh-<target>, egress -> cgh-egress-<target>, plus .exe on
|
|
11
|
+
// Windows); selectVariant reads a leading --egress flag or the
|
|
12
|
+
// env and returns the variant plus the untouched pass-through
|
|
13
|
+
// args; downloadUrl joins the release base, version and asset. No
|
|
14
|
+
// I/O here so the launcher's logic is unit-testable without a
|
|
15
|
+
// network or a real binary.
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
// Node (platform, arch) -> the release asset target. These are the targets the
|
|
20
|
+
// release matrix builds; anything else has no prebuilt binary. macOS x64
|
|
21
|
+
// (Intel) is absent on purpose: GitHub retired the Intel macOS hosted runner
|
|
22
|
+
// and PyInstaller cannot cross-compile one, so Intel-Mac users get the
|
|
23
|
+
// install-with-Python hint below instead of a download that 404s.
|
|
24
|
+
const PLATFORM_MAP = {
|
|
25
|
+
'darwin arm64': 'macos-arm64',
|
|
26
|
+
'linux x64': 'linux-x64',
|
|
27
|
+
'linux arm64': 'linux-arm64',
|
|
28
|
+
'win32 x64': 'windows-x64',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function targetFor(platform, arch) {
|
|
32
|
+
const target = PLATFORM_MAP[`${platform} ${arch}`];
|
|
33
|
+
if (!target) {
|
|
34
|
+
const supported = Object.keys(PLATFORM_MAP).join(', ');
|
|
35
|
+
throw new Error(
|
|
36
|
+
`no prebuilt cgh binary for ${platform}/${arch}. Supported: ${supported}. ` +
|
|
37
|
+
'Install with Python instead: uvx cgh (or pip install cgh).',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return target;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function assetName(target, variant) {
|
|
44
|
+
const prefix = variant === 'egress' ? 'cgh-egress' : 'cgh';
|
|
45
|
+
const ext = target.startsWith('windows') ? '.exe' : '';
|
|
46
|
+
return `${prefix}-${target}${ext}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// A leading --egress selects the egress build; CGH_VARIANT=egress or
|
|
50
|
+
// CGH_EGRESS=1 do the same. Everything else is passed through to the binary
|
|
51
|
+
// untouched, so tool flags are never swallowed. Only a LEADING --egress is
|
|
52
|
+
// consumed, so a stray later occurrence reaches the binary and errors loudly
|
|
53
|
+
// rather than being silently reinterpreted.
|
|
54
|
+
function selectVariant(argv, env) {
|
|
55
|
+
env = env || {};
|
|
56
|
+
const wantEgress =
|
|
57
|
+
env.CGH_VARIANT === 'egress' || env.CGH_EGRESS === '1' || env.CGH_EGRESS === 'true';
|
|
58
|
+
let variant = wantEgress ? 'egress' : 'sealed';
|
|
59
|
+
const rest = argv.slice();
|
|
60
|
+
if (rest[0] === '--egress') {
|
|
61
|
+
variant = 'egress';
|
|
62
|
+
rest.shift();
|
|
63
|
+
}
|
|
64
|
+
return { variant, rest };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function downloadUrl(base, version, asset) {
|
|
68
|
+
return `${base.replace(/\/+$/, '')}/v${version}/${asset}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { PLATFORM_MAP, targetFor, assetName, selectVariant, downloadUrl };
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@altikva/cgh",
|
|
3
|
+
"version": "0.12.0",
|
|
4
|
+
"description": "Local code graph + memory + plans + knowledge for AI coding agents. Downloads and runs the standalone cgh binary; no Python required.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"cgh": "bin/cgh.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"lib/",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test test/*.test.js"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"code-graph",
|
|
21
|
+
"cgh",
|
|
22
|
+
"codegraph",
|
|
23
|
+
"mcp",
|
|
24
|
+
"ai",
|
|
25
|
+
"navigation"
|
|
26
|
+
],
|
|
27
|
+
"homepage": "https://github.com/altikva/cgh#readme",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/altikva/cgh.git",
|
|
31
|
+
"directory": "packaging/npm"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/altikva/cgh/issues"
|
|
35
|
+
},
|
|
36
|
+
"author": "Joy Ndjama <joy.ndjama@altikva.com>",
|
|
37
|
+
"license": "MIT AND CC-BY-NC-SA-4.0"
|
|
38
|
+
}
|