@neetru/cli 2.7.2 → 2.7.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/CHANGELOG.md +26 -0
- package/dist/commands/ui.d.ts +31 -3
- package/dist/commands/ui.js +511 -279
- package/dist/commands/ui.js.map +1 -1
- package/dist/lib/config-schema.d.ts +2 -2
- package/dist/utils/logger.d.ts +10 -1
- package/dist/utils/logger.js +55 -2
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/logo.d.ts +1 -1
- package/dist/utils/logo.js +1 -1
- package/package.json +1 -1
package/dist/commands/ui.js
CHANGED
|
@@ -9,11 +9,13 @@ import { resolveSessionEmail } from '../lib/auth.js';
|
|
|
9
9
|
import { checkForUpdate } from '../lib/version-check.js';
|
|
10
10
|
import { CLI_VERSION } from '../version.js';
|
|
11
11
|
import { log } from '../utils/logger.js';
|
|
12
|
-
export const MENU_OUTLINE_COLOR = '#
|
|
12
|
+
export const MENU_OUTLINE_COLOR = '#334C65';
|
|
13
|
+
export const MENU_FOOTER_TEXT_COLOR = '#CBD5E1';
|
|
13
14
|
const MENU_COLUMNS = 2;
|
|
14
15
|
const MENU_GAP = 2;
|
|
15
16
|
const MENU_MIN_WIDTH = 58;
|
|
16
17
|
const MENU_MAX_WIDTH = 112;
|
|
18
|
+
const DOCS_RECENT_LIMIT = 5;
|
|
17
19
|
/**
|
|
18
20
|
* Erro lançado no lugar de `process.exit()` enquanto o menu interativo está
|
|
19
21
|
* ativo. Os comandos do CLI chamam `process.exit()` em erro (ex.: "Nenhum
|
|
@@ -40,9 +42,9 @@ export const MAIN_MENU = [
|
|
|
40
42
|
description: 'Projeto no diretório atual, login, versão e config',
|
|
41
43
|
},
|
|
42
44
|
{
|
|
43
|
-
label: '
|
|
45
|
+
label: 'Começar um projeto',
|
|
44
46
|
value: 'quickstart',
|
|
45
|
-
description: '
|
|
47
|
+
description: 'Scaffold local (init), criar produto (new) e sobre o SDK',
|
|
46
48
|
},
|
|
47
49
|
{
|
|
48
50
|
label: 'Produtos',
|
|
@@ -80,7 +82,12 @@ export const MAIN_MENU = [
|
|
|
80
82
|
description: 'Tickets de suporte — listar, responder, mudar status',
|
|
81
83
|
},
|
|
82
84
|
{
|
|
83
|
-
label: '
|
|
85
|
+
label: 'Docs live',
|
|
86
|
+
value: 'docs',
|
|
87
|
+
description: 'Registry publicado no Core - listar e ler markdown',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
label: 'Status e diagnostico',
|
|
84
91
|
value: 'health',
|
|
85
92
|
description: 'Saúde das superfícies, doctor e whoami',
|
|
86
93
|
},
|
|
@@ -124,6 +131,39 @@ function padMenuText(text, width) {
|
|
|
124
131
|
function menuBorder(text) {
|
|
125
132
|
return chalk.hex(MENU_OUTLINE_COLOR)(text);
|
|
126
133
|
}
|
|
134
|
+
function menuFooterText(text) {
|
|
135
|
+
return chalk.hex(MENU_FOOTER_TEXT_COLOR)(text);
|
|
136
|
+
}
|
|
137
|
+
function renderProgressBar(progress, width = 10) {
|
|
138
|
+
const safeProgress = Math.max(0, Math.min(100, Math.round(progress)));
|
|
139
|
+
const filled = Math.round((safeProgress / 100) * width);
|
|
140
|
+
return `${menuBorder('[')}${chalk.hex('#1E90FF')('#'.repeat(filled))}${chalk.dim('-'.repeat(width - filled))}${menuBorder(']')} ${safeProgress}%`;
|
|
141
|
+
}
|
|
142
|
+
function renderTaskFooter(tasks, width) {
|
|
143
|
+
if (!tasks || tasks.length === 0) {
|
|
144
|
+
return menuFooterText(padMenuText('tasks sem tarefas ativas', width));
|
|
145
|
+
}
|
|
146
|
+
const task = tasks[0];
|
|
147
|
+
const remaining = tasks.length > 1 ? ` +${tasks.length - 1}` : '';
|
|
148
|
+
return menuFooterText(padMenuText(`task ${task.label}${remaining} `, Math.max(0, width - 18))) + renderProgressBar(task.progress);
|
|
149
|
+
}
|
|
150
|
+
function renderFooterColumns(left, right, width) {
|
|
151
|
+
const gap = ' ';
|
|
152
|
+
const leftWidth = Math.max(20, Math.floor((width - gap.length) * 0.45));
|
|
153
|
+
const rightWidth = Math.max(10, width - leftWidth - gap.length);
|
|
154
|
+
return `${menuFooterText(padMenuText(left, leftWidth))}${gap}${menuFooterText(padMenuText(right, rightWidth))}`;
|
|
155
|
+
}
|
|
156
|
+
function renderMenuFooter(footer, width) {
|
|
157
|
+
const session = footer.sessionEmail
|
|
158
|
+
? `logado como ${footer.sessionEmail}`
|
|
159
|
+
: 'sem login - neetru login';
|
|
160
|
+
const taskWidth = Math.max(18, Math.floor(width * 0.52));
|
|
161
|
+
return [
|
|
162
|
+
` ${menuBorder('─'.repeat(width))}`,
|
|
163
|
+
` ${renderFooterColumns('Neetru CLI', `Developer Kit neetru.com v${CLI_VERSION}`, width)}`,
|
|
164
|
+
` ${menuFooterText(padMenuText(session, Math.max(20, width - taskWidth - 2)))} ${renderTaskFooter(footer.tasks, taskWidth)}`,
|
|
165
|
+
];
|
|
166
|
+
}
|
|
127
167
|
function renderMenuCell(item, selected, width) {
|
|
128
168
|
if (!item) {
|
|
129
169
|
const empty = ' '.repeat(width);
|
|
@@ -166,7 +206,7 @@ export function moveMenuSelection(currentIndex, direction, totalItems, columns =
|
|
|
166
206
|
}
|
|
167
207
|
return current;
|
|
168
208
|
}
|
|
169
|
-
export function renderMainMenu(items, selectedIndex, terminalColumns = process.stdout.columns) {
|
|
209
|
+
export function renderMainMenu(items, selectedIndex, terminalColumns = process.stdout.columns, footer = {}) {
|
|
170
210
|
const targetWidth = clampMenuWidth(terminalColumns);
|
|
171
211
|
const columnWidth = Math.max(24, Math.floor((targetWidth - 2 - MENU_GAP) / MENU_COLUMNS));
|
|
172
212
|
const innerWidth = columnWidth * MENU_COLUMNS + MENU_GAP;
|
|
@@ -182,8 +222,39 @@ export function renderMainMenu(items, selectedIndex, terminalColumns = process.s
|
|
|
182
222
|
});
|
|
183
223
|
lines.push(` ${menuBorder('│')}${cells.map((cell) => cell[0]).join(' '.repeat(MENU_GAP))}${menuBorder('│')}`);
|
|
184
224
|
lines.push(` ${menuBorder('│')}${cells.map((cell) => cell[1]).join(' '.repeat(MENU_GAP))}${menuBorder('│')}`);
|
|
225
|
+
if (row < rows - 1) {
|
|
226
|
+
lines.push(` ${menuBorder('│')}${' '.repeat(innerWidth)}${menuBorder('│')}`);
|
|
227
|
+
}
|
|
185
228
|
}
|
|
186
229
|
lines.push(` ${menuBorder(`└${'─'.repeat(innerWidth)}┘`)}`);
|
|
230
|
+
lines.push(...renderMenuFooter(footer, innerWidth + 2));
|
|
231
|
+
return lines.join('\n');
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Renderiza um submenu em coluna única, alinhado à esquerda, com a mesma
|
|
235
|
+
* moldura azul-marinho e o marcador `> <` do menu principal. A largura
|
|
236
|
+
* acompanha o item mais longo (clampada entre 18 e 48).
|
|
237
|
+
*/
|
|
238
|
+
export function renderSubMenu(title, items, selectedIndex) {
|
|
239
|
+
const longest = items.reduce((max, item) => Math.max(max, item.label.length), 0);
|
|
240
|
+
const labelWidth = Math.max(18, Math.min(48, longest));
|
|
241
|
+
const innerWidth = labelWidth + 6;
|
|
242
|
+
const lines = [
|
|
243
|
+
` ${chalk.bold.hex(MENU_OUTLINE_COLOR)(title)}`,
|
|
244
|
+
` ${chalk.dim('↑/↓ navega · Enter abre · Esc volta')}`,
|
|
245
|
+
` ${menuBorder(`┌${'─'.repeat(innerWidth)}┐`)}`,
|
|
246
|
+
];
|
|
247
|
+
items.forEach((item, index) => {
|
|
248
|
+
const padded = padMenuText(item.label, labelWidth);
|
|
249
|
+
if (index === selectedIndex) {
|
|
250
|
+
const label = chalk.bgHex('#D7E9FF').hex(MENU_OUTLINE_COLOR).bold(padded);
|
|
251
|
+
lines.push(` ${menuBorder('│')} ${menuBorder('>')} ${label} ${menuBorder('<')} ${menuBorder('│')}`);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
lines.push(` ${menuBorder('│')} ${chalk.bold(padded)} ${menuBorder('│')}`);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
lines.push(` ${menuBorder(`└${'─'.repeat(innerWidth)}┘`)}`);
|
|
187
258
|
return lines.join('\n');
|
|
188
259
|
}
|
|
189
260
|
function decodeMenuKey(data) {
|
|
@@ -207,9 +278,50 @@ function readMenuKey() {
|
|
|
207
278
|
process.stdin.once('data', (data) => resolve(decodeMenuKey(data)));
|
|
208
279
|
});
|
|
209
280
|
}
|
|
210
|
-
|
|
281
|
+
function formatDocDate(value) {
|
|
282
|
+
if (!value)
|
|
283
|
+
return undefined;
|
|
284
|
+
const date = new Date(value);
|
|
285
|
+
if (Number.isNaN(date.getTime()))
|
|
286
|
+
return undefined;
|
|
287
|
+
return date.toISOString().slice(0, 10);
|
|
288
|
+
}
|
|
289
|
+
function docPublishedTime(doc) {
|
|
290
|
+
const time = Date.parse(doc.publishedAt ?? '');
|
|
291
|
+
return Number.isNaN(time) ? 0 : time;
|
|
292
|
+
}
|
|
293
|
+
async function loadDocsBannerInfo() {
|
|
294
|
+
const token = await resolveToken();
|
|
295
|
+
if (!token)
|
|
296
|
+
return { message: 'login para consultar registry' };
|
|
297
|
+
const ctrl = new AbortController();
|
|
298
|
+
const timer = setTimeout(() => ctrl.abort(), 1500);
|
|
299
|
+
try {
|
|
300
|
+
const result = await apiRequest('/api/cli/v1/docs', {
|
|
301
|
+
token,
|
|
302
|
+
signal: ctrl.signal,
|
|
303
|
+
});
|
|
304
|
+
const recent = [...(result.docs ?? [])]
|
|
305
|
+
.sort((a, b) => docPublishedTime(b) - docPublishedTime(a))
|
|
306
|
+
.slice(0, DOCS_RECENT_LIMIT)
|
|
307
|
+
.map((doc) => ({
|
|
308
|
+
title: doc.title || doc.slug,
|
|
309
|
+
slug: doc.slug,
|
|
310
|
+
updated: formatDocDate(doc.publishedAt),
|
|
311
|
+
}));
|
|
312
|
+
return { count: result.count ?? result.docs?.length ?? 0, recent };
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return { message: 'registry indisponivel agora' };
|
|
316
|
+
}
|
|
317
|
+
finally {
|
|
318
|
+
clearTimeout(timer);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async function promptMainMenu(items, footer = {}) {
|
|
211
322
|
let selectedIndex = 0;
|
|
212
323
|
let renderedLines = 0;
|
|
324
|
+
let result = null;
|
|
213
325
|
const wasRaw = process.stdin.isRaw === true;
|
|
214
326
|
const clearRenderedMenu = () => {
|
|
215
327
|
if (renderedLines > 0) {
|
|
@@ -223,15 +335,19 @@ async function promptMainMenu(items) {
|
|
|
223
335
|
process.stdout.write('\x1B[?25l');
|
|
224
336
|
try {
|
|
225
337
|
for (;;) {
|
|
226
|
-
const frame = renderMainMenu(items, selectedIndex);
|
|
338
|
+
const frame = renderMainMenu(items, selectedIndex, process.stdout.columns, footer);
|
|
227
339
|
clearRenderedMenu();
|
|
228
340
|
process.stdout.write(`${frame}\n`);
|
|
229
341
|
renderedLines = frame.split('\n').length;
|
|
230
342
|
const key = await readMenuKey();
|
|
231
|
-
if (key === 'abort')
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
343
|
+
if (key === 'abort') {
|
|
344
|
+
result = null;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
if (key === 'enter') {
|
|
348
|
+
result = { action: items[selectedIndex].value };
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
235
351
|
if (key)
|
|
236
352
|
selectedIndex = moveMenuSelection(selectedIndex, key, items.length);
|
|
237
353
|
}
|
|
@@ -239,10 +355,58 @@ async function promptMainMenu(items) {
|
|
|
239
355
|
finally {
|
|
240
356
|
clearRenderedMenu();
|
|
241
357
|
process.stdout.write('\x1B[?25h');
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
358
|
+
// Só restaura/pausa o stdin ao DEIXAR o menu (Ctrl+C ou "Sair"). Quando
|
|
359
|
+
// uma ação/submenu roda a seguir, o stdin fica "quente" (raw + resumed)
|
|
360
|
+
// pro inquirer do submenu assumir o stream sem comer a 1ª seta — o
|
|
361
|
+
// pause()/toggle de raw mode aqui é o que engolia a primeira tecla.
|
|
362
|
+
const leavingMenu = result === null || result.action === 'exit';
|
|
363
|
+
if (leavingMenu) {
|
|
364
|
+
if (typeof process.stdin.setRawMode === 'function') {
|
|
365
|
+
process.stdin.setRawMode(wasRaw);
|
|
366
|
+
}
|
|
367
|
+
if (!wasRaw)
|
|
368
|
+
process.stdin.pause();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Submenu interativo em coluna única — espelha o `promptMainMenu`, mas com
|
|
375
|
+
* uma lista vertical estreita. Retorna o `value` do item escolhido; Esc ou
|
|
376
|
+
* Ctrl+C retornam `'back'`. Deixa o stdin "quente" na saída (a ação a seguir,
|
|
377
|
+
* ou o menu principal, assumem o stream sem comer a 1ª tecla).
|
|
378
|
+
*/
|
|
379
|
+
async function promptSubMenu(title, items) {
|
|
380
|
+
let selectedIndex = 0;
|
|
381
|
+
let renderedLines = 0;
|
|
382
|
+
const clearRendered = () => {
|
|
383
|
+
if (renderedLines > 0) {
|
|
384
|
+
process.stdout.write(`\x1B[${renderedLines}A\x1B[J`);
|
|
385
|
+
renderedLines = 0;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
process.stdin.resume();
|
|
389
|
+
if (typeof process.stdin.setRawMode === 'function')
|
|
390
|
+
process.stdin.setRawMode(true);
|
|
391
|
+
process.stdout.write('\x1B[?25l');
|
|
392
|
+
try {
|
|
393
|
+
for (;;) {
|
|
394
|
+
const frame = renderSubMenu(title, items, selectedIndex);
|
|
395
|
+
clearRendered();
|
|
396
|
+
process.stdout.write(`${frame}\n`);
|
|
397
|
+
renderedLines = frame.split('\n').length;
|
|
398
|
+
const key = await readMenuKey();
|
|
399
|
+
if (key === 'abort')
|
|
400
|
+
return 'back';
|
|
401
|
+
if (key === 'enter')
|
|
402
|
+
return items[selectedIndex].value;
|
|
403
|
+
if (key)
|
|
404
|
+
selectedIndex = moveMenuSelection(selectedIndex, key, items.length, 1);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
finally {
|
|
408
|
+
clearRendered();
|
|
409
|
+
process.stdout.write('\x1B[?25h');
|
|
246
410
|
}
|
|
247
411
|
}
|
|
248
412
|
function canPrompt() {
|
|
@@ -253,7 +417,7 @@ async function pause() {
|
|
|
253
417
|
{
|
|
254
418
|
type: 'input',
|
|
255
419
|
name: 'continue',
|
|
256
|
-
message: 'Enter para
|
|
420
|
+
message: 'Enter para continuar',
|
|
257
421
|
},
|
|
258
422
|
]);
|
|
259
423
|
}
|
|
@@ -267,13 +431,24 @@ async function safeRun(fn, opts = {}) {
|
|
|
267
431
|
// `CommandExitError` = um comando chamou `process.exit()` e já imprimiu a
|
|
268
432
|
// própria mensagem. Qualquer outro erro: mostramos a mensagem. Nos dois
|
|
269
433
|
// casos pausamos (a função não chegou ao seu próprio `pause()`) e voltamos
|
|
270
|
-
// ao
|
|
434
|
+
// ao fluxo interativo — o CLI NUNCA encerra por causa de um comando.
|
|
271
435
|
if (!(error instanceof CommandExitError)) {
|
|
272
436
|
log.error(error instanceof Error ? error.message : String(error));
|
|
273
437
|
}
|
|
274
438
|
await pause();
|
|
275
439
|
}
|
|
276
440
|
}
|
|
441
|
+
async function runSubmenuAction(fn) {
|
|
442
|
+
try {
|
|
443
|
+
await fn();
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
if (!(error instanceof CommandExitError)) {
|
|
447
|
+
log.error(error instanceof Error ? error.message : String(error));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
await pause();
|
|
451
|
+
}
|
|
277
452
|
async function readLocalProject(cwd) {
|
|
278
453
|
for (const file of ['neetru.config.json', '.neetru.json']) {
|
|
279
454
|
const full = path.join(cwd, file);
|
|
@@ -355,286 +530,340 @@ async function quickstart() {
|
|
|
355
530
|
skipOpen: answers.skipOpen,
|
|
356
531
|
});
|
|
357
532
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
533
|
+
/** Tela de info sobre o SDK Neetru (@neetru/sdk). */
|
|
534
|
+
function showSdkInfo() {
|
|
535
|
+
log.heading('SDK Neetru — @neetru/sdk');
|
|
536
|
+
console.log(` ${chalk.dim('O que é')} biblioteca que o SEU produto importa pra falar com o Core`);
|
|
537
|
+
console.log(` ${chalk.dim('Instala')} ${chalk.cyan('npm install @neetru/sdk')} (dependência do projeto)`);
|
|
538
|
+
console.log(` ${chalk.dim('No scaffold')} o ${chalk.cyan('neetru init')} já adiciona o @neetru/sdk + exemplos`);
|
|
539
|
+
console.log();
|
|
540
|
+
console.log(` ${chalk.dim('Uso no código:')}`);
|
|
541
|
+
console.log(chalk.cyan(" import { createNeetruClient } from '@neetru/sdk';"));
|
|
542
|
+
console.log(chalk.cyan(" const neetru = createNeetruClient({ apiKey, env: 'prod' });"));
|
|
543
|
+
console.log(chalk.cyan(' await neetru.auth.signIn();'));
|
|
544
|
+
console.log();
|
|
545
|
+
console.log(` ${chalk.dim('React:')} ${chalk.cyan('@neetru/sdk/react')} — <EntitlementGate>, <CheckoutLink>`);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Sub-menu "Começar um projeto": scaffold local (`neetru init`), a macro
|
|
549
|
+
* de criação completa (`neetru new`) e a info do SDK.
|
|
550
|
+
*/
|
|
551
|
+
async function projectMenu() {
|
|
552
|
+
const action = await promptSubMenu('Começar um projeto', [
|
|
553
|
+
{ label: 'Scaffold local — neetru init', value: 'init' },
|
|
554
|
+
{ label: 'Criar produto + workspace — neetru new', value: 'new' },
|
|
555
|
+
{ label: 'Sobre o SDK Neetru', value: 'sdk' },
|
|
556
|
+
{ label: 'Voltar', value: 'back' },
|
|
372
557
|
]);
|
|
373
|
-
if (
|
|
558
|
+
if (action === 'back')
|
|
374
559
|
return;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
560
|
+
if (action === 'init') {
|
|
561
|
+
const { name } = await inquirer.prompt([
|
|
562
|
+
{
|
|
563
|
+
type: 'input',
|
|
564
|
+
name: 'name',
|
|
565
|
+
message: 'Nome do projeto:',
|
|
566
|
+
validate: (v) => v.trim().length > 0 || 'Obrigatório.',
|
|
567
|
+
},
|
|
568
|
+
]);
|
|
569
|
+
const { runInit } = await import('./init.js');
|
|
570
|
+
await runInit({ name, type: 'nextjs' });
|
|
571
|
+
}
|
|
572
|
+
if (action === 'new')
|
|
573
|
+
await quickstart();
|
|
574
|
+
if (action === 'sdk')
|
|
575
|
+
showSdkInfo();
|
|
384
576
|
await pause();
|
|
385
577
|
}
|
|
578
|
+
async function productsMenu() {
|
|
579
|
+
for (;;) {
|
|
580
|
+
const action = await promptSubMenu('Produtos', [
|
|
581
|
+
{ label: 'Listar produtos', value: 'list' },
|
|
582
|
+
{ label: 'Criar produto', value: 'create' },
|
|
583
|
+
{ label: 'Publicar no catálogo', value: 'publish' },
|
|
584
|
+
{ label: 'Remover do catálogo', value: 'unpublish' },
|
|
585
|
+
{ label: 'Voltar', value: 'back' },
|
|
586
|
+
]);
|
|
587
|
+
if (action === 'back')
|
|
588
|
+
return;
|
|
589
|
+
await runSubmenuAction(async () => {
|
|
590
|
+
const products = await import('./products.js');
|
|
591
|
+
if (action === 'list')
|
|
592
|
+
await products.runProductsList({});
|
|
593
|
+
if (action === 'create')
|
|
594
|
+
await products.runProductsCreate({});
|
|
595
|
+
if (action === 'publish')
|
|
596
|
+
await products.runProductsPublish(undefined, {});
|
|
597
|
+
if (action === 'unpublish')
|
|
598
|
+
await products.runProductsUnpublish(undefined, {});
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
386
602
|
async function workspacesMenu() {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
await workspaces.runWorkspacesOpen(undefined);
|
|
413
|
-
if (answer.action === 'advance')
|
|
414
|
-
await workspaces.runWorkspacesAdvance(undefined, {});
|
|
415
|
-
await pause();
|
|
603
|
+
for (;;) {
|
|
604
|
+
const action = await promptSubMenu('Workspaces', [
|
|
605
|
+
{ label: 'Listar workspaces', value: 'list' },
|
|
606
|
+
{ label: 'Criar workspace', value: 'create' },
|
|
607
|
+
{ label: 'Ver detalhes', value: 'get' },
|
|
608
|
+
{ label: 'Abrir no painel', value: 'open' },
|
|
609
|
+
{ label: 'Promover bundle', value: 'advance' },
|
|
610
|
+
{ label: 'Voltar', value: 'back' },
|
|
611
|
+
]);
|
|
612
|
+
if (action === 'back')
|
|
613
|
+
return;
|
|
614
|
+
await runSubmenuAction(async () => {
|
|
615
|
+
const workspaces = await import('./workspaces.js');
|
|
616
|
+
if (action === 'list')
|
|
617
|
+
await workspaces.runWorkspacesList({});
|
|
618
|
+
if (action === 'create')
|
|
619
|
+
await workspaces.runWorkspacesCreate({});
|
|
620
|
+
if (action === 'get')
|
|
621
|
+
await workspaces.runWorkspacesGet(undefined, {});
|
|
622
|
+
if (action === 'open')
|
|
623
|
+
await workspaces.runWorkspacesOpen(undefined);
|
|
624
|
+
if (action === 'advance')
|
|
625
|
+
await workspaces.runWorkspacesAdvance(undefined, {});
|
|
626
|
+
});
|
|
627
|
+
}
|
|
416
628
|
}
|
|
417
629
|
async function serversMenu() {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
await servers.runServersDispatch(undefined, undefined, {});
|
|
441
|
-
if (answer.action === 'deactivate')
|
|
442
|
-
await servers.runServersDeactivate(undefined, {});
|
|
443
|
-
await pause();
|
|
630
|
+
for (;;) {
|
|
631
|
+
const action = await promptSubMenu('Servers', [
|
|
632
|
+
{ label: 'Listar servers', value: 'list' },
|
|
633
|
+
{ label: 'Provisionar VM', value: 'provision' },
|
|
634
|
+
{ label: 'Despachar comando', value: 'dispatch' },
|
|
635
|
+
{ label: 'Desativar server', value: 'deactivate' },
|
|
636
|
+
{ label: 'Voltar', value: 'back' },
|
|
637
|
+
]);
|
|
638
|
+
if (action === 'back')
|
|
639
|
+
return;
|
|
640
|
+
await runSubmenuAction(async () => {
|
|
641
|
+
const servers = await import('./servers.js');
|
|
642
|
+
if (action === 'list')
|
|
643
|
+
await servers.runServersList({ capacity: true });
|
|
644
|
+
if (action === 'provision')
|
|
645
|
+
await servers.runServersProvision({});
|
|
646
|
+
if (action === 'dispatch')
|
|
647
|
+
await servers.runServersDispatch(undefined, undefined, {});
|
|
648
|
+
if (action === 'deactivate')
|
|
649
|
+
await servers.runServersDeactivate(undefined, {});
|
|
650
|
+
});
|
|
651
|
+
}
|
|
444
652
|
}
|
|
445
653
|
async function databasesMenu() {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
await db.runDbRotate(undefined, {});
|
|
478
|
-
if (answer.action === 'delete')
|
|
479
|
-
await db.runDbDelete(undefined, {});
|
|
480
|
-
await pause();
|
|
654
|
+
for (;;) {
|
|
655
|
+
const action = await promptSubMenu('Bancos de produto', [
|
|
656
|
+
{ label: 'Listar bancos', value: 'list' },
|
|
657
|
+
{ label: 'Criar banco', value: 'create' },
|
|
658
|
+
{ label: 'Detalhar banco', value: 'get' },
|
|
659
|
+
{ label: 'Listar engines', value: 'engines' },
|
|
660
|
+
{ label: 'Retry provisionamento', value: 'retry' },
|
|
661
|
+
{ label: 'Rotacionar credenciais', value: 'rotate' },
|
|
662
|
+
{ label: 'Arquivar banco', value: 'delete' },
|
|
663
|
+
{ label: 'Voltar', value: 'back' },
|
|
664
|
+
]);
|
|
665
|
+
if (action === 'back')
|
|
666
|
+
return;
|
|
667
|
+
await runSubmenuAction(async () => {
|
|
668
|
+
const db = await import('./products-db.js');
|
|
669
|
+
if (action === 'list')
|
|
670
|
+
await db.runDbList({});
|
|
671
|
+
if (action === 'create')
|
|
672
|
+
await db.runDbCreate({ region: 'us-central1' });
|
|
673
|
+
if (action === 'get')
|
|
674
|
+
await db.runDbGet(undefined, {});
|
|
675
|
+
if (action === 'engines')
|
|
676
|
+
await db.runDbEngines({});
|
|
677
|
+
if (action === 'retry')
|
|
678
|
+
await db.runDbRetry(undefined, {});
|
|
679
|
+
if (action === 'rotate')
|
|
680
|
+
await db.runDbRotate(undefined, {});
|
|
681
|
+
if (action === 'delete')
|
|
682
|
+
await db.runDbDelete(undefined, {});
|
|
683
|
+
});
|
|
684
|
+
}
|
|
481
685
|
}
|
|
482
686
|
async function tenantsMenu() {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
{ name: 'Atualizar tenant', value: 'update' },
|
|
493
|
-
{ name: 'Suspender tenant', value: 'suspend' },
|
|
494
|
-
{ name: 'Reativar tenant', value: 'reactivate' },
|
|
495
|
-
{ name: 'Voltar', value: 'back' },
|
|
496
|
-
],
|
|
497
|
-
},
|
|
498
|
-
]);
|
|
499
|
-
if (answer.action === 'back')
|
|
500
|
-
return;
|
|
501
|
-
const tenants = await import('./tenants.js');
|
|
502
|
-
if (answer.action === 'list')
|
|
503
|
-
await tenants.runTenantsList({});
|
|
504
|
-
if (answer.action === 'get')
|
|
505
|
-
await tenants.runTenantsGet(undefined, {});
|
|
506
|
-
if (answer.action === 'create')
|
|
507
|
-
await tenants.runTenantsCreate({});
|
|
508
|
-
if (answer.action === 'update')
|
|
509
|
-
await tenants.runTenantsUpdate(undefined, {});
|
|
510
|
-
if (answer.action === 'suspend')
|
|
511
|
-
await tenants.runTenantsSuspend(undefined, {});
|
|
512
|
-
if (answer.action === 'reactivate')
|
|
513
|
-
await tenants.runTenantsReactivate(undefined, {});
|
|
514
|
-
await pause();
|
|
515
|
-
}
|
|
516
|
-
async function supportMenu() {
|
|
517
|
-
const answer = await inquirer.prompt([
|
|
518
|
-
{
|
|
519
|
-
type: 'list',
|
|
520
|
-
name: 'action',
|
|
521
|
-
message: 'Suporte',
|
|
522
|
-
choices: [
|
|
523
|
-
{ name: 'Listar tickets', value: 'list' },
|
|
524
|
-
{ name: 'Detalhar ticket', value: 'describe' },
|
|
525
|
-
{ name: 'Responder ticket', value: 'reply' },
|
|
526
|
-
{ name: 'Mudar status', value: 'status' },
|
|
527
|
-
{ name: 'Voltar', value: 'back' },
|
|
528
|
-
],
|
|
529
|
-
},
|
|
530
|
-
]);
|
|
531
|
-
if (answer.action === 'back')
|
|
532
|
-
return;
|
|
533
|
-
const support = await import('./support.js');
|
|
534
|
-
if (answer.action === 'list')
|
|
535
|
-
await support.runSupportTicketsList({});
|
|
536
|
-
if (answer.action === 'describe') {
|
|
537
|
-
const { id } = await inquirer.prompt([
|
|
538
|
-
{ type: 'input', name: 'id', message: 'Ticket ID:', validate: Boolean },
|
|
687
|
+
for (;;) {
|
|
688
|
+
const action = await promptSubMenu('Tenants', [
|
|
689
|
+
{ label: 'Listar tenants', value: 'list' },
|
|
690
|
+
{ label: 'Detalhar tenant', value: 'get' },
|
|
691
|
+
{ label: 'Criar tenant', value: 'create' },
|
|
692
|
+
{ label: 'Atualizar tenant', value: 'update' },
|
|
693
|
+
{ label: 'Suspender tenant', value: 'suspend' },
|
|
694
|
+
{ label: 'Reativar tenant', value: 'reactivate' },
|
|
695
|
+
{ label: 'Voltar', value: 'back' },
|
|
539
696
|
]);
|
|
540
|
-
|
|
697
|
+
if (action === 'back')
|
|
698
|
+
return;
|
|
699
|
+
await runSubmenuAction(async () => {
|
|
700
|
+
const tenants = await import('./tenants.js');
|
|
701
|
+
if (action === 'list')
|
|
702
|
+
await tenants.runTenantsList({});
|
|
703
|
+
if (action === 'get')
|
|
704
|
+
await tenants.runTenantsGet(undefined, {});
|
|
705
|
+
if (action === 'create')
|
|
706
|
+
await tenants.runTenantsCreate({});
|
|
707
|
+
if (action === 'update')
|
|
708
|
+
await tenants.runTenantsUpdate(undefined, {});
|
|
709
|
+
if (action === 'suspend')
|
|
710
|
+
await tenants.runTenantsSuspend(undefined, {});
|
|
711
|
+
if (action === 'reactivate')
|
|
712
|
+
await tenants.runTenantsReactivate(undefined, {});
|
|
713
|
+
});
|
|
541
714
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
715
|
+
}
|
|
716
|
+
async function supportMenu() {
|
|
717
|
+
for (;;) {
|
|
718
|
+
const action = await promptSubMenu('Suporte', [
|
|
719
|
+
{ label: 'Listar tickets', value: 'list' },
|
|
720
|
+
{ label: 'Detalhar ticket', value: 'describe' },
|
|
721
|
+
{ label: 'Responder ticket', value: 'reply' },
|
|
722
|
+
{ label: 'Mudar status', value: 'status' },
|
|
723
|
+
{ label: 'Voltar', value: 'back' },
|
|
546
724
|
]);
|
|
547
|
-
|
|
725
|
+
if (action === 'back')
|
|
726
|
+
return;
|
|
727
|
+
await runSubmenuAction(async () => {
|
|
728
|
+
const support = await import('./support.js');
|
|
729
|
+
if (action === 'list')
|
|
730
|
+
await support.runSupportTicketsList({});
|
|
731
|
+
if (action === 'describe') {
|
|
732
|
+
const { id } = await inquirer.prompt([
|
|
733
|
+
{ type: 'input', name: 'id', message: 'Ticket ID:', validate: Boolean },
|
|
734
|
+
]);
|
|
735
|
+
await support.runSupportTicketsDescribe(id, {});
|
|
736
|
+
}
|
|
737
|
+
if (action === 'reply') {
|
|
738
|
+
const { id, message } = await inquirer.prompt([
|
|
739
|
+
{ type: 'input', name: 'id', message: 'Ticket ID:', validate: Boolean },
|
|
740
|
+
{ type: 'editor', name: 'message', message: 'Mensagem:' },
|
|
741
|
+
]);
|
|
742
|
+
await support.runSupportTicketsReply(id, { message });
|
|
743
|
+
}
|
|
744
|
+
if (action === 'status') {
|
|
745
|
+
const { id, to } = await inquirer.prompt([
|
|
746
|
+
{ type: 'input', name: 'id', message: 'Ticket ID:', validate: Boolean },
|
|
747
|
+
{
|
|
748
|
+
type: 'list',
|
|
749
|
+
name: 'to',
|
|
750
|
+
message: 'Novo status:',
|
|
751
|
+
choices: ['open', 'in_progress', 'resolved', 'closed'],
|
|
752
|
+
},
|
|
753
|
+
]);
|
|
754
|
+
await support.runSupportTicketsStatus(id, { to });
|
|
755
|
+
}
|
|
756
|
+
});
|
|
548
757
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
758
|
+
}
|
|
759
|
+
export function buildDocsMenuChoices(docsInfo) {
|
|
760
|
+
const recent = docsInfo?.recent?.slice(0, DOCS_RECENT_LIMIT) ?? [];
|
|
761
|
+
const recentChoices = recent.map((doc) => ({
|
|
762
|
+
name: `Abrir ${chalk.bold(doc.title)}${doc.updated ? chalk.dim(` ${doc.updated}`) : ''}`,
|
|
763
|
+
value: `recent:${doc.slug}`,
|
|
764
|
+
short: doc.title,
|
|
765
|
+
}));
|
|
766
|
+
return [
|
|
767
|
+
...recentChoices,
|
|
768
|
+
{ name: 'Listar todos os docs publicados', value: 'list', short: 'Listar docs' },
|
|
769
|
+
{ name: 'Buscar por slug', value: 'get', short: 'Buscar por slug' },
|
|
770
|
+
{ name: 'Ler metadata por slug', value: 'meta', short: 'Ler metadata' },
|
|
771
|
+
{ name: 'Voltar', value: 'back', short: 'Voltar' },
|
|
772
|
+
];
|
|
773
|
+
}
|
|
774
|
+
async function docsMenu(docsInfo) {
|
|
775
|
+
for (;;) {
|
|
776
|
+
const items = buildDocsMenuChoices(docsInfo).map((choice) => ({
|
|
777
|
+
label: choice.short,
|
|
778
|
+
value: choice.value,
|
|
779
|
+
}));
|
|
780
|
+
const action = (await promptSubMenu('Docs live', items));
|
|
781
|
+
if (action === 'back')
|
|
782
|
+
return;
|
|
783
|
+
await runSubmenuAction(async () => {
|
|
784
|
+
const docs = await import('./docs.js');
|
|
785
|
+
if (action.startsWith('recent:')) {
|
|
786
|
+
await docs.runDocsGet(action.slice('recent:'.length), { metaOnly: false });
|
|
787
|
+
}
|
|
788
|
+
if (action === 'list') {
|
|
789
|
+
await docs.runDocsList({});
|
|
790
|
+
}
|
|
791
|
+
if (action === 'get' || action === 'meta') {
|
|
792
|
+
const { slug } = await inquirer.prompt([
|
|
793
|
+
{ type: 'input', name: 'slug', message: 'Slug do doc:', validate: Boolean },
|
|
794
|
+
]);
|
|
795
|
+
await docs.runDocsGet(slug, { metaOnly: action === 'meta' });
|
|
796
|
+
}
|
|
797
|
+
});
|
|
560
798
|
}
|
|
561
|
-
await pause();
|
|
562
799
|
}
|
|
563
800
|
async function healthMenu() {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
if (answer.action === 'whoami') {
|
|
599
|
-
const { runWhoami } = await import('./whoami.js');
|
|
600
|
-
await runWhoami({});
|
|
801
|
+
for (;;) {
|
|
802
|
+
const action = await promptSubMenu('Status e diagnóstico', [
|
|
803
|
+
{ label: 'Status das superfícies', value: 'status' },
|
|
804
|
+
{ label: 'Doctor', value: 'doctor' },
|
|
805
|
+
{ label: 'Whoami', value: 'whoami' },
|
|
806
|
+
{ label: 'Voltar', value: 'back' },
|
|
807
|
+
]);
|
|
808
|
+
if (action === 'back')
|
|
809
|
+
return;
|
|
810
|
+
await runSubmenuAction(async () => {
|
|
811
|
+
if (action === 'status') {
|
|
812
|
+
const { runSurfaceStatus } = await import('./surface-status.js');
|
|
813
|
+
await runSurfaceStatus({});
|
|
814
|
+
}
|
|
815
|
+
if (action === 'doctor') {
|
|
816
|
+
const doctor = await import('./doctor.js');
|
|
817
|
+
const checks = [
|
|
818
|
+
await doctor.checkToken(),
|
|
819
|
+
await doctor.checkCoreHealth(),
|
|
820
|
+
await doctor.checkConfigSchema(process.cwd()),
|
|
821
|
+
await doctor.checkNeetruEnv(process.cwd()),
|
|
822
|
+
await doctor.checkCliVersion(),
|
|
823
|
+
];
|
|
824
|
+
log.heading('Doctor');
|
|
825
|
+
for (const check of checks) {
|
|
826
|
+
const marker = check.ok ? chalk.green('*') : check.warning ? chalk.yellow('!') : chalk.red('x');
|
|
827
|
+
console.log(` ${marker} ${check.name}${check.detail ? chalk.dim(` - ${check.detail}`) : ''}`);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (action === 'whoami') {
|
|
831
|
+
const { runWhoami } = await import('./whoami.js');
|
|
832
|
+
await runWhoami({});
|
|
833
|
+
}
|
|
834
|
+
});
|
|
601
835
|
}
|
|
602
|
-
await pause();
|
|
603
836
|
}
|
|
604
837
|
async function authMenu() {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
if (answer.action === 'path') {
|
|
634
|
-
const { configPath } = await import('./config.js');
|
|
635
|
-
configPath();
|
|
838
|
+
for (;;) {
|
|
839
|
+
const action = await promptSubMenu('Login e configuração', [
|
|
840
|
+
{ label: 'Login', value: 'login' },
|
|
841
|
+
{ label: 'Logout', value: 'logout' },
|
|
842
|
+
{ label: 'Ver config', value: 'config' },
|
|
843
|
+
{ label: 'Caminho da config', value: 'path' },
|
|
844
|
+
{ label: 'Voltar', value: 'back' },
|
|
845
|
+
]);
|
|
846
|
+
if (action === 'back')
|
|
847
|
+
return;
|
|
848
|
+
await runSubmenuAction(async () => {
|
|
849
|
+
if (action === 'login') {
|
|
850
|
+
const { runLogin } = await import('./login.js');
|
|
851
|
+
await runLogin({});
|
|
852
|
+
}
|
|
853
|
+
if (action === 'logout') {
|
|
854
|
+
const { runLogout } = await import('./logout.js');
|
|
855
|
+
await runLogout();
|
|
856
|
+
}
|
|
857
|
+
if (action === 'config') {
|
|
858
|
+
const { configGet } = await import('./config.js');
|
|
859
|
+
configGet();
|
|
860
|
+
}
|
|
861
|
+
if (action === 'path') {
|
|
862
|
+
const { configPath } = await import('./config.js');
|
|
863
|
+
configPath();
|
|
864
|
+
}
|
|
865
|
+
});
|
|
636
866
|
}
|
|
637
|
-
await pause();
|
|
638
867
|
}
|
|
639
868
|
export async function runTerminalUi() {
|
|
640
869
|
if (!canPrompt()) {
|
|
@@ -650,16 +879,17 @@ export async function runTerminalUi() {
|
|
|
650
879
|
throw new CommandExitError(typeof code === 'number' ? code : 0);
|
|
651
880
|
});
|
|
652
881
|
try {
|
|
653
|
-
const [email, version] = await Promise.all([
|
|
882
|
+
const [email, version, docsInfo] = await Promise.all([
|
|
654
883
|
resolveSessionEmail(),
|
|
655
884
|
checkForUpdate(),
|
|
885
|
+
loadDocsBannerInfo(),
|
|
656
886
|
]);
|
|
657
|
-
log.banner(email, version);
|
|
887
|
+
log.banner(email, version, docsInfo);
|
|
658
888
|
let running = true;
|
|
659
889
|
while (running) {
|
|
660
890
|
let answer;
|
|
661
891
|
try {
|
|
662
|
-
answer = await promptMainMenu(MAIN_MENU);
|
|
892
|
+
answer = await promptMainMenu(MAIN_MENU, { sessionEmail: email });
|
|
663
893
|
}
|
|
664
894
|
catch {
|
|
665
895
|
// Ctrl+C / fechamento do prompt = o usuário encerrando por vontade
|
|
@@ -678,7 +908,7 @@ export async function runTerminalUi() {
|
|
|
678
908
|
if (answer.action === 'overview')
|
|
679
909
|
await safeRun(showOverview);
|
|
680
910
|
if (answer.action === 'quickstart')
|
|
681
|
-
await safeRun(
|
|
911
|
+
await safeRun(projectMenu, { pauseAfter: false });
|
|
682
912
|
if (answer.action === 'products')
|
|
683
913
|
await safeRun(productsMenu, { pauseAfter: false });
|
|
684
914
|
if (answer.action === 'workspaces')
|
|
@@ -695,6 +925,8 @@ export async function runTerminalUi() {
|
|
|
695
925
|
await safeRun(tenantsMenu, { pauseAfter: false });
|
|
696
926
|
if (answer.action === 'support')
|
|
697
927
|
await safeRun(supportMenu, { pauseAfter: false });
|
|
928
|
+
if (answer.action === 'docs')
|
|
929
|
+
await safeRun(() => docsMenu(docsInfo), { pauseAfter: false });
|
|
698
930
|
if (answer.action === 'health')
|
|
699
931
|
await safeRun(healthMenu, { pauseAfter: false });
|
|
700
932
|
if (answer.action === 'auth')
|