@mandors/cli 0.0.13 → 0.0.16

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 CHANGED
@@ -69,22 +69,58 @@ Mandor provides:
69
69
 
70
70
  ## Installation
71
71
 
72
- ### From Source
72
+ Choose one of the following methods:
73
+
74
+ ### Option 1: curl (Recommended)
73
75
 
74
76
  ```bash
75
- git clone https://github.com/budisantoso/mandor.git
76
- cd mandor
77
- go build -o build/mandor ./cmd/mandor
78
- sudo mv build/mandor /usr/local/bin/
77
+ # Install latest stable version
78
+ curl -fsSL https://raw.githubusercontent.com/sanxzy/mandor/main/scripts/install.sh | sh
79
+
80
+ # Or install to custom directory
81
+ curl -fsSL https://raw.githubusercontent.com/sanxzy/mandor/main/scripts/install.sh | sh -s -- --prefix ~/bin
82
+
83
+ # Install latest prerelease
84
+ curl -fsSL https://raw.githubusercontent.com/sanxzy/mandor/main/scripts/install.sh | sh -s -- --prerelease
85
+
86
+ # Install specific version
87
+ curl -fsSL https://raw.githubusercontent.com/sanxzy/mandor/main/scripts/install.sh | sh -s -- --version v0.0.14
88
+ ```
89
+
90
+ Default install location: `$HOME/.local/bin/mandor`
91
+
92
+ Add to PATH if needed:
93
+ ```bash
94
+ export PATH="$HOME/.local/bin:$PATH"
79
95
  ```
80
96
 
81
- ### From NPM
97
+ ### Option 2: NPM
82
98
 
83
99
  ```bash
100
+ # Install globally
84
101
  npm install -g @mandor/cli
102
+
103
+ # Or use npx without installing
85
104
  npx @mandor/cli init "My Project"
86
105
  ```
87
106
 
107
+ ### Option 3: From Source
108
+
109
+ ```bash
110
+ git clone https://github.com/sanxzy/mandor.git
111
+ cd mandor
112
+ go build -o build/mandor ./cmd/mandor
113
+ sudo mv build/mandor /usr/local/bin/
114
+ ```
115
+
116
+ ### Platform Support
117
+
118
+ | Method | macOS | Linux | Windows |
119
+ |--------|-------|-------|---------|
120
+ | curl | ✅ arm64, x64 | ✅ arm64, x64 | ❌ |
121
+ | NPM | ✅ arm64, x64 | ✅ arm64, x64 | ✅ arm64, x64 |
122
+ | Source | ✅ | ✅ | ✅ |
123
+
88
124
  ---
89
125
 
90
126
  ## Quick Start
Binary file
Binary file
Binary file
Binary file
@@ -1,144 +1,118 @@
1
1
  /**
2
2
  * @fileoverview Post-install hook for Mandor CLI
3
- * @description Handles binary download and caching during npm install
4
- * @version 0.0.1
3
+ * @description Handles binary extraction during npm install
4
+ * @version 0.0.2
5
5
  */
6
6
 
7
- const { downloadBinary, getCurrentPlatform } = require('./download');
8
7
  const fs = require('fs');
9
8
  const path = require('path');
10
9
  const os = require('os');
10
+ const { execSync } = require('child_process');
11
11
 
12
- /** @type {string} Cache directory for binaries */
13
- const CACHE_DIR = path.join(__dirname, '..', '.cache');
14
-
15
- /** @type {string} Bundled binaries directory */
12
+ const REPO = 'sanxzy/mandor';
13
+ const GITHUB_API = 'https://api.github.com';
16
14
  const BUNDLE_DIR = path.join(__dirname, '..', 'binaries');
15
+ const CACHE_DIR = path.join(os.homedir(), '.mandor', 'bin');
16
+
17
+ function getPlatform() {
18
+ const platform = os.platform();
19
+ const arch = os.arch();
20
+ const platformMap = { darwin: 'darwin', linux: 'linux', win32: 'win32' };
21
+ const archMap = { x64: 'x64', arm64: 'arm64', amd64: 'x64', aarch64: 'arm64' };
22
+ return {
23
+ platform: platformMap[platform] || platform,
24
+ arch: archMap[arch] || arch
25
+ };
26
+ }
27
+
28
+ async function getLatestVersion(prerelease = false) {
29
+ const url = prerelease
30
+ ? `${GITHUB_API}/repos/${REPO}/releases`
31
+ : `${GITHUB_API}/repos/${REPO}/releases/latest`;
32
+
33
+ const response = await fetch(url);
34
+ if (!response.ok) {
35
+ throw new Error(`Failed to fetch releases: ${response.statusText}`);
36
+ }
37
+ const data = await response.json();
38
+ const tagName = Array.isArray(data) ? data[0].tag_name : data.tag_name;
39
+ return tagName.replace(/^v/, '');
40
+ }
17
41
 
18
- /**
19
- * Installs the Mandor binary for the current platform
20
- * @async
21
- * @param {Object} [options] - Installation options
22
- * @param {string} [options.version] - Version to install (default: 'latest')
23
- * @returns {Promise<string>} Path to the installed binary
24
- * @throws {Error} If download fails
25
- * @example
26
- * // Called automatically by npm postinstall
27
- * await install();
28
- */
29
42
  async function install(options = {}) {
43
+ const { platform, arch } = getPlatform();
30
44
  const version = options.version || 'latest';
31
- const { platform, arch } = getCurrentPlatform();
32
-
33
- console.log(`Installing Mandor ${version} for ${platform}-${arch}...`);
45
+ const prerelease = options.prerelease || false;
46
+ const osArch = `${platform}-${arch}`;
47
+ const assetName = `${osArch}.tar.gz`;
34
48
 
35
- let binaryPath;
49
+ console.log('Mandor Installer');
50
+ console.log('================');
51
+ console.log(`OS: ${osArch}`);
36
52
 
37
- // Try bundled binary first
38
- const bundledPath = useBundledBinary(platform, arch);
39
- if (bundledPath) {
40
- console.log(`✓ Using bundled binary`);
41
- binaryPath = bundledPath;
42
- } else {
43
- // Download from GitHub releases
44
- binaryPath = await downloadBinary(version, platform, arch);
53
+ let installVersion = version;
54
+ if (version === 'latest') {
55
+ console.log(`Fetching latest ${prerelease ? 'prerelease' : 'release'}...`);
56
+ installVersion = await getLatestVersion(prerelease);
45
57
  }
46
58
 
47
- console.log(`✓ Mandor installed: ${binaryPath}`);
59
+ console.log(`Version: ${installVersion}`);
60
+ console.log('');
48
61
 
49
- return binaryPath;
50
- }
62
+ const cachePath = path.join(CACHE_DIR, installVersion, osArch);
63
+ const binaryPath = path.join(cachePath, 'mandor');
51
64
 
52
- /**
53
- * Uses bundled binary if available for current platform
54
- * @param {string} platform - Target platform
55
- * @param {string} arch - Target architecture
56
- * @returns {string|null} Path to binary or null if not bundled
57
- */
58
- function useBundledBinary(platform, arch) {
59
- const osArch = `${platform}-${arch}`;
60
- const tarball = path.join(BUNDLE_DIR, `${osArch}.tar.gz`);
61
- const cacheDir = path.join(os.homedir(), '.mandor', 'bin');
62
- const dest = path.join(cacheDir, osArch);
63
-
64
- console.log(`DEBUG: Looking for binary for ${osArch}`);
65
- console.log(`DEBUG: BUNDLE_DIR: ${BUNDLE_DIR}`);
66
- console.log(`DEBUG: Files in BUNDLE_DIR: ${fs.readdirSync(BUNDLE_DIR).join(', ')}`);
67
-
68
- // First check if binary already exists in cache
69
- if (fs.existsSync(dest)) {
70
- console.log(`DEBUG: Using cached binary: ${dest}`);
71
- return dest;
65
+ if (platform === 'win32') {
66
+ binaryPath = binaryPath + '.exe';
72
67
  }
73
68
 
74
- // Check if tarball exists
75
- if (!fs.existsSync(tarball)) {
76
- console.log(`DEBUG: No tarball found at: ${tarball}`);
77
- return null;
69
+ if (fs.existsSync(binaryPath)) {
70
+ console.log(`Using cached binary: ${binaryPath}`);
71
+ return binaryPath;
78
72
  }
79
73
 
80
- console.log(`DEBUG: Extracting tarball: ${tarball}`);
81
-
82
- if (!fs.existsSync(cacheDir)) {
83
- fs.mkdirSync(cacheDir, { recursive: true });
84
- }
85
-
86
- try {
87
- const { execSync } = require('child_process');
88
- execSync(`tar -xzf "${tarball}" -C "${cacheDir}"`, { stdio: 'pipe' });
89
- fs.chmodSync(dest, '755');
90
- console.log(`DEBUG: Extracted to: ${dest}`);
91
- return dest;
92
- } catch (e) {
93
- console.log(`DEBUG: Failed to extract tarball: ${e.message}`);
94
- return null;
74
+ const bundledPath = path.join(BUNDLE_DIR, assetName);
75
+ if (fs.existsSync(bundledPath)) {
76
+ console.log(`Using bundled binary: ${bundledPath}`);
77
+ if (!fs.existsSync(cachePath)) {
78
+ fs.mkdirSync(cachePath, { recursive: true });
79
+ }
80
+ execSync(`tar -xzf "${bundledPath}" -C "${cachePath}"`, { stdio: 'inherit' });
81
+ fs.chmodSync(binaryPath, '755');
82
+ console.log(`Installed: ${binaryPath}`);
83
+ return binaryPath;
95
84
  }
96
- }
97
85
 
98
- /**
99
- * Cleans up old binary caches
100
- * @returns {number} Number of files removed
101
- * @example
102
- * const removed = cleanupCache();
103
- * console.log(`Removed ${removed} old binary files`);
104
- */
105
- function cleanupCache() {
106
- if (!fs.existsSync(CACHE_DIR)) return 0;
86
+ console.log('Downloading from GitHub releases...');
87
+ const downloadUrl = `https://github.com/${REPO}/releases/download/v${installVersion}/${assetName}`;
88
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mandor-install-'));
89
+ const tarball = path.join(tempDir, assetName);
107
90
 
108
- const files = fs.readdirSync(CACHE_DIR);
109
- let removed = 0;
91
+ const response = await fetch(downloadUrl);
92
+ if (!response.ok) {
93
+ throw new Error(`Download failed: ${response.statusText} (${downloadUrl})`);
94
+ }
110
95
 
111
- for (const file of files) {
112
- const filePath = path.join(CACHE_DIR, file);
113
- const stats = fs.statSync(filePath);
96
+ const file = fs.createWriteStream(tarball);
97
+ await new Promise((resolve, reject) => {
98
+ response.body.pipe(file);
99
+ file.on('finish', resolve);
100
+ file.on('error', reject);
101
+ });
114
102
 
115
- // Remove files older than 30 days
116
- const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
117
- if (stats.mtimeMs < thirtyDaysAgo) {
118
- fs.unlinkSync(filePath);
119
- removed++;
120
- }
103
+ if (!fs.existsSync(cachePath)) {
104
+ fs.mkdirSync(cachePath, { recursive: true });
121
105
  }
122
106
 
123
- return removed;
124
- }
107
+ execSync(`tar -xzf "${tarball}" -C "${cachePath}"`, { stdio: 'inherit' });
108
+ fs.chmodSync(binaryPath, '755');
125
109
 
126
- /**
127
- * Gets the installed binary version
128
- * @returns {string|null} Version string or null if not installed
129
- * @example
130
- * const version = getInstalledVersion();
131
- * if (version) { console.log(`Using Mandor ${version}`); }
132
- */
133
- function getInstalledVersion() {
134
- const versionPath = path.join(CACHE_DIR, 'version.txt');
135
- if (fs.existsSync(versionPath)) {
136
- return fs.readFileSync(versionPath, 'utf-8').trim();
137
- }
138
- return null;
110
+ fs.rmSync(tempDir, { recursive: true });
111
+
112
+ console.log(`Installed: ${binaryPath}`);
113
+ return binaryPath;
139
114
  }
140
115
 
141
- // Run install on postinstall
142
116
  if (require.main === module || process.env.npm_lifecycle_event === 'postinstall') {
143
117
  install().catch(error => {
144
118
  console.error('Failed to install Mandor:', error.message);
@@ -146,8 +120,4 @@ if (require.main === module || process.env.npm_lifecycle_event === 'postinstall'
146
120
  });
147
121
  }
148
122
 
149
- module.exports = {
150
- install,
151
- cleanupCache,
152
- getInstalledVersion
153
- };
123
+ module.exports = { install, getLatestVersion, getPlatform };
@@ -1,59 +1,22 @@
1
1
  /**
2
2
  * @fileoverview Version resolution module for Mandor CLI
3
3
  * @description Resolves the correct binary path based on version and platform
4
- * @version 0.0.1
4
+ * @version 0.0.2
5
5
  */
6
6
 
7
7
  const path = require('path');
8
8
  const fs = require('fs');
9
9
  const os = require('os');
10
- const { downloadBinary, getCurrentPlatform, binaryExists } = require('./download');
10
+ const { downloadBinary, getCurrentPlatform } = require('./download');
11
11
 
12
- /** @type {string} Default version to use */
12
+ const REPO = 'sanxzy/mandor';
13
+ const GITHUB_API = 'https://api.github.com';
13
14
  const DEFAULT_VERSION = 'latest';
14
15
 
15
- /**
16
- * Resolves the binary path for the requested version
17
- * @async
18
- * @param {Object} [options] - Resolution options
19
- * @param {string} [options.version] - Requested version (default: 'latest')
20
- * @param {boolean} [options.forceDownload] - Force re-download even if cached
21
- * @returns {Promise<string>} Path to the Mandor binary
22
- * @throws {Error} If binary cannot be resolved or downloaded
23
- * @example
24
- * const binaryPath = await resolve({ version: '1.0.0' });
25
- * console.log(`Using: ${binaryPath}`);
26
- */
27
- async function resolve(options = {}) {
28
- const version = options.version || DEFAULT_VERSION;
29
- const { platform, arch } = getCurrentPlatform();
30
-
31
- // Check cache first
32
- const cachedPath = getCachedBinary(version, platform, arch);
33
- if (cachedPath && !options.forceDownload) {
34
- return cachedPath;
35
- }
36
-
37
- // Download if not cached
38
- const binaryPath = await downloadBinary(version, platform, arch);
39
- cacheBinary(binaryPath, version, platform, arch);
40
-
41
- return binaryPath;
42
- }
43
-
44
- /**
45
- * Gets the cached binary path for a specific version
46
- * @param {string} version - Version to look for
47
- * @param {string} platform - Target platform
48
- * @param {string} arch - Target architecture
49
- * @returns {string|null} Path to cached binary or null
50
- * @example
51
- * const cached = getCachedBinary('1.0.0', 'darwin', 'x64');
52
- */
53
16
  function getCachedBinary(version, platform, arch) {
54
- const cacheDir = path.join(os.homedir(), '.mandor', 'bin');
17
+ const osArch = `${platform}-${arch}`;
55
18
  const binaryName = platform === 'win32' ? 'mandor.exe' : 'mandor';
56
- const binaryPath = path.join(cacheDir, `${version}-${platform}-${arch}`, binaryName);
19
+ const binaryPath = path.join(os.homedir(), '.mandor', 'bin', version, osArch, binaryName);
57
20
 
58
21
  if (fs.existsSync(binaryPath)) {
59
22
  return binaryPath;
@@ -61,18 +24,23 @@ function getCachedBinary(version, platform, arch) {
61
24
  return null;
62
25
  }
63
26
 
64
- /**
65
- * Caches a binary for future use
66
- * @param {string} binaryPath - Path to the binary
67
- * @param {string} version - Version identifier
68
- * @param {string} platform - Target platform
69
- * @param {string} arch - Target architecture
70
- * @returns {void}
71
- * @example
72
- * cacheBinary('/home/user/.mandor/bin/1.0.0-darwin-x64/mandor', '1.0.0', 'darwin', 'x64');
73
- */
27
+ async function getLatestVersion(prerelease = false) {
28
+ const url = prerelease
29
+ ? `${GITHUB_API}/repos/${REPO}/releases`
30
+ : `${GITHUB_API}/repos/${REPO}/releases/latest`;
31
+
32
+ const response = await fetch(url);
33
+ if (!response.ok) {
34
+ throw new Error(`Failed to fetch releases: ${response.statusText}`);
35
+ }
36
+ const data = await response.json();
37
+ const tagName = Array.isArray(data) ? data[0].tag_name : data.tag_name;
38
+ return tagName.replace(/^v/, '');
39
+ }
40
+
74
41
  function cacheBinary(binaryPath, version, platform, arch) {
75
- const cacheDir = path.join(os.homedir(), '.mandor', 'bin', `${version}-${platform}-${arch}`);
42
+ const osArch = `${platform}-${arch}`;
43
+ const cacheDir = path.join(os.homedir(), '.mandor', 'bin', version, osArch);
76
44
  fs.mkdirSync(cacheDir, { recursive: true });
77
45
 
78
46
  const binaryName = platform === 'win32' ? 'mandor.exe' : 'mandor';
@@ -82,13 +50,54 @@ function cacheBinary(binaryPath, version, platform, arch) {
82
50
  fs.chmodSync(destPath, '755');
83
51
  }
84
52
 
85
- /**
86
- * Lists all cached binary versions
87
- * @returns {Object[]} Array of cached binary info
88
- * @example
89
- * const cached = listCachedBinaries();
90
- * cached.forEach(b => console.log(`${b.version} (${b.platform}-${b.arch})`));
91
- */
53
+ async function resolve(options = {}) {
54
+ const version = options.version || DEFAULT_VERSION;
55
+ const { platform, arch } = getCurrentPlatform();
56
+ const prerelease = options.prerelease || false;
57
+
58
+ let resolveVersion = version;
59
+ if (version === 'latest') {
60
+ resolveVersion = await getLatestVersion(prerelease);
61
+ }
62
+
63
+ const cachedPath = getCachedBinary(resolveVersion, platform, arch);
64
+ if (cachedPath && !options.forceDownload) {
65
+ return cachedPath;
66
+ }
67
+
68
+ const osArch = `${platform}-${arch}`;
69
+ const downloadUrl = `https://github.com/${REPO}/releases/download/v${resolveVersion}/${osArch}.tar.gz`;
70
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mandor-download-'));
71
+ const tarball = path.join(tempDir, `${osArch}.tar.gz`);
72
+
73
+ console.log(`Downloading Mandor ${resolveVersion}...`);
74
+
75
+ const response = await fetch(downloadUrl);
76
+ if (!response.ok) {
77
+ fs.rmSync(tempDir, { recursive: true });
78
+ throw new Error(`Download failed: ${response.statusText}`);
79
+ }
80
+
81
+ const file = fs.createWriteStream(tarball);
82
+ await new Promise((resolve, reject) => {
83
+ response.body.pipe(file);
84
+ file.on('finish', resolve);
85
+ file.on('error', reject);
86
+ });
87
+
88
+ const binaryName = platform === 'win32' ? 'mandor.exe' : 'mandor';
89
+ const cacheDir = path.join(os.homedir(), '.mandor', 'bin', resolveVersion, osArch);
90
+ fs.mkdirSync(cacheDir, { recursive: true });
91
+
92
+ const { execSync } = require('child_process');
93
+ execSync(`tar -xzf "${tarball}" -C "${cacheDir}"`, { stdio: 'pipe' });
94
+ fs.chmodSync(path.join(cacheDir, binaryName), '755');
95
+
96
+ fs.rmSync(tempDir, { recursive: true });
97
+
98
+ return path.join(cacheDir, binaryName);
99
+ }
100
+
92
101
  function listCachedBinaries() {
93
102
  const cacheDir = path.join(os.homedir(), '.mandor', 'bin');
94
103
 
@@ -100,23 +109,24 @@ function listCachedBinaries() {
100
109
  const entries = fs.readdirSync(cacheDir);
101
110
 
102
111
  for (const entry of entries) {
103
- const entryPath = path.join(cacheDir, entry);
104
- if (fs.statSync(entryPath).isDirectory()) {
105
- const [version, platform, arch] = entry.split('-');
106
- versions.push({ version, platform, arch, path: entryPath });
112
+ const versionPath = path.join(cacheDir, entry);
113
+ if (fs.statSync(versionPath).isDirectory()) {
114
+ const subEntries = fs.readdirSync(versionPath);
115
+ for (const subEntry of subEntries) {
116
+ const subPath = path.join(versionPath, subEntry);
117
+ if (fs.statSync(subPath).isDirectory()) {
118
+ const parts = subEntry.split('-');
119
+ const arch = parts.pop();
120
+ const platform = parts.join('-');
121
+ versions.push({ version: entry, platform, arch, path: subPath });
122
+ }
123
+ }
107
124
  }
108
125
  }
109
126
 
110
127
  return versions;
111
128
  }
112
129
 
113
- /**
114
- * Clears all cached binaries
115
- * @returns {number} Number of binaries removed
116
- * @example
117
- * const removed = clearCache();
118
- * console.log(`Cleared ${removed} cached binaries`);
119
- */
120
130
  function clearCache() {
121
131
  const cacheDir = path.join(os.homedir(), '.mandor', 'bin');
122
132
 
@@ -144,5 +154,6 @@ module.exports = {
144
154
  cacheBinary,
145
155
  listCachedBinaries,
146
156
  clearCache,
147
- DEFAULT_VERSION
157
+ DEFAULT_VERSION,
158
+ getLatestVersion
148
159
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandors/cli",
3
- "version": "0.0.13",
3
+ "version": "0.0.16",
4
4
  "description": "Event-based task manager CLI for AI agent workflows",
5
5
  "main": "npm/lib/index.js",
6
6
  "bin": {
@@ -0,0 +1,105 @@
1
+ #!/bin/sh
2
+ #
3
+ # install.sh - Install Mandor CLI
4
+ #
5
+ # Usage:
6
+ # curl -fsSL https://raw.githubusercontent.com/sanxzy/mandor/main/scripts/install.sh | sh
7
+ # curl -fsSL https://raw.githubusercontent.com/sanxzy/mandor/main/scripts/install.sh | sh -s -- --help
8
+ #
9
+ # Options:
10
+ # --prefix DIR Install prefix (default: $HOME/.local)
11
+ # --version VER Install specific version (default: latest)
12
+ # --prerelease Install latest prerelease
13
+ # --help Show this help
14
+ #
15
+
16
+ set -e
17
+
18
+ REPO="sanxzy/mandor"
19
+ INSTALL_DIR="${HOME}/.local/bin"
20
+ VERSION="latest"
21
+ PRERELEASE=""
22
+ TAG="latest"
23
+
24
+ while [ $# -gt 0 ]; do
25
+ case "$1" in
26
+ --prefix)
27
+ INSTALL_DIR="$2"
28
+ shift 2
29
+ ;;
30
+ --version)
31
+ VERSION="$2"
32
+ shift 2
33
+ ;;
34
+ --prerelease)
35
+ PRERELEASE="1"
36
+ TAG="latest"
37
+ shift
38
+ ;;
39
+ --help|-h)
40
+ head -20 "$0"
41
+ exit 0
42
+ ;;
43
+ esac
44
+ done
45
+
46
+ OS=$(uname -s | tr '[:upper:]' '[:lower:]')
47
+ ARCH=$(uname -m)
48
+ case "$ARCH" in
49
+ x86_64|amd64) ARCH="x64" ;;
50
+ arm64|aarch64) ARCH="arm64" ;;
51
+ esac
52
+
53
+ case "$OS" in
54
+ darwin) ;;
55
+ linux) ;;
56
+ *)
57
+ echo "Unsupported OS: $OS"
58
+ exit 1
59
+ ;;
60
+ esac
61
+
62
+ echo "Mandor Installer"
63
+ echo "================"
64
+ echo "OS: $OS-$ARCH"
65
+
66
+ if [ "$VERSION" = "latest" ]; then
67
+ if [ -n "$PRERELEASE" ]; then
68
+ echo "Fetching latest prerelease..."
69
+ VERSION=$(curl -s "https://api.github.com/repos/${REPO}/releases" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -1)
70
+ else
71
+ echo "Fetching latest release..."
72
+ VERSION=$(curl -s "https://api.github.com/repos/${REPO}/releases/latest" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')
73
+ fi
74
+ fi
75
+
76
+ echo "Version: $VERSION"
77
+ echo "Install dir: $INSTALL_DIR"
78
+ echo ""
79
+
80
+ ASSET_NAME="${OS}-${ARCH}.tar.gz"
81
+ DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${ASSET_NAME}"
82
+ TEMP_DIR=$(mktemp -d)
83
+ TARFILE="${TEMP_DIR}/${ASSET_NAME}"
84
+
85
+ echo "Downloading ${ASSET_NAME}..."
86
+ if ! curl -fsSL -o "$TARFILE" "$DOWNLOAD_URL"; then
87
+ echo "Download failed: $DOWNLOAD_URL"
88
+ rm -rf "$TEMP_DIR"
89
+ exit 1
90
+ fi
91
+
92
+ echo "Extracting..."
93
+ mkdir -p "$INSTALL_DIR"
94
+ tar -xzf "$TARFILE" -C "$INSTALL_DIR"
95
+ chmod 755 "${INSTALL_DIR}/mandor"
96
+
97
+ rm -rf "$TEMP_DIR"
98
+
99
+ echo ""
100
+ echo "Installed: ${INSTALL_DIR}/mandor"
101
+ echo ""
102
+
103
+ if [ -d "$HOME/.local/bin" ]; then
104
+ echo "Add to PATH: export PATH=\"\$HOME/.local/bin:\$PATH\""
105
+ fi