ninja-terminals 2.4.0 → 2.4.1

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/server.js CHANGED
@@ -35,10 +35,10 @@ const {
35
35
  } = require('./lib/runtime-session');
36
36
 
37
37
  // ── Config ──────────────────────────────────────────────────
38
- const PREFERRED_PORT = parseInt(process.env.PORT || '3300', 10);
38
+ const PREFERRED_PORT = parseInt(process.env.PORT || process.env.HTTP_PORT || '3300', 10);
39
39
  const BIND_HOST = process.env.NINJA_BIND_HOST || '127.0.0.1';
40
40
  const DEFAULT_TERMINALS = parseInt(process.env.DEFAULT_TERMINALS || '4', 10);
41
- const CLAUDE_CMD = process.env.CLAUDE_CMD || 'claude --dangerously-skip-permissions';
41
+ const CLAUDE_CMD = process.env.CLAUDE_CMD || process.env.CLAUDE_CHROME_CMD || 'claude --chrome --model claude-opus-4-5-20251101';
42
42
  const SHELL = process.env.SHELL || '/bin/zsh';
43
43
  const PROJECT_DIR = __dirname;
44
44
  const DEFAULT_CWD = process.env.DEFAULT_CWD || null; // Set to target project path to avoid cross-project prompts
@@ -47,12 +47,17 @@ const INJECT_GUIDANCE = process.env.INJECT_GUIDANCE !== 'false'; // Default tru
47
47
  // Fleet modes — preset terminal configurations
48
48
  const FLEET_MODES = {
49
49
  claude: ['claude', 'claude', 'claude', 'claude'],
50
+ codex: ['codex', 'codex', 'codex', 'codex'],
51
+ opencode: ['opencode', 'opencode', 'opencode', 'opencode'],
50
52
  teams: ['claude', 'opencode', 'codex', 'shell'],
51
53
  mixed: ['claude', 'claude', 'opencode', 'shell'],
52
54
  shell: ['shell', 'shell', 'shell', 'shell'],
53
55
  duo: ['claude', 'opencode'],
54
56
  };
55
57
  const FLEET_MODE = process.env.NINJA_MODE || 'claude';
58
+ // Resolve the claude binary from PATH by default so it works on any machine.
59
+ // Override with CLAUDE_CMD (full command) or CLAUDE_BIN (binary path) if needed.
60
+ const CLAUDE_BIN = process.env.CLAUDE_BIN || 'claude';
56
61
 
57
62
  const sleep = ms => new Promise(r => setTimeout(r, ms));
58
63
 
@@ -152,7 +157,7 @@ function getTerminalRules(terminalId) {
152
157
  // ── Terminal Spawning ───────────────────────────────────────
153
158
 
154
159
  const AGENT_COMMANDS = {
155
- claude: process.env.CLAUDE_CMD || 'claude --dangerously-skip-permissions',
160
+ claude: process.env.CLAUDE_CMD || `${CLAUDE_BIN} --dangerously-skip-permissions`,
156
161
  opencode: 'opencode',
157
162
  codex: 'codex',
158
163
  shell: null, // No command — just shell
@@ -197,6 +202,9 @@ function spawnTerminal(label, scope = [], cwd = null, tier = 'pro', agentType =
197
202
  env: {
198
203
  ...cleanEnv,
199
204
  TERM: 'xterm-256color',
205
+ COLORTERM: 'truecolor',
206
+ FORCE_COLOR: '1',
207
+ CLICOLOR_FORCE: '1',
200
208
  HOME: require('os').homedir(),
201
209
  PATH: `${require('os').homedir()}/.local/bin:/opt/homebrew/bin:${process.env.PATH || ''}`,
202
210
  SHELL_SESSIONS_DISABLE: '1',
@@ -1292,13 +1300,16 @@ async function startServer() {
1292
1300
  server.listen(selectedPort, BIND_HOST, () => {
1293
1301
  const url = `http://localhost:${selectedPort}`;
1294
1302
  console.log(`[bind] Listening on ${BIND_HOST}:${selectedPort}`);
1303
+ const fleetConfig = FLEET_MODES[FLEET_MODE] || FLEET_MODES.claude;
1304
+ const terminalCount = DEFAULT_TERMINALS > 0 ? Math.min(DEFAULT_TERMINALS, fleetConfig.length) : fleetConfig.length;
1295
1305
  const session = writeRuntimeSession({
1296
1306
  port: selectedPort,
1297
1307
  host: BIND_HOST,
1298
1308
  url,
1299
1309
  cwd: DEFAULT_CWD || process.cwd(),
1300
- terminals: DEFAULT_TERMINALS,
1310
+ terminals: terminalCount,
1301
1311
  command: 'ninja-terminals',
1312
+ launchConfig: { mode: FLEET_MODE, terminalCount },
1302
1313
  });
1303
1314
 
1304
1315
  console.log(`Ninja Terminals v2 running on ${url}`);
@@ -1314,8 +1325,6 @@ async function startServer() {
1314
1325
  startSessionHeartbeat(sessionCache, handleSessionInvalidation, 5 * 60 * 1000);
1315
1326
 
1316
1327
  // Auto-spawn terminals based on fleet mode
1317
- const fleetConfig = FLEET_MODES[FLEET_MODE] || FLEET_MODES.claude;
1318
- const terminalCount = DEFAULT_TERMINALS > 0 ? Math.min(DEFAULT_TERMINALS, fleetConfig.length) : fleetConfig.length;
1319
1328
  const agentLabels = { claude: 'Claude', opencode: 'OpenCode', codex: 'Codex', shell: 'Shell' };
1320
1329
 
1321
1330
  console.log(`Auto-spawning ${terminalCount} terminals (mode: ${FLEET_MODE})...`);
@@ -1,46 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
- const os = require('os');
7
-
8
- const NINJA_DIR = path.join(os.homedir(), '.ninja');
9
- const REQUEST_FILE = path.join(NINJA_DIR, 'ninja-request.json');
10
-
11
- const NINJA_PATTERNS = [
12
- /ninja\s*terminal/i,
13
- /use\s+ninja/i,
14
- /build\s+with\s+ninja/i,
15
- /orchestrat/i,
16
- /\bPRD\b.*\bbuild\b/i,
17
- /\bbuild\b.*\bPRD\b/i,
18
- ];
19
-
20
- function isNinjaRequest(prompt) {
21
- if (!prompt) return false;
22
- return NINJA_PATTERNS.some(pattern => pattern.test(prompt));
23
- }
24
-
25
- function writeNinjaRequest(cwd, promptPreview) {
26
- try {
27
- fs.mkdirSync(NINJA_DIR, { recursive: true, mode: 0o700 });
28
- const data = {
29
- timestamp: new Date().toISOString(),
30
- cwd: cwd || process.cwd(),
31
- promptPreview: (promptPreview || '').slice(0, 200),
32
- };
33
- fs.writeFileSync(REQUEST_FILE, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
34
- return data;
35
- } catch (err) {
36
- console.error(`Warning: Could not write ninja request: ${err.message}`);
37
- return null;
38
- }
39
- }
40
-
41
- module.exports = {
42
- NINJA_DIR,
43
- REQUEST_FILE,
44
- isNinjaRequest,
45
- writeNinjaRequest,
46
- };
@@ -1,33 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const { isNinjaRequest, writeNinjaRequest } = require('./ninja-common');
5
-
6
- function main() {
7
- let input = '';
8
- process.stdin.setEncoding('utf8');
9
- process.stdin.on('data', chunk => { input += chunk; });
10
- process.stdin.on('end', () => {
11
- try {
12
- const event = JSON.parse(input);
13
- const prompt = event.prompt || '';
14
- const cwd = event.cwd || process.cwd();
15
-
16
- if (isNinjaRequest(prompt)) {
17
- writeNinjaRequest(cwd, prompt);
18
- const output = {
19
- result: 'continue',
20
- message: `NINJA REQUEST DETECTED. Follow ORCHESTRATOR-PROMPT.md workflow.`,
21
- };
22
- console.log(JSON.stringify(output));
23
- } else {
24
- console.log(JSON.stringify({ result: 'continue' }));
25
- }
26
- } catch (err) {
27
- console.error(`Warning: ninja-prompt-submit hook error: ${err.message}`);
28
- console.log(JSON.stringify({ result: 'continue' }));
29
- }
30
- });
31
- }
32
-
33
- main();
@@ -1,95 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const http = require('http');
5
- const { execSync } = require('child_process');
6
-
7
- const PORT = parseInt(process.env.HTTP_PORT || '3300', 10);
8
- const MAX_WAIT_MS = 10000;
9
- const POLL_INTERVAL_MS = 500;
10
-
11
- function log(msg) {
12
- process.stderr.write(`[ninja-startup] ${msg}\n`);
13
- }
14
-
15
- function healthCheck(port) {
16
- return new Promise((resolve) => {
17
- const req = http.get(`http://localhost:${port}/health`, (res) => {
18
- let data = '';
19
- res.on('data', chunk => data += chunk);
20
- res.on('end', () => {
21
- try {
22
- const json = JSON.parse(data);
23
- resolve(json.status === 'ok');
24
- } catch {
25
- resolve(false);
26
- }
27
- });
28
- });
29
- req.on('error', () => resolve(false));
30
- req.setTimeout(2000, () => {
31
- req.destroy();
32
- resolve(false);
33
- });
34
- });
35
- }
36
-
37
- async function waitForHealth(port, maxWait = MAX_WAIT_MS) {
38
- const start = Date.now();
39
- while (Date.now() - start < maxWait) {
40
- if (await healthCheck(port)) return true;
41
- await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
42
- }
43
- return false;
44
- }
45
-
46
- function openInBrowser(url) {
47
- try {
48
- if (process.platform === 'darwin') {
49
- execSync(`open "${url}"`, { stdio: 'ignore' });
50
- } else if (process.platform === 'linux') {
51
- execSync(`xdg-open "${url}"`, { stdio: 'ignore' });
52
- } else if (process.platform === 'win32') {
53
- execSync(`start "" "${url}"`, { stdio: 'ignore', shell: true });
54
- }
55
- } catch {
56
- log(`Could not auto-open ${url}`);
57
- }
58
- }
59
-
60
- async function main() {
61
- // MCP server should already be running (started by Claude Code via mcpServers config)
62
- // Just wait for it to be healthy
63
- log(`Checking Ninja Terminal server on port ${PORT}...`);
64
-
65
- const healthy = await waitForHealth(PORT);
66
-
67
- if (!healthy) {
68
- // Server not running - tell user to check MCP config
69
- console.log(JSON.stringify({
70
- status: 'error',
71
- message: `Ninja Terminal server not responding on port ${PORT}. Check MCP config or run: npx ninja-terminals`,
72
- }));
73
- process.exit(0); // Don't fail the hook, just report
74
- }
75
-
76
- const mainUrl = `http://localhost:${PORT}/`;
77
- const logViewerUrl = `http://localhost:${PORT}/log-viewer.html`;
78
-
79
- log('Opening Ninja UI in browser...');
80
- openInBrowser(mainUrl);
81
-
82
- await new Promise(r => setTimeout(r, 300));
83
- openInBrowser(logViewerUrl);
84
-
85
- console.log(JSON.stringify({
86
- status: 'ready',
87
- port: PORT,
88
- urls: { main: mainUrl, logViewer: logViewerUrl },
89
- }));
90
- }
91
-
92
- main().catch(err => {
93
- log(`Error: ${err.message}`);
94
- process.exit(0); // Don't fail session start
95
- });