@mandors/cli 0.0.14 → 0.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 CHANGED
@@ -69,20 +69,120 @@ Mandor provides:
69
69
 
70
70
  ## Installation
71
71
 
72
- ### From Source
72
+ Mandor can be installed via curl, npm, or built from source.
73
73
 
74
+ ### Option 1: curl (Recommended for macOS/Linux)
75
+
76
+ The fastest way to install Mandor on macOS or Linux:
77
+
78
+ ```bash
79
+ # Install latest stable version (to ~/.local/bin)
80
+ curl -fsSL https://raw.githubusercontent.com/sanxzy/mandor/main/scripts/install.sh | sh
81
+
82
+ # Install to custom directory
83
+ curl -fsSL https://raw.githubusercontent.com/sanxzy/mandor/main/scripts/install.sh | sh -s -- --prefix /usr/local/bin
84
+
85
+ # Install latest prerelease (beta versions)
86
+ curl -fsSL https://raw.githubusercontent.com/sanxzy/mandor/main/scripts/install.sh | sh -s -- --prerelease
87
+
88
+ # Install specific version
89
+ curl -fsSL https://raw.githubusercontent.com/sanxzy/mandor/main/scripts/install.sh | sh -s -- --version v0.0.16
90
+ ```
91
+
92
+ **Default install location:** `$HOME/.local/bin/mandor`
93
+
94
+ **Add to PATH:**
95
+ ```bash
96
+ # For bash
97
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
98
+
99
+ # For zsh
100
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc
101
+ ```
102
+
103
+ **Verify installation:**
104
+ ```bash
105
+ mandor --help
106
+ ```
107
+
108
+ ### Option 2: NPM (Cross-platform)
109
+
110
+ Install via npm for macOS, Linux, or Windows:
111
+
112
+ ```bash
113
+ # Install globally
114
+ npm install -g @mandor/cli
115
+
116
+ # Verify installation
117
+ mandor --help
118
+ ```
119
+
120
+ **Or use npx to run without installing:**
74
121
  ```bash
75
- git clone https://github.com/budisantoso/mandor.git
122
+ npx @mandor/cli init "My Project"
123
+ ```
124
+
125
+ **Programmatic usage:**
126
+ ```javascript
127
+ const mandor = require('@mandor/cli');
128
+
129
+ const cli = new mandor.Mandor({ json: true, cwd: '/project/path' });
130
+ await cli.init('My Project');
131
+ await cli.projectCreate('api', { name: 'API Service' });
132
+ const tasks = await cli.taskList({ project: 'api', status: 'pending' });
133
+ ```
134
+
135
+ ### Option 3: From Source
136
+
137
+ Build from Go source code:
138
+
139
+ ```bash
140
+ # Clone repository
141
+ git clone https://github.com/sanxzy/mandor.git
76
142
  cd mandor
143
+
144
+ # Build binary
77
145
  go build -o build/mandor ./cmd/mandor
146
+
147
+ # Install to system
78
148
  sudo mv build/mandor /usr/local/bin/
149
+
150
+ # Verify
151
+ mandor --help
79
152
  ```
80
153
 
81
- ### From NPM
154
+ ### Platform Support
155
+
156
+ | Method | macOS | Linux | Windows |
157
+ |--------|-------|-------|---------|
158
+ | curl | ✅ arm64, x64 | ✅ arm64, x64 | ❌ |
159
+ | npm | ✅ arm64, x64 | ✅ arm64, x64 | ✅ arm64, x64 |
160
+ | Source | ✅ | ✅ | ✅ |
161
+
162
+ ### Troubleshooting
163
+
164
+ **curl: command not found**
165
+ Install curl: `brew install curl` (macOS) or `sudo apt install curl` (Linux)
166
+
167
+ **mandor: command not found**
168
+ Ensure the install directory is in your PATH (see above)
82
169
 
170
+ **Permission denied**
83
171
  ```bash
84
- npm install -g @mandor/cli
172
+ # Fix permissions for ~/.local/bin
173
+ chmod +x ~/.local/bin/mandor
174
+ ```
175
+
176
+ **NPM permission errors**
177
+ ```bash
178
+ # Use npx (no install)
85
179
  npx @mandor/cli init "My Project"
180
+
181
+ # Or fix npm global permissions
182
+ mkdir -p ~/.npm-global
183
+ npm config set prefix '~/.npm-global'
184
+ echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
185
+ source ~/.bashrc
86
186
  ```
87
187
 
88
188
  ---
Binary file
Binary file
@@ -1,154 +1,108 @@
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 Downloads binary from GitHub releases during npm install
4
+ * @version 0.0.3
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
+
12
+ const REPO = 'sanxzy/mandor';
13
+ const GITHUB_API = 'https://api.github.com';
14
+ const CACHE_DIR = path.join(os.homedir(), '.mandor', 'bin');
15
+
16
+ function getPlatform() {
17
+ const platform = os.platform();
18
+ const arch = os.arch();
19
+ const platformMap = { darwin: 'darwin', linux: 'linux', win32: 'win32' };
20
+ const archMap = { x64: 'x64', arm64: 'arm64', amd64: 'x64', aarch64: 'arm64' };
21
+ return {
22
+ platform: platformMap[platform] || platform,
23
+ arch: archMap[arch] || arch
24
+ };
25
+ }
11
26
 
12
- /** @type {string} Cache directory for binaries */
13
- const CACHE_DIR = path.join(__dirname, '..', '.cache');
27
+ async function getLatestVersion(prerelease = false) {
28
+ const url = prerelease
29
+ ? `${GITHUB_API}/repos/${REPO}/releases`
30
+ : `${GITHUB_API}/repos/${REPO}/releases/latest`;
14
31
 
15
- /** @type {string} Bundled binaries directory */
16
- const BUNDLE_DIR = path.join(__dirname, '..', 'binaries');
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
+ }
17
40
 
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
41
  async function install(options = {}) {
42
+ const { platform, arch } = getPlatform();
30
43
  const version = options.version || 'latest';
31
- const { platform, arch } = getCurrentPlatform();
32
-
33
- console.log(`Installing Mandor ${version} for ${platform}-${arch}...`);
44
+ const prerelease = options.prerelease || false;
45
+ const osArch = `${platform}-${arch}`;
46
+ const assetName = `${osArch}.tar.gz`;
34
47
 
35
- let binaryPath;
48
+ console.log('Mandor Installer');
49
+ console.log('================');
50
+ console.log(`OS: ${osArch}`);
36
51
 
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);
52
+ let installVersion = version;
53
+ if (version === 'latest') {
54
+ console.log(`Fetching latest ${prerelease ? 'prerelease' : 'release'}...`);
55
+ installVersion = await getLatestVersion(prerelease);
45
56
  }
46
57
 
47
- console.log(`✓ Mandor installed: ${binaryPath}`);
58
+ console.log(`Version: ${installVersion}`);
59
+ console.log('');
48
60
 
49
- return binaryPath;
50
- }
61
+ const cachePath = path.join(CACHE_DIR, installVersion, osArch);
62
+ const binaryPath = path.join(cachePath, platform === 'win32' ? 'mandor.exe' : 'mandor');
51
63
 
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 version = 'latest';
62
- const cacheDir = path.join(os.homedir(), '.mandor', 'bin', `${version}-${osArch}`);
63
- const dest = path.join(cacheDir, 'mandor');
64
-
65
- console.log(`DEBUG: Looking for binary for ${osArch}`);
66
- console.log(`DEBUG: BUNDLE_DIR: ${BUNDLE_DIR}`);
67
- console.log(`DEBUG: Files in BUNDLE_DIR: ${fs.readdirSync(BUNDLE_DIR).join(', ')}`);
68
-
69
- // First check if binary already exists in cache
70
- if (fs.existsSync(dest)) {
71
- console.log(`DEBUG: Using cached binary: ${dest}`);
72
- return dest;
64
+ if (fs.existsSync(binaryPath)) {
65
+ console.log(`Using cached binary: ${binaryPath}`);
66
+ return binaryPath;
73
67
  }
74
68
 
75
- // Check if tarball exists
76
- if (!fs.existsSync(tarball)) {
77
- console.log(`DEBUG: No tarball found at: ${tarball}`);
78
- return null;
79
- }
69
+ console.log('Downloading from GitHub releases...');
70
+ const downloadUrl = `https://github.com/${REPO}/releases/download/v${installVersion}/${assetName}`;
71
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mandor-install-'));
72
+ const tarball = path.join(tempDir, assetName);
80
73
 
81
- console.log(`DEBUG: Extracting tarball: ${tarball}`);
82
-
83
- if (!fs.existsSync(cacheDir)) {
84
- fs.mkdirSync(cacheDir, { recursive: true });
74
+ const response = await fetch(downloadUrl);
75
+ if (!response.ok) {
76
+ fs.rmSync(tempDir, { recursive: true });
77
+ throw new Error(`Download failed: ${response.statusText} (${downloadUrl})`);
85
78
  }
86
79
 
87
- try {
88
- const { execSync } = require('child_process');
89
- execSync(`tar -xzf "${tarball}" -C "${cacheDir}"`, { stdio: 'pipe' });
90
- fs.chmodSync(dest, '755');
91
- console.log(`DEBUG: Extracted to: ${dest}`);
92
- return dest;
93
- } catch (e) {
94
- console.log(`DEBUG: Failed to extract tarball: ${e.message}`);
95
- return null;
96
- }
97
- }
80
+ const file = fs.createWriteStream(tarball);
81
+ await new Promise((resolve, reject) => {
82
+ response.body.pipe(file);
83
+ file.on('finish', resolve);
84
+ file.on('error', reject);
85
+ });
98
86
 
99
- /**
100
- * Cleans up old binary caches
101
- * @returns {number} Number of files removed
102
- * @example
103
- * const removed = cleanupCache();
104
- * console.log(`Removed ${removed} old binary files`);
105
- */
106
- function cleanupCache() {
107
- if (!fs.existsSync(CACHE_DIR)) return 0;
108
-
109
- const files = fs.readdirSync(CACHE_DIR);
110
- let removed = 0;
111
-
112
- for (const file of files) {
113
- const filePath = path.join(CACHE_DIR, file);
114
- const stats = fs.statSync(filePath);
115
-
116
- // Remove files older than 30 days
117
- const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
118
- if (stats.mtimeMs < thirtyDaysAgo) {
119
- fs.unlinkSync(filePath);
120
- removed++;
121
- }
87
+ if (!fs.existsSync(cachePath)) {
88
+ fs.mkdirSync(cachePath, { recursive: true });
122
89
  }
123
90
 
124
- return removed;
125
- }
91
+ execSync(`tar -xzf "${tarball}" -C "${cachePath}"`, { stdio: 'inherit' });
92
+ fs.chmodSync(binaryPath, '755');
126
93
 
127
- /**
128
- * Gets the installed binary version
129
- * @returns {string|null} Version string or null if not installed
130
- * @example
131
- * const version = getInstalledVersion();
132
- * if (version) { console.log(`Using Mandor ${version}`); }
133
- */
134
- function getInstalledVersion() {
135
- const versionPath = path.join(CACHE_DIR, 'version.txt');
136
- if (fs.existsSync(versionPath)) {
137
- return fs.readFileSync(versionPath, 'utf-8').trim();
138
- }
139
- return null;
94
+ fs.rmSync(tempDir, { recursive: true });
95
+
96
+ console.log(`Installed: ${binaryPath}`);
97
+ return binaryPath;
140
98
  }
141
99
 
142
- // Run install on postinstall
143
100
  if (require.main === module || process.env.npm_lifecycle_event === 'postinstall') {
144
- install().catch(error => {
101
+ const prerelease = process.argv.includes('--prerelease') || process.argv.includes('-p');
102
+ install({ prerelease }).catch(error => {
145
103
  console.error('Failed to install Mandor:', error.message);
146
104
  process.exit(1);
147
105
  });
148
106
  }
149
107
 
150
- module.exports = {
151
- install,
152
- cleanupCache,
153
- getInstalledVersion
154
- };
108
+ 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
  };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Cross-platform build script for Mandor binaries
3
3
  * @description Compiles Go binaries for all supported platforms and creates distribution archives
4
- * @version 0.0.1
4
+ * @version 0.0.2
5
5
  */
6
6
 
7
7
  const { execSync } = require('child_process');
@@ -9,13 +9,11 @@ const fs = require('fs');
9
9
  const path = require('path');
10
10
  const os = require('os');
11
11
 
12
- /**
13
- * @typedef {Object} PlatformConfig
14
- * @property {string} os - Operating system (darwin, linux, win32)
15
- * @property {string} arch - Architecture (x64, arm64)
16
- */
12
+ const ROOT_DIR = path.join(__dirname, '..', '..');
13
+ const BINARIES_DIR = path.join(ROOT_DIR, 'binaries');
14
+ const NPM_DIR = path.join(ROOT_DIR, 'npm');
15
+ const NPM_BINARIES_DIR = path.join(NPM_DIR, 'binaries');
17
16
 
18
- /** @type {PlatformConfig[]} All supported platform configurations */
19
17
  const PLATFORMS = [
20
18
  { os: 'darwin', arch: 'x64' },
21
19
  { os: 'darwin', arch: 'arm64' },
@@ -25,118 +23,105 @@ const PLATFORMS = [
25
23
  { os: 'win32', arch: 'arm64' }
26
24
  ];
27
25
 
28
- /**
29
- * Builds the Mandor binary for a specific platform
30
- * @param {PlatformConfig} platform - Platform configuration
31
- * @param {string} sourceDir - Source directory containing Go code
32
- * @returns {string|null} Path to the compiled binary, or null if unsupported
33
- * @example
34
- * const binaryPath = buildForPlatform({ os: 'darwin', arch: 'arm64' }, './cmd/mandor');
35
- */
36
- function buildForPlatform(platform, sourceDir) {
26
+ function buildForPlatform(platform) {
37
27
  const { os, arch } = platform;
38
- const outputDir = path.join(__dirname, '..', 'binaries', `${os}-${arch}`);
39
- const outputPath = path.join(outputDir, os === 'win32' ? 'mandor.exe' : 'mandor');
28
+ const outputDir = path.join(BINARIES_DIR, `${os}-${arch}`);
29
+ const binaryName = os === 'win32' ? 'mandor.exe' : 'mandor';
30
+ const outputPath = path.join(outputDir, binaryName);
40
31
 
41
32
  fs.mkdirSync(outputDir, { recursive: true });
42
33
 
43
34
  console.log(`Building for ${os}-${arch}...`);
44
35
 
45
36
  try {
46
- execSync(`GOOS=${os} GOARCH=${arch} go build -o "${outputPath}" ${sourceDir}`, {
37
+ execSync(`GOOS=${os} GOARCH=${arch} go build -o "${outputPath}" ./cmd/mandor`, {
47
38
  stdio: 'pipe',
48
39
  shell: process.platform === 'win32'
49
40
  });
50
41
  return outputPath;
51
42
  } catch (error) {
52
- // Check stderr for unsupported pair message
53
43
  const stderr = error.stderr ? error.stderr.toString() : '';
54
44
  if (stderr.includes('unsupported GOOS/GOARCH pair')) {
55
- console.log(` Not supported on this system`);
45
+ console.log(` Not supported on this system`);
56
46
  return null;
57
47
  }
58
- console.error(` Build failed:`, error.message);
48
+ console.error(` Build failed:`, error.message);
59
49
  throw error;
60
50
  }
61
51
  }
62
52
 
63
- /**
64
- * Creates a distribution archive for a platform
65
- * @param {PlatformConfig} platform - Platform configuration
66
- * @returns {string|null} Path to the archive file, or null if binary doesn't exist
67
- * @example
68
- * const archivePath = createArchive({ os: 'linux', arch: 'x64' });
69
- */
70
53
  function createArchive(platform) {
71
54
  const { os, arch } = platform;
72
- const sourceDir = path.join(__dirname, '..', 'binaries', `${os}-${arch}`);
55
+ const sourceDir = path.join(BINARIES_DIR, `${os}-${arch}`);
56
+ const binaryName = os === 'win32' ? 'mandor.exe' : 'mandor';
73
57
 
74
- // Check if binary exists
75
- const binaryExists = fs.existsSync(path.join(sourceDir, os === 'win32' ? 'mandor.exe' : 'mandor'));
76
- if (!binaryExists) {
58
+ if (!fs.existsSync(path.join(sourceDir, binaryName))) {
77
59
  return null;
78
60
  }
79
61
 
80
- const archivePath = path.join(__dirname, '..', 'binaries', `${os}-${arch}.tar.gz`);
62
+ const archivePath = path.join(BINARIES_DIR, `${os}-${arch}.tar.gz`);
63
+
64
+ try {
65
+ execSync(`tar -czf "${archivePath}" -C "${sourceDir}" .`, {
66
+ stdio: 'pipe',
67
+ shell: true
68
+ });
69
+ return archivePath;
70
+ } catch (error) {
71
+ console.error(` Failed to create archive:`, error.message);
72
+ return null;
73
+ }
74
+ }
81
75
 
82
- console.log(`Creating archive for ${os}-${arch}...`);
76
+ function copyToNpm() {
77
+ fs.mkdirSync(NPM_BINARIES_DIR, { recursive: true });
83
78
 
84
- execSync(`tar -czf "${archivePath}" -C "${sourceDir}" .`, {
85
- stdio: 'inherit',
86
- shell: true
87
- });
79
+ for (const platform of PLATFORMS) {
80
+ const { os, arch } = platform;
81
+ const archivePath = path.join(BINARIES_DIR, `${os}-${arch}.tar.gz`);
82
+ const destPath = path.join(NPM_BINARIES_DIR, `${os}-${arch}.tar.gz`);
88
83
 
89
- return archivePath;
84
+ if (fs.existsSync(archivePath)) {
85
+ fs.cpSync(archivePath, destPath);
86
+ console.log(`Copied ${os}-${arch}.tar.gz to npm/`);
87
+ }
88
+ }
90
89
  }
91
90
 
92
- /**
93
- * Main build function - attempts to build all platforms
94
- * @returns {Object[]} Build results for each successfully built platform
95
- * @example
96
- * const results = mainBuild();
97
- * console.log(`Built ${results.length} platforms`);
98
- */
99
- function mainBuild() {
100
- const sourceDir = path.join(__dirname, '..', '..', 'cmd', 'mandor');
101
- const results = [];
102
- const unsupported = [];
91
+ function cleanBuildDirs() {
92
+ if (fs.existsSync(BINARIES_DIR)) {
93
+ for (const entry of fs.readdirSync(BINARIES_DIR)) {
94
+ if (entry.endsWith('.tar.gz')) {
95
+ fs.unlinkSync(path.join(BINARIES_DIR, entry));
96
+ }
97
+ }
98
+ }
99
+ }
103
100
 
101
+ function mainBuild() {
104
102
  console.log(`Running on: ${os.platform()}/${os.arch()}`);
105
103
  console.log('Building cross-platform binaries...\n');
106
104
 
107
- // Build all platforms
105
+ cleanBuildDirs();
106
+ fs.mkdirSync(NPM_BINARIES_DIR, { recursive: true });
107
+
108
+ const results = [];
109
+ const unsupported = [];
110
+
108
111
  for (const platform of PLATFORMS) {
109
- const binaryPath = buildForPlatform(platform, sourceDir);
112
+ const binaryPath = buildForPlatform(platform);
110
113
  if (binaryPath) {
111
- results.push({
112
- platform: platform.os,
113
- arch: platform.arch,
114
- binaryPath,
115
- status: 'built'
116
- });
117
- console.log(` ✓ Built successfully\n`);
114
+ results.push({ platform: platform.os, arch: platform.arch, binaryPath, status: 'built' });
115
+ console.log(` Built successfully\n`);
118
116
  } else {
119
- unsupported.push({
120
- platform: platform.os,
121
- arch: platform.arch,
122
- status: 'unsupported'
123
- });
117
+ unsupported.push({ platform: platform.os, arch: platform.arch, status: 'unsupported' });
124
118
  }
125
119
  }
126
120
 
127
- // Create archives for successfully built platforms
128
- const archiveResults = [];
129
121
  for (const platform of PLATFORMS) {
130
122
  const archivePath = createArchive(platform);
131
123
  if (archivePath) {
132
124
  const stats = fs.statSync(archivePath);
133
- archiveResults.push({
134
- platform: platform.os,
135
- arch: platform.arch,
136
- archivePath,
137
- archiveSize: stats.size
138
- });
139
-
140
125
  const existing = results.find(r => r.platform === platform.os && r.arch === platform.arch);
141
126
  if (existing) {
142
127
  existing.archivePath = archivePath;
@@ -145,10 +130,12 @@ function mainBuild() {
145
130
  }
146
131
  }
147
132
 
148
- // Summary
133
+ console.log('Copying archives to npm/binaries/ for npm package...');
134
+ copyToNpm();
135
+
149
136
  console.log('─'.repeat(50));
150
137
  console.log(`Build complete!`);
151
- console.log(` Built: ${archiveResults.length} platforms`);
138
+ console.log(` Built: ${results.length} platforms`);
152
139
  console.log(` Unsupported: ${unsupported.length} platforms`);
153
140
 
154
141
  if (unsupported.length > 0) {
@@ -158,10 +145,12 @@ function mainBuild() {
158
145
  });
159
146
  }
160
147
 
161
- return archiveResults;
148
+ console.log(`\nArchives location: ${BINARIES_DIR}/`);
149
+ console.log(`NPM package binaries: ${NPM_BINARIES_DIR}/`);
150
+
151
+ return results;
162
152
  }
163
153
 
164
- // Run if executed directly
165
154
  if (require.main === module) {
166
155
  const results = mainBuild();
167
156
  if (results.length > 0) {
@@ -170,14 +159,7 @@ if (require.main === module) {
170
159
  Platform: `${r.platform}/${r.arch}`,
171
160
  'Archive Size': `${(r.archiveSize / 1024).toFixed(1)} KB`
172
161
  })));
173
-
174
- console.log('Archives location: npm/binaries/');
175
162
  }
176
163
  }
177
164
 
178
- module.exports = {
179
- PLATFORMS,
180
- buildForPlatform,
181
- createArchive,
182
- mainBuild
183
- };
165
+ module.exports = { PLATFORMS, buildForPlatform, createArchive, mainBuild, copyToNpm };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandors/cli",
3
- "version": "0.0.14",
3
+ "version": "0.0.17",
4
4
  "description": "Event-based task manager CLI for AI agent workflows",
5
5
  "main": "npm/lib/index.js",
6
6
  "bin": {
@@ -19,9 +19,7 @@
19
19
  "node": ">=16.0.0"
20
20
  },
21
21
  "scripts": {
22
- "postinstall": "node npm/lib/install.js",
23
- "prepublishOnly": "npm run build",
24
- "preversion": "npm run build",
22
+ "postinstall": "node npm/lib/install.js --prerelease",
25
23
  "build": "node npm/scripts/build.js",
26
24
  "build:darwin:x64": "GOOS=darwin GOARCH=x64 go build -o binaries/darwin-x64/mandor ./cmd/mandor",
27
25
  "build:darwin:arm64": "GOOS=darwin GOARCH=arm64 go build -o binaries/darwin-arm64/mandor ./cmd/mandor",
@@ -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
Binary file
Binary file