@that-company/dat 0.1.30
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 +30 -0
- package/bin/dat.js +26 -0
- package/package.json +39 -0
- package/scripts/install.js +137 -0
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# D.A.T. CLI
|
|
2
|
+
|
|
3
|
+
Install D.A.T. with npm:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm i -g @that-company/dat
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Then sign in:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
dat login
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This npm package is a thin installer. During `postinstall`, it downloads the
|
|
16
|
+
matching prebuilt `dat` binary from GitHub Releases, verifies it against
|
|
17
|
+
`SHA256SUMS`, and exposes it as the `dat` command.
|
|
18
|
+
|
|
19
|
+
Alternative install:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
curl -fsSL https://thatcompany.ai/install.sh | sh
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Environment Overrides
|
|
26
|
+
|
|
27
|
+
- `DAT_RELEASE_REPO`: release repo, default `theSalted/dat-releases`
|
|
28
|
+
- `DAT_INSTALL_BASE`: full release asset base URL
|
|
29
|
+
- `DAT_CHANNEL`: release path under `/releases`, default `download/v<package version>`
|
|
30
|
+
- `DAT_SKIP_INSTALL=1`: skip binary download during npm install
|
package/bin/dat.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require("node:child_process");
|
|
5
|
+
const { existsSync } = require("node:fs");
|
|
6
|
+
const { join } = require("node:path");
|
|
7
|
+
|
|
8
|
+
const binary = join(__dirname, "..", "vendor", process.platform === "win32" ? "dat.exe" : "dat");
|
|
9
|
+
|
|
10
|
+
if (!existsSync(binary)) {
|
|
11
|
+
console.error("dat binary is missing. Reinstall with: npm rebuild -g @thatcompany/dat");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
16
|
+
|
|
17
|
+
if (result.error) {
|
|
18
|
+
console.error(`failed to run dat: ${result.error.message}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (result.signal) {
|
|
23
|
+
process.kill(process.pid, result.signal);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
process.exit(result.status === null ? 1 : result.status);
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@that-company/dat",
|
|
3
|
+
"version": "0.1.30",
|
|
4
|
+
"description": "Installer for the D.A.T. CLI",
|
|
5
|
+
"bin": {
|
|
6
|
+
"dat": "bin/dat.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall": "node scripts/install.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"scripts",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/that-company/that.git",
|
|
19
|
+
"directory": "npm/dat"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://thatcompany.ai",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/that-company/that/issues"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"dat",
|
|
27
|
+
"agent",
|
|
28
|
+
"training",
|
|
29
|
+
"machine-learning",
|
|
30
|
+
"cli"
|
|
31
|
+
],
|
|
32
|
+
"license": "UNLICENSED",
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { createHash } = require("node:crypto");
|
|
5
|
+
const { createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, rmSync, chmodSync } = require("node:fs");
|
|
6
|
+
const { get } = require("node:https");
|
|
7
|
+
const { tmpdir } = require("node:os");
|
|
8
|
+
const { join } = require("node:path");
|
|
9
|
+
const { URL } = require("node:url");
|
|
10
|
+
|
|
11
|
+
const pkg = require("../package.json");
|
|
12
|
+
|
|
13
|
+
const REPO = process.env.DAT_RELEASE_REPO || "theSalted/dat-releases";
|
|
14
|
+
const CHANNEL = process.env.DAT_CHANNEL || `download/v${pkg.version}`;
|
|
15
|
+
const RELEASE_BASE = trimTrailingSlash(
|
|
16
|
+
process.env.DAT_INSTALL_BASE || `https://github.com/${REPO}/releases/${CHANNEL}`,
|
|
17
|
+
);
|
|
18
|
+
const PKG_ROOT = join(__dirname, "..");
|
|
19
|
+
const VENDOR_DIR = join(PKG_ROOT, "vendor");
|
|
20
|
+
const BIN_PATH = join(VENDOR_DIR, process.platform === "win32" ? "dat.exe" : "dat");
|
|
21
|
+
|
|
22
|
+
main().catch((error) => {
|
|
23
|
+
console.error(`dat install failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
async function main() {
|
|
28
|
+
if (process.env.DAT_SKIP_INSTALL === "1") {
|
|
29
|
+
console.error("DAT_SKIP_INSTALL=1; skipping dat binary download");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const asset = releaseAssetName(process.platform, process.arch);
|
|
34
|
+
mkdirSync(VENDOR_DIR, { recursive: true });
|
|
35
|
+
|
|
36
|
+
const tempDir = join(tmpdir(), `dat-npm-${process.pid}-${Date.now()}`);
|
|
37
|
+
mkdirSync(tempDir, { recursive: true });
|
|
38
|
+
try {
|
|
39
|
+
const tempBin = join(tempDir, asset);
|
|
40
|
+
const tempSums = join(tempDir, "SHA256SUMS");
|
|
41
|
+
const assetUrl = `${RELEASE_BASE}/${asset}`;
|
|
42
|
+
const sumsUrl = `${RELEASE_BASE}/SHA256SUMS`;
|
|
43
|
+
|
|
44
|
+
console.error(`downloading ${asset} from ${RELEASE_BASE}`);
|
|
45
|
+
await download(assetUrl, tempBin);
|
|
46
|
+
await download(sumsUrl, tempSums);
|
|
47
|
+
verifyChecksum(tempBin, tempSums, asset);
|
|
48
|
+
|
|
49
|
+
renameSync(tempBin, BIN_PATH);
|
|
50
|
+
chmodSync(BIN_PATH, 0o755);
|
|
51
|
+
console.error(`installed dat ${pkg.version} -> ${BIN_PATH}`);
|
|
52
|
+
} finally {
|
|
53
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function releaseAssetName(platform, arch) {
|
|
58
|
+
let os;
|
|
59
|
+
switch (platform) {
|
|
60
|
+
case "darwin":
|
|
61
|
+
os = "darwin";
|
|
62
|
+
break;
|
|
63
|
+
case "linux":
|
|
64
|
+
os = "linux";
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
throw new Error(`unsupported platform ${platform}; dat npm install currently supports macOS and Linux`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let cpu;
|
|
71
|
+
switch (arch) {
|
|
72
|
+
case "arm64":
|
|
73
|
+
cpu = "arm64";
|
|
74
|
+
break;
|
|
75
|
+
case "x64":
|
|
76
|
+
cpu = "x64";
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
throw new Error(`unsupported architecture ${arch}; dat npm install supports arm64 and x64`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return `dat-${os}-${cpu}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function trimTrailingSlash(value) {
|
|
86
|
+
return value.endsWith("/") ? trimTrailingSlash(value.slice(0, -1)) : value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function download(url, dest, redirects = 0) {
|
|
90
|
+
if (redirects > 5) {
|
|
91
|
+
return Promise.reject(new Error(`too many redirects while downloading ${url}`));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
const request = get(new URL(url), (response) => {
|
|
96
|
+
const status = response.statusCode || 0;
|
|
97
|
+
const location = response.headers.location;
|
|
98
|
+
if (status >= 300 && status < 400 && location) {
|
|
99
|
+
response.resume();
|
|
100
|
+
const next = new URL(location, url).toString();
|
|
101
|
+
download(next, dest, redirects + 1).then(resolve, reject);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (status !== 200) {
|
|
105
|
+
response.resume();
|
|
106
|
+
reject(new Error(`GET ${url} -> HTTP ${status}`));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const file = createWriteStream(dest, { mode: 0o600 });
|
|
110
|
+
response.pipe(file);
|
|
111
|
+
file.on("finish", () => file.close(resolve));
|
|
112
|
+
file.on("error", reject);
|
|
113
|
+
});
|
|
114
|
+
request.on("error", reject);
|
|
115
|
+
request.setTimeout(120_000, () => {
|
|
116
|
+
request.destroy(new Error(`timeout downloading ${url}`));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function verifyChecksum(filePath, sumsPath, asset) {
|
|
122
|
+
if (!existsSync(sumsPath)) {
|
|
123
|
+
throw new Error("SHA256SUMS was not downloaded");
|
|
124
|
+
}
|
|
125
|
+
const line = readFileSync(sumsPath, "utf8")
|
|
126
|
+
.split(/\r?\n/)
|
|
127
|
+
.find((entry) => entry.trim().endsWith(` ${asset}`) || entry.trim().endsWith(` ${asset}`));
|
|
128
|
+
if (!line) {
|
|
129
|
+
throw new Error(`SHA256SUMS has no entry for ${asset}`);
|
|
130
|
+
}
|
|
131
|
+
const expected = line.trim().split(/\s+/)[0];
|
|
132
|
+
const actual = createHash("sha256").update(readFileSync(filePath)).digest("hex");
|
|
133
|
+
if (actual !== expected) {
|
|
134
|
+
throw new Error(`checksum mismatch for ${asset} (expected ${expected}, got ${actual})`);
|
|
135
|
+
}
|
|
136
|
+
console.error("checksum ok");
|
|
137
|
+
}
|