@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 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
+ }