@onurbyte/vexen 0.0.0-stage → 0.1.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 +46 -2
- package/bin/vexen.js +156 -0
- package/package.json +30 -4
package/README.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @onurbyte/vexen
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Bootstrap for [Vexen](https://github.com/OnurByte/Vexen), local evidence-based memory for coding agents.
|
|
4
|
+
|
|
5
|
+
> **Status: not published.** No npm package and no release binary exist yet. This directory is the wrapper that will ship with the first tagged release. Until then, build from source: `cargo install --git https://github.com/OnurByte/Vexen --locked`.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
`npx @onurbyte/vexen <args>` runs `vexen <args>`:
|
|
10
|
+
|
|
11
|
+
1. Picks the release asset for your platform (Windows x64, Linux x64/arm64, macOS x64/arm64).
|
|
12
|
+
2. Downloads `vexen-<version>-<target>.(tar.gz|zip)` and its `.sha256` from the matching GitHub release.
|
|
13
|
+
3. Refuses to run the archive unless the SHA-256 matches.
|
|
14
|
+
4. Unpacks to a per-version cache and runs the binary with your arguments.
|
|
15
|
+
|
|
16
|
+
Node 18+ is needed for this step only. Nothing is installed globally and no config files are touched.
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npx @onurbyte/vexen init
|
|
20
|
+
npx @onurbyte/vexen doctor
|
|
21
|
+
npx @onurbyte/vexen check --diff HEAD
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
As an MCP server command (stdout carries only protocol messages; diagnostics go to stderr):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{ "command": "npx", "args": ["-y", "@onurbyte/vexen", "mcp", "--stdio"] }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`vexen install` (client configuration) is not implemented yet.
|
|
31
|
+
|
|
32
|
+
## Environment
|
|
33
|
+
|
|
34
|
+
| Variable | Effect |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `VEXEN_BINARY` | Run this binary and skip the download (offline installs). |
|
|
37
|
+
| `VEXEN_HOME` | Cache directory. Default: `%LOCALAPPDATA%\vexen` on Windows, `$XDG_CACHE_HOME/vexen` or `~/.cache/vexen` elsewhere. |
|
|
38
|
+
|
|
39
|
+
The only network access is the explicit download above, on first run of a version.
|
|
40
|
+
|
|
41
|
+
## Release checklist
|
|
42
|
+
|
|
43
|
+
1. Bump `version` in `Cargo.toml` and `npm/package.json` to the same value.
|
|
44
|
+
2. Tag `vX.Y.Z`. The release workflow builds the five targets and uploads archives with `.sha256` files.
|
|
45
|
+
3. Publish the package after the release assets exist (the workflow does this when the repository variable `PUBLISH_NPM` is `true` and the `NPM_TOKEN` secret is set).
|
|
46
|
+
|
|
47
|
+
The package name `@onurbyte/vexen` needs an npm account or organization named `onurbyte`; the unscoped name `vexen` is already taken by another project.
|
package/bin/vexen.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// Bootstrap only: fetch the release binary for this platform, verify its
|
|
5
|
+
// SHA-256, cache it, then run it with the caller's arguments. Everything
|
|
6
|
+
// diagnostic goes to stderr because stdout belongs to the MCP protocol.
|
|
7
|
+
|
|
8
|
+
const crypto = require("node:crypto");
|
|
9
|
+
const fs = require("node:fs");
|
|
10
|
+
const https = require("node:https");
|
|
11
|
+
const os = require("node:os");
|
|
12
|
+
const path = require("node:path");
|
|
13
|
+
const { spawnSync } = require("node:child_process");
|
|
14
|
+
|
|
15
|
+
const version = require("../package.json").version;
|
|
16
|
+
const repo = "OnurByte/Vexen";
|
|
17
|
+
const MAX_ARCHIVE_BYTES = 200 * 1024 * 1024;
|
|
18
|
+
const TIMEOUT_MS = 60_000;
|
|
19
|
+
|
|
20
|
+
const targets = {
|
|
21
|
+
"win32-x64": { triple: "x86_64-pc-windows-msvc", ext: "zip", exe: "vexen.exe" },
|
|
22
|
+
"linux-x64": { triple: "x86_64-unknown-linux-gnu", ext: "tar.gz", exe: "vexen" },
|
|
23
|
+
"linux-arm64": { triple: "aarch64-unknown-linux-gnu", ext: "tar.gz", exe: "vexen" },
|
|
24
|
+
"darwin-x64": { triple: "x86_64-apple-darwin", ext: "tar.gz", exe: "vexen" },
|
|
25
|
+
"darwin-arm64": { triple: "aarch64-apple-darwin", ext: "tar.gz", exe: "vexen" },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function log(message) {
|
|
29
|
+
process.stderr.write(`vexen: ${message}\n`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function fail(message) {
|
|
33
|
+
log(message);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function cacheRoot() {
|
|
38
|
+
if (process.env.VEXEN_HOME) return process.env.VEXEN_HOME;
|
|
39
|
+
if (process.platform === "win32" && process.env.LOCALAPPDATA) {
|
|
40
|
+
return path.join(process.env.LOCALAPPDATA, "vexen");
|
|
41
|
+
}
|
|
42
|
+
return path.join(process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache"), "vexen");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function get(url, redirects = 5) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const request = https.get(url, { headers: { "user-agent": "vexen-npm-bootstrap" } }, (res) => {
|
|
48
|
+
if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
|
|
49
|
+
res.resume();
|
|
50
|
+
if (redirects === 0) return reject(new Error("too many redirects"));
|
|
51
|
+
const next = new URL(res.headers.location, url);
|
|
52
|
+
if (next.protocol !== "https:") return reject(new Error("refusing a non-HTTPS redirect"));
|
|
53
|
+
return resolve(get(next.toString(), redirects - 1));
|
|
54
|
+
}
|
|
55
|
+
if (res.statusCode !== 200) {
|
|
56
|
+
res.resume();
|
|
57
|
+
return reject(Object.assign(new Error(`HTTP ${res.statusCode} for ${url}`), { status: res.statusCode }));
|
|
58
|
+
}
|
|
59
|
+
const chunks = [];
|
|
60
|
+
let size = 0;
|
|
61
|
+
res.on("data", (chunk) => {
|
|
62
|
+
size += chunk.length;
|
|
63
|
+
if (size > MAX_ARCHIVE_BYTES) return request.destroy(new Error("download exceeds the size limit"));
|
|
64
|
+
chunks.push(chunk);
|
|
65
|
+
});
|
|
66
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
67
|
+
res.on("error", reject);
|
|
68
|
+
});
|
|
69
|
+
request.setTimeout?.(TIMEOUT_MS, () => request.destroy(new Error("download timed out")));
|
|
70
|
+
request.on("error", reject);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function tarCommand() {
|
|
75
|
+
// Only bsdtar reads zip; Git Bash can put GNU tar first on PATH.
|
|
76
|
+
if (process.platform === "win32") {
|
|
77
|
+
return path.join(process.env.SystemRoot || "C:\\Windows", "System32", "tar.exe");
|
|
78
|
+
}
|
|
79
|
+
return "tar";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function install(target, binary) {
|
|
83
|
+
const name = `vexen-${version}-${target.triple}`;
|
|
84
|
+
const base = `https://github.com/${repo}/releases/download/v${version}`;
|
|
85
|
+
let archive;
|
|
86
|
+
let checksum;
|
|
87
|
+
try {
|
|
88
|
+
[archive, checksum] = await Promise.all([
|
|
89
|
+
get(`${base}/${name}.${target.ext}`),
|
|
90
|
+
get(`${base}/${name}.${target.ext}.sha256`),
|
|
91
|
+
]);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error.status === 404) {
|
|
94
|
+
fail(
|
|
95
|
+
`no release binary is published for v${version} on ${target.triple} yet. ` +
|
|
96
|
+
`Build from source instead: cargo install --git https://github.com/${repo} --locked`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
fail(`download failed: ${error.message}`);
|
|
100
|
+
}
|
|
101
|
+
const expected = checksum.toString("utf8").trim().split(/\s+/)[0].toLowerCase();
|
|
102
|
+
const actual = crypto.createHash("sha256").update(archive).digest("hex");
|
|
103
|
+
if (!/^[0-9a-f]{64}$/.test(expected) || expected !== actual) {
|
|
104
|
+
fail("checksum mismatch; refusing to run the downloaded archive.");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Unpack in a private temp directory, then rename: two parallel runs never
|
|
108
|
+
// see (or run) a half-written binary.
|
|
109
|
+
const dir = path.dirname(binary);
|
|
110
|
+
fs.mkdirSync(path.dirname(dir), { recursive: true });
|
|
111
|
+
const work = fs.mkdtempSync(`${dir}.tmp-`);
|
|
112
|
+
try {
|
|
113
|
+
const archivePath = path.join(work, `${name}.${target.ext}`);
|
|
114
|
+
fs.writeFileSync(archivePath, archive);
|
|
115
|
+
const unpacked = spawnSync(tarCommand(), ["-xf", archivePath, "-C", work], {
|
|
116
|
+
stdio: ["ignore", process.stderr, process.stderr],
|
|
117
|
+
});
|
|
118
|
+
const staged = path.join(work, target.exe);
|
|
119
|
+
if (unpacked.status !== 0 || !fs.existsSync(staged)) {
|
|
120
|
+
fail("could not unpack the downloaded archive (is `tar` available?).");
|
|
121
|
+
}
|
|
122
|
+
if (process.platform !== "win32") fs.chmodSync(staged, 0o755);
|
|
123
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
124
|
+
try {
|
|
125
|
+
fs.renameSync(staged, binary);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (!fs.existsSync(binary)) throw error; // lost a race to an identical copy
|
|
128
|
+
}
|
|
129
|
+
} finally {
|
|
130
|
+
fs.rmSync(work, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function main() {
|
|
135
|
+
let binary = process.env.VEXEN_BINARY;
|
|
136
|
+
if (!binary) {
|
|
137
|
+
const target = targets[`${process.platform}-${process.arch}`];
|
|
138
|
+
if (!target) {
|
|
139
|
+
fail(
|
|
140
|
+
`unsupported platform ${process.platform}-${process.arch}. ` +
|
|
141
|
+
`Build from source: cargo install --git https://github.com/${repo} --locked`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
binary = path.join(cacheRoot(), version, target.triple, target.exe);
|
|
145
|
+
if (!fs.existsSync(binary)) {
|
|
146
|
+
log(`downloading v${version} for ${target.triple}`);
|
|
147
|
+
await install(target, binary);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
151
|
+
if (result.error) fail(`could not start ${binary}: ${result.error.message}`);
|
|
152
|
+
if (result.signal) process.kill(process.pid, result.signal);
|
|
153
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
main().catch((error) => fail(error.message));
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onurbyte/vexen",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bootstrap for Vexen: downloads and runs the checksummed Vexen binary for your platform.",
|
|
5
|
+
"license": "GPL-3.0-or-later",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vexen": "bin/vexen.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/vexen.js",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/OnurByte/Vexen#readme",
|
|
20
|
+
"bugs": "https://github.com/OnurByte/Vexen/issues",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/OnurByte/Vexen.git",
|
|
24
|
+
"directory": "npm"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"mcp",
|
|
28
|
+
"coding-agents",
|
|
29
|
+
"memory",
|
|
30
|
+
"local-first"
|
|
31
|
+
]
|
|
32
|
+
}
|