ninja-terminals 2.3.1 → 2.3.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/agent-send.js ADDED
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const http = require('http');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const {
9
+ SESSION_FILE,
10
+ LEDGER_FILE,
11
+ VERIFICATION_LEDGER_FILE,
12
+ readRuntimeSession,
13
+ healthCheckSession,
14
+ requestJson,
15
+ appendLedgerEntry,
16
+ readLedgerEntries,
17
+ appendVerificationEntry,
18
+ readVerificationEntries,
19
+ } = require('./lib/runtime-session');
20
+
21
+ const USAGE = `
22
+ Usage:
23
+ agent-send --status
24
+ agent-send --task-status [terminal-id]
25
+ agent-send --ledger [N]
26
+ agent-send --output <terminal-id> [--lines N]
27
+ agent-send --verifications [N]
28
+ agent-send <terminal-id> <message...>
29
+
30
+ Sends a message to a Ninja Terminal via PTY stdin.
31
+
32
+ Environment:
33
+ NINJA_HOST Server host (default: localhost)
34
+ NINJA_PORT Server port (overrides ${SESSION_FILE})
35
+ NINJA_AUTH_TOKEN Auth token (or use ${SESSION_FILE} / ~/.ninja/token)
36
+
37
+ Files:
38
+ ${SESSION_FILE} Runtime session (port, host, token)
39
+ ${LEDGER_FILE} Dispatch history (NDJSON)
40
+ ${VERIFICATION_LEDGER_FILE} Output/status verification history (NDJSON)
41
+
42
+ Examples:
43
+ agent-send --status # Check process status (logged as verification)
44
+ agent-send --task-status # Check all terminals task status
45
+ agent-send --task-status 1 # Check terminal 1 task status
46
+ agent-send --ledger # Show last 20 dispatches
47
+ agent-send --output 1 # Read terminal 1 output (logged as verification)
48
+ agent-send --output 1 --lines 50 # Read last 50 lines
49
+ agent-send --verifications # Show verification ledger
50
+ agent-send 2 "Hello from agent"
51
+ `;
52
+
53
+ function getToken(session) {
54
+ // 1. Environment variable
55
+ if (process.env.NINJA_AUTH_TOKEN) {
56
+ return process.env.NINJA_AUTH_TOKEN;
57
+ }
58
+
59
+ // 2. Runtime session file written by Ninja Terminals after browser auth
60
+ if (session && session.authToken) {
61
+ return session.authToken;
62
+ }
63
+
64
+ // 3. Token file
65
+ const tokenPath = path.join(os.homedir(), '.ninja', 'token');
66
+ if (fs.existsSync(tokenPath)) {
67
+ return fs.readFileSync(tokenPath, 'utf8').trim();
68
+ }
69
+
70
+ return null;
71
+ }
72
+
73
+ function resolveTarget(session) {
74
+ const port = process.env.NINJA_PORT || session?.port;
75
+ const host = process.env.NINJA_HOST || session?.host || 'localhost';
76
+
77
+ if (!port) {
78
+ throw new Error(`No live Ninja Terminal session found.
79
+
80
+ Start Ninja Terminals first. Expected runtime file:
81
+ ${SESSION_FILE}
82
+
83
+ Or set NINJA_PORT explicitly.`);
84
+ }
85
+
86
+ return { host, port: parseInt(port, 10) };
87
+ }
88
+
89
+ async function printStatus(session, token) {
90
+ const target = resolveTarget(session);
91
+ const health = await healthCheckSession({ ...session, ...target });
92
+ if (!health.ok) {
93
+ throw new Error(`Ninja Terminal health check failed on ${target.host}:${target.port}: ${health.error}`);
94
+ }
95
+
96
+ console.log(`Ninja Terminal: http://${target.host}:${target.port}`);
97
+ console.log(`Session file: ${SESSION_FILE}`);
98
+ console.log(`Health: ok (${health.response.terminals} terminals)`);
99
+
100
+ if (!token) {
101
+ console.log('Auth: missing token. Run: ninja-login');
102
+ return;
103
+ }
104
+
105
+ const res = await requestJson({
106
+ host: target.host,
107
+ port: target.port,
108
+ path: '/api/terminals',
109
+ token,
110
+ });
111
+
112
+ if (res.statusCode === 200 && Array.isArray(res.body)) {
113
+ for (const t of res.body) {
114
+ console.log(`T${t.id} ${t.label || ''} ${t.status || 'unknown'} ${t.taskName ? `— ${t.taskName}` : ''}`.trim());
115
+ }
116
+ } else {
117
+ console.log(`Terminals: unavailable (HTTP ${res.statusCode})`);
118
+ }
119
+ }
120
+
121
+ async function main() {
122
+ const args = process.argv.slice(2);
123
+ const invokedAs = path.basename(process.argv[1] || '');
124
+ const session = readRuntimeSession();
125
+ const token = getToken(session);
126
+
127
+ if (invokedAs === 'ninja-status') {
128
+ await printStatus(session, token);
129
+ appendVerificationEntry({ type: 'status', command: 'ninja-status' });
130
+ return;
131
+ }
132
+
133
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
134
+ console.log(USAGE);
135
+ process.exit(args.length === 0 ? 1 : 0);
136
+ }
137
+
138
+ if (args[0] === '--status' || args[0] === 'status') {
139
+ await printStatus(session, token);
140
+ appendVerificationEntry({ type: 'status', command: 'agent-send --status' });
141
+ return;
142
+ }
143
+
144
+ if (args[0] === '--ledger' || args[0] === 'ledger') {
145
+ const limit = parseInt(args[1], 10) || 20;
146
+ const entries = readLedgerEntries(limit);
147
+ if (entries.length === 0) {
148
+ console.log('No dispatch ledger entries found.');
149
+ console.log(`Ledger file: ${LEDGER_FILE}`);
150
+ return;
151
+ }
152
+ console.log(`Last ${entries.length} dispatch(es):\n`);
153
+ for (const e of entries) {
154
+ const status = e.success ? '✓' : '✗';
155
+ const preview = e.messagePreview || '';
156
+ console.log(`${status} ${e.timestamp} T${e.terminalId} ${e.command || 'dispatch'}`);
157
+ if (preview) console.log(` "${preview.slice(0, 80)}${preview.length > 80 ? '...' : ''}"`);
158
+ if (!e.success && e.error) console.log(` Error: ${e.error}`);
159
+ }
160
+ return;
161
+ }
162
+
163
+ if (args[0] === '--output' || args[0] === 'output') {
164
+ const terminalId = args[1];
165
+ if (!terminalId) {
166
+ console.error('Error: --output requires terminal ID');
167
+ process.exit(1);
168
+ }
169
+ const linesIdx = args.indexOf('--lines');
170
+ const lines = linesIdx >= 0 ? parseInt(args[linesIdx + 1], 10) || 50 : 50;
171
+
172
+ const target = resolveTarget(session);
173
+ if (!token) {
174
+ console.error('Error: Auth token required for output read');
175
+ process.exit(1);
176
+ }
177
+
178
+ const res = await requestJson({
179
+ host: target.host,
180
+ port: target.port,
181
+ path: `/api/terminals/${terminalId}/output?lines=${lines}`,
182
+ token,
183
+ });
184
+
185
+ if (res.statusCode === 200 && res.body) {
186
+ const output = res.body.lines || res.body;
187
+ if (Array.isArray(output)) {
188
+ for (const line of output) {
189
+ console.log(line);
190
+ }
191
+ } else {
192
+ console.log(JSON.stringify(res.body, null, 2));
193
+ }
194
+ appendVerificationEntry({
195
+ type: 'output_read',
196
+ terminalId,
197
+ lines,
198
+ command: 'agent-send --output',
199
+ });
200
+ } else {
201
+ console.error(`Error: HTTP ${res.statusCode}`);
202
+ process.exit(1);
203
+ }
204
+ return;
205
+ }
206
+
207
+ if (args[0] === '--verifications' || args[0] === 'verifications') {
208
+ const limit = parseInt(args[1], 10) || 20;
209
+ const entries = readVerificationEntries(limit);
210
+ if (entries.length === 0) {
211
+ console.log('No verification ledger entries found.');
212
+ console.log(`Ledger file: ${VERIFICATION_LEDGER_FILE}`);
213
+ return;
214
+ }
215
+ console.log(`Last ${entries.length} verification(s):\n`);
216
+ for (const e of entries) {
217
+ console.log(`${e.timestamp} ${e.type} ${e.terminalId ? `T${e.terminalId}` : ''} ${e.command || ''}`.trim());
218
+ if (e.statusMarker) console.log(` Marker: ${e.statusMarker}`);
219
+ }
220
+ return;
221
+ }
222
+
223
+ if (args[0] === '--task-status' || args[0] === 'task-status') {
224
+ const terminalId = args[1];
225
+ const target = resolveTarget(session);
226
+
227
+ if (!token) {
228
+ console.error('Error: Auth token required for task-status');
229
+ process.exit(1);
230
+ }
231
+
232
+ const apiPath = terminalId
233
+ ? `/api/terminals/${terminalId}/task-status`
234
+ : '/api/terminals/task-status';
235
+
236
+ const res = await requestJson({
237
+ host: target.host,
238
+ port: target.port,
239
+ path: apiPath,
240
+ token,
241
+ });
242
+
243
+ if (res.statusCode === 200 && res.body) {
244
+ const data = Array.isArray(res.body) ? res.body : [res.body];
245
+
246
+ // Task status icons
247
+ const icons = {
248
+ done: '\u2713', // ✓
249
+ blocked: '\u26A0', // ⚠
250
+ error: '\u2717', // ✗
251
+ running: '\u25CB', // ○
252
+ pending: '\u25CB', // ○
253
+ unknown: '?',
254
+ };
255
+
256
+ console.log('');
257
+ for (const t of data) {
258
+ const taskIcon = icons[t.taskStatus] || '?';
259
+ const processLabel = (t.processStatus || 'unknown').toUpperCase();
260
+ const taskLabel = (t.taskStatus || 'unknown').toUpperCase();
261
+ console.log(`T${t.id} ${t.label || ''}`);
262
+ console.log(` PROCESS: ${processLabel}`);
263
+ console.log(` TASK: ${taskIcon} ${taskLabel}${t.message ? ` — ${t.message}` : ''}`);
264
+ if (t.marker) console.log(` Marker: ${t.marker}`);
265
+ console.log('');
266
+ }
267
+
268
+ appendVerificationEntry({
269
+ type: 'task_status',
270
+ terminalId: terminalId || 'all',
271
+ command: 'agent-send --task-status',
272
+ });
273
+ } else {
274
+ console.error(`Error: HTTP ${res.statusCode}`);
275
+ process.exit(1);
276
+ }
277
+ return;
278
+ }
279
+
280
+ if (args.length < 2) {
281
+ console.error('Error: Missing arguments. Need <terminal-id> <message>');
282
+ console.log(USAGE);
283
+ process.exit(1);
284
+ }
285
+
286
+ const terminalId = args[0];
287
+ const message = args.slice(1).join(' ');
288
+
289
+ if (!/^\d+$/.test(terminalId)) {
290
+ console.error(`Error: terminal-id must be numeric, got: ${terminalId}`);
291
+ process.exit(1);
292
+ }
293
+
294
+ if (!token) {
295
+ appendLedgerEntry({
296
+ terminalId,
297
+ messagePreview: message.slice(0, 160),
298
+ command: invokedAs === 'ninja-dispatch' ? 'ninja-dispatch' : 'agent-send',
299
+ success: false,
300
+ error: 'No auth token found',
301
+ });
302
+ console.error(`Error: No auth token found.
303
+
304
+ Run: ninja-login
305
+
306
+ Or set NINJA_AUTH_TOKEN environment variable.
307
+ `);
308
+ process.exit(1);
309
+ }
310
+
311
+ const { host, port } = resolveTarget(session);
312
+ const health = await healthCheckSession({ ...session, host, port });
313
+ if (!health.ok) {
314
+ appendLedgerEntry({
315
+ terminalId,
316
+ messagePreview: message.slice(0, 160),
317
+ host,
318
+ port,
319
+ command: invokedAs === 'ninja-dispatch' ? 'ninja-dispatch' : 'agent-send',
320
+ success: false,
321
+ error: `Health check failed: ${health.error}`,
322
+ });
323
+ throw new Error(`Ninja Terminal health check failed on ${host}:${port}: ${health.error}`);
324
+ }
325
+
326
+ // Build JSON body - server handles delayed Enter for proper Claude Code submission
327
+ const body = JSON.stringify({ text: message });
328
+
329
+ const options = {
330
+ hostname: host,
331
+ port: parseInt(port, 10),
332
+ path: `/api/terminals/${terminalId}/input`,
333
+ method: 'POST',
334
+ headers: {
335
+ 'Content-Type': 'application/json',
336
+ 'Content-Length': Buffer.byteLength(body),
337
+ 'Authorization': `Bearer ${token}`
338
+ }
339
+ };
340
+
341
+ const ledgerBase = {
342
+ terminalId,
343
+ messagePreview: message.slice(0, 160),
344
+ host,
345
+ port,
346
+ url: `http://${host}:${port}`,
347
+ cwd: session?.cwd || null,
348
+ command: invokedAs === 'ninja-dispatch' ? 'ninja-dispatch' : 'agent-send',
349
+ };
350
+
351
+ const req = http.request(options, (res) => {
352
+ let data = '';
353
+ res.on('data', chunk => { data += chunk; });
354
+ res.on('end', () => {
355
+ if (res.statusCode === 200) {
356
+ try {
357
+ const result = JSON.parse(data);
358
+ if (result.ok) {
359
+ appendLedgerEntry({ ...ledgerBase, success: true });
360
+ console.log(`Sent to terminal ${terminalId}`);
361
+ if (result.guidanceInjected) {
362
+ console.log('(guidance was injected)');
363
+ }
364
+ } else {
365
+ appendLedgerEntry({ ...ledgerBase, success: false, error: `Server rejected: ${data}` });
366
+ console.error('Server returned:', data);
367
+ process.exit(1);
368
+ }
369
+ } catch {
370
+ appendLedgerEntry({ ...ledgerBase, success: true });
371
+ console.log('Response:', data);
372
+ }
373
+ } else {
374
+ appendLedgerEntry({ ...ledgerBase, success: false, error: `HTTP ${res.statusCode}: ${data}` });
375
+ console.error(`HTTP ${res.statusCode}: ${data}`);
376
+ process.exit(1);
377
+ }
378
+ });
379
+ });
380
+
381
+ req.on('error', (err) => {
382
+ appendLedgerEntry({ ...ledgerBase, success: false, error: err.message });
383
+ console.error(`Connection error: ${err.message}`);
384
+ console.error(`Is Ninja Terminal running on ${host}:${port}?`);
385
+ process.exit(1);
386
+ });
387
+
388
+ req.write(body);
389
+ req.end();
390
+ }
391
+
392
+ main().catch((err) => {
393
+ console.error(`Error: ${err.message}`);
394
+ process.exit(1);
395
+ });
package/cli.js CHANGED
@@ -54,24 +54,43 @@ if (hasFlag('--version') || hasFlag('-v')) {
54
54
 
55
55
  // ── Setup command ───────────────────────────────────────────
56
56
  if (hasFlag('--setup')) {
57
+ runSetup().then(() => process.exit(0)).catch(e => { console.error(e); process.exit(1); });
58
+ }
59
+
60
+ async function runSetup() {
57
61
  const fs = require('fs');
58
62
  const path = require('path');
59
63
  const os = require('os');
60
64
 
61
65
  console.log('\n🥷 NINJA TERMINALS SETUP\n');
62
66
 
63
- // 1. Find or create .mcp.json
64
- const projectMcp = path.join(process.cwd(), '.mcp.json');
65
- const globalMcp = path.join(os.homedir(), '.mcp.json');
66
- const mcpPath = fs.existsSync(projectMcp) ? projectMcp : globalMcp;
67
+ // 1. Find or create .claude/settings.json (Claude Code format)
68
+ const projectClaudeDir = path.join(process.cwd(), '.claude');
69
+ const globalClaudeDir = path.join(os.homedir(), '.claude');
70
+ const projectSettings = path.join(projectClaudeDir, 'settings.json');
71
+ const globalSettings = path.join(globalClaudeDir, 'settings.json');
72
+
73
+ // Prefer project-level if .claude dir exists, else use global
74
+ let settingsPath, claudeDir;
75
+ if (fs.existsSync(projectClaudeDir)) {
76
+ settingsPath = projectSettings;
77
+ claudeDir = projectClaudeDir;
78
+ } else {
79
+ settingsPath = globalSettings;
80
+ claudeDir = globalClaudeDir;
81
+ // Create global .claude dir if needed
82
+ if (!fs.existsSync(claudeDir)) {
83
+ fs.mkdirSync(claudeDir, { recursive: true });
84
+ }
85
+ }
67
86
 
68
87
  let mcpConfig = { mcpServers: {} };
69
- if (fs.existsSync(mcpPath)) {
88
+ if (fs.existsSync(settingsPath)) {
70
89
  try {
71
- mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));
90
+ mcpConfig = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
72
91
  if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
73
92
  } catch (e) {
74
- console.log(`⚠️ Could not parse ${mcpPath}, creating new config`);
93
+ console.log(`⚠️ Could not parse ${settingsPath}, creating new config`);
75
94
  }
76
95
  }
77
96
 
@@ -85,11 +104,16 @@ if (hasFlag('--setup')) {
85
104
  }
86
105
  };
87
106
 
88
- // Get npm root for copying orchestrator prompt
89
- const npmRoot = path.dirname(require.resolve('ninja-terminals/package.json'));
107
+ // Get npm root for copying orchestrator prompt (works in dev and installed mode)
108
+ let npmRoot;
109
+ try {
110
+ npmRoot = path.dirname(require.resolve('ninja-terminals/package.json'));
111
+ } catch {
112
+ npmRoot = __dirname; // Dev mode fallback
113
+ }
90
114
 
91
- fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n');
92
- console.log(`✅ Added ninja-terminals to ${mcpPath}`);
115
+ fs.writeFileSync(settingsPath, JSON.stringify(mcpConfig, null, 2) + '\n');
116
+ console.log(`✅ Added ninja-terminals to ${settingsPath}`);
93
117
 
94
118
  // 3. Copy orchestrator prompt to CLAUDE.md
95
119
  const claudeMd = path.join(process.cwd(), 'CLAUDE.md');
@@ -139,7 +163,7 @@ if (hasFlag('--setup')) {
139
163
  }
140
164
 
141
165
  // Save updated config with all MCPs
142
- fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n');
166
+ fs.writeFileSync(settingsPath, JSON.stringify(mcpConfig, null, 2) + '\n');
143
167
 
144
168
  // 5. Check for Claude in Chrome (optional but recommended)
145
169
  const chromeExt = mcpConfig.mcpServers['claude-in-chrome'];
@@ -151,28 +175,108 @@ if (hasFlag('--setup')) {
151
175
 
152
176
  console.log(`
153
177
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
178
+ ✅ MCP configured!
179
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
180
+ `);
181
+
182
+ // Now prompt for login
183
+ const readline = require('readline');
184
+ const https = require('https');
185
+ const { execSync } = require('child_process');
186
+ const { writeAuthToken } = require('./lib/runtime-session');
187
+
188
+ const BACKEND_URL = process.env.NINJA_BACKEND_URL || 'https://emtchat-backend.onrender.com';
189
+
190
+ function prompt(question) {
191
+ return new Promise((resolve) => {
192
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
193
+ rl.question(question, (answer) => { rl.close(); resolve(answer); });
194
+ });
195
+ }
196
+
197
+ function promptPassword(question) {
198
+ return new Promise((resolve) => {
199
+ try { execSync('stty -echo', { stdio: 'pipe' }); } catch {}
200
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
201
+ process.stdout.write(question);
202
+ rl.question('', (answer) => {
203
+ try { execSync('stty echo', { stdio: 'pipe' }); } catch {}
204
+ console.log();
205
+ rl.close();
206
+ resolve(answer);
207
+ });
208
+ });
209
+ }
210
+
211
+ function postJson(url, body) {
212
+ return new Promise((resolve, reject) => {
213
+ const parsed = new URL(url);
214
+ const payload = JSON.stringify(body);
215
+ const req = https.request({
216
+ hostname: parsed.hostname,
217
+ port: parsed.port || 443,
218
+ path: parsed.pathname,
219
+ method: 'POST',
220
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
221
+ }, (res) => {
222
+ let data = '';
223
+ res.on('data', chunk => { data += chunk; });
224
+ res.on('end', () => {
225
+ try { resolve({ status: res.statusCode, body: JSON.parse(data) }); }
226
+ catch { resolve({ status: res.statusCode, body: data }); }
227
+ });
228
+ });
229
+ req.on('error', reject);
230
+ req.write(payload);
231
+ req.end();
232
+ });
233
+ }
234
+
235
+ async function doLogin() {
236
+ console.log('Login to your Ninja Terminals account:\n');
237
+ const username = await prompt('Username or email: ');
238
+ const password = await promptPassword('Password: ');
239
+
240
+ if (!username || !password) {
241
+ console.log('\n⚠️ Skipped login. Run `npx ninja-login` later to authenticate.');
242
+ return false;
243
+ }
244
+
245
+ console.log('Authenticating...');
246
+ try {
247
+ const res = await postJson(`${BACKEND_URL}/api/auth/login`, { username, password });
248
+ if (res.status === 200 && res.body.token) {
249
+ writeAuthToken(res.body.token);
250
+ console.log(`\n✅ Logged in as ${username}`);
251
+ console.log(` Token saved to ~/.ninja/token`);
252
+ return true;
253
+ } else {
254
+ console.log(`\n❌ Login failed: ${res.body.message || res.body.error || 'Unknown error'}`);
255
+ console.log(' Run `npx ninja-login` to try again.');
256
+ return false;
257
+ }
258
+ } catch (err) {
259
+ console.log(`\n❌ Connection failed: ${err.message}`);
260
+ console.log(' Run `npx ninja-login` to try again.');
261
+ return false;
262
+ }
263
+ }
264
+
265
+ await doLogin();
266
+
267
+ console.log(`
268
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
154
269
  ✨ Setup complete!
155
270
 
156
- MCPs configured:
157
- ninja-terminals - orchestrates parallel Claude Code instances
158
- • playwright - browser automation (screenshots, clicks, reading)
159
- • fetch - API calls to /api/terminals
160
-
161
- Next steps:
162
- 1. Restart Claude Code to load MCP servers
163
- 2. Run: npx ninja-terminals
164
- 3. Or use MCP tools directly in Claude Code
165
-
166
- MCP tools available after restart:
167
- mcp__ninja-terminals__spawn_terminal
168
- mcp__ninja-terminals__send_input
169
- mcp__ninja-terminals__list_terminals
170
- ... and 9 more
271
+ Next: Restart Claude Code, then tell Claude:
272
+ "use ninja terminals"
171
273
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
172
274
  `);
173
- process.exit(0);
174
275
  }
175
276
 
277
+ // If setup mode was requested, we're done (setup function calls process.exit)
278
+ if (!hasFlag('--setup')) {
279
+
176
280
  const port = parseInt(getArg('--port', '3300'), 10);
177
281
  const terminals = parseInt(getArg('--terminals', '2'), 10); // Free tier default
178
282
  const cwd = getArg('--cwd', process.cwd());
@@ -252,3 +356,5 @@ setTimeout(() => {
252
356
  // ── Start the server ─────────────────────────────────────────
253
357
 
254
358
  require('./server.js');
359
+
360
+ } // end if (!hasFlag('--setup'))