gigaclaw 1.7.0 → 1.9.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.
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * GigaClaw One-Command Bootstrap — v1.9.0
4
+ *
5
+ * Invoked when user runs: npx gigaclaw@latest (no subcommand)
6
+ *
7
+ * Flow:
8
+ * 1. Detect environment (Node, Docker, Ollama)
9
+ * 2. Auto-create project directory (default: ./gigaclaw-app, or cwd if empty)
10
+ * 3. Run scaffolding silently
11
+ * 4. Reliable dependency installation (retry + cache clean + pnpm fallback)
12
+ * 5. Auto-run setup with smart defaults (hybrid mode, Claude Sonnet, auto routing)
13
+ * 6. Write .env automatically
14
+ * 7. Start dev server and auto-open browser
15
+ *
16
+ * Pass --interactive to enable the full interactive setup wizard instead of smart defaults.
17
+ */
18
+
19
+ import { execSync, execFileSync, spawn } from 'child_process';
20
+ import fs from 'fs';
21
+ import path from 'path';
22
+ import os from 'os';
23
+ import { fileURLToPath } from 'url';
24
+ import { createRequire } from 'module';
25
+
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = path.dirname(__filename);
28
+ const PACKAGE_DIR = path.join(__dirname, '..');
29
+
30
+ // ─── Structured Logger ────────────────────────────────────────────────────────
31
+ // Replaces verbose npm/node output with clean phase-aware status lines.
32
+
33
+ const PHASES = {
34
+ ENV: '[ 1/7 ] Detecting environment',
35
+ DIR: '[ 2/7 ] Creating project directory',
36
+ SCAF: '[ 3/7 ] Scaffolding project files',
37
+ DEPS: '[ 4/7 ] Installing dependencies',
38
+ SETUP: '[ 5/7 ] Configuring GigaClaw',
39
+ ENV_W: '[ 6/7 ] Writing .env',
40
+ START: '[ 7/7 ] Starting dev server',
41
+ };
42
+
43
+ function log(phase, msg) {
44
+ const ts = new Date().toLocaleTimeString('en-US', { hour12: false });
45
+ process.stdout.write(`\n ${phase}\n ${ts} ${msg}\n`);
46
+ }
47
+
48
+ function logStep(msg) {
49
+ process.stdout.write(` → ${msg}\n`);
50
+ }
51
+
52
+ function logOk(msg) {
53
+ process.stdout.write(` ✓ ${msg}\n`);
54
+ }
55
+
56
+ function logWarn(msg) {
57
+ process.stdout.write(` ⚠ ${msg}\n`);
58
+ }
59
+
60
+ function logErr(msg) {
61
+ process.stderr.write(` ✗ ${msg}\n`);
62
+ }
63
+
64
+ function printBanner() {
65
+ const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_DIR, 'package.json'), 'utf8'));
66
+ console.log(`
67
+ _______ ________
68
+ / ____(_)___ _____ _/ ____/ /___ _ __
69
+ / / __/ / __ \\/ __ \`/ / / / __ \\ | /| / /
70
+ / /_/ / / /_/ / /_/ / /___/ / /_/ / |/ |/ /
71
+ \\____/_/\\__, /\\__,_/\\____/_/\\____/|__/|__/
72
+ /____/
73
+
74
+ India's Autonomous AI Agent · Powered by Gignaati
75
+ v${pkg.version} — One-Command Bootstrap
76
+ `);
77
+ }
78
+
79
+ // ─── Section 1: Reliable Dependency Installation ─────────────────────────────
80
+
81
+ const SLEEP = (ms) => new Promise((r) => setTimeout(r, ms));
82
+
83
+ async function installDependencies(cwd) {
84
+ const MAX_ATTEMPTS = 3;
85
+ const BACKOFF = [2000, 5000, 10000]; // exponential: 2s, 5s, 10s
86
+
87
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
88
+ try {
89
+ logStep(`npm install attempt ${attempt}/${MAX_ATTEMPTS}...`);
90
+
91
+ // On retry: clean cache and remove node_modules + lock file to start fresh
92
+ if (attempt > 1) {
93
+ logStep('Cleaning npm cache before retry...');
94
+ try {
95
+ execSync('npm cache clean --force', { stdio: 'pipe', cwd, shell: true });
96
+ } catch (_) { /* non-fatal */ }
97
+
98
+ const nmPath = path.join(cwd, 'node_modules');
99
+ const lockPath = path.join(cwd, 'package-lock.json');
100
+ if (fs.existsSync(nmPath)) {
101
+ logStep('Removing node_modules...');
102
+ fs.rmSync(nmPath, { recursive: true, force: true });
103
+ }
104
+ if (fs.existsSync(lockPath)) {
105
+ logStep('Removing package-lock.json...');
106
+ fs.rmSync(lockPath);
107
+ }
108
+
109
+ const delay = BACKOFF[attempt - 2] || 10000;
110
+ logStep(`Waiting ${delay / 1000}s before retry...`);
111
+ await SLEEP(delay);
112
+ }
113
+
114
+ execSync(
115
+ 'npm install --no-audit --no-fund --prefer-online',
116
+ { stdio: 'pipe', cwd, shell: true }
117
+ );
118
+ logOk('Dependencies installed successfully.');
119
+ return;
120
+ } catch (err) {
121
+ logWarn(`npm install attempt ${attempt} failed: ${err.message.split('\n')[0]}`);
122
+ }
123
+ }
124
+
125
+ // All npm attempts failed — try pnpm fallback
126
+ logWarn('All npm attempts failed. Trying pnpm fallback...');
127
+ try {
128
+ execSync('pnpm --version', { stdio: 'pipe', shell: true });
129
+ logStep('pnpm detected — running pnpm install...');
130
+ execSync('pnpm install --prefer-offline', { stdio: 'inherit', cwd, shell: true });
131
+ logOk('Dependencies installed via pnpm.');
132
+ return;
133
+ } catch (_) {
134
+ logErr('pnpm not available or also failed.');
135
+ }
136
+
137
+ logErr('Dependency installation failed after 3 attempts + pnpm fallback.');
138
+ logErr('Please check your network connection and run: npm install');
139
+ process.exit(1);
140
+ }
141
+
142
+ // ─── Section 2: Environment Detection ────────────────────────────────────────
143
+
144
+ async function detectEnvironment() {
145
+ const env = {
146
+ nodeVersion: process.version,
147
+ platform: process.platform,
148
+ docker: false,
149
+ ollama: false,
150
+ ollamaModels: [],
151
+ ramGb: Math.floor(os.totalmem() / (1024 ** 3)),
152
+ };
153
+
154
+ // Docker
155
+ try {
156
+ execSync('docker --version', { stdio: 'pipe', shell: true });
157
+ env.docker = true;
158
+ } catch (_) {}
159
+
160
+ // Ollama
161
+ try {
162
+ const res = await fetch('http://localhost:11434/api/tags', {
163
+ signal: AbortSignal.timeout(3000),
164
+ });
165
+ if (res.ok) {
166
+ env.ollama = true;
167
+ const data = await res.json();
168
+ env.ollamaModels = (data.models || []).map((m) => m.name);
169
+ }
170
+ } catch (_) {}
171
+
172
+ return env;
173
+ }
174
+
175
+ function recommendOllamaModel(ramGb) {
176
+ if (ramGb >= 32) return 'llama3.1:8b';
177
+ if (ramGb >= 16) return 'llama3.1:8b';
178
+ return 'llama3.2:3b';
179
+ }
180
+
181
+ // ─── Section 2: Smart Defaults Setup (non-interactive) ───────────────────────
182
+
183
+ async function runSmartSetup(cwd, envInfo) {
184
+ const { randomBytes } = await import('crypto');
185
+
186
+ const authSecret = randomBytes(32).toString('base64url');
187
+ const nextAuthSecret = randomBytes(32).toString('base64url');
188
+
189
+ const localModel = envInfo.ollamaModels.length > 0
190
+ ? envInfo.ollamaModels[0]
191
+ : recommendOllamaModel(envInfo.ramGb);
192
+
193
+ const envVars = {
194
+ // Mode
195
+ GIGACLAW_MODE: 'hybrid',
196
+
197
+ // Cloud: default to Claude Sonnet (user can change via npm run setup)
198
+ LLM_PROVIDER: 'anthropic',
199
+ LLM_MODEL: 'claude-sonnet-4-6',
200
+ // Note: ANTHROPIC_API_KEY intentionally left blank — user must provide it
201
+ ANTHROPIC_API_KEY: '',
202
+
203
+ // Local: Ollama if running, else blank
204
+ LOCAL_LLM_PROVIDER: envInfo.ollama ? 'ollama' : '',
205
+ LOCAL_LLM_MODEL: envInfo.ollama ? localModel : '',
206
+ OLLAMA_BASE_URL: 'http://localhost:11434',
207
+
208
+ // Routing
209
+ HYBRID_ROUTING: 'auto',
210
+
211
+ // Auth
212
+ NEXTAUTH_URL: 'http://localhost:3000',
213
+ NEXTAUTH_SECRET: nextAuthSecret,
214
+ AUTH_SECRET: authSecret,
215
+ AUTH_TRUST_HOST: 'true',
216
+
217
+ // Version
218
+ GIGACLAW_VERSION: JSON.parse(
219
+ fs.readFileSync(path.join(PACKAGE_DIR, 'package.json'), 'utf8')
220
+ ).version,
221
+ };
222
+
223
+ // Write .env
224
+ const envPath = path.join(cwd, '.env');
225
+ let content = '';
226
+ if (fs.existsSync(envPath)) {
227
+ content = fs.readFileSync(envPath, 'utf-8');
228
+ }
229
+
230
+ for (const [key, value] of Object.entries(envVars)) {
231
+ // Don't overwrite existing non-empty values (preserves user's API keys on re-run)
232
+ const regex = new RegExp(`^${key}=(.*)$`, 'm');
233
+ const match = content.match(regex);
234
+ if (match && match[1].trim()) {
235
+ // Already set — skip
236
+ continue;
237
+ }
238
+ if (regex.test(content)) {
239
+ content = content.replace(regex, `${key}=${value}`);
240
+ } else {
241
+ content = content.trimEnd() + `\n${key}=${value}\n`;
242
+ }
243
+ }
244
+
245
+ fs.writeFileSync(envPath, content);
246
+ return { localModel, envVars };
247
+ }
248
+
249
+ // ─── Section 4+5: Auto Start Dev Server + Auto Open Browser ─────────────────
250
+
251
+ function openBrowser(url) {
252
+ const platform = process.platform;
253
+ try {
254
+ if (platform === 'win32') {
255
+ execSync(`start ${url}`, { stdio: 'pipe', shell: true });
256
+ } else if (platform === 'darwin') {
257
+ execSync(`open ${url}`, { stdio: 'pipe', shell: true });
258
+ } else {
259
+ execSync(`xdg-open ${url}`, { stdio: 'pipe', shell: true });
260
+ }
261
+ logOk(`Browser opened at ${url}`);
262
+ } catch (_) {
263
+ logWarn(`Could not auto-open browser. Visit manually: ${url}`);
264
+ }
265
+ }
266
+
267
+ async function startDevServer(cwd) {
268
+ const APP_URL = 'http://localhost:3000';
269
+
270
+ logStep('Launching Next.js dev server (turbopack)...');
271
+
272
+ const child = spawn('npm', ['run', 'dev'], {
273
+ cwd,
274
+ shell: true,
275
+ stdio: ['ignore', 'pipe', 'pipe'],
276
+ detached: false,
277
+ });
278
+
279
+ let serverReady = false;
280
+ let output = '';
281
+
282
+ return new Promise((resolve, reject) => {
283
+ const timeout = setTimeout(() => {
284
+ if (!serverReady) {
285
+ logWarn('Server startup timed out after 120s. Check logs manually.');
286
+ logWarn(`Run: cd ${cwd} && npm run dev`);
287
+ resolve();
288
+ }
289
+ }, 120_000);
290
+
291
+ function onData(chunk) {
292
+ const text = chunk.toString();
293
+ output += text;
294
+
295
+ // Forward server output with indent
296
+ for (const line of text.split('\n')) {
297
+ if (line.trim()) process.stdout.write(` ${line}\n`);
298
+ }
299
+
300
+ // Detect "Local: http://localhost:3000" — server is ready
301
+ if (!serverReady && (
302
+ text.includes('Local:') && text.includes('localhost:3000') ||
303
+ text.includes('Ready in') ||
304
+ text.includes('✓ Ready')
305
+ )) {
306
+ serverReady = true;
307
+ clearTimeout(timeout);
308
+ logOk(`Dev server ready at ${APP_URL}`);
309
+ openBrowser(APP_URL);
310
+ resolve(child);
311
+ }
312
+ }
313
+
314
+ child.stdout.on('data', onData);
315
+ child.stderr.on('data', onData);
316
+
317
+ child.on('error', (err) => {
318
+ clearTimeout(timeout);
319
+ logErr(`Dev server failed to start: ${err.message}`);
320
+ reject(err);
321
+ });
322
+
323
+ child.on('exit', (code) => {
324
+ clearTimeout(timeout);
325
+ if (!serverReady) {
326
+ logErr(`Dev server exited with code ${code}`);
327
+ reject(new Error(`Server exited with code ${code}`));
328
+ }
329
+ });
330
+ });
331
+ }
332
+
333
+ // ─── Main Bootstrap Orchestrator ─────────────────────────────────────────────
334
+
335
+ export async function bootstrap() {
336
+ const interactive = process.argv.includes('--interactive');
337
+
338
+ printBanner();
339
+
340
+ // ── Phase 1: Detect environment ──────────────────────────────────────────
341
+ log(PHASES.ENV, 'Checking Node.js, Docker, Ollama...');
342
+
343
+ const envInfo = await detectEnvironment();
344
+
345
+ logOk(`Node.js ${envInfo.nodeVersion}`);
346
+ logOk(`Platform: ${envInfo.platform} | RAM: ${envInfo.ramGb} GB`);
347
+
348
+ if (envInfo.docker) {
349
+ logOk('Docker: available');
350
+ } else {
351
+ logWarn('Docker: not found (optional — needed for cloud agent mode)');
352
+ }
353
+
354
+ if (envInfo.ollama) {
355
+ logOk(`Ollama: running — ${envInfo.ollamaModels.length} model(s) available`);
356
+ if (envInfo.ollamaModels.length > 0) {
357
+ logStep(`Models: ${envInfo.ollamaModels.slice(0, 3).join(', ')}${envInfo.ollamaModels.length > 3 ? '...' : ''}`);
358
+ }
359
+ } else {
360
+ logWarn('Ollama: not running (optional — enables local AI mode)');
361
+ logStep('Install: https://ollama.com/download');
362
+ }
363
+
364
+ // ── Phase 2: Determine project directory ─────────────────────────────────
365
+ log(PHASES.DIR, 'Preparing project directory...');
366
+
367
+ let cwd = process.cwd();
368
+ const entries = fs.readdirSync(cwd).filter(e => !e.startsWith('.'));
369
+
370
+ if (entries.length > 0) {
371
+ // Non-empty directory — check if it's already a gigaclaw project
372
+ const pkgPath = path.join(cwd, 'package.json');
373
+ let isExistingProject = false;
374
+ if (fs.existsSync(pkgPath)) {
375
+ try {
376
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
377
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
378
+ if (deps.gigaclaw) isExistingProject = true;
379
+ } catch (_) {}
380
+ }
381
+
382
+ if (!isExistingProject) {
383
+ const dirName = 'gigaclaw-app';
384
+ const newDir = path.resolve(cwd, dirName);
385
+ fs.mkdirSync(newDir, { recursive: true });
386
+ process.chdir(newDir);
387
+ cwd = newDir;
388
+ logOk(`Created ${dirName}/ (current directory was not empty)`);
389
+ } else {
390
+ logOk(`Existing GigaClaw project detected — updating in place`);
391
+ }
392
+ } else {
393
+ logOk(`Using current directory: ${path.basename(cwd)}`);
394
+ }
395
+
396
+ // ── Phase 3: Scaffolding ─────────────────────────────────────────────────
397
+ log(PHASES.SCAF, 'Copying project templates...');
398
+
399
+ // Dynamically import init logic from cli.js by re-using the scaffolding function
400
+ // We call the init logic directly to avoid spawning a child process
401
+ const { scaffoldProject } = await import('./scaffold.mjs');
402
+ const { created, updated } = await scaffoldProject(cwd, PACKAGE_DIR);
403
+
404
+ if (created.length > 0) logOk(`Created ${created.length} file(s)`);
405
+ if (updated.length > 0) logOk(`Updated ${updated.length} managed file(s)`);
406
+ if (created.length === 0 && updated.length === 0) logOk('All files up to date');
407
+
408
+ // ── Phase 4: Install dependencies ───────────────────────────────────────
409
+ log(PHASES.DEPS, 'Installing npm packages (retry-safe)...');
410
+ await installDependencies(cwd);
411
+
412
+ // ── Phase 5+6: Setup + .env ──────────────────────────────────────────────
413
+ if (interactive) {
414
+ log(PHASES.SETUP, 'Launching interactive setup wizard...');
415
+ const setupScript = path.join(PACKAGE_DIR, 'setup', 'setup.mjs');
416
+ try {
417
+ execFileSync(process.execPath, [setupScript], { stdio: 'inherit', cwd });
418
+ } catch (e) {
419
+ logErr(`Setup wizard failed: ${e.message}`);
420
+ process.exit(1);
421
+ }
422
+ } else {
423
+ log(PHASES.SETUP, 'Applying smart defaults (hybrid mode, Claude Sonnet, auto routing)...');
424
+ const { localModel } = await runSmartSetup(cwd, envInfo);
425
+
426
+ log(PHASES.ENV_W, 'Writing .env configuration...');
427
+ logOk('GIGACLAW_MODE=hybrid');
428
+ logOk('LLM_PROVIDER=anthropic | LLM_MODEL=claude-sonnet-4-6');
429
+ logOk(`LOCAL_LLM_PROVIDER=${envInfo.ollama ? 'ollama' : '(not configured)'} | LOCAL_LLM_MODEL=${envInfo.ollama ? localModel : '(not configured)'}`);
430
+ logOk('HYBRID_ROUTING=auto');
431
+ logWarn('ANTHROPIC_API_KEY is empty — run: npm run setup to add your API key');
432
+ }
433
+
434
+ // ── Phase 7: Start dev server + open browser ─────────────────────────────
435
+ log(PHASES.START, 'Starting Next.js dev server...');
436
+
437
+ console.log(`
438
+ ┌─────────────────────────────────────────────────────────┐
439
+ │ │
440
+ │ GigaClaw is starting... │
441
+ │ │
442
+ │ App URL: http://localhost:3000 │
443
+ │ Mode: Hybrid (Cloud + Local) │
444
+ │ │
445
+ │ Next step: add your ANTHROPIC_API_KEY to .env │
446
+ │ or run: npm run setup for the full wizard │
447
+ │ │
448
+ └─────────────────────────────────────────────────────────┘
449
+ `);
450
+
451
+ await startDevServer(cwd);
452
+ }
package/bin/cli.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { execSync, execFileSync } from 'child_process';
4
4
  import fs from 'fs';
5
5
  import path from 'path';
6
+ import os from 'os';
6
7
  import { fileURLToPath } from 'url';
7
8
  import { createDirLink } from '../setup/lib/fs-utils.mjs';
8
9
 
@@ -81,9 +82,13 @@ function parseUpgradeTarget(arg) {
81
82
 
82
83
  function printUsage() {
83
84
  console.log(`
84
- Usage: gigaclaw <command>
85
+ Usage: gigaclaw [command]
85
86
 
86
- Commands:
87
+ One-command bootstrap (recommended):
88
+ npx gigaclaw@latest Full auto-setup: scaffold + install + configure + start
89
+ npx gigaclaw@latest --interactive Same, but with interactive setup wizard
90
+
91
+ Manual commands:
87
92
  init Scaffold a new gigaclaw project
88
93
  upgrade|update [@beta|version] Upgrade gigaclaw (install, init, build, commit, push)
89
94
  setup Run interactive setup wizard
@@ -94,7 +99,7 @@ Commands:
94
99
  set-agent-secret <KEY> [VALUE] Set a GitHub secret with AGENT_ prefix (also updates .env)
95
100
  set-agent-llm-secret <KEY> [VALUE] Set a GitHub secret with AGENT_LLM_ prefix
96
101
  set-var <KEY> [VALUE] Set a GitHub repository variable
97
- --version, -v Show gigaclaw version
102
+ --version, -v Show gigaclaw version
98
103
 
99
104
  Powered by Gignaati — https://www.gignaati.com
100
105
  `);
@@ -307,10 +312,9 @@ async function init() {
307
312
  console.log(' To reset to default: npx gigaclaw reset <file>');
308
313
  }
309
314
 
310
- // Run npm install
315
+ // Run npm install with retry-safe logic
311
316
  console.log('\nInstalling dependencies...\n');
312
- // shell:true is required on Windows so npm resolves via PATH (npm.cmd)
313
- execSync('npm install', { stdio: 'inherit', cwd, shell: true });
317
+ await installDependenciesWithRetry(cwd);
314
318
 
315
319
  // Create or update .env with auto-generated infrastructure values
316
320
  const envPath = path.join(cwd, '.env');
@@ -348,6 +352,51 @@ GIGACLAW_VERSION=${version}
348
352
  console.log('\nDone! Run: npm run setup\n');
349
353
  }
350
354
 
355
+ /**
356
+ * Retry-safe npm install with cache clean, exponential backoff, and pnpm fallback.
357
+ * Used by both `gigaclaw init` and the one-command bootstrap.
358
+ */
359
+ async function installDependenciesWithRetry(cwd) {
360
+ const MAX_ATTEMPTS = 3;
361
+ const BACKOFF = [2000, 5000, 10000];
362
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
363
+
364
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
365
+ try {
366
+ if (attempt > 1) {
367
+ console.log(` Cleaning npm cache before retry (attempt ${attempt})...`);
368
+ try { execSync('npm cache clean --force', { stdio: 'pipe', cwd, shell: true }); } catch (_) {}
369
+ const nmPath = path.join(cwd, 'node_modules');
370
+ const lockPath = path.join(cwd, 'package-lock.json');
371
+ if (fs.existsSync(nmPath)) {
372
+ console.log(' Removing node_modules...');
373
+ fs.rmSync(nmPath, { recursive: true, force: true });
374
+ }
375
+ if (fs.existsSync(lockPath)) fs.rmSync(lockPath);
376
+ const delay = BACKOFF[attempt - 2] || 10000;
377
+ console.log(` Waiting ${delay / 1000}s...`);
378
+ await sleep(delay);
379
+ }
380
+ execSync('npm install --no-audit --no-fund --prefer-online', { stdio: 'inherit', cwd, shell: true });
381
+ return; // success
382
+ } catch (err) {
383
+ console.warn(` ⚠ npm install attempt ${attempt} failed: ${err.message.split('\n')[0]}`);
384
+ }
385
+ }
386
+
387
+ // pnpm fallback
388
+ console.warn(' All npm attempts failed. Trying pnpm fallback...');
389
+ try {
390
+ execSync('pnpm --version', { stdio: 'pipe', shell: true });
391
+ execSync('pnpm install --prefer-offline', { stdio: 'inherit', cwd, shell: true });
392
+ console.log(' ✓ Dependencies installed via pnpm.');
393
+ return;
394
+ } catch (_) {}
395
+
396
+ console.error(' ✗ Dependency installation failed. Check your network and run: npm install');
397
+ process.exit(1);
398
+ }
399
+
351
400
  /**
352
401
  * List all available template files, or restore a specific one.
353
402
  */
@@ -798,6 +847,15 @@ async function setVar(key, value) {
798
847
  }
799
848
 
800
849
  switch (command) {
850
+ case undefined:
851
+ case null:
852
+ case '':
853
+ case '--interactive': {
854
+ // No subcommand — run one-command bootstrap
855
+ const { bootstrap } = await import('./bootstrap.mjs');
856
+ await bootstrap();
857
+ break;
858
+ }
801
859
  case 'init':
802
860
  await init();
803
861
  break;
@@ -831,5 +889,5 @@ switch (command) {
831
889
  break;
832
890
  default:
833
891
  printUsage();
834
- process.exit(command ? 1 : 0);
892
+ process.exit(1);
835
893
  }