chainlesschain 0.37.6 → 0.37.8
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/package.json +2 -2
- package/src/constants.js +2 -9
- package/src/lib/downloader.js +107 -35
- package/src/lib/platform.js +0 -29
- package/src/lib/process-manager.js +33 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chainlesschain",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.8",
|
|
4
4
|
"description": "CLI for ChainlessChain - install, configure, and manage your personal AI management system",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "https://github.com/
|
|
39
|
+
"url": "https://github.com/chainlesschain/chainlesschain.git",
|
|
40
40
|
"directory": "packages/cli"
|
|
41
41
|
},
|
|
42
42
|
"homepage": "https://www.chainlesschain.com",
|
package/src/constants.js
CHANGED
|
@@ -9,7 +9,7 @@ const pkg = require(join(__dirname, "..", "package.json"));
|
|
|
9
9
|
|
|
10
10
|
export const VERSION = pkg.version;
|
|
11
11
|
|
|
12
|
-
export const GITHUB_OWNER = "
|
|
12
|
+
export const GITHUB_OWNER = "chainlesschain";
|
|
13
13
|
export const GITHUB_REPO = "chainlesschain";
|
|
14
14
|
export const GITHUB_API_BASE = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}`;
|
|
15
15
|
export const GITHUB_RELEASES_URL = `${GITHUB_API_BASE}/releases`;
|
|
@@ -69,14 +69,7 @@ export const EDITIONS = {
|
|
|
69
69
|
},
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
win32: { x64: "chainlesschain-desktop-vue-{version}-win-x64.exe" },
|
|
74
|
-
darwin: {
|
|
75
|
-
x64: "chainlesschain-desktop-vue-{version}-mac-x64.dmg",
|
|
76
|
-
arm64: "chainlesschain-desktop-vue-{version}-mac-arm64.dmg",
|
|
77
|
-
},
|
|
78
|
-
linux: { x64: "chainlesschain-desktop-vue-{version}-linux-x64.deb" },
|
|
79
|
-
};
|
|
72
|
+
// Asset matching is now done by platform patterns in downloader.js
|
|
80
73
|
|
|
81
74
|
export const CONFIG_DIR_NAME = ".chainlesschain";
|
|
82
75
|
|
package/src/lib/downloader.js
CHANGED
|
@@ -1,30 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createWriteStream,
|
|
3
|
+
existsSync,
|
|
4
|
+
unlinkSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
chmodSync,
|
|
7
|
+
} from "node:fs";
|
|
2
8
|
import { join } from "node:path";
|
|
3
9
|
import { pipeline } from "node:stream/promises";
|
|
4
10
|
import { Readable } from "node:stream";
|
|
11
|
+
import { execSync } from "node:child_process";
|
|
5
12
|
import ora from "ora";
|
|
6
13
|
import { GITHUB_RELEASES_URL } from "../constants.js";
|
|
7
14
|
import { getBinDir, ensureDir } from "./paths.js";
|
|
8
|
-
import {
|
|
9
|
-
import { verifySha256 } from "./checksum.js";
|
|
15
|
+
import { getPlatform, getArch } from "./platform.js";
|
|
10
16
|
import logger from "./logger.js";
|
|
11
17
|
|
|
18
|
+
// Platform-specific asset matching patterns
|
|
19
|
+
const ASSET_PATTERNS = {
|
|
20
|
+
win32: { x64: /chainlesschain.*win32.*x64.*\.zip$/i },
|
|
21
|
+
darwin: {
|
|
22
|
+
x64: /chainlesschain.*darwin.*x64.*\.zip$/i,
|
|
23
|
+
arm64: /chainlesschain.*darwin.*arm64.*\.zip$/i,
|
|
24
|
+
},
|
|
25
|
+
linux: { x64: /chainlesschain.*linux.*x64.*\.zip$/i },
|
|
26
|
+
};
|
|
27
|
+
|
|
12
28
|
export async function downloadRelease(version, options = {}) {
|
|
13
|
-
const binaryName = getBinaryName(version);
|
|
14
29
|
const binDir = ensureDir(getBinDir());
|
|
15
|
-
const destPath = join(binDir, binaryName);
|
|
16
30
|
|
|
17
|
-
if
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
// Check if we already have an extracted app
|
|
32
|
+
if (!options.force && hasExtractedApp(binDir)) {
|
|
33
|
+
logger.info("Application already installed");
|
|
34
|
+
return binDir;
|
|
20
35
|
}
|
|
21
36
|
|
|
22
|
-
const
|
|
23
|
-
options.url
|
|
24
|
-
|
|
37
|
+
const { url: assetUrl, name: assetName } = options.url
|
|
38
|
+
? { url: options.url, name: "download.zip" }
|
|
39
|
+
: await resolveAssetUrl(version);
|
|
40
|
+
|
|
41
|
+
const destPath = join(binDir, assetName);
|
|
42
|
+
const spinner = ora(`Downloading ${assetName}...`).start();
|
|
25
43
|
|
|
26
44
|
try {
|
|
27
|
-
const response = await fetch(
|
|
45
|
+
const response = await fetch(assetUrl, {
|
|
28
46
|
headers: { Accept: "application/octet-stream" },
|
|
29
47
|
redirect: "follow",
|
|
30
48
|
});
|
|
@@ -52,7 +70,7 @@ export async function downloadRelease(version, options = {}) {
|
|
|
52
70
|
downloadedBytes += value.byteLength;
|
|
53
71
|
if (totalBytes > 0) {
|
|
54
72
|
const pct = ((downloadedBytes / totalBytes) * 100).toFixed(1);
|
|
55
|
-
spinner.text = `Downloading ${
|
|
73
|
+
spinner.text = `Downloading ${assetName}... ${pct}% (${formatBytes(downloadedBytes)}/${formatBytes(totalBytes)})`;
|
|
56
74
|
}
|
|
57
75
|
controller.enqueue(value);
|
|
58
76
|
},
|
|
@@ -62,23 +80,23 @@ export async function downloadRelease(version, options = {}) {
|
|
|
62
80
|
await pipeline(nodeStream, createWriteStream(destPath));
|
|
63
81
|
|
|
64
82
|
spinner.succeed(
|
|
65
|
-
`Downloaded ${
|
|
83
|
+
`Downloaded ${assetName} (${formatBytes(downloadedBytes)})`,
|
|
66
84
|
);
|
|
67
85
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
86
|
+
// Extract zip
|
|
87
|
+
if (destPath.endsWith(".zip")) {
|
|
88
|
+
const extractSpinner = ora("Extracting...").start();
|
|
89
|
+
try {
|
|
90
|
+
extractZip(destPath, binDir);
|
|
72
91
|
unlinkSync(destPath);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
extractSpinner.succeed("Extracted and ready");
|
|
93
|
+
} catch (err) {
|
|
94
|
+
extractSpinner.fail(`Extraction failed: ${err.message}`);
|
|
95
|
+
throw err;
|
|
77
96
|
}
|
|
78
|
-
verifySpinner.succeed("Checksum verified");
|
|
79
97
|
}
|
|
80
98
|
|
|
81
|
-
return
|
|
99
|
+
return binDir;
|
|
82
100
|
} catch (err) {
|
|
83
101
|
spinner.fail(`Download failed: ${err.message}`);
|
|
84
102
|
if (existsSync(destPath)) {
|
|
@@ -88,27 +106,81 @@ export async function downloadRelease(version, options = {}) {
|
|
|
88
106
|
}
|
|
89
107
|
}
|
|
90
108
|
|
|
91
|
-
async function resolveAssetUrl(version
|
|
109
|
+
async function resolveAssetUrl(version) {
|
|
110
|
+
// Try exact version first, then fall back to latest release
|
|
92
111
|
const tagName = `v${version}`;
|
|
93
|
-
|
|
112
|
+
let release = await fetchRelease(`${GITHUB_RELEASES_URL}/tags/${tagName}`);
|
|
113
|
+
|
|
114
|
+
if (!release) {
|
|
115
|
+
logger.info(`Release ${tagName} not found, fetching latest release...`);
|
|
116
|
+
release = await fetchRelease(`${GITHUB_RELEASES_URL}/latest`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!release) {
|
|
120
|
+
throw new Error("No releases found on GitHub");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
logger.info(`Using release ${release.tag_name}`);
|
|
124
|
+
|
|
125
|
+
// Match asset by platform/arch pattern
|
|
126
|
+
const p = getPlatform();
|
|
127
|
+
const a = getArch();
|
|
128
|
+
const patterns = ASSET_PATTERNS[p];
|
|
129
|
+
if (!patterns) {
|
|
130
|
+
throw new Error(`Unsupported platform: ${p}`);
|
|
131
|
+
}
|
|
132
|
+
const pattern = patterns[a];
|
|
133
|
+
if (!pattern) {
|
|
134
|
+
throw new Error(`Unsupported architecture: ${a} on ${p}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const asset = release.assets.find((ast) => pattern.test(ast.name));
|
|
138
|
+
if (!asset) {
|
|
139
|
+
const available = release.assets.map((ast) => ast.name).join(", ");
|
|
140
|
+
throw new Error(
|
|
141
|
+
`No matching asset for ${p}/${a} in release ${release.tag_name}. Available: ${available}`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
94
144
|
|
|
145
|
+
return { url: asset.browser_download_url, name: asset.name };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function fetchRelease(url) {
|
|
95
149
|
const response = await fetch(url, {
|
|
96
150
|
headers: { Accept: "application/vnd.github.v3+json" },
|
|
97
151
|
});
|
|
152
|
+
if (!response.ok) return null;
|
|
153
|
+
return response.json();
|
|
154
|
+
}
|
|
98
155
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
156
|
+
function extractZip(zipPath, destDir) {
|
|
157
|
+
const p = getPlatform();
|
|
158
|
+
if (p === "win32") {
|
|
159
|
+
// Use PowerShell on Windows
|
|
160
|
+
execSync(
|
|
161
|
+
`powershell -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`,
|
|
162
|
+
{ stdio: "ignore" },
|
|
102
163
|
);
|
|
164
|
+
} else {
|
|
165
|
+
execSync(`unzip -o "${zipPath}" -d "${destDir}"`, { stdio: "ignore" });
|
|
103
166
|
}
|
|
167
|
+
}
|
|
104
168
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
169
|
+
function hasExtractedApp(binDir) {
|
|
170
|
+
if (!existsSync(binDir)) return false;
|
|
171
|
+
try {
|
|
172
|
+
const files = readdirSync(binDir, { recursive: true });
|
|
173
|
+
return files.some(
|
|
174
|
+
(f) =>
|
|
175
|
+
(typeof f === "string" ? f : f.toString())
|
|
176
|
+
.toLowerCase()
|
|
177
|
+
.includes("chainlesschain") &&
|
|
178
|
+
((typeof f === "string" ? f : f.toString()).endsWith(".exe") ||
|
|
179
|
+
!(typeof f === "string" ? f : f.toString()).includes(".")),
|
|
180
|
+
);
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
109
183
|
}
|
|
110
|
-
|
|
111
|
-
return asset.browser_download_url;
|
|
112
184
|
}
|
|
113
185
|
|
|
114
186
|
function formatBytes(bytes) {
|
package/src/lib/platform.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { platform, arch } from "node:os";
|
|
2
|
-
import { BINARY_NAMES } from "../constants.js";
|
|
3
2
|
|
|
4
3
|
export function getPlatform() {
|
|
5
4
|
return platform();
|
|
@@ -12,34 +11,6 @@ export function getArch() {
|
|
|
12
11
|
return a;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
export function getBinaryName(version) {
|
|
16
|
-
const p = getPlatform();
|
|
17
|
-
const a = getArch();
|
|
18
|
-
const platformBinaries = BINARY_NAMES[p];
|
|
19
|
-
if (!platformBinaries) {
|
|
20
|
-
throw new Error(`Unsupported platform: ${p}`);
|
|
21
|
-
}
|
|
22
|
-
const template = platformBinaries[a];
|
|
23
|
-
if (!template) {
|
|
24
|
-
throw new Error(`Unsupported architecture: ${a} on ${p}`);
|
|
25
|
-
}
|
|
26
|
-
return template.replace("{version}", version);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function getBinaryExtension() {
|
|
30
|
-
const p = getPlatform();
|
|
31
|
-
switch (p) {
|
|
32
|
-
case "win32":
|
|
33
|
-
return ".exe";
|
|
34
|
-
case "darwin":
|
|
35
|
-
return ".dmg";
|
|
36
|
-
case "linux":
|
|
37
|
-
return ".deb";
|
|
38
|
-
default:
|
|
39
|
-
return "";
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
14
|
export function isWindows() {
|
|
44
15
|
return getPlatform() === "win32";
|
|
45
16
|
}
|
|
@@ -110,19 +110,40 @@ export function getAppPid() {
|
|
|
110
110
|
function findExecutable(binDir, extension) {
|
|
111
111
|
if (!existsSync(binDir)) return null;
|
|
112
112
|
try {
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
isChainless &&
|
|
119
|
-
!f.endsWith(".dmg") &&
|
|
120
|
-
!f.endsWith(".deb") &&
|
|
121
|
-
!f.endsWith(".sha256")
|
|
122
|
-
);
|
|
123
|
-
});
|
|
124
|
-
return match ? join(binDir, match) : null;
|
|
113
|
+
// Search recursively for the executable
|
|
114
|
+
const results = [];
|
|
115
|
+
searchDir(binDir, extension, results);
|
|
116
|
+
return results.length > 0 ? results[0] : null;
|
|
125
117
|
} catch {
|
|
126
118
|
return null;
|
|
127
119
|
}
|
|
128
120
|
}
|
|
121
|
+
|
|
122
|
+
function searchDir(dir, extension, results) {
|
|
123
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
const fullPath = join(dir, entry.name);
|
|
126
|
+
if (entry.isDirectory()) {
|
|
127
|
+
searchDir(fullPath, extension, results);
|
|
128
|
+
} else {
|
|
129
|
+
const name = entry.name.toLowerCase();
|
|
130
|
+
const isChainless = name.includes("chainlesschain");
|
|
131
|
+
if (!isChainless) continue;
|
|
132
|
+
if (extension && name.endsWith(extension)) {
|
|
133
|
+
results.push(fullPath);
|
|
134
|
+
} else if (
|
|
135
|
+
!extension &&
|
|
136
|
+
!name.endsWith(".dmg") &&
|
|
137
|
+
!name.endsWith(".deb") &&
|
|
138
|
+
!name.endsWith(".zip") &&
|
|
139
|
+
!name.endsWith(".sha256") &&
|
|
140
|
+
!name.endsWith(".dll") &&
|
|
141
|
+
!name.endsWith(".dat") &&
|
|
142
|
+
!name.endsWith(".pak") &&
|
|
143
|
+
!name.endsWith(".json")
|
|
144
|
+
) {
|
|
145
|
+
results.push(fullPath);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|