@surenkid/acli-hub 1.0.2 → 1.0.3

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": "@surenkid/acli-hub",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Web workbench for AI CLIs — run Claude Code, Codex, Kimi and more in your browser (ttyd + tmux)",
5
5
  "license": "MIT",
6
6
  "bin": {
package/server/index.js CHANGED
@@ -18,6 +18,14 @@ const authRoute = require('./routes/auth');
18
18
  const sessionsRoute = require('./routes/sessions');
19
19
  const filesRoute = require('./routes/files');
20
20
  const setupWebSocket = require('./ws');
21
+ const { checkDependencies, dependencyReport } = require('./services/deps');
22
+
23
+ // Fail fast with actionable install hints instead of a half-broken UI.
24
+ const missingDependencies = checkDependencies();
25
+ if (missingDependencies.length) {
26
+ console.error(dependencyReport(missingDependencies));
27
+ process.exit(1);
28
+ }
21
29
 
22
30
  const PORT = parseInt(process.env.ACLI_HUB_PORT || '3000', 10);
23
31
  const HOST = process.env.ACLI_HUB_HOST || '127.0.0.1';
@@ -0,0 +1,94 @@
1
+ const { spawnSync } = require('child_process');
2
+
3
+ // Homebrew paths are not always on PATH when the process is launched from a
4
+ // GUI (Dock, Spotlight), so probe with the same augmentation SPAWN_ENV uses.
5
+ const EXTRA_PATH = `/usr/local/bin:/opt/homebrew/bin:${process.env.PATH || ''}`;
6
+
7
+ // Placeholder doc links — replace with the real FAQ pages when published.
8
+ const DOCS = 'https://acli-hub.uum.cc/faq';
9
+
10
+ const TOOLS = {
11
+ tmux: {
12
+ why: 'to keep terminal sessions alive across browser disconnects',
13
+ hints: [
14
+ 'macOS: brew install tmux',
15
+ 'Debian/Ubuntu: sudo apt install tmux',
16
+ 'Fedora: sudo dnf install tmux',
17
+ 'Arch: sudo pacman -S tmux',
18
+ 'Alpine: sudo apk add tmux',
19
+ ],
20
+ docs: `${DOCS}/install-tmux.html`,
21
+ },
22
+ ttyd: {
23
+ why: 'to stream the terminal into your browser',
24
+ hints: [
25
+ 'macOS: brew install ttyd',
26
+ 'Debian/Ubuntu: sudo apt install ttyd',
27
+ 'any Linux: sudo snap install ttyd --classic',
28
+ ' (or download a binary from https://github.com/tsl0922/ttyd/releases)',
29
+ ],
30
+ docs: `${DOCS}/install-ttyd.html`,
31
+ },
32
+ };
33
+
34
+ // A non-zero exit code still proves the binary exists — only a spawn error
35
+ // (ENOENT, EACCES) counts as "not installed".
36
+ function defaultProbe(tool, env) {
37
+ const args = tool === 'tmux' ? ['-V'] : ['--version'];
38
+ return { ok: !spawnSync(tool, args, { env, stdio: 'ignore' }).error };
39
+ }
40
+
41
+ function checkDependencies({ platform = process.platform, env = { ...process.env, PATH: EXTRA_PATH }, probe = defaultProbe } = {}) {
42
+ const missing = [];
43
+ for (const tool of Object.keys(TOOLS)) {
44
+ if (!probe(tool, env).ok) missing.push({ tool });
45
+ }
46
+ return missing;
47
+ }
48
+
49
+ // tmux has no native Windows build (its ncurses dependency is POSIX-only),
50
+ // so any missing dependency on Windows gets one shared WSL walkthrough that
51
+ // also covers the shell: the Ubuntu that wsl --install provides ships bash.
52
+ function windowsReport() {
53
+ return [
54
+ '✖ Windows is not supported natively',
55
+ '',
56
+ ' ACLI Hub is built on tmux + ttyd + a POSIX shell, and tmux has no',
57
+ ' native Windows build. Run it inside WSL instead:',
58
+ '',
59
+ ' 1. wsl --install (PowerShell as admin —',
60
+ ' installs Ubuntu, which already includes bash)',
61
+ ' 2. inside WSL: sudo apt install tmux ttyd',
62
+ ' 3. inside WSL: npm install -g @surenkid/acli-hub && acli-hub',
63
+ '',
64
+ ' Note: Git Bash alone is not enough — tmux cannot run outside WSL.',
65
+ '',
66
+ ` Full Windows guide: ${DOCS}/install-windows.html`,
67
+ ].join('\n');
68
+ }
69
+
70
+ // Claude-Code-style actionable error: what is missing, why it matters, the
71
+ // shortest working command per platform, and where to read more. macOS users
72
+ // only see the brew line; Linux keeps the whole distro family (the distro
73
+ // itself is not probed, so all of apt/dnf/pacman/apk stay listed).
74
+ function relevantHints(hints, platform) {
75
+ return platform === 'darwin' ? hints.filter(line => line.startsWith('macOS:')) : hints.filter(line => !line.startsWith('macOS:'));
76
+ }
77
+
78
+ function dependencyReport(missing, platform = process.platform) {
79
+ if (platform === 'win32') return windowsReport();
80
+ return missing.map(({ tool }) => {
81
+ const spec = TOOLS[tool];
82
+ return [
83
+ `✖ ${tool} is not installed`,
84
+ '',
85
+ ` ACLI Hub needs ${tool} ${spec.why}, but it wasn't found on your PATH.`,
86
+ '',
87
+ ...relevantHints(spec.hints, platform).map(line => ` ${line}`),
88
+ '',
89
+ ` Full install guide: ${spec.docs}`,
90
+ ].join('\n');
91
+ }).join('\n\n');
92
+ }
93
+
94
+ module.exports = { checkDependencies, dependencyReport };
@@ -0,0 +1,46 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert');
3
+ const { checkDependencies, dependencyReport } = require('./deps');
4
+
5
+ const found = () => ({ ok: true });
6
+ const notFound = () => ({ ok: false });
7
+
8
+ test('nothing is reported when both tools probe successfully', () => {
9
+ assert.deepEqual(checkDependencies({ probe: found }), []);
10
+ });
11
+
12
+ test('a missing tool lists a complete set of per-platform quick installs', () => {
13
+ const missing = checkDependencies({ probe: tool => tool === 'ttyd' ? found() : notFound() });
14
+ assert.deepEqual(missing, [{ tool: 'tmux' }]);
15
+
16
+ const report = dependencyReport(missing, 'darwin');
17
+ assert.match(report, /✖ tmux is not installed/);
18
+ assert.match(report, /brew install tmux/);
19
+ assert.match(report, /Full install guide: https:\/\/acli-hub\.uum\.cc\/faq\/install-tmux\.html/);
20
+ assert.ok(!report.includes('apt'), 'darwin report should not suggest apt');
21
+ });
22
+
23
+ test('linux reports cover apt/dnf/pacman/apk, snap and the release binaries', () => {
24
+ const report = dependencyReport(checkDependencies({ probe: notFound }), 'linux');
25
+ assert.match(report, /✖ tmux is not installed/);
26
+ assert.match(report, /sudo apt install tmux/);
27
+ assert.match(report, /sudo dnf install tmux/);
28
+ assert.match(report, /sudo pacman -S tmux/);
29
+ assert.match(report, /sudo apk add tmux/);
30
+ assert.match(report, /✖ ttyd is not installed/);
31
+ assert.match(report, /sudo apt install ttyd/);
32
+ assert.match(report, /snap install ttyd --classic/);
33
+ assert.match(report, /github\.com\/tsl0922\/ttyd\/releases/);
34
+ assert.match(report, /install-ttyd\.html/);
35
+ });
36
+
37
+ test('any missing dependency on Windows collapses into one WSL walkthrough', () => {
38
+ const report = dependencyReport([{ tool: 'tmux' }], 'win32');
39
+ assert.match(report, /✖ Windows is not supported natively/);
40
+ assert.match(report, /wsl --install/);
41
+ assert.match(report, /sudo apt install tmux ttyd/);
42
+ assert.match(report, /includes bash/);
43
+ assert.match(report, /Git Bash alone is not enough/);
44
+ assert.match(report, /install-windows\.html/);
45
+ assert.ok(!report.includes('brew install'), 'windows report should not suggest brew');
46
+ });