vigthoria-cli 1.6.59 → 1.6.60
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/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +148 -61
- package/dist/commands/chat.js +68 -24
- package/dist/utils/logger.js +29 -24
- package/package.json +1 -1
package/dist/commands/auth.d.ts
CHANGED
package/dist/commands/auth.js
CHANGED
|
@@ -2,15 +2,100 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Auth Command - Authentication management
|
|
4
4
|
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
5
38
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
39
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
40
|
};
|
|
8
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
42
|
exports.AuthCommand = void 0;
|
|
10
43
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
-
const
|
|
44
|
+
const readline = __importStar(require("readline"));
|
|
12
45
|
const logger_js_1 = require("../utils/logger.js");
|
|
13
46
|
const api_js_1 = require("../utils/api.js");
|
|
47
|
+
/**
|
|
48
|
+
* Prompt helpers using Node's built-in readline.
|
|
49
|
+
* inquirer 9.x destroys the readline interface after each prompt() call
|
|
50
|
+
* which triggers ERR_USE_AFTER_CLOSE on Node 20 Windows TTY when a second
|
|
51
|
+
* prompt is attempted on the same process.stdin. Using raw readline avoids
|
|
52
|
+
* this entirely.
|
|
53
|
+
*/
|
|
54
|
+
function ask(rl, question) {
|
|
55
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
56
|
+
}
|
|
57
|
+
function askHidden(question) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
60
|
+
// Temporarily mute output to hide typed characters
|
|
61
|
+
const stdout = process.stdout;
|
|
62
|
+
let answer = '';
|
|
63
|
+
process.stdout.write(question);
|
|
64
|
+
if (process.stdin.isTTY) {
|
|
65
|
+
process.stdin.setRawMode(true);
|
|
66
|
+
}
|
|
67
|
+
process.stdin.resume();
|
|
68
|
+
const onData = (ch) => {
|
|
69
|
+
const c = ch.toString('utf8');
|
|
70
|
+
if (c === '\n' || c === '\r' || c === '\u0004') {
|
|
71
|
+
if (process.stdin.isTTY) {
|
|
72
|
+
process.stdin.setRawMode(false);
|
|
73
|
+
}
|
|
74
|
+
process.stdin.removeListener('data', onData);
|
|
75
|
+
stdout.write('\n');
|
|
76
|
+
rl.close();
|
|
77
|
+
resolve(answer);
|
|
78
|
+
}
|
|
79
|
+
else if (c === '\u007f' || c === '\b') {
|
|
80
|
+
// backspace
|
|
81
|
+
if (answer.length > 0) {
|
|
82
|
+
answer = answer.slice(0, -1);
|
|
83
|
+
stdout.write('\b \b');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (c === '\u0003') {
|
|
87
|
+
// Ctrl-C
|
|
88
|
+
rl.close();
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
answer += c;
|
|
93
|
+
stdout.write('*');
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
process.stdin.on('data', onData);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
14
99
|
class AuthCommand {
|
|
15
100
|
config;
|
|
16
101
|
logger;
|
|
@@ -29,60 +114,66 @@ class AuthCommand {
|
|
|
29
114
|
console.log();
|
|
30
115
|
console.log(chalk_1.default.cyan(`${logger_js_1.CH.hDouble.repeat(3)} Vigthoria Login ${logger_js_1.CH.hDouble.repeat(3)}`));
|
|
31
116
|
console.log();
|
|
32
|
-
//
|
|
33
|
-
// inquirer 9.x
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
type: 'input',
|
|
50
|
-
name: 'email',
|
|
51
|
-
message: 'Email:',
|
|
52
|
-
when: (ans) => ans.method === 'credentials',
|
|
53
|
-
validate: (input) => {
|
|
117
|
+
// Use Node's built-in readline instead of inquirer.
|
|
118
|
+
// inquirer 9.x destroys and recreates readline interfaces per prompt
|
|
119
|
+
// which causes ERR_USE_AFTER_CLOSE on Node 20 Windows TTY.
|
|
120
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
121
|
+
try {
|
|
122
|
+
console.log(chalk_1.default.white(' 1) Email & Password'));
|
|
123
|
+
console.log(chalk_1.default.white(' 2) API Token'));
|
|
124
|
+
console.log(chalk_1.default.white(' 3) Browser Login'));
|
|
125
|
+
console.log();
|
|
126
|
+
const choice = (await ask(rl, chalk_1.default.cyan('? ') + 'Choose login method (1/2/3): ')).trim();
|
|
127
|
+
rl.close();
|
|
128
|
+
switch (choice) {
|
|
129
|
+
case '1':
|
|
130
|
+
case 'credentials': {
|
|
131
|
+
const email = await this.askInput('Email: ');
|
|
54
132
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
133
|
+
if (!emailRegex.test(email)) {
|
|
134
|
+
this.logger.error('Please enter a valid email');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const password = await askHidden(chalk_1.default.cyan('? ') + 'Password: ');
|
|
138
|
+
if (password.length < 6) {
|
|
139
|
+
this.logger.error('Password must be at least 6 characters');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
await this.doCredentialLogin(email, password);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case '2':
|
|
146
|
+
case 'token': {
|
|
147
|
+
const token = await askHidden(chalk_1.default.cyan('? ') + 'API Token: ');
|
|
148
|
+
if (!token) {
|
|
149
|
+
this.logger.error('Please enter your API token');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
await this.loginWithToken(token);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case '3':
|
|
156
|
+
case 'browser':
|
|
157
|
+
await this.loginWithBrowser();
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
this.logger.error('Invalid choice. Please enter 1, 2, or 3.');
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
85
163
|
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
rl.close();
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
askInput(label) {
|
|
170
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
171
|
+
return new Promise((resolve) => {
|
|
172
|
+
rl.question(chalk_1.default.cyan('? ') + label, (answer) => {
|
|
173
|
+
rl.close();
|
|
174
|
+
resolve(answer.trim());
|
|
175
|
+
});
|
|
176
|
+
});
|
|
86
177
|
}
|
|
87
178
|
async doCredentialLogin(email, password) {
|
|
88
179
|
const spinner = (0, logger_js_1.createSpinner)('Logging in...').start();
|
|
@@ -117,14 +208,10 @@ class AuthCommand {
|
|
|
117
208
|
console.log();
|
|
118
209
|
}
|
|
119
210
|
async logout() {
|
|
120
|
-
const {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
message: 'Are you sure you want to logout?',
|
|
125
|
-
default: false,
|
|
126
|
-
},
|
|
127
|
-
]);
|
|
211
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
212
|
+
const answer = await ask(rl, chalk_1.default.cyan('? ') + 'Are you sure you want to logout? (y/N): ');
|
|
213
|
+
rl.close();
|
|
214
|
+
const confirm = /^y(es)?$/i.test(answer.trim());
|
|
128
215
|
if (confirm) {
|
|
129
216
|
this.config.clearAuth();
|
|
130
217
|
this.logger.success('Logged out successfully');
|
package/dist/commands/chat.js
CHANGED
|
@@ -325,20 +325,24 @@ class ChatCommand {
|
|
|
325
325
|
const toolDesc = this.describeV3AgentTool(event.tool || event.name || event.tool_name);
|
|
326
326
|
const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
|
|
327
327
|
const shortTarget = toolTarget ? ` → ${String(toolTarget).split('/').slice(-2).join('/')}` : '';
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
328
|
+
const stepLabel = chalk_1.default.cyan(` [${this.v3IterationCount}/${this.v3ToolCallCount}]`) + ` ${toolDesc}${shortTarget}`;
|
|
329
|
+
// Print each tool call as a persistent log line so the user sees progress
|
|
330
|
+
if (spinner.isSpinning)
|
|
331
|
+
spinner.stop();
|
|
332
|
+
process.stderr.write(stepLabel + '\n');
|
|
333
|
+
spinner.start();
|
|
334
|
+
spinner.text = `Running ${toolDesc}...`;
|
|
332
335
|
return;
|
|
333
336
|
}
|
|
334
337
|
if (event.type === 'tool_result') {
|
|
335
338
|
const success = event.success !== false;
|
|
336
339
|
const toolName = event.name || event.tool || '';
|
|
337
|
-
const indicator = success ? chalk_1.default.green('✓') : chalk_1.default.red('✗');
|
|
338
|
-
if (
|
|
339
|
-
spinner.
|
|
340
|
-
}
|
|
341
|
-
spinner.
|
|
340
|
+
const indicator = success ? chalk_1.default.green(' ✓') : chalk_1.default.red(' ✗');
|
|
341
|
+
if (spinner.isSpinning)
|
|
342
|
+
spinner.stop();
|
|
343
|
+
process.stderr.write(`${indicator} ${toolName}\n`);
|
|
344
|
+
spinner.start();
|
|
345
|
+
spinner.text = 'Next step...';
|
|
342
346
|
return;
|
|
343
347
|
}
|
|
344
348
|
if (event.type === 'thinking') {
|
|
@@ -346,10 +350,11 @@ class ChatCommand {
|
|
|
346
350
|
const iterText = event.content || '';
|
|
347
351
|
const iterMatch = iterText.match(/Iteration (\d+)/i);
|
|
348
352
|
const iterNum = iterMatch ? iterMatch[1] : String(this.v3IterationCount);
|
|
349
|
-
if (
|
|
350
|
-
spinner.
|
|
351
|
-
}
|
|
352
|
-
spinner.
|
|
353
|
+
if (spinner.isSpinning)
|
|
354
|
+
spinner.stop();
|
|
355
|
+
process.stderr.write(chalk_1.default.cyan(`\n── Iteration ${iterNum} ──\n`));
|
|
356
|
+
spinner.start();
|
|
357
|
+
spinner.text = 'Analyzing...';
|
|
353
358
|
return;
|
|
354
359
|
}
|
|
355
360
|
if (event.type === 'message' || event.type === 'assistant' || event.type === 'text' || event.type === 'content_block_delta') {
|
|
@@ -378,31 +383,50 @@ class ChatCommand {
|
|
|
378
383
|
const elapsed = event.elapsed || '';
|
|
379
384
|
const iters = event.iterations || this.v3IterationCount;
|
|
380
385
|
const tools = event.tool_calls || this.v3ToolCallCount;
|
|
381
|
-
spinner.
|
|
386
|
+
if (spinner.isSpinning)
|
|
387
|
+
spinner.stop();
|
|
388
|
+
process.stderr.write(chalk_1.default.green(`\n✓ Complete`) + ` — ${iters} iterations, ${tools} tool calls${elapsed ? `, ${elapsed}` : ''}\n`);
|
|
382
389
|
return;
|
|
383
390
|
}
|
|
384
391
|
if (event.type === 'plan') {
|
|
385
392
|
const planKind = event.plan?.task_kind || event.task_kind || '';
|
|
386
|
-
spinner.
|
|
393
|
+
if (spinner.isSpinning)
|
|
394
|
+
spinner.stop();
|
|
395
|
+
process.stderr.write(chalk_1.default.cyan(` [Plan] `) + `Task: ${planKind || 'analyzing'}...\n`);
|
|
396
|
+
spinner.start();
|
|
397
|
+
spinner.text = 'Planning...';
|
|
387
398
|
return;
|
|
388
399
|
}
|
|
389
400
|
if (event.type === 'error') {
|
|
390
401
|
if (event.checkpointed) {
|
|
391
|
-
spinner.
|
|
402
|
+
if (spinner.isSpinning)
|
|
403
|
+
spinner.stop();
|
|
404
|
+
process.stderr.write(chalk_1.default.yellow(' [Checkpoint] ') + 'Budget reached — auto-continuing...\n');
|
|
405
|
+
spinner.start();
|
|
392
406
|
}
|
|
393
407
|
else {
|
|
394
|
-
|
|
408
|
+
if (spinner.isSpinning)
|
|
409
|
+
spinner.stop();
|
|
410
|
+
process.stderr.write(chalk_1.default.red(' [Error] ') + (event.message || 'Agent error') + '\n');
|
|
395
411
|
}
|
|
396
412
|
return;
|
|
397
413
|
}
|
|
398
414
|
if (event.type === 'context') {
|
|
399
|
-
spinner.
|
|
415
|
+
if (spinner.isSpinning)
|
|
416
|
+
spinner.stop();
|
|
417
|
+
process.stderr.write(chalk_1.default.cyan(' [Context] ') + 'Workspace bound\n');
|
|
418
|
+
spinner.start();
|
|
419
|
+
spinner.text = 'Starting agent...';
|
|
400
420
|
return;
|
|
401
421
|
}
|
|
402
422
|
if (event.type === 'start') {
|
|
403
423
|
this.v3IterationCount = 0;
|
|
404
424
|
this.v3ToolCallCount = 0;
|
|
405
|
-
spinner.
|
|
425
|
+
if (spinner.isSpinning)
|
|
426
|
+
spinner.stop();
|
|
427
|
+
process.stderr.write(chalk_1.default.cyan(' [Start] ') + 'Agent initialized\n');
|
|
428
|
+
spinner.start();
|
|
429
|
+
spinner.text = 'Working...';
|
|
406
430
|
}
|
|
407
431
|
}
|
|
408
432
|
updateOperatorSpinner(spinner, event) {
|
|
@@ -410,24 +434,44 @@ class ChatCommand {
|
|
|
410
434
|
return;
|
|
411
435
|
}
|
|
412
436
|
if (event.type === 'started') {
|
|
413
|
-
spinner.
|
|
437
|
+
if (spinner.isSpinning)
|
|
438
|
+
spinner.stop();
|
|
439
|
+
process.stderr.write(chalk_1.default.cyan(' [Operator] ') + 'Starting BMAD workflow...\n');
|
|
440
|
+
spinner.start();
|
|
441
|
+
spinner.text = 'Connecting...';
|
|
414
442
|
return;
|
|
415
443
|
}
|
|
416
444
|
if (event.type === 'connected') {
|
|
417
|
-
spinner.
|
|
445
|
+
if (spinner.isSpinning)
|
|
446
|
+
spinner.stop();
|
|
447
|
+
process.stderr.write(chalk_1.default.green(' ✓') + ' Connected to BMAD stream\n');
|
|
448
|
+
spinner.start();
|
|
449
|
+
spinner.text = 'Working...';
|
|
418
450
|
return;
|
|
419
451
|
}
|
|
420
452
|
if (event.type === 'agent') {
|
|
421
|
-
|
|
453
|
+
const agentName = event.agent || 'BMAD agent';
|
|
454
|
+
if (spinner.isSpinning)
|
|
455
|
+
spinner.stop();
|
|
456
|
+
process.stderr.write(chalk_1.default.cyan(` [Agent] `) + agentName + '\n');
|
|
457
|
+
spinner.start();
|
|
458
|
+
spinner.text = `Running ${agentName}...`;
|
|
422
459
|
return;
|
|
423
460
|
}
|
|
424
461
|
if (event.type === 'status') {
|
|
425
462
|
const progress = Number.isFinite(Number(event.progress)) ? ` (${event.progress}%)` : '';
|
|
426
|
-
|
|
463
|
+
const statusText = `${event.status || 'Working'}${progress}`;
|
|
464
|
+
if (spinner.isSpinning)
|
|
465
|
+
spinner.stop();
|
|
466
|
+
process.stderr.write(chalk_1.default.cyan(' [Status] ') + statusText + '\n');
|
|
467
|
+
spinner.start();
|
|
468
|
+
spinner.text = statusText;
|
|
427
469
|
return;
|
|
428
470
|
}
|
|
429
471
|
if (event.type === 'result') {
|
|
430
|
-
spinner.
|
|
472
|
+
if (spinner.isSpinning)
|
|
473
|
+
spinner.stop();
|
|
474
|
+
process.stderr.write(chalk_1.default.green('\n ✓ Operator workflow complete\n'));
|
|
431
475
|
return;
|
|
432
476
|
}
|
|
433
477
|
}
|
package/dist/utils/logger.js
CHANGED
|
@@ -11,41 +11,46 @@ exports.createSpinner = createSpinner;
|
|
|
11
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
12
12
|
const ora_1 = __importDefault(require("ora"));
|
|
13
13
|
/**
|
|
14
|
-
* Platform-aware character map.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Platform-aware character map.
|
|
15
|
+
*
|
|
16
|
+
* Modern Windows terminals (Windows Terminal, PowerShell 7, VS Code,
|
|
17
|
+
* cmd.exe with chcp 65001) render Unicode box-drawing correctly.
|
|
18
|
+
* Only truly legacy code-page 437/850 consoles cannot, and those are
|
|
19
|
+
* vanishingly rare on Node 20+. We now default to full Unicode
|
|
20
|
+
* everywhere and only strip emoji (multi-codepoint) on Windows where
|
|
21
|
+
* monospace rendering can break.
|
|
17
22
|
*/
|
|
18
23
|
const isWin = process.platform === 'win32';
|
|
19
24
|
exports.CH = {
|
|
20
|
-
info:
|
|
21
|
-
warn:
|
|
22
|
-
error:
|
|
23
|
-
success:
|
|
25
|
+
info: 'ℹ',
|
|
26
|
+
warn: '⚠',
|
|
27
|
+
error: '✗',
|
|
28
|
+
success: '✓',
|
|
24
29
|
ai: isWin ? '>' : '🤖',
|
|
25
30
|
user: isWin ? '$' : '👤',
|
|
26
|
-
hLine:
|
|
27
|
-
hDouble:
|
|
28
|
-
vLine:
|
|
29
|
-
tl:
|
|
30
|
-
tr:
|
|
31
|
-
bl:
|
|
32
|
-
br:
|
|
33
|
-
teeR:
|
|
34
|
-
teeL:
|
|
35
|
-
cross:
|
|
36
|
-
bullet:
|
|
31
|
+
hLine: '─',
|
|
32
|
+
hDouble: '═',
|
|
33
|
+
vLine: '│',
|
|
34
|
+
tl: '┌',
|
|
35
|
+
tr: '┐',
|
|
36
|
+
bl: '└',
|
|
37
|
+
br: '┘',
|
|
38
|
+
teeR: '├',
|
|
39
|
+
teeL: '┤',
|
|
40
|
+
cross: '┼',
|
|
41
|
+
bullet: '•',
|
|
37
42
|
cloud: isWin ? '[cloud]' : '☁️ ',
|
|
38
43
|
home: isWin ? '[local]' : '🏠 ',
|
|
39
44
|
rocket: isWin ? '>>' : '🚀',
|
|
40
45
|
lock: isWin ? '[lock]' : '🔒',
|
|
41
46
|
warnEmoji: isWin ? '[!]' : '⚠️',
|
|
42
47
|
// Double-line box drawing (used for main banner)
|
|
43
|
-
dTl:
|
|
44
|
-
dTr:
|
|
45
|
-
dBl:
|
|
46
|
-
dBr:
|
|
47
|
-
dH:
|
|
48
|
-
dV:
|
|
48
|
+
dTl: '╔',
|
|
49
|
+
dTr: '╗',
|
|
50
|
+
dBl: '╚',
|
|
51
|
+
dBr: '╝',
|
|
52
|
+
dH: '═',
|
|
53
|
+
dV: '║',
|
|
49
54
|
};
|
|
50
55
|
/**
|
|
51
56
|
* Create an ora spinner that writes to stderr so it never
|