aether-colony 1.0.17
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 +59 -0
- package/bin/aether.js +9 -0
- package/lib/bootstrap.js +463 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="https://raw.githubusercontent.com/calcosmic/Aether/main/assets/banner/banner.jpg" alt="Aether Banner" width="100%" />
|
|
4
|
+
|
|
5
|
+
<img src="https://raw.githubusercontent.com/calcosmic/Aether/main/assets/logo/logo.jpg" alt="Aether Logo" width="140" />
|
|
6
|
+
|
|
7
|
+
# Aether
|
|
8
|
+
|
|
9
|
+
**Artificial Ecology for Thought and Emergent Reasoning**
|
|
10
|
+
|
|
11
|
+
[](https://github.com/calcosmic/Aether/releases)
|
|
12
|
+
[](https://github.com/calcosmic/Aether/blob/main/LICENSE)
|
|
13
|
+
[](https://github.com/calcosmic/Aether/stargazers)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx --yes aether-colony@latest
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
`aether-colony` is the low-friction npm bootstrap for Aether. It is not a second runtime. It downloads the matching published Go `aether` binary for your platform, installs it into a stable local directory, and then hands off to the real CLI.
|
|
22
|
+
|
|
23
|
+
The npm package version intentionally matches the published Go release version.
|
|
24
|
+
There is one public Aether version, not one version for npm and another for the
|
|
25
|
+
runtime.
|
|
26
|
+
|
|
27
|
+
## What happens on first run
|
|
28
|
+
|
|
29
|
+
1. The wrapper resolves the matching Aether release for your platform.
|
|
30
|
+
2. It downloads and verifies the release archive from GitHub Releases.
|
|
31
|
+
3. It installs the binary locally.
|
|
32
|
+
4. It runs `aether install` so the hub and companion files are populated.
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx --yes aether-colony@latest
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Hand off to the real CLI
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx --yes aether-colony@latest -- status
|
|
44
|
+
npx --yes aether-colony@latest -- update --force --download-binary
|
|
45
|
+
npx --yes aether-colony@latest -- init "Build feature X"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Important distinction
|
|
49
|
+
|
|
50
|
+
- `aether-colony` is the bootstrap and discovery path.
|
|
51
|
+
- The real runtime is the Go `aether` binary.
|
|
52
|
+
- After the first install, users should normally run `aether ...` directly.
|
|
53
|
+
- `aether-colony@1.0.17` is expected to bootstrap Aether `1.0.17`.
|
|
54
|
+
|
|
55
|
+
## Source and docs
|
|
56
|
+
|
|
57
|
+
- GitHub: https://github.com/calcosmic/Aether
|
|
58
|
+
- Install guide: https://github.com/calcosmic/Aether#-install
|
|
59
|
+
- Release notes: https://github.com/calcosmic/Aether/releases
|
package/bin/aether.js
ADDED
package/lib/bootstrap.js
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const fsp = require("node:fs/promises");
|
|
6
|
+
const http = require("node:http");
|
|
7
|
+
const https = require("node:https");
|
|
8
|
+
const os = require("node:os");
|
|
9
|
+
const path = require("node:path");
|
|
10
|
+
const { spawnSync, execFileSync } = require("node:child_process");
|
|
11
|
+
const packageJson = require("../package.json");
|
|
12
|
+
|
|
13
|
+
const REPO_OWNER = "calcosmic";
|
|
14
|
+
const REPO_NAME = "Aether";
|
|
15
|
+
const DEFAULT_AETHER_VERSION = packageJson.version;
|
|
16
|
+
const PACKAGE_VERSION = packageJson.version;
|
|
17
|
+
const MAX_REDIRECTS = 5;
|
|
18
|
+
const BANNER = `
|
|
19
|
+
█████╗ ███████╗████████╗██╗ ██╗███████╗██████╗
|
|
20
|
+
██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔════╝██╔══██╗
|
|
21
|
+
███████║█████╗ ██║ ███████║█████╗ ██████╔╝
|
|
22
|
+
██╔══██║██╔══╝ ██║ ██╔══██║██╔══╝ ██╔══██╗
|
|
23
|
+
██║ ██║███████╗ ██║ ██║ ██║███████╗██║ ██║
|
|
24
|
+
╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
function printBootstrapHelp() {
|
|
28
|
+
console.log(BANNER.trimEnd());
|
|
29
|
+
console.log("");
|
|
30
|
+
console.log("Aether npm bootstrap");
|
|
31
|
+
console.log("");
|
|
32
|
+
console.log("Usage:");
|
|
33
|
+
console.log(" npx --yes aether-colony@latest");
|
|
34
|
+
console.log(" npx --yes aether-colony@latest -- <aether args>");
|
|
35
|
+
console.log("");
|
|
36
|
+
console.log("What it does:");
|
|
37
|
+
console.log(" 1. Downloads the matching Go release binary for your platform");
|
|
38
|
+
console.log(" 2. Installs it into a stable local directory");
|
|
39
|
+
console.log(" 3. Runs the real `aether` CLI");
|
|
40
|
+
console.log("");
|
|
41
|
+
console.log("Flags:");
|
|
42
|
+
console.log(" --bootstrap-help Show this help");
|
|
43
|
+
console.log(" --bootstrap-version Print the npm wrapper version");
|
|
44
|
+
console.log(" --aether-version <ver> Override the Go release version to install");
|
|
45
|
+
console.log(" --dest <path> Override the install directory for the Go binary");
|
|
46
|
+
console.log("");
|
|
47
|
+
console.log("Examples:");
|
|
48
|
+
console.log(" npx --yes aether-colony@latest");
|
|
49
|
+
console.log(" npx --yes aether-colony@latest -- status");
|
|
50
|
+
console.log(" npx --yes aether-colony@latest -- update --force --download-binary");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeArgs(argv) {
|
|
54
|
+
const args = [...argv];
|
|
55
|
+
const bootstrap = {
|
|
56
|
+
help: false,
|
|
57
|
+
wrapperVersion: false,
|
|
58
|
+
aetherVersion: DEFAULT_AETHER_VERSION,
|
|
59
|
+
dest: null,
|
|
60
|
+
passthrough: []
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
64
|
+
const arg = args[i];
|
|
65
|
+
if (arg === "--") {
|
|
66
|
+
bootstrap.passthrough = args.slice(i + 1);
|
|
67
|
+
return bootstrap;
|
|
68
|
+
}
|
|
69
|
+
if (arg === "--bootstrap-help") {
|
|
70
|
+
bootstrap.help = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (arg === "--bootstrap-version") {
|
|
74
|
+
bootstrap.wrapperVersion = true;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (arg === "--aether-version") {
|
|
78
|
+
bootstrap.aetherVersion = normalizeVersion(args[i + 1]);
|
|
79
|
+
i += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (arg.startsWith("--aether-version=")) {
|
|
83
|
+
bootstrap.aetherVersion = normalizeVersion(arg.split("=", 2)[1]);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (arg === "--dest") {
|
|
87
|
+
bootstrap.dest = args[i + 1];
|
|
88
|
+
i += 1;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (arg.startsWith("--dest=")) {
|
|
92
|
+
bootstrap.dest = arg.split("=", 2)[1];
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
bootstrap.passthrough = args.slice(i);
|
|
96
|
+
return bootstrap;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return bootstrap;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function normalizeVersion(version) {
|
|
103
|
+
return String(version || "").trim().replace(/^v/, "");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function detectPlatform(nodePlatform = process.platform, nodeArch = process.arch) {
|
|
107
|
+
const osMap = {
|
|
108
|
+
darwin: "darwin",
|
|
109
|
+
linux: "linux",
|
|
110
|
+
win32: "windows"
|
|
111
|
+
};
|
|
112
|
+
const archMap = {
|
|
113
|
+
x64: "amd64",
|
|
114
|
+
arm64: "arm64"
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const osName = osMap[nodePlatform];
|
|
118
|
+
const archName = archMap[nodeArch];
|
|
119
|
+
if (!osName || !archName) {
|
|
120
|
+
throw new Error(`Unsupported platform: ${nodePlatform}/${nodeArch}. Supported: darwin/linux/windows + x64/arm64.`);
|
|
121
|
+
}
|
|
122
|
+
return { os: osName, arch: archName };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function archiveFilename(version, platform) {
|
|
126
|
+
const ext = platform.os === "windows" ? ".zip" : ".tar.gz";
|
|
127
|
+
return `aether_v${version}_${platform.os}_${platform.arch}${ext}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function checksumsFilename(version) {
|
|
131
|
+
return `aether_v${version}_checksums.txt`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function releaseBaseURL(version) {
|
|
135
|
+
return `https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/v${version}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function archiveURL(version, platform) {
|
|
139
|
+
return `${releaseBaseURL(version)}/${archiveFilename(version, platform)}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function checksumsURL(version) {
|
|
143
|
+
return `${releaseBaseURL(version)}/${checksumsFilename(version)}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function binaryName(platform) {
|
|
147
|
+
return platform.os === "windows" ? "aether.exe" : "aether";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function defaultDestDir(homeDir = os.homedir(), nodePlatform = process.platform) {
|
|
151
|
+
if (process.env.AETHER_BINARY_DEST) {
|
|
152
|
+
return process.env.AETHER_BINARY_DEST;
|
|
153
|
+
}
|
|
154
|
+
if (nodePlatform === "win32") {
|
|
155
|
+
return path.join(homeDir, ".aether", "bin");
|
|
156
|
+
}
|
|
157
|
+
const localBin = path.join(homeDir, ".local", "bin");
|
|
158
|
+
if (fs.existsSync(localBin) && fs.statSync(localBin).isDirectory()) {
|
|
159
|
+
return localBin;
|
|
160
|
+
}
|
|
161
|
+
return path.join(homeDir, ".aether", "bin");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function installedBinaryPath(destDir, platform) {
|
|
165
|
+
return path.join(destDir, binaryName(platform));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function hubVersionFile(homeDir = os.homedir()) {
|
|
169
|
+
return path.join(homeDir, ".aether", "version.json");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function hasHubInstalled(homeDir = os.homedir()) {
|
|
173
|
+
return fs.existsSync(hubVersionFile(homeDir));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isBinaryOnPath(binaryPath) {
|
|
177
|
+
const pathEntries = String(process.env.PATH || "").split(path.delimiter);
|
|
178
|
+
return pathEntries.some((entry) => path.resolve(entry || ".") === path.resolve(path.dirname(binaryPath)));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function parseChecksum(content, filename) {
|
|
182
|
+
const lines = String(content).split(/\r?\n/);
|
|
183
|
+
for (const line of lines) {
|
|
184
|
+
const parts = line.split(/\s{2,}/);
|
|
185
|
+
if (parts.length >= 2 && parts[1] === filename) {
|
|
186
|
+
return parts[0].trim();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
throw new Error(`Checksum not found for ${filename}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function parseVersionOutput(stdout) {
|
|
193
|
+
const trimmed = String(stdout || "").trim();
|
|
194
|
+
if (!trimmed) {
|
|
195
|
+
return "";
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const parsed = JSON.parse(trimmed);
|
|
199
|
+
if (parsed && parsed.ok === true) {
|
|
200
|
+
return normalizeVersion(parsed.result);
|
|
201
|
+
}
|
|
202
|
+
} catch (_) {
|
|
203
|
+
// fall through
|
|
204
|
+
}
|
|
205
|
+
return normalizeVersion(trimmed);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function getInstalledVersion(binaryPath) {
|
|
209
|
+
if (!fs.existsSync(binaryPath)) {
|
|
210
|
+
return "";
|
|
211
|
+
}
|
|
212
|
+
const result = spawnSync(binaryPath, ["version"], {
|
|
213
|
+
encoding: "utf8",
|
|
214
|
+
env: {
|
|
215
|
+
...process.env,
|
|
216
|
+
AETHER_OUTPUT_MODE: "json"
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
if (result.status !== 0) {
|
|
220
|
+
return "";
|
|
221
|
+
}
|
|
222
|
+
return parseVersionOutput(result.stdout);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function needsInstall(binaryPath, targetVersion) {
|
|
226
|
+
const installedVersion = getInstalledVersion(binaryPath);
|
|
227
|
+
return installedVersion !== normalizeVersion(targetVersion);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function requestWithRedirects(url, redirectsLeft = MAX_REDIRECTS) {
|
|
231
|
+
return new Promise((resolve, reject) => {
|
|
232
|
+
const transport = url.startsWith("https:") ? https : http;
|
|
233
|
+
const req = transport.get(
|
|
234
|
+
url,
|
|
235
|
+
{
|
|
236
|
+
headers: {
|
|
237
|
+
"User-Agent": `${packageJson.name}/${PACKAGE_VERSION}`
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
(res) => {
|
|
241
|
+
const status = res.statusCode || 0;
|
|
242
|
+
if ([301, 302, 303, 307, 308].includes(status) && res.headers.location) {
|
|
243
|
+
if (redirectsLeft <= 0) {
|
|
244
|
+
reject(new Error(`Too many redirects while fetching ${url}`));
|
|
245
|
+
res.resume();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const nextURL = new URL(res.headers.location, url).toString();
|
|
249
|
+
res.resume();
|
|
250
|
+
resolve(requestWithRedirects(nextURL, redirectsLeft - 1));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (status < 200 || status >= 300) {
|
|
254
|
+
reject(new Error(`HTTP ${status} for ${url}`));
|
|
255
|
+
res.resume();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
resolve(res);
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
req.on("error", reject);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function downloadText(url) {
|
|
266
|
+
const response = await requestWithRedirects(url);
|
|
267
|
+
const chunks = [];
|
|
268
|
+
for await (const chunk of response) {
|
|
269
|
+
chunks.push(chunk);
|
|
270
|
+
}
|
|
271
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function downloadFile(url, destination) {
|
|
275
|
+
const response = await requestWithRedirects(url);
|
|
276
|
+
await fsp.mkdir(path.dirname(destination), { recursive: true });
|
|
277
|
+
const out = fs.createWriteStream(destination);
|
|
278
|
+
await new Promise((resolve, reject) => {
|
|
279
|
+
response.pipe(out);
|
|
280
|
+
response.on("error", reject);
|
|
281
|
+
out.on("error", reject);
|
|
282
|
+
out.on("finish", resolve);
|
|
283
|
+
});
|
|
284
|
+
await fsp.chmod(destination, 0o644);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function sha256File(filePath) {
|
|
288
|
+
const hash = crypto.createHash("sha256");
|
|
289
|
+
const stream = fs.createReadStream(filePath);
|
|
290
|
+
return new Promise((resolve, reject) => {
|
|
291
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
292
|
+
stream.on("error", reject);
|
|
293
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function extractArchive(archivePath, extractDir, platform) {
|
|
298
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
299
|
+
if (platform.os === "windows") {
|
|
300
|
+
execFileSync(
|
|
301
|
+
"powershell.exe",
|
|
302
|
+
[
|
|
303
|
+
"-NoLogo",
|
|
304
|
+
"-NoProfile",
|
|
305
|
+
"-NonInteractive",
|
|
306
|
+
"-Command",
|
|
307
|
+
`Expand-Archive -LiteralPath '${archivePath.replace(/'/g, "''")}' -DestinationPath '${extractDir.replace(/'/g, "''")}' -Force`
|
|
308
|
+
],
|
|
309
|
+
{ stdio: "ignore" }
|
|
310
|
+
);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
execFileSync("tar", ["-xzf", archivePath, "-C", extractDir], {
|
|
315
|
+
stdio: "ignore"
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function findBinaryRecursively(baseDir, binaryFile) {
|
|
320
|
+
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
321
|
+
for (const entry of entries) {
|
|
322
|
+
const fullPath = path.join(baseDir, entry.name);
|
|
323
|
+
if (entry.isFile() && entry.name === binaryFile) {
|
|
324
|
+
return fullPath;
|
|
325
|
+
}
|
|
326
|
+
if (entry.isDirectory()) {
|
|
327
|
+
const nested = findBinaryRecursively(fullPath, binaryFile);
|
|
328
|
+
if (nested) {
|
|
329
|
+
return nested;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return "";
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function installReleaseBinary(version, destDir) {
|
|
337
|
+
const platform = detectPlatform();
|
|
338
|
+
const archiveFile = archiveFilename(version, platform);
|
|
339
|
+
const tmpRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "aether-npm-"));
|
|
340
|
+
const archivePath = path.join(tmpRoot, archiveFile);
|
|
341
|
+
const extractDir = path.join(tmpRoot, "extract");
|
|
342
|
+
const destPath = installedBinaryPath(destDir, platform);
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
const checksums = await downloadText(checksumsURL(version));
|
|
346
|
+
const expected = parseChecksum(checksums, archiveFile);
|
|
347
|
+
await downloadFile(archiveURL(version, platform), archivePath);
|
|
348
|
+
const actual = await sha256File(archivePath);
|
|
349
|
+
if (actual !== expected) {
|
|
350
|
+
throw new Error(`Checksum mismatch for ${archiveFile}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
extractArchive(archivePath, extractDir, platform);
|
|
354
|
+
const extractedBinary = findBinaryRecursively(extractDir, binaryName(platform));
|
|
355
|
+
if (!extractedBinary) {
|
|
356
|
+
throw new Error(`Binary ${binaryName(platform)} not found in extracted archive`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
await fsp.mkdir(destDir, { recursive: true });
|
|
360
|
+
const tmpDest = `${destPath}.tmp-${process.pid}`;
|
|
361
|
+
await fsp.copyFile(extractedBinary, tmpDest);
|
|
362
|
+
if (platform.os !== "windows") {
|
|
363
|
+
await fsp.chmod(tmpDest, 0o755);
|
|
364
|
+
}
|
|
365
|
+
await fsp.rm(destPath, { force: true });
|
|
366
|
+
await fsp.rename(tmpDest, destPath);
|
|
367
|
+
return destPath;
|
|
368
|
+
} finally {
|
|
369
|
+
await fsp.rm(tmpRoot, { recursive: true, force: true });
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function runAether(binaryPath, args) {
|
|
374
|
+
const result = spawnSync(binaryPath, args, {
|
|
375
|
+
stdio: "inherit",
|
|
376
|
+
env: process.env
|
|
377
|
+
});
|
|
378
|
+
if (typeof result.status === "number") {
|
|
379
|
+
process.exit(result.status);
|
|
380
|
+
}
|
|
381
|
+
if (result.error) {
|
|
382
|
+
throw result.error;
|
|
383
|
+
}
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async function main(argv) {
|
|
388
|
+
const options = normalizeArgs(argv);
|
|
389
|
+
if (options.help) {
|
|
390
|
+
printBootstrapHelp();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (options.wrapperVersion) {
|
|
394
|
+
console.log(PACKAGE_VERSION);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const aetherVersion = normalizeVersion(options.aetherVersion);
|
|
399
|
+
if (!aetherVersion) {
|
|
400
|
+
throw new Error("Missing Aether release version for bootstrap");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const platform = detectPlatform();
|
|
404
|
+
const destDir = path.resolve(options.dest || defaultDestDir());
|
|
405
|
+
const binaryPath = installedBinaryPath(destDir, platform);
|
|
406
|
+
const firstInstall = !fs.existsSync(binaryPath);
|
|
407
|
+
const hubMissing = !hasHubInstalled();
|
|
408
|
+
|
|
409
|
+
console.log(BANNER.trimEnd());
|
|
410
|
+
console.log("");
|
|
411
|
+
console.log(`Bootstrapping Aether ${aetherVersion} via npm package ${packageJson.name}@${PACKAGE_VERSION}`);
|
|
412
|
+
console.log(`Install directory: ${destDir}`);
|
|
413
|
+
|
|
414
|
+
if (needsInstall(binaryPath, aetherVersion)) {
|
|
415
|
+
console.log(`Downloading Go release binary for ${platform.os}/${platform.arch}...`);
|
|
416
|
+
await installReleaseBinary(aetherVersion, destDir);
|
|
417
|
+
console.log(`Installed ${binaryPath}`);
|
|
418
|
+
} else {
|
|
419
|
+
console.log(`Found Aether ${aetherVersion} already installed at ${binaryPath}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (!isBinaryOnPath(binaryPath)) {
|
|
423
|
+
console.log("");
|
|
424
|
+
console.log("PATH note:");
|
|
425
|
+
console.log(` Add ${destDir} to your PATH if you want to run \`aether\` directly outside npm.`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const passthrough = options.passthrough.length > 0 ? options.passthrough : ["install"];
|
|
429
|
+
if ((firstInstall || hubMissing) && passthrough[0] !== "install") {
|
|
430
|
+
console.log("");
|
|
431
|
+
console.log("Initial setup detected. Initializing companion files before handing off to the requested command.");
|
|
432
|
+
const installResult = spawnSync(binaryPath, ["install"], {
|
|
433
|
+
stdio: "inherit",
|
|
434
|
+
env: process.env
|
|
435
|
+
});
|
|
436
|
+
if (installResult.status !== 0) {
|
|
437
|
+
process.exit(typeof installResult.status === "number" ? installResult.status : 1);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
console.log("");
|
|
442
|
+
console.log(`Handing off to: ${path.basename(binaryPath)} ${passthrough.join(" ")}`);
|
|
443
|
+
runAether(binaryPath, passthrough);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
module.exports = {
|
|
447
|
+
MAX_REDIRECTS,
|
|
448
|
+
archiveFilename,
|
|
449
|
+
archiveURL,
|
|
450
|
+
binaryName,
|
|
451
|
+
checksumsFilename,
|
|
452
|
+
checksumsURL,
|
|
453
|
+
defaultDestDir,
|
|
454
|
+
detectPlatform,
|
|
455
|
+
installedBinaryPath,
|
|
456
|
+
main,
|
|
457
|
+
normalizeArgs,
|
|
458
|
+
normalizeVersion,
|
|
459
|
+
parseChecksum,
|
|
460
|
+
parseVersionOutput,
|
|
461
|
+
hasHubInstalled,
|
|
462
|
+
releaseBaseURL
|
|
463
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aether-colony",
|
|
3
|
+
"version": "1.0.17",
|
|
4
|
+
"description": "Bootstrap installer for the Aether ant colony CLI",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://github.com/calcosmic/Aether#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/calcosmic/Aether.git",
|
|
10
|
+
"directory": "npm"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/calcosmic/Aether/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public",
|
|
17
|
+
"tag": "latest"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"aether",
|
|
21
|
+
"aether-colony",
|
|
22
|
+
"ai",
|
|
23
|
+
"agents",
|
|
24
|
+
"multi-agent",
|
|
25
|
+
"ant-colony",
|
|
26
|
+
"claude-code",
|
|
27
|
+
"opencode",
|
|
28
|
+
"codex"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"bin": {
|
|
34
|
+
"aether": "./bin/aether.js"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"bin",
|
|
38
|
+
"lib",
|
|
39
|
+
"README.md"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"test": "node --test test/*.test.js"
|
|
43
|
+
}
|
|
44
|
+
}
|