eval-kanban 0.1.0

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.
Files changed (3) hide show
  1. package/bin/cli.js +164 -0
  2. package/bin/download.js +132 -0
  3. package/package.json +33 -0
package/bin/cli.js ADDED
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync, spawn } = require('child_process');
4
+ const AdmZip = require('adm-zip');
5
+ const path = require('path');
6
+ const fs = require('fs');
7
+ const { ensureBinary, getLatestRelease, CACHE_DIR } = require('./download');
8
+
9
+ const CLI_VERSION = require('../package.json').version;
10
+
11
+ function getEffectiveArch() {
12
+ const platform = process.platform;
13
+ const nodeArch = process.arch;
14
+
15
+ if (platform === 'darwin') {
16
+ if (nodeArch === 'arm64') return 'arm64';
17
+ try {
18
+ const translated = execSync('sysctl -in sysctl.proc_translated', {
19
+ encoding: 'utf8',
20
+ }).trim();
21
+ if (translated === '1') return 'arm64';
22
+ } catch {}
23
+ return 'x64';
24
+ }
25
+
26
+ if (/arm/i.test(nodeArch)) return 'arm64';
27
+
28
+ if (platform === 'win32') {
29
+ const pa = process.env.PROCESSOR_ARCHITECTURE || '';
30
+ const paw = process.env.PROCESSOR_ARCHITEW6432 || '';
31
+ if (/arm/i.test(pa) || /arm/i.test(paw)) return 'arm64';
32
+ }
33
+
34
+ return 'x64';
35
+ }
36
+
37
+ function getPlatformDir() {
38
+ const platform = process.platform;
39
+ const arch = getEffectiveArch();
40
+
41
+ if (platform === 'linux' && arch === 'x64') return 'linux-x64';
42
+ if (platform === 'win32' && arch === 'x64') return 'windows-x64';
43
+ if (platform === 'darwin' && arch === 'arm64') return 'macos-arm64';
44
+
45
+ if (platform === 'darwin' && arch === 'x64') {
46
+ console.error('Intel Macs are not supported.');
47
+ console.error('eval-kanban requires Apple Silicon (M1 or later).');
48
+ process.exit(1);
49
+ }
50
+
51
+ if (platform === 'linux' && arch === 'arm64') {
52
+ console.error('Linux ARM64 is not supported yet.');
53
+ console.error('Please use Linux x64 or open an issue for ARM64 support.');
54
+ process.exit(1);
55
+ }
56
+
57
+ console.error(`Unsupported platform: ${platform}-${arch}`);
58
+ console.error('Supported platforms:');
59
+ console.error(' - Linux x64');
60
+ console.error(' - Windows x64');
61
+ console.error(' - macOS ARM64 (Apple Silicon M1+)');
62
+ process.exit(1);
63
+ }
64
+
65
+ function getBinaryName() {
66
+ return process.platform === 'win32' ? 'eval-kanban-server.exe' : 'eval-kanban-server';
67
+ }
68
+
69
+ function showProgress(downloaded, total) {
70
+ const percent = total ? Math.round((downloaded / total) * 100) : 0;
71
+ const mb = (downloaded / (1024 * 1024)).toFixed(1);
72
+ const totalMb = total ? (total / (1024 * 1024)).toFixed(1) : '?';
73
+ process.stderr.write(`\r Downloading: ${mb}MB / ${totalMb}MB (${percent}%)`);
74
+ }
75
+
76
+ async function main() {
77
+ const platformDir = getPlatformDir();
78
+ const cwd = process.cwd();
79
+
80
+ console.log(`eval-kanban v${CLI_VERSION}`);
81
+
82
+ // Get latest release info
83
+ let release;
84
+ try {
85
+ release = await getLatestRelease();
86
+ } catch (err) {
87
+ console.error(`Error: ${err.message}`);
88
+ console.error('Make sure you have internet connection and the release exists.');
89
+ process.exit(1);
90
+ }
91
+
92
+ const version = release.tag_name;
93
+ const versionDir = path.join(CACHE_DIR, version, platformDir);
94
+ const binPath = path.join(versionDir, getBinaryName());
95
+ const extractedMarker = path.join(versionDir, '.extracted');
96
+
97
+ // Download and extract if needed
98
+ if (!fs.existsSync(extractedMarker)) {
99
+ try {
100
+ const { zipPath } = await ensureBinary(platformDir, version, showProgress);
101
+
102
+ console.log('Extracting...');
103
+ const zip = new AdmZip(zipPath);
104
+ zip.extractAllTo(versionDir, true);
105
+
106
+ // Mark as extracted
107
+ fs.writeFileSync(extractedMarker, new Date().toISOString());
108
+
109
+ // Set permissions on Unix
110
+ if (process.platform !== 'win32') {
111
+ try {
112
+ fs.chmodSync(binPath, 0o755);
113
+ } catch {}
114
+ }
115
+
116
+ // Clean up zip
117
+ try {
118
+ fs.unlinkSync(zipPath);
119
+ } catch {}
120
+ } catch (err) {
121
+ console.error(`\nError: ${err.message}`);
122
+ process.exit(1);
123
+ }
124
+ }
125
+
126
+ if (!fs.existsSync(binPath)) {
127
+ console.error(`Error: Binary not found at ${binPath}`);
128
+ console.error('Try deleting ~/.eval-kanban/bin and running again.');
129
+ process.exit(1);
130
+ }
131
+
132
+ console.log('Starting server...');
133
+
134
+ const server = spawn(binPath, [], {
135
+ cwd: cwd,
136
+ stdio: 'inherit',
137
+ env: {
138
+ ...process.env,
139
+ RUST_LOG: process.env.RUST_LOG || 'eval_kanban_server=info',
140
+ },
141
+ });
142
+
143
+ server.on('error', (err) => {
144
+ console.error('Server error:', err.message);
145
+ process.exit(1);
146
+ });
147
+
148
+ server.on('exit', (code) => {
149
+ process.exit(code || 0);
150
+ });
151
+
152
+ process.on('SIGINT', () => {
153
+ server.kill('SIGINT');
154
+ });
155
+
156
+ process.on('SIGTERM', () => {
157
+ server.kill('SIGTERM');
158
+ });
159
+ }
160
+
161
+ main().catch((err) => {
162
+ console.error('Fatal error:', err.message);
163
+ process.exit(1);
164
+ });
@@ -0,0 +1,132 @@
1
+ const https = require('https');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const crypto = require('crypto');
5
+
6
+ const GITHUB_REPO = 'reverendjah/eval-kanban';
7
+ const CACHE_DIR = path.join(require('os').homedir(), '.eval-kanban', 'bin');
8
+
9
+ async function fetchJson(url) {
10
+ return new Promise((resolve, reject) => {
11
+ https.get(url, { headers: { 'User-Agent': 'eval-kanban-cli' } }, (res) => {
12
+ if (res.statusCode === 301 || res.statusCode === 302) {
13
+ return fetchJson(res.headers.location).then(resolve).catch(reject);
14
+ }
15
+ if (res.statusCode !== 200) {
16
+ return reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
17
+ }
18
+ let data = '';
19
+ res.on('data', (chunk) => (data += chunk));
20
+ res.on('end', () => {
21
+ try {
22
+ resolve(JSON.parse(data));
23
+ } catch (e) {
24
+ reject(new Error(`Failed to parse JSON from ${url}`));
25
+ }
26
+ });
27
+ }).on('error', reject);
28
+ });
29
+ }
30
+
31
+ async function downloadFile(url, destPath, onProgress) {
32
+ const tempPath = destPath + '.tmp';
33
+ return new Promise((resolve, reject) => {
34
+ const file = fs.createWriteStream(tempPath);
35
+
36
+ const cleanup = () => {
37
+ try {
38
+ fs.unlinkSync(tempPath);
39
+ } catch {}
40
+ };
41
+
42
+ const doRequest = (requestUrl) => {
43
+ https.get(requestUrl, { headers: { 'User-Agent': 'eval-kanban-cli' } }, (res) => {
44
+ if (res.statusCode === 301 || res.statusCode === 302) {
45
+ file.close();
46
+ cleanup();
47
+ return doRequest(res.headers.location);
48
+ }
49
+
50
+ if (res.statusCode !== 200) {
51
+ file.close();
52
+ cleanup();
53
+ return reject(new Error(`HTTP ${res.statusCode} downloading ${requestUrl}`));
54
+ }
55
+
56
+ const totalSize = parseInt(res.headers['content-length'], 10);
57
+ let downloadedSize = 0;
58
+
59
+ res.on('data', (chunk) => {
60
+ downloadedSize += chunk.length;
61
+ if (onProgress) onProgress(downloadedSize, totalSize);
62
+ });
63
+ res.pipe(file);
64
+
65
+ file.on('finish', () => {
66
+ file.close();
67
+ try {
68
+ fs.renameSync(tempPath, destPath);
69
+ resolve(destPath);
70
+ } catch (err) {
71
+ cleanup();
72
+ reject(err);
73
+ }
74
+ });
75
+ }).on('error', (err) => {
76
+ file.close();
77
+ cleanup();
78
+ reject(err);
79
+ });
80
+ };
81
+
82
+ doRequest(url);
83
+ });
84
+ }
85
+
86
+ async function getLatestRelease() {
87
+ const url = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
88
+ try {
89
+ const release = await fetchJson(url);
90
+ return release;
91
+ } catch (err) {
92
+ throw new Error(`Failed to fetch latest release: ${err.message}`);
93
+ }
94
+ }
95
+
96
+ async function ensureBinary(platform, version, onProgress) {
97
+ const versionDir = path.join(CACHE_DIR, version, platform);
98
+ const zipPath = path.join(versionDir, 'eval-kanban.zip');
99
+ const extractedMarker = path.join(versionDir, '.extracted');
100
+
101
+ // Already downloaded and extracted
102
+ if (fs.existsSync(extractedMarker)) {
103
+ return versionDir;
104
+ }
105
+
106
+ fs.mkdirSync(versionDir, { recursive: true });
107
+
108
+ // Download if not cached
109
+ if (!fs.existsSync(zipPath)) {
110
+ const release = await getLatestRelease();
111
+ const asset = release.assets.find((a) => a.name === `eval-kanban-${platform}.zip`);
112
+
113
+ if (!asset) {
114
+ throw new Error(`No binary available for platform: ${platform}`);
115
+ }
116
+
117
+ console.error(`Downloading eval-kanban ${version} for ${platform}...`);
118
+ await downloadFile(asset.browser_download_url, zipPath, onProgress);
119
+ console.error(''); // newline after progress
120
+ }
121
+
122
+ return { zipPath, versionDir };
123
+ }
124
+
125
+ module.exports = {
126
+ GITHUB_REPO,
127
+ CACHE_DIR,
128
+ fetchJson,
129
+ downloadFile,
130
+ getLatestRelease,
131
+ ensureBinary,
132
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "eval-kanban",
3
+ "version": "0.1.0",
4
+ "description": "Kanban board for orchestrating tasks with Claude Code",
5
+ "bin": {
6
+ "eval-kanban": "./bin/cli.js"
7
+ },
8
+ "scripts": {
9
+ "start": "node bin/cli.js"
10
+ },
11
+ "keywords": [
12
+ "claude",
13
+ "kanban",
14
+ "ai",
15
+ "cli",
16
+ "automation"
17
+ ],
18
+ "author": "",
19
+ "license": "MIT",
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "files": [
24
+ "bin"
25
+ ],
26
+ "dependencies": {
27
+ "adm-zip": "^0.5.16"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/reverendjah/eval-kanban.git"
32
+ }
33
+ }