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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chainlesschain",
3
- "version": "0.37.6",
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/yourname/chainlesschain.git",
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 = "yourname";
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
- export const BINARY_NAMES = {
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
 
@@ -1,30 +1,48 @@
1
- import { createWriteStream, existsSync, unlinkSync } from "node:fs";
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 { getBinaryName } from "./platform.js";
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 (existsSync(destPath) && !options.force) {
18
- logger.info(`Binary already exists: ${binaryName}`);
19
- return destPath;
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 releaseUrl =
23
- options.url || (await resolveAssetUrl(version, binaryName));
24
- const spinner = ora(`Downloading ${binaryName}...`).start();
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(releaseUrl, {
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 ${binaryName}... ${pct}% (${formatBytes(downloadedBytes)}/${formatBytes(totalBytes)})`;
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 ${binaryName} (${formatBytes(downloadedBytes)})`,
83
+ `Downloaded ${assetName} (${formatBytes(downloadedBytes)})`,
66
84
  );
67
85
 
68
- if (options.checksum) {
69
- const verifySpinner = ora("Verifying checksum...").start();
70
- const result = await verifySha256(destPath, options.checksum);
71
- if (!result.valid) {
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
- verifySpinner.fail("Checksum verification failed");
74
- throw new Error(
75
- `SHA256 mismatch: expected ${result.expected}, got ${result.actual}`,
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 destPath;
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, binaryName) {
109
+ async function resolveAssetUrl(version) {
110
+ // Try exact version first, then fall back to latest release
92
111
  const tagName = `v${version}`;
93
- const url = `${GITHUB_RELEASES_URL}/tags/${tagName}`;
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
- if (!response.ok) {
100
- throw new Error(
101
- `Failed to find release ${tagName}: HTTP ${response.status}`,
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
- const release = await response.json();
106
- const asset = release.assets.find((a) => a.name === binaryName);
107
- if (!asset) {
108
- throw new Error(`Asset ${binaryName} not found in release ${tagName}`);
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) {
@@ -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
- const files = readdirSync(binDir);
114
- const match = files.find((f) => {
115
- const isChainless = f.toLowerCase().includes("chainlesschain");
116
- if (extension) return isChainless && f.endsWith(extension);
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
+ }