@chro-ai/cli 0.1.0 → 0.1.1

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/bin/cli.js CHANGED
@@ -1,26 +1,140 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- const { spawn } = require("child_process");
4
+ const { execSync, spawn } = require("child_process");
5
5
  const path = require("path");
6
6
  const fs = require("fs");
7
- const { getBinaryName } = require("./platform");
7
+ const https = require("https");
8
+ const http = require("http");
9
+ const {
10
+ assertPublicUrlConfigured,
11
+ getPlatformDir,
12
+ getBinaryName,
13
+ R2_PUBLIC_URL,
14
+ } = require("./platform");
8
15
 
9
- const binName = getBinaryName("chro");
10
- const localBinary = path.resolve(__dirname, "..", "..", "target", "release", binName);
16
+ const platformDir = getPlatformDir();
17
+ const extractDir = path.join(__dirname, "..", "dist", platformDir);
11
18
 
12
- if (!fs.existsSync(localBinary)) {
13
- console.error("chro binary not found.");
14
- console.error("Build it first with: cd apps/cli && cargo build --release");
15
- console.error("This wrapper is scaffold-only; remote download is not wired yet.");
16
- process.exit(1);
19
+ fs.mkdirSync(extractDir, { recursive: true });
20
+
21
+ function download(url) {
22
+ return new Promise((resolve, reject) => {
23
+ const client = url.startsWith("https") ? https : http;
24
+ client
25
+ .get(url, (res) => {
26
+ if (
27
+ res.statusCode >= 300 &&
28
+ res.statusCode < 400 &&
29
+ res.headers.location
30
+ ) {
31
+ return download(res.headers.location).then(resolve, reject);
32
+ }
33
+ if (res.statusCode !== 200) {
34
+ reject(new Error(`HTTP ${res.statusCode} for ${url}`));
35
+ res.resume();
36
+ return;
37
+ }
38
+ resolve(res);
39
+ })
40
+ .on("error", reject);
41
+ });
17
42
  }
18
43
 
44
+ async function ensureBinaryZip(baseName) {
45
+ const zipPath = path.join(extractDir, `${baseName}.zip`);
46
+ if (fs.existsSync(zipPath)) return;
47
+
48
+ assertPublicUrlConfigured();
49
+
50
+ const version = require("../package.json").version;
51
+ const url = `${R2_PUBLIC_URL}/releases/v${version}/${platformDir}/${baseName}.zip`;
52
+ console.log("Binary not found locally. Downloading from R2...");
53
+
54
+ const res = await download(url);
55
+ const file = fs.createWriteStream(zipPath);
56
+ await new Promise((resolve, reject) => {
57
+ res.pipe(file);
58
+ file.on("finish", () => file.close(resolve));
59
+ file.on("error", (err) => {
60
+ fs.unlink(zipPath, () => {});
61
+ reject(err);
62
+ });
63
+ });
64
+ console.log("Download complete.");
65
+ }
66
+
67
+ function extractAndRun(baseName, launch) {
68
+ const binName = getBinaryName(baseName);
69
+ const binPath = path.join(extractDir, binName);
70
+ const zipPath = path.join(extractDir, `${baseName}.zip`);
71
+
72
+ if (fs.existsSync(binPath)) {
73
+ try {
74
+ fs.unlinkSync(binPath);
75
+ } catch (err) {
76
+ if (process.env.CHRO_DEBUG) {
77
+ console.warn(`Warning: Could not delete existing binary: ${err.message}`);
78
+ }
79
+ }
80
+ }
81
+
82
+ if (!fs.existsSync(zipPath)) {
83
+ console.error(`${baseName}.zip not found at: ${zipPath}`);
84
+ console.error(`Current platform: ${platformDir}`);
85
+ process.exit(1);
86
+ }
87
+
88
+ const unzipCmd =
89
+ process.platform === "win32"
90
+ ? `powershell -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${extractDir}' -Force"`
91
+ : `unzip -qq -o "${zipPath}" -d "${extractDir}"`;
92
+
93
+ try {
94
+ execSync(unzipCmd, { stdio: "inherit" });
95
+ } catch (err) {
96
+ console.error("Extraction failed:", err.message);
97
+ process.exit(1);
98
+ }
99
+
100
+ if (!fs.existsSync(binPath)) {
101
+ console.error(`Extracted binary not found at: ${binPath}`);
102
+ console.error("This usually indicates a corrupt download. Please reinstall.");
103
+ process.exit(1);
104
+ }
105
+
106
+ if (process.platform !== "win32") {
107
+ try {
108
+ fs.chmodSync(binPath, 0o755);
109
+ } catch {}
110
+ }
111
+
112
+ return launch(binPath);
113
+ }
114
+
115
+ const cliVersion = require("../package.json").version;
116
+ console.log(`Starting chro v${cliVersion}...`);
117
+
19
118
  const args = process.argv.slice(2);
20
- const proc = spawn(localBinary, args, { stdio: "inherit" });
21
119
 
22
- proc.on("exit", (code) => process.exit(code || 0));
23
- proc.on("error", (error) => {
24
- console.error("failed to launch chro:", error.message);
25
- process.exit(1);
26
- });
120
+ ensureBinaryZip("chro-server")
121
+ .then(() => {
122
+ extractAndRun("chro-server", (binPath) => {
123
+ const proc = spawn(binPath, args, { stdio: "inherit" });
124
+ proc.on("exit", (code) => process.exit(code || 0));
125
+ proc.on("error", (error) => {
126
+ console.error("failed to launch chro-server:", error.message);
127
+ process.exit(1);
128
+ });
129
+
130
+ if (process.platform !== "win32") {
131
+ process.on("SIGINT", () => proc.kill("SIGINT"));
132
+ process.on("SIGTERM", () => proc.kill("SIGTERM"));
133
+ }
134
+ });
135
+ })
136
+ .catch((error) => {
137
+ console.error(`Failed to obtain chro binary: ${error.message}`);
138
+ console.error("Please check your network connection and try again.");
139
+ process.exit(1);
140
+ });
package/bin/platform.js CHANGED
@@ -3,8 +3,16 @@
3
3
 
4
4
  const { execSync } = require("child_process");
5
5
 
6
- // Public binary host URL placeholder for future releases.
7
- const R2_PUBLIC_URL = "https://example.com/chro";
6
+ const packageJson = require("../package.json");
7
+
8
+ function normalizePublicUrl(value) {
9
+ if (typeof value !== "string") return "";
10
+ return value.trim().replace(/\/+$/, "");
11
+ }
12
+
13
+ const R2_PUBLIC_URL = normalizePublicUrl(
14
+ process.env.R2_PUBLIC_URL || packageJson.config?.r2PublicUrl,
15
+ );
8
16
 
9
17
  function getEffectiveArch() {
10
18
  const platform = process.platform;
@@ -59,4 +67,25 @@ function getBinaryName(base) {
59
67
  return process.platform === "win32" ? `${base}.exe` : base;
60
68
  }
61
69
 
62
- module.exports = { getEffectiveArch, getPlatformDir, getBinaryName, R2_PUBLIC_URL };
70
+ function hasConfiguredPublicUrl() {
71
+ return R2_PUBLIC_URL.length > 0;
72
+ }
73
+
74
+ function assertPublicUrlConfigured() {
75
+ if (hasConfiguredPublicUrl()) return;
76
+
77
+ console.error("chro: R2 public URL is not configured.");
78
+ console.error(
79
+ "Set R2_PUBLIC_URL or populate package.json config.r2PublicUrl before publishing.",
80
+ );
81
+ process.exit(1);
82
+ }
83
+
84
+ module.exports = {
85
+ assertPublicUrlConfigured,
86
+ getEffectiveArch,
87
+ getPlatformDir,
88
+ getBinaryName,
89
+ hasConfiguredPublicUrl,
90
+ R2_PUBLIC_URL,
91
+ };
@@ -1,5 +1,81 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- console.log("chro: scaffold package installed");
5
- console.log("chro: remote binary download is not wired yet");
4
+ const https = require("https");
5
+ const http = require("http");
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const {
9
+ getPlatformDir,
10
+ hasConfiguredPublicUrl,
11
+ R2_PUBLIC_URL,
12
+ } = require("./platform");
13
+
14
+ const version = require("../package.json").version;
15
+ const platformDir = getPlatformDir();
16
+ const distDir = path.join(__dirname, "..", "dist", platformDir);
17
+ const zipPath = path.join(distDir, "chro-server.zip");
18
+
19
+ function download(url) {
20
+ return new Promise((resolve, reject) => {
21
+ const client = url.startsWith("https") ? https : http;
22
+ client
23
+ .get(url, (res) => {
24
+ if (
25
+ res.statusCode >= 300 &&
26
+ res.statusCode < 400 &&
27
+ res.headers.location
28
+ ) {
29
+ return download(res.headers.location).then(resolve, reject);
30
+ }
31
+ if (res.statusCode !== 200) {
32
+ reject(new Error(`HTTP ${res.statusCode} for ${url}`));
33
+ res.resume();
34
+ return;
35
+ }
36
+ resolve(res);
37
+ })
38
+ .on("error", reject);
39
+ });
40
+ }
41
+
42
+ async function main() {
43
+ if (!hasConfiguredPublicUrl()) {
44
+ console.warn("chro: R2 public URL is not configured");
45
+ console.warn("chro: binary will be downloaded on first run if R2_PUBLIC_URL is set");
46
+ return;
47
+ }
48
+
49
+ if (fs.existsSync(zipPath)) {
50
+ console.log(`chro: binary already present at ${zipPath}, skipping download`);
51
+ return;
52
+ }
53
+
54
+ const url = `${R2_PUBLIC_URL}/releases/v${version}/${platformDir}/chro-server.zip`;
55
+ console.log(`chro: downloading binary for ${platformDir}...`);
56
+
57
+ try {
58
+ fs.mkdirSync(distDir, { recursive: true });
59
+ const res = await download(url);
60
+ const file = fs.createWriteStream(zipPath);
61
+
62
+ await new Promise((resolve, reject) => {
63
+ res.pipe(file);
64
+ file.on("finish", () => file.close(resolve));
65
+ file.on("error", (err) => {
66
+ fs.unlink(zipPath, () => {});
67
+ reject(err);
68
+ });
69
+ });
70
+
71
+ console.log("chro: binary downloaded successfully");
72
+ } catch (err) {
73
+ console.warn(`chro: postinstall download failed: ${err.message}`);
74
+ console.warn("chro: binary will be downloaded on first run");
75
+ try {
76
+ fs.unlinkSync(zipPath);
77
+ } catch {}
78
+ }
79
+ }
80
+
81
+ main();
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@chro-ai/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Chro CLI",
5
- "license": "MIT",
5
+ "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/n-asuy/chro"
9
9
  },
10
10
  "homepage": "https://chro-ai.com",
11
+ "config": {
12
+ "r2PublicUrl": "https://dl.chro-ai.com"
13
+ },
11
14
  "bin": {
12
15
  "chro": "bin/cli.js"
13
16
  },
@@ -20,6 +23,9 @@
20
23
  "engines": {
21
24
  "node": ">=18"
22
25
  },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
23
29
  "keywords": [
24
30
  "cli",
25
31
  "skills",