vibecodingmachine-cli 2025.12.1-534 → 2025.12.22-2230
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/bin/vibecodingmachine.js +301 -12
- package/package.json +5 -2
- package/repro_open.js +13 -0
- package/reproduce_issue.js +160 -0
- package/scripts/postinstall.js +80 -0
- package/src/commands/auth.js +0 -1
- package/src/commands/auto-direct.js +455 -136
- package/src/commands/auto.js +488 -163
- package/src/commands/computers.js +306 -0
- package/src/commands/repo.js +0 -1
- package/src/commands/requirements-remote.js +308 -0
- package/src/commands/requirements.js +233 -16
- package/src/commands/status.js +0 -1
- package/src/commands/sync.js +280 -0
- package/src/utils/agent-selector.js +50 -0
- package/src/utils/antigravity-installer.js +212 -0
- package/src/utils/antigravity-js-handler.js +60 -0
- package/src/utils/asset-cleanup.js +60 -0
- package/src/utils/auth.js +232 -8
- package/src/utils/auto-mode-ansi-ui.js +0 -1
- package/src/utils/auto-mode-simple-ui.js +3 -23
- package/src/utils/compliance-check.js +166 -0
- package/src/utils/config.js +27 -1
- package/src/utils/copy-with-progress.js +167 -0
- package/src/utils/download-with-progress.js +84 -0
- package/src/utils/first-run.js +410 -0
- package/src/utils/interactive.js +1197 -391
- package/src/utils/kiro-installer.js +178 -0
- package/src/utils/persistent-header.js +1 -3
- package/src/utils/provider-registry.js +13 -4
- package/src/utils/status-card.js +2 -1
- package/src/utils/user-tracking.js +300 -0
- package/tests/requirements-navigator-buildtree-await.test.js +28 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const boxen = require('boxen');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const { getProviderDefinitions, saveProviderPreferences, getDefaultProviderOrder } = require('./provider-registry');
|
|
8
|
+
const { isKiroInstalled } = require('./kiro-installer');
|
|
9
|
+
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
|
|
12
|
+
async function checkAppOrBinary(names = [], binaries = []) {
|
|
13
|
+
// names: app bundle base names (e.g., 'Cursor' -> /Applications/Cursor.app)
|
|
14
|
+
// binaries: CLI binary names to check on PATH (e.g., 'code')
|
|
15
|
+
const platform = os.platform();
|
|
16
|
+
// Check common application directories
|
|
17
|
+
if (platform === 'darwin') {
|
|
18
|
+
const appDirs = ['/Applications', path.join(os.homedir(), 'Applications')];
|
|
19
|
+
for (const appName of names) {
|
|
20
|
+
for (const dir of appDirs) {
|
|
21
|
+
try {
|
|
22
|
+
const p = path.join(dir, `${appName}.app`);
|
|
23
|
+
if (await fs.pathExists(p)) {
|
|
24
|
+
// Ensure this is a real application bundle (has Contents/MacOS executable)
|
|
25
|
+
try {
|
|
26
|
+
const macosDir = path.join(p, 'Contents', 'MacOS');
|
|
27
|
+
const exists = await fs.pathExists(macosDir);
|
|
28
|
+
if (exists) {
|
|
29
|
+
const files = await fs.readdir(macosDir);
|
|
30
|
+
if (files && files.length > 0) {
|
|
31
|
+
// Prefer to ensure the app is usable: use spctl to assess, fallback to quarantine xattr
|
|
32
|
+
try {
|
|
33
|
+
// spctl returns non-zero on rejected/invalid apps
|
|
34
|
+
execSync(`spctl --assess -v "${p}"`, { stdio: 'ignore', timeout: 5000 });
|
|
35
|
+
// additionally validate codesign quickly (timeout to avoid hangs)
|
|
36
|
+
try {
|
|
37
|
+
execSync(`codesign -v --deep --strict "${p}"`, { stdio: 'ignore', timeout: 5000 });
|
|
38
|
+
return true;
|
|
39
|
+
} catch (csErr) {
|
|
40
|
+
// codesign failed or timed out — treat as not usable/damaged
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// spctl failed or timed out — check if app has quarantine attribute
|
|
45
|
+
try {
|
|
46
|
+
const out = execSync(`xattr -p com.apple.quarantine "${p}" 2>/dev/null || true`, { encoding: 'utf8' }).trim();
|
|
47
|
+
if (!out) {
|
|
48
|
+
// no quarantine attribute but spctl failed — be conservative and treat as not installed
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
// If quarantine attribute exists, treat as not installed (damaged/not allowed)
|
|
52
|
+
return false;
|
|
53
|
+
} catch (e2) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (e) {
|
|
60
|
+
// if we can't stat inside, be conservative and continue searching
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {
|
|
64
|
+
/* ignore */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check PATH for known binaries
|
|
71
|
+
for (const bin of binaries) {
|
|
72
|
+
try {
|
|
73
|
+
execSync(`which ${bin}`, { stdio: 'ignore' });
|
|
74
|
+
return true;
|
|
75
|
+
} catch (e) {
|
|
76
|
+
/* not found */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check common Homebrew bin locations
|
|
81
|
+
const brewPaths = ['/opt/homebrew/bin', '/usr/local/bin'];
|
|
82
|
+
for (const bin of binaries) {
|
|
83
|
+
for (const brew of brewPaths) {
|
|
84
|
+
try {
|
|
85
|
+
if (await fs.pathExists(path.join(brew, bin))) return true;
|
|
86
|
+
} catch (e) { /* ignore */ }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function checkFirstRun() {
|
|
94
|
+
const configDir = path.join(os.homedir(), '.vibecodingmachine');
|
|
95
|
+
|
|
96
|
+
// If directory exists, we assume it's not the first run
|
|
97
|
+
if (await fs.pathExists(configDir)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// FIRST RUN EXPERIENCE
|
|
102
|
+
console.clear();
|
|
103
|
+
|
|
104
|
+
// 1. Big Welcome Banner
|
|
105
|
+
console.log('\n' + boxen(
|
|
106
|
+
chalk.bold.cyan('Welcome to Vibe Coding Machine') + '\n\n' +
|
|
107
|
+
chalk.white('The future of coding is here.') + '\n' +
|
|
108
|
+
chalk.gray('Just describe what you want, and we code it for you.'),
|
|
109
|
+
{
|
|
110
|
+
padding: 1,
|
|
111
|
+
margin: 1,
|
|
112
|
+
borderStyle: 'double',
|
|
113
|
+
borderColor: 'cyan',
|
|
114
|
+
align: 'center'
|
|
115
|
+
}
|
|
116
|
+
));
|
|
117
|
+
|
|
118
|
+
console.log(chalk.cyan('Let\'s get you set up with the best AI coding environment.\n'));
|
|
119
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
120
|
+
|
|
121
|
+
// --- NEW: Vibe Coding Introduction ---
|
|
122
|
+
const { showIntro } = await inquirer.prompt([
|
|
123
|
+
{
|
|
124
|
+
type: 'confirm',
|
|
125
|
+
name: 'showIntro',
|
|
126
|
+
message: 'Are you new to Vibe Coding? Would you like a quick 30-second tour?',
|
|
127
|
+
default: true
|
|
128
|
+
}
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
if (showIntro) {
|
|
132
|
+
const introContent = [
|
|
133
|
+
chalk.bold.cyan('╔════════════════════════ Vibe Coding 101 ════════════════════════╗'),
|
|
134
|
+
chalk.cyan('║ ║'),
|
|
135
|
+
chalk.cyan('║ 1. What is an IDE? ║'),
|
|
136
|
+
chalk.cyan('║ Think of it like MS Word, but for writing code. ║'),
|
|
137
|
+
chalk.cyan('║ You need one to build apps, but don\'t worry—we\'ll install ║'),
|
|
138
|
+
chalk.cyan('║ the best ones for you automatically. ║'),
|
|
139
|
+
chalk.cyan('║ ║'),
|
|
140
|
+
chalk.cyan('║ 2. How it works ║'),
|
|
141
|
+
chalk.cyan('║ You simply describe the app or web page you want. ║'),
|
|
142
|
+
chalk.cyan('║ VibeCodingMachine then takes over your keyboard to write ║'),
|
|
143
|
+
chalk.cyan('║ the code inside the IDE (GUI mode) or runs in the ║'),
|
|
144
|
+
chalk.cyan('║ background (CLI mode) which is even faster. ║'),
|
|
145
|
+
chalk.cyan('║ ║'),
|
|
146
|
+
chalk.bold.cyan('╚═════════════════════════════════════════════════════════════════╝')
|
|
147
|
+
].join('\n');
|
|
148
|
+
|
|
149
|
+
console.log('\n' + introContent + '\n');
|
|
150
|
+
|
|
151
|
+
await inquirer.prompt([{
|
|
152
|
+
type: 'input',
|
|
153
|
+
name: 'continue',
|
|
154
|
+
message: 'Press Enter to continue...'
|
|
155
|
+
}]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
// 2. Detect IDEs
|
|
162
|
+
const definitions = getProviderDefinitions();
|
|
163
|
+
const ideDefinitions = definitions.filter(d => d.type === 'ide');
|
|
164
|
+
|
|
165
|
+
const detectedIDEs = [];
|
|
166
|
+
const otherIDEs = [];
|
|
167
|
+
|
|
168
|
+
// Simple simulation of detection for standard apps on macOS
|
|
169
|
+
// In a real scenario, we might check other paths or registry on Windows
|
|
170
|
+
const platform = os.platform();
|
|
171
|
+
|
|
172
|
+
for (const ide of ideDefinitions) {
|
|
173
|
+
let installed = false;
|
|
174
|
+
|
|
175
|
+
if (ide.id === 'kiro') {
|
|
176
|
+
installed = isKiroInstalled();
|
|
177
|
+
} else if (ide.id === 'cursor') {
|
|
178
|
+
// Cursor: check app bundle and 'cursor' binary
|
|
179
|
+
installed = await checkAppOrBinary(['Cursor'], ['cursor']);
|
|
180
|
+
} else if (ide.id === 'windsurf') {
|
|
181
|
+
// Windsurf: check app bundle and common binary
|
|
182
|
+
installed = await checkAppOrBinary(['Windsurf'], ['windsurf']);
|
|
183
|
+
} else if (ide.id === 'vscode') {
|
|
184
|
+
// VS Code: check app bundle and 'code' CLI
|
|
185
|
+
installed = await checkAppOrBinary(['Visual Studio Code', 'Visual Studio Code - Insiders'], ['code', 'code-insiders']);
|
|
186
|
+
} else if (ide.id === 'antigravity') {
|
|
187
|
+
// Antigravity: check app bundle and 'antigravity' binary
|
|
188
|
+
installed = await checkAppOrBinary(['Antigravity'], ['antigravity']);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (installed) {
|
|
192
|
+
detectedIDEs.push(ide);
|
|
193
|
+
} else {
|
|
194
|
+
otherIDEs.push(ide);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 3. Status Report & Selection
|
|
199
|
+
if (detectedIDEs.length > 0) {
|
|
200
|
+
console.log(chalk.green('\n✓ We found these IDEs and enabled them for you:'));
|
|
201
|
+
detectedIDEs.forEach(ide => {
|
|
202
|
+
console.log(chalk.gray(` • ${ide.name}`));
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let selectedIDEs = [];
|
|
207
|
+
|
|
208
|
+
// Only prompt for uninstalled IDEs if there are any
|
|
209
|
+
if (otherIDEs.length > 0) {
|
|
210
|
+
console.log(); // Spacing
|
|
211
|
+
const choices = otherIDEs.map(ide => ({
|
|
212
|
+
name: ide.name,
|
|
213
|
+
value: ide.id,
|
|
214
|
+
checked: true // Select by default as requested
|
|
215
|
+
}));
|
|
216
|
+
|
|
217
|
+
const response = await inquirer.prompt([{
|
|
218
|
+
type: 'checkbox',
|
|
219
|
+
name: 'selectedIDEs',
|
|
220
|
+
message: 'Select additional IDEs to install & enable:',
|
|
221
|
+
choices: choices,
|
|
222
|
+
pageSize: 10
|
|
223
|
+
}]);
|
|
224
|
+
selectedIDEs = response.selectedIDEs;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 4. Handle Installations (Generic)
|
|
228
|
+
// For any selected IDE that wasn't detected, try to invoke a matching installer module
|
|
229
|
+
// Installer modules are expected to live next to this file as `<id>-installer.js` and
|
|
230
|
+
// export an installation function like `install<IdPascal>()` or `install`.
|
|
231
|
+
for (const ideId of selectedIDEs) {
|
|
232
|
+
// determine if already installed using existing checks
|
|
233
|
+
let alreadyInstalled = false;
|
|
234
|
+
try {
|
|
235
|
+
if (ideId === 'kiro') {
|
|
236
|
+
alreadyInstalled = isKiroInstalled() || await checkAppOrBinary(['AWS Kiro', 'Kiro'], ['kiro']);
|
|
237
|
+
} else if (ideId === 'cursor') {
|
|
238
|
+
alreadyInstalled = await checkAppOrBinary(['Cursor'], ['cursor']);
|
|
239
|
+
} else if (ideId === 'windsurf') {
|
|
240
|
+
alreadyInstalled = await checkAppOrBinary(['Windsurf'], ['windsurf']);
|
|
241
|
+
} else if (ideId === 'vscode') {
|
|
242
|
+
alreadyInstalled = await checkAppOrBinary(['Visual Studio Code', 'Visual Studio Code - Insiders'], ['code', 'code-insiders']);
|
|
243
|
+
} else if (ideId === 'antigravity') {
|
|
244
|
+
alreadyInstalled = await checkAppOrBinary(['Antigravity'], ['antigravity']);
|
|
245
|
+
} else {
|
|
246
|
+
// default fallback: try binary name same as id
|
|
247
|
+
alreadyInstalled = await checkAppOrBinary([], [ideId]);
|
|
248
|
+
}
|
|
249
|
+
} catch (e) {
|
|
250
|
+
// detection error: assume not installed
|
|
251
|
+
alreadyInstalled = false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (alreadyInstalled) continue;
|
|
255
|
+
|
|
256
|
+
// Attempt to locate and run an installer module for this IDE
|
|
257
|
+
let installerModule = null;
|
|
258
|
+
const tryPaths = [`./${ideId}-installer`, `../utils/${ideId}-installer`];
|
|
259
|
+
for (const p of tryPaths) {
|
|
260
|
+
try {
|
|
261
|
+
// require may throw if module doesn't exist
|
|
262
|
+
installerModule = require(p);
|
|
263
|
+
break;
|
|
264
|
+
} catch (e) {
|
|
265
|
+
// ignore and try next
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!installerModule) {
|
|
270
|
+
console.log(chalk.gray(`No installer module found for ${ideId}, skipping automated install.`));
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Resolve a callable install function from the module
|
|
275
|
+
const pascal = ideId.split(/[-_]/).map(s => s[0].toUpperCase() + s.slice(1)).join('');
|
|
276
|
+
const candidateNames = [`install${pascal}`, 'install', pascal, 'default'];
|
|
277
|
+
let installerFn = null;
|
|
278
|
+
for (const name of candidateNames) {
|
|
279
|
+
if (name === 'default' && typeof installerModule === 'function') {
|
|
280
|
+
installerFn = installerModule;
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
if (installerModule && typeof installerModule[name] === 'function') {
|
|
284
|
+
installerFn = installerModule[name];
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!installerFn) {
|
|
290
|
+
console.log(chalk.gray(`Installer found for ${ideId} but no callable export (tried ${candidateNames.join(', ')}).`));
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
console.log(chalk.cyan(`\n🔧 Installing ${ideId}...`));
|
|
295
|
+
try {
|
|
296
|
+
const ok = await installerFn();
|
|
297
|
+
if (!ok) {
|
|
298
|
+
console.log(chalk.yellow(`\n⚠️ ${ideId} installation failed or was skipped.`));
|
|
299
|
+
} else {
|
|
300
|
+
console.log(chalk.green(`\n✅ ${ideId} installation complete.`));
|
|
301
|
+
}
|
|
302
|
+
} catch (err) {
|
|
303
|
+
console.log(chalk.red(`${ideId} installation error:`), err.message || err);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 5. Configure Preferences
|
|
308
|
+
// Re-detect IDEs to ensure we only enable actually installed ones
|
|
309
|
+
const reDetected = [];
|
|
310
|
+
for (const ideDef of ideDefinitions) {
|
|
311
|
+
let installedNow = false;
|
|
312
|
+
if (ideDef.id === 'kiro') {
|
|
313
|
+
installedNow = isKiroInstalled() || await checkAppOrBinary(['AWS Kiro', 'Kiro'], ['kiro']);
|
|
314
|
+
} else if (ideDef.id === 'cursor') {
|
|
315
|
+
installedNow = await checkAppOrBinary(['Cursor'], ['cursor']);
|
|
316
|
+
} else if (ideDef.id === 'windsurf') {
|
|
317
|
+
installedNow = await checkAppOrBinary(['Windsurf'], ['windsurf']);
|
|
318
|
+
} else if (ideDef.id === 'vscode') {
|
|
319
|
+
installedNow = await checkAppOrBinary(['Visual Studio Code', 'Visual Studio Code - Insiders'], ['code', 'code-insiders']);
|
|
320
|
+
} else if (ideDef.id === 'antigravity') {
|
|
321
|
+
installedNow = await checkAppOrBinary(['Antigravity'], ['antigravity']);
|
|
322
|
+
}
|
|
323
|
+
if (installedNow) reDetected.push(ideDef.id);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const defaultOrder = getDefaultProviderOrder();
|
|
327
|
+
const enabledMap = {};
|
|
328
|
+
for (const id of defaultOrder) {
|
|
329
|
+
const def = definitions.find(d => d.id === id);
|
|
330
|
+
if (def && def.type === 'ide') {
|
|
331
|
+
const isDetectedNow = reDetected.includes(id);
|
|
332
|
+
const isSelected = selectedIDEs.includes(id);
|
|
333
|
+
enabledMap[id] = isDetectedNow || isSelected;
|
|
334
|
+
} else {
|
|
335
|
+
enabledMap[id] = true; // Keep LLMs enabled by default
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Save initial preferences. provider-registry.js usually saves to ~/.config/vibecodingmachine/config.json
|
|
340
|
+
// But here we are checking ~/.vibecodingmachine.
|
|
341
|
+
// We should create the ~/.vibecodingmachine directory to mark first run as complete.
|
|
342
|
+
await fs.ensureDir(configDir);
|
|
343
|
+
await saveProviderPreferences(defaultOrder, enabledMap);
|
|
344
|
+
|
|
345
|
+
// --- NEW: CLI Usage Onboarding ---
|
|
346
|
+
// Moved here so IDEs are set up first
|
|
347
|
+
const { isFamiliar } = await inquirer.prompt([
|
|
348
|
+
{
|
|
349
|
+
type: 'confirm',
|
|
350
|
+
name: 'isFamiliar',
|
|
351
|
+
message: 'Are you familiar with how to use VibeCodingMachine CLI?',
|
|
352
|
+
default: false
|
|
353
|
+
}
|
|
354
|
+
]);
|
|
355
|
+
|
|
356
|
+
if (!isFamiliar) {
|
|
357
|
+
const cliUsageContent = [
|
|
358
|
+
chalk.bold.green('╔════════════════════════ CLI Crash Course ═══════════════════════╗'),
|
|
359
|
+
chalk.green('║ ║'),
|
|
360
|
+
chalk.green('║ 1. Navigation ║'),
|
|
361
|
+
chalk.green('║ [↑/↓] Arrow keys to move selection ║'),
|
|
362
|
+
chalk.green('║ [Space] to toggle checkboxes [x] ║'),
|
|
363
|
+
chalk.green('║ [Enter] to confirm your choice ║'),
|
|
364
|
+
chalk.green('║ ║'),
|
|
365
|
+
chalk.green('║ 2. Key Features ║'),
|
|
366
|
+
chalk.green('║ • Auto Mode: The autonomous coding agent ║'),
|
|
367
|
+
chalk.green('║ • Direct Mode: Chat directly with LLMs ║'),
|
|
368
|
+
chalk.green('║ • IDE Setup: Install and configure tools ║'),
|
|
369
|
+
chalk.green('║ ║'),
|
|
370
|
+
chalk.green('║ 3. Pro Tip ║'),
|
|
371
|
+
chalk.green('║ If you ever get stuck, just restart the CLI with \'vcm\'. ║'),
|
|
372
|
+
chalk.green('║ ║'),
|
|
373
|
+
chalk.bold.green('╚═════════════════════════════════════════════════════════════════╝')
|
|
374
|
+
].join('\n');
|
|
375
|
+
|
|
376
|
+
console.log('\n' + cliUsageContent + '\n');
|
|
377
|
+
|
|
378
|
+
await inquirer.prompt([{
|
|
379
|
+
type: 'input',
|
|
380
|
+
name: 'continue',
|
|
381
|
+
message: 'Press Enter to continue...'
|
|
382
|
+
}]);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// 6. Explanation
|
|
386
|
+
console.log('\n' + boxen(
|
|
387
|
+
chalk.bold.green(' Setup Complete! ') + '\n\n' +
|
|
388
|
+
chalk.white('Vibe Coding is about flow. You describe, we code.') + '\n' +
|
|
389
|
+
chalk.white('Using your local IDEs + Cloud LLMs for maximum power.'),
|
|
390
|
+
{
|
|
391
|
+
padding: 1,
|
|
392
|
+
margin: 1,
|
|
393
|
+
borderStyle: 'round',
|
|
394
|
+
borderColor: 'green'
|
|
395
|
+
}
|
|
396
|
+
));
|
|
397
|
+
|
|
398
|
+
console.log(chalk.cyan('Press any key to continue to the main menu...'));
|
|
399
|
+
|
|
400
|
+
await new Promise((resolve) => {
|
|
401
|
+
process.stdin.setRawMode(true);
|
|
402
|
+
process.stdin.resume();
|
|
403
|
+
process.stdin.once('data', () => {
|
|
404
|
+
process.stdin.setRawMode(false);
|
|
405
|
+
resolve();
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
module.exports = { checkFirstRun };
|