@mmmbuto/nexuscrew 0.2.2 → 0.2.4

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
@@ -1,72 +1,73 @@
1
- ## Overview
1
+ # NexusCrew
2
2
 
3
- NexusCrew is a **tmux-based** AI cockpit that orchestrates Claude Code, Codex CLI, Gemini CLI, and Qwen Code through persistent tmux sessions.
3
+ [![npm](https://img.shields.io/npm/v/@mmmbuto/nexuscrew?style=flat-square&logo=npm)](https://www.npmjs.org/package/@mmmbuto/nexuscrew)
4
4
 
5
- This is a **study project** exploring an alternative approach to AI CLI orchestration, based on experience with [NexusCLI](https://www.npmjs.com/package/@mmmbuto/nexuscli). Instead of managing PTY processes in-memory, NexusCrew delegates session lifecycle to **tmux** so sessions survive server restarts and can be accessed directly from the terminal.
5
+ NexusCrew is a tmux-first AI cockpit for Claude Code, Codex CLI, Gemini CLI, and Qwen Code.
6
6
 
7
- > **Status**: Beta experimental, API may change
8
-
9
- > **Disclaimer**: This project is experimental. The automation pipeline is fast enough that it may be generated, tested, and published before I finish thinking about it. Expect incomplete features, mistakes, and breaking changes.
7
+ It is built around one core rule: chat targets real tmux sessions that already exist, or new tmux sessions that are created explicitly from launchers detected on the current machine.
10
8
 
11
9
  ---
12
10
 
13
- ## Why tmux?
14
-
15
- | Problem | NexusCLI (PTY) | NexusCrew (tmux) |
16
- |---------|---------------|-------------------|
17
- | Server crash | All sessions die | Sessions survive |
18
- | Direct access | Web UI only | Web UI + `tmux attach` |
19
- | Remote machines | Not supported | Experimental host registry |
20
- | Native deps | node-pty required | SQLite native addon |
21
- | Session recovery | Manual | Automatic |
11
+ ## About
22
12
 
23
- ---
13
+ - Runtime model: active tmux sessions + explicit tmux creation
14
+ - Launcher discovery: runtime-driven, shell-agnostic, user-agnostic
15
+ - Primary verified environment: Termux on Android
16
+ - Linux/macOS: supported by design, not yet fully revalidated in the current cycle
17
+ - Remote hosts: registry and connectivity exist, but end-to-end remote session flow is still experimental
24
18
 
25
- ## Features
19
+ ## Project Scope
26
20
 
27
- - **Persistent sessions** — standalone tmux sessions survive server restarts
28
- - **Multi-engine** — Claude Code, Codex CLI, Gemini CLI, Qwen Code
29
- - **Auto-discovery** — detects installed CLIs plus live shell aliases/functions and generic shell config fallbacks
30
- - **Explicit tmux creation** — create a new tmux session only from a detected runnable launcher
31
- - **Unified web UI** — attach chat to real active tmux sessions
32
- - **SSE streaming** — real-time output from tmux sessions
33
- - **Cross-platform** — Linux, macOS, Termux (Android)
21
+ ### What It Does
34
22
 
35
- ---
23
+ - Lists real active tmux sessions on the host
24
+ - Lets the UI attach chat to an existing tmux session
25
+ - Detects runnable launchers from:
26
+ - PATH binaries
27
+ - live shell aliases/functions
28
+ - generic shell config fallbacks
29
+ - Creates a new standalone tmux session only when you explicitly ask for it
30
+ - Stores conversation state locally in SQLite
31
+ - Streams output back through SSE
36
32
 
37
- ## Engine Discovery
33
+ ### What It Does Not Do
38
34
 
39
- NexusCrew automatically detects:
35
+ - It does not create hidden tmux master sessions
36
+ - It does not keep a hardcoded list of AI models or wrappers
37
+ - It does not assume a user-specific home like `/home/dag`
38
+ - It does not treat shell-file-only detections as runnable unless the active shell confirms them
40
39
 
41
- 1. **PATH binaries** — `claude`, `codex`, `gemini`, `qwen` when present in the runtime PATH
42
- 2. **Live shell wrappers** — aliases/functions available in the current login shell
43
- 3. **Shell-file fallbacks** — wrappers detected from `.zshrc`, `.bashrc`, `.profile`, and optional extra shell sources
40
+ ---
44
41
 
45
- Launchers detected only from shell files are shown as detected metadata but are not treated as runnable until they are verified in the active shell context.
42
+ ## Installation
46
43
 
47
- ---
44
+ ### Quick Start
48
45
 
49
- ## Install
46
+ Install:
50
47
 
51
48
  ```bash
52
49
  npm install -g @mmmbuto/nexuscrew
53
50
  ```
54
51
 
55
- Then initialize:
52
+ Initialize local config:
56
53
 
57
54
  ```bash
58
55
  nexuscrew init
59
56
  ```
60
57
 
61
- And start:
58
+ Start the server:
62
59
 
63
60
  ```bash
64
61
  nexuscrew start
65
62
  ```
66
63
 
67
- Open `http://localhost:41820` in your browser.
64
+ Open:
65
+
66
+ ```text
67
+ http://localhost:41820
68
+ ```
68
69
 
69
- ### Termux (Android)
70
+ ### Termux Quick Start
70
71
 
71
72
  ```bash
72
73
  pkg install tmux nodejs
@@ -75,103 +76,126 @@ nexuscrew init
75
76
  nexuscrew start
76
77
  ```
77
78
 
79
+ Notes:
80
+
81
+ - the package uses a Termux-compatible SQLite dependency path during install
82
+ - detailed install notes live in [docs/INSTALLATION.md](docs/INSTALLATION.md)
83
+
78
84
  ---
79
85
 
80
- ## Hosts Registry
86
+ ## Runtime Model
81
87
 
82
- You can store SSH host definitions for later work:
88
+ NexusCrew now works with standalone tmux sessions.
83
89
 
84
- ```bash
85
- nexuscrew hosts add --name vps1 --host example.com --user user
86
- nexuscrew hosts add --name vps2 --host 192.168.1.10 --user admin --port 2222
87
- ```
90
+ That means:
88
91
 
89
- Host storage and connectivity tests are available, but the current UI and default session flow are local tmux-first. Treat remote execution as experimental until it is revalidated end-to-end.
92
+ - one AI runtime = one tmux session
93
+ - chat sends to an existing tmux session
94
+ - new tmux sessions are created explicitly from a detected launcher
95
+ - session output is piped to log files and watched by the server
96
+
97
+ Architecture:
98
+
99
+ ```text
100
+ Browser <--SSE--> Express server <--tmux--> active tmux sessions
101
+ |- claude-work
102
+ |- codex-debug
103
+ `- qwen-sandbox
104
+ ```
90
105
 
91
106
  ---
92
107
 
93
- ## Commands
108
+ ## Launcher Discovery
94
109
 
95
- | Command | Description |
96
- |---------|-------------|
97
- | `nexuscrew init` | First-time setup (config, hosts, tmux check) |
98
- | `nexuscrew start` | Start server |
99
- | `nexuscrew stop` | Stop server |
100
- | `nexuscrew status` | Server + tmux + hosts status |
101
- | `nexuscrew engines` | Discover available AI CLIs and aliases |
102
- | `nexuscrew sessions` | List tmux sessions |
103
- | `nexuscrew attach` | Attach to tmux session directly |
104
- | `nexuscrew config` | View/edit configuration |
105
- | `nexuscrew hosts list` | List configured hosts |
106
- | `nexuscrew hosts add` | Add remote SSH host |
107
- | `nexuscrew hosts remove` | Remove a host |
110
+ Discovery is environment-agnostic and shell-agnostic.
108
111
 
109
- ---
112
+ Sources, in order:
110
113
 
111
- ## Configuration
114
+ 1. live shell introspection
115
+ 2. generic shell files such as `.zshrc`, `.bashrc`, `.profile`
116
+ 3. PATH binaries
112
117
 
113
- Config stored in `~/.nexuscrew/config.json`:
118
+ Detection families are inferred from what is actually present, not from a static product catalog.
114
119
 
115
- ```json
116
- {
117
- "port": 41820,
118
- "logDir": "~/.nexuscrew/logs",
119
- "autoDiscovery": true,
120
- "preferredShell": "",
121
- "extraShellSources": []
122
- }
123
- ```
120
+ Current launcher families:
124
121
 
125
- Hosts stored in `~/.nexuscrew/hosts.json`:
122
+ - `claude`
123
+ - `codex`
124
+ - `gemini`
125
+ - `qwen`
126
+ - `custom`
126
127
 
127
- ```json
128
- [
129
- { "name": "local", "type": "local", "default": true },
130
- { "name": "vps1", "type": "ssh", "host": "example.com", "user": "user", "port": "22" }
131
- ]
132
- ```
128
+ Important distinction:
129
+
130
+ - `runnable: true` means the launcher is valid for explicit tmux creation now
131
+ - `runnable: false` means it was detected from shell config metadata only
133
132
 
134
133
  ---
135
134
 
136
- ## Network
135
+ ## Commands
137
136
 
138
- | Protocol | Default Port | Use Case |
139
- |----------|--------------|----------|
140
- | HTTP | `41820` | Local access |
137
+ | Command | Description |
138
+ |---------|-------------|
139
+ | `nexuscrew init` | Create config, hosts file, and logs directory |
140
+ | `nexuscrew start` | Start the web server |
141
+ | `nexuscrew stop` | Stop the server |
142
+ | `nexuscrew status` | Show server, tmux, and host status |
143
+ | `nexuscrew sessions` | List active tmux sessions |
144
+ | `nexuscrew attach -s <name>` | Attach to a tmux session directly |
145
+ | `nexuscrew engines` | Print discovered launchers |
146
+ | `nexuscrew config --list` | Show config |
147
+ | `nexuscrew config --set key=value` | Update config |
148
+ | `nexuscrew hosts list` | List stored hosts |
149
+ | `nexuscrew hosts add ...` | Add an SSH host entry |
150
+ | `nexuscrew hosts remove ...` | Remove an SSH host entry |
141
151
 
142
152
  ---
143
153
 
144
- ## Architecture
154
+ ## Documentation
145
155
 
146
- ```
147
- Browser ←SSE→ Express Server ←tmux→ active tmux sessions
148
- ├─ session: claude-work
149
- ├─ session: codex-debug
150
- └─ session: qwen-sandbox
151
- ```
156
+ - [Installation Guide](docs/INSTALLATION.md)
157
+ - [Configuration Guide](docs/CONFIGURATION.md)
158
+ - [Current State](docs/CURRENT_STATE.md)
159
+ - [Changelog](CHANGELOG.md)
160
+
161
+ ---
152
162
 
153
- Each AI session = one standalone tmux session. Output is piped to log files, parsed by the watcher, and streamed to the UI via SSE.
163
+ ## Support Matrix
164
+
165
+ | Area | State |
166
+ |------|----------------|
167
+ | Local tmux session listing | Verified |
168
+ | Explicit local tmux creation | Verified |
169
+ | Runtime launcher discovery | Verified |
170
+ | Bucketed launcher discovery | Verified |
171
+ | Shell-file-only launcher gating | Verified |
172
+ | Host-scoped tmux/session selection | Implemented |
173
+ | SSE send flow | Improved, basic smoke tested |
174
+ | Interrupt flow | Improved, not deeply stress-tested |
175
+ | Remote host registry | Present |
176
+ | Remote host execution | Implemented, pending live smoke |
154
177
 
155
178
  ---
156
179
 
157
180
  ## Development
158
181
 
159
182
  ```bash
160
- git clone https://github.com/dionanos/nexuscrew
183
+ git clone https://github.com/DioNanos/nexuscrew
161
184
  cd nexuscrew
162
185
  npm install
163
- cd frontend && npm install && npm run build && cd ..
186
+ npm run build
164
187
  npm run dev
165
188
  ```
166
189
 
167
190
  ---
168
191
 
169
- ## Relation to NexusCLI
192
+ ## Known Gaps
170
193
 
171
- NexusCrew is a **study branch** inspired by [NexusCLI](https://www.npmjs.com/package/@mmmbuto/nexuscli). It shares the same web UI direction but replaces PTY-based process management with tmux-based persistent sessions. The goal is to explore whether tmux provides a simpler, more robust foundation for local AI CLI orchestration and crash recovery.
194
+ - remote tmux workflows still need live multi-host smoke before being advertised as stable
195
+ - send/interrupt should still be stress-tested against long-running CLI pauses
172
196
 
173
197
  ---
174
198
 
175
199
  ## License
176
200
 
177
- MIT License. See [LICENSE](LICENSE).
201
+ MIT. See `LICENSE`.
package/bin/nexuscrew.js CHANGED
@@ -8,11 +8,13 @@ const os = require('os');
8
8
  const { execSync, spawn } = require('child_process');
9
9
 
10
10
  const EngineDiscovery = require('../lib/services/engine-discovery');
11
+ const TmuxManager = require('../lib/services/tmux-manager');
12
+ const { getConfigDir, getConfigPath, getHostsPath, getDefaultLogDir } = require('../lib/config/paths');
11
13
  const packageJson = require('../package.json');
12
14
 
13
- const CONFIG_DIR = path.join(os.homedir(), '.nexuscrew');
14
- const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
15
- const HOSTS_PATH = path.join(CONFIG_DIR, 'hosts.json');
15
+ const CONFIG_DIR = getConfigDir();
16
+ const CONFIG_PATH = getConfigPath();
17
+ const HOSTS_PATH = getHostsPath();
16
18
 
17
19
  const DEFAULT_PORT = '41820';
18
20
 
@@ -41,7 +43,7 @@ program.command('init')
41
43
  if (!fs.existsSync(CONFIG_PATH)) {
42
44
  fs.writeFileSync(CONFIG_PATH, JSON.stringify({
43
45
  port: Number(DEFAULT_PORT),
44
- logDir: path.join(CONFIG_DIR, 'logs'),
46
+ logDir: getDefaultLogDir(),
45
47
  autoDiscovery: true,
46
48
  preferredShell: '',
47
49
  extraShellSources: []
@@ -56,7 +58,7 @@ program.command('init')
56
58
  log('Created hosts.json');
57
59
  }
58
60
 
59
- const logDir = path.join(CONFIG_DIR, 'logs');
61
+ const logDir = getDefaultLogDir();
60
62
  if (!fs.existsSync(logDir)) {
61
63
  fs.mkdirSync(logDir, { recursive: true });
62
64
  }
@@ -241,13 +243,28 @@ hostsCmd.command('remove')
241
243
 
242
244
  program.command('engines')
243
245
  .description('Discover available launchers')
244
- .action(() => {
245
- const launchers = new EngineDiscovery(readConfig()).getLaunchers();
246
+ .option('-H, --host <name>', 'Host name', 'local')
247
+ .action((opts) => {
248
+ const config = readConfig();
249
+ const hosts = fs.existsSync(HOSTS_PATH)
250
+ ? JSON.parse(fs.readFileSync(HOSTS_PATH, 'utf8'))
251
+ : [{ name: 'local', type: 'local', default: true }];
252
+ const tmux = new TmuxManager({
253
+ configDir: CONFIG_DIR,
254
+ logDir: config.logDir || getDefaultLogDir(),
255
+ preferredShell: config.preferredShell || process.env.SHELL || '/bin/sh',
256
+ hosts
257
+ });
258
+ const launchers = new EngineDiscovery({ ...config, tmux }).discover(opts.host || 'local');
246
259
  console.log('\nDiscovered Launchers:\n');
247
- for (const launcher of launchers) {
248
- console.log(` ${launcher.label}: ${launcher.kind} [${launcher.family}]${launcher.runnable ? '' : ' (detected only)'}`);
249
- console.log(` command: ${launcher.commandPreview}`);
250
- console.log(` source: ${launcher.source}`);
260
+ for (const [bucket, items] of Object.entries(launchers.buckets)) {
261
+ if (items.length === 0) continue;
262
+ console.log(` ${bucket}:`);
263
+ for (const launcher of items) {
264
+ console.log(` ${launcher.label}: ${launcher.kind} [${launcher.family}]${launcher.runnable ? '' : ' (detected only)'}`);
265
+ console.log(` command: ${launcher.commandPreview}`);
266
+ console.log(` source: ${launcher.source}`);
267
+ }
251
268
  }
252
269
  console.log('');
253
270
  });