uwonbot 1.0.8 → 1.1.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.
package/bin/uwonbot.js CHANGED
@@ -6,13 +6,14 @@ import { listAssistants, selectAssistant } from '../src/assistants.js';
6
6
  import { startChat } from '../src/chat.js';
7
7
  import { getConfig } from '../src/config.js';
8
8
  import { startAgent } from '../src/agent.js';
9
+ import { runSetupWizard } from '../src/setup.js';
9
10
 
10
11
  showBanner();
11
12
 
12
13
  program
13
14
  .name('uwonbot')
14
15
  .description('Uwonbot AI Assistant — Your AI controls your computer')
15
- .version('1.0.8');
16
+ .version('1.1.0');
16
17
 
17
18
  program
18
19
  .command('login')
@@ -79,6 +80,19 @@ program
79
80
  await startAgent(parseInt(opts.port), { noMic: !opts.mic });
80
81
  });
81
82
 
83
+ program
84
+ .command('setup')
85
+ .description('Run initial setup wizard again')
86
+ .action(async () => {
87
+ const config = getConfig();
88
+ if (!config.get('uid')) {
89
+ console.log('\n ⚠️ Please log in first: uwonbot login\n');
90
+ process.exit(1);
91
+ }
92
+ config.set('setupComplete', false);
93
+ await runSetupWizard();
94
+ });
95
+
82
96
  program
83
97
  .command('upgrade')
84
98
  .description('Upgrade uwonbot to the latest version')
@@ -127,18 +141,22 @@ if (process.argv.length <= 2) {
127
141
  const config = getConfig();
128
142
  const uid = config.get('uid');
129
143
  if (uid) {
130
- const email = config.get('email') || 'unknown';
131
- console.log(` Logged in as: ${email}`);
132
- console.log('');
133
- console.log(' Commands:');
134
- console.log(' uwonbot chat Start chatting with your AI assistant');
135
- console.log(' uwonbot assistants List your AI assistants');
136
- console.log(' uwonbot run "..." Ask AI to run a task');
137
- console.log(' uwonbot agent Start local agent (OS control)');
138
- console.log(' uwonbot upgrade Upgrade to latest version');
139
- console.log(' uwonbot reset-password Reset password via email');
140
- console.log(' uwonbot logout Log out');
141
- console.log('');
144
+ if (!config.get('setupComplete')) {
145
+ await runSetupWizard();
146
+ } else {
147
+ const email = config.get('email') || 'unknown';
148
+ console.log(` Logged in as: ${email}`);
149
+ console.log('');
150
+ console.log(' Commands:');
151
+ console.log(' uwonbot chat Start chatting with your AI assistant');
152
+ console.log(' uwonbot assistants List your AI assistants');
153
+ console.log(' uwonbot run "..." Ask AI to run a task');
154
+ console.log(' uwonbot agent Start local agent (OS control)');
155
+ console.log(' uwonbot upgrade Upgrade to latest version');
156
+ console.log(' uwonbot reset-password Reset password via email');
157
+ console.log(' uwonbot logout Log out');
158
+ console.log('');
159
+ }
142
160
  } else {
143
161
  console.log(' Get started:');
144
162
  console.log(' uwonbot login Log in to your account');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uwonbot",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "Uwonbot AI Assistant CLI — Your AI controls your computer",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/auth.js CHANGED
@@ -3,6 +3,7 @@ import inquirer from 'inquirer';
3
3
  import ora from 'ora';
4
4
  import { getConfig } from './config.js';
5
5
  import { loginWithEmail, sendPasswordReset } from './firebase-client.js';
6
+ import { runSetupWizard } from './setup.js';
6
7
 
7
8
  export async function loginCommand() {
8
9
  const config = getConfig();
@@ -44,10 +45,15 @@ export async function loginCommand() {
44
45
  config.set('idToken', user.idToken);
45
46
  config.set('refreshToken', user.refreshToken);
46
47
  spinner.succeed(chalk.green(`Logged in as ${user.email}`));
47
- console.log('');
48
- console.log(chalk.gray(' Next: uwonbot chat — Start chatting with your AI assistant'));
49
- console.log(chalk.gray(' uwonbot assistants — List your assistants'));
50
- console.log('');
48
+
49
+ if (!config.get('setupComplete')) {
50
+ await runSetupWizard();
51
+ } else {
52
+ console.log('');
53
+ console.log(chalk.gray(' Next: uwonbot chat — Start chatting with your AI assistant'));
54
+ console.log(chalk.gray(' uwonbot assistants — List your assistants'));
55
+ console.log('');
56
+ }
51
57
  } catch (err) {
52
58
  spinner.fail(chalk.red('Login failed'));
53
59
  const msg = (err.message || '').toUpperCase();
package/src/chat.js CHANGED
@@ -7,6 +7,7 @@ import open from 'open';
7
7
  import { getConfig } from './config.js';
8
8
  import { sendToBrain } from './brain.js';
9
9
  import { showMiniBar } from './banner.js';
10
+ import { printOrb, animateOrb } from './terminalOrb.js';
10
11
  import {
11
12
  hasRegisteredDevices,
12
13
  createCLISession,
@@ -16,6 +17,40 @@ import {
16
17
 
17
18
  const WEB_APP_URL = 'https://chartapp-653e1.web.app';
18
19
 
20
+ function hexToRgb(hex) {
21
+ const h = hex.replace('#', '');
22
+ return [
23
+ parseInt(h.substring(0, 2), 16) || 37,
24
+ parseInt(h.substring(2, 4), 16) || 99,
25
+ parseInt(h.substring(4, 6), 16) || 235,
26
+ ];
27
+ }
28
+
29
+ async function sleep(ms) {
30
+ return new Promise(r => setTimeout(r, ms));
31
+ }
32
+
33
+ async function bootSequence(assistant, brainLabel, brainColor) {
34
+ const c = chalk.hex(brainColor);
35
+ const g = chalk.gray;
36
+ const w = chalk.white;
37
+
38
+ const steps = [
39
+ [300, `${g(' [SYS]')} ${w('INITIATING SYSTEM I...')}`],
40
+ [300, `${g(' [SYS]')} ${c(`NETWARE ${assistant.name.toUpperCase()} v1.0`)}`],
41
+ [400, `${g(' [SYS]')} ${w('PROTOCOL: SCANNING...')}`],
42
+ [300, `${g(' [SYS]')} ${w('RELEASING CONFIGURATION')}`],
43
+ [200, `${g(' [SYS]')} ${w(`Brain: ${brainLabel} | API: connected`)}`],
44
+ [200, `${g(' [SYS]')} ${w('Authentication verified.')}`],
45
+ [500, `${g(' [SYS]')} ${chalk.green('All systems operational. Standing by.')}`],
46
+ ];
47
+
48
+ for (const [delay, text] of steps) {
49
+ await sleep(delay);
50
+ console.log(text);
51
+ }
52
+ }
53
+
19
54
  async function requireBiometricAuth(uid) {
20
55
  try {
21
56
  const hasDevices = await hasRegisteredDevices(uid);
@@ -107,12 +142,23 @@ export async function startChat(assistantName, assistant, initialCommand) {
107
142
  : assistant.brain === 'gemini' ? '#8b5cf6'
108
143
  : '#2563eb';
109
144
 
145
+ const orbColorHex = assistant.orbColor || brainColor;
146
+ const orbRgb = hexToRgb(orbColorHex);
147
+
148
+ console.clear();
149
+ printOrb({
150
+ radius: 10,
151
+ color: orbRgb,
152
+ label: assistant.name.toUpperCase(),
153
+ status: '준비됨',
154
+ });
155
+
156
+ await bootSequence(assistant, brainLabel, brainColor);
157
+
110
158
  console.log('');
111
- console.log(chalk.hex('#2563eb')(' ╔══════════════════════════════════════════╗'));
112
- console.log(chalk.hex('#2563eb')(' ║') + chalk.white.bold(` ${assistant.avatar || '🤖'} ${assistant.name}`) + ' '.repeat(Math.max(1, 38 - assistant.name.length)) + chalk.hex('#2563eb')('║'));
113
- console.log(chalk.hex('#2563eb')(' ') + chalk.gray(` Brain: `) + chalk.hex(brainColor)(brainLabel) + ' '.repeat(Math.max(1, 30 - brainLabel.length)) + chalk.hex('#2563eb')('║'));
114
- console.log(chalk.hex('#2563eb')(' ║') + chalk.gray(' Type "exit" to quit, "clear" to reset ') + chalk.hex('#2563eb')('║'));
115
- console.log(chalk.hex('#2563eb')(' ╚══════════════════════════════════════════╝'));
159
+ console.log(chalk.gray(' ─────────────────────────────────────────'));
160
+ console.log(chalk.gray(' "exit" 종료 | "clear" 대화 초기화'));
161
+ console.log(chalk.gray(' ─────────────────────────────────────────'));
116
162
  console.log('');
117
163
 
118
164
  const messages = [];
package/src/config.js CHANGED
@@ -14,6 +14,9 @@ export function getConfig() {
14
14
  refreshToken: { type: 'string', default: '' },
15
15
  activeAssistantId: { type: 'string', default: '' },
16
16
  activeAssistantName: { type: 'string', default: '' },
17
+ setupComplete: { type: 'boolean', default: false },
18
+ agentAutoStart: { type: 'boolean', default: false },
19
+ clapDetection: { type: 'boolean', default: true },
17
20
  },
18
21
  });
19
22
  }
package/src/setup.js ADDED
@@ -0,0 +1,128 @@
1
+ import chalk from 'chalk';
2
+ import inquirer from 'inquirer';
3
+ import { execSync } from 'child_process';
4
+ import { getConfig } from './config.js';
5
+
6
+ function checkSoxInstalled() {
7
+ try {
8
+ execSync('which sox', { stdio: 'ignore' });
9
+ return true;
10
+ } catch {
11
+ try {
12
+ execSync('which rec', { stdio: 'ignore' });
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+ }
19
+
20
+ export async function runSetupWizard() {
21
+ const config = getConfig();
22
+
23
+ console.log('');
24
+ console.log(chalk.bold.cyan(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
25
+ console.log(chalk.bold.cyan(' 🚀 Uwonbot 초기 설정'));
26
+ console.log(chalk.bold.cyan(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
27
+ console.log('');
28
+ console.log(chalk.white(' 환영합니다! Uwonbot을 최대한 활용하기 위한'));
29
+ console.log(chalk.white(' 초기 설정을 진행합니다.'));
30
+ console.log('');
31
+
32
+ console.log(chalk.bold.white(' 1️⃣ 에이전트 (Agent)'));
33
+ console.log(chalk.gray(' AI 비서가 컴퓨터를 제어하고, 박수로 비서를'));
34
+ console.log(chalk.gray(' 활성화하려면 에이전트가 필요합니다.'));
35
+ console.log('');
36
+
37
+ const { enableAgent } = await inquirer.prompt([{
38
+ type: 'confirm',
39
+ name: 'enableAgent',
40
+ message: '에이전트를 자동 시작하시겠습니까?',
41
+ default: true,
42
+ }]);
43
+
44
+ config.set('agentAutoStart', enableAgent);
45
+
46
+ if (enableAgent) {
47
+ console.log('');
48
+ console.log(chalk.bold.white(' 2️⃣ 박수 감지 (Clap Detection)'));
49
+ console.log(chalk.gray(' 박수 2번으로 비서를 활성화할 수 있습니다.'));
50
+ console.log(chalk.gray(' 마이크와 SoX가 필요합니다.'));
51
+ console.log('');
52
+
53
+ const hasSox = checkSoxInstalled();
54
+ if (!hasSox) {
55
+ console.log(chalk.yellow(' ⚠ SoX가 설치되어 있지 않습니다.'));
56
+ console.log(chalk.gray(' 박수 감지를 위해 SoX를 설치해주세요:'));
57
+ console.log('');
58
+ console.log(chalk.white(' macOS: ') + chalk.cyan('brew install sox'));
59
+ console.log(chalk.white(' Ubuntu: ') + chalk.cyan('sudo apt install sox'));
60
+ console.log('');
61
+
62
+ const { installSox } = await inquirer.prompt([{
63
+ type: 'confirm',
64
+ name: 'installSox',
65
+ message: 'SoX를 지금 설치하시겠습니까? (macOS - Homebrew)',
66
+ default: true,
67
+ }]);
68
+
69
+ if (installSox) {
70
+ try {
71
+ console.log(chalk.cyan(' SoX 설치 중...'));
72
+ execSync('brew install sox', { stdio: 'inherit' });
73
+ console.log(chalk.green(' ✓ SoX 설치 완료'));
74
+ } catch {
75
+ console.log(chalk.yellow(' ⚠ SoX 설치에 실패했습니다. 나중에 수동으로 설치해주세요.'));
76
+ }
77
+ }
78
+ } else {
79
+ console.log(chalk.green(' ✓ SoX가 이미 설치되어 있습니다.'));
80
+ }
81
+
82
+ const { enableClap } = await inquirer.prompt([{
83
+ type: 'confirm',
84
+ name: 'enableClap',
85
+ message: '박수 감지를 활성화하시겠습니까?',
86
+ default: true,
87
+ }]);
88
+
89
+ config.set('clapDetection', enableClap);
90
+ }
91
+
92
+ console.log('');
93
+ console.log(chalk.bold.white(' 3️⃣ 기기 생체인증'));
94
+ console.log(chalk.gray(' 보안을 위해 비서 사용 시 지문/Face ID 인증을'));
95
+ console.log(chalk.gray(' 요구할 수 있습니다.'));
96
+ console.log(chalk.gray(' 웹사이트 마이페이지에서 기기를 등록해주세요:'));
97
+ console.log(chalk.cyan(' https://chartapp-653e1.web.app/profile'));
98
+ console.log('');
99
+
100
+ config.set('setupComplete', true);
101
+
102
+ console.log(chalk.bold.cyan(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
103
+ console.log(chalk.bold.green(' ✓ 설정 완료!'));
104
+ console.log(chalk.bold.cyan(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
105
+ console.log('');
106
+ console.log(chalk.white(' 다음 단계:'));
107
+
108
+ if (enableAgent) {
109
+ console.log(chalk.cyan(' uwonbot agent') + chalk.gray(' — 에이전트 시작 (박수 감지 + OS 제어)'));
110
+ }
111
+ console.log(chalk.cyan(' uwonbot chat') + chalk.gray(' — AI 비서와 대화'));
112
+ console.log(chalk.cyan(' uwonbot assistants') + chalk.gray(' — 비서 목록 확인'));
113
+ console.log('');
114
+
115
+ if (enableAgent) {
116
+ const { startNow } = await inquirer.prompt([{
117
+ type: 'confirm',
118
+ name: 'startNow',
119
+ message: '에이전트를 지금 바로 시작하시겠습니까?',
120
+ default: true,
121
+ }]);
122
+
123
+ if (startNow) {
124
+ const { startAgent } = await import('./agent.js');
125
+ await startAgent(9876, { noMic: !config.get('clapDetection') });
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,232 @@
1
+ import chalk from 'chalk';
2
+
3
+ const SHADING_CHARS = ' .:-=+*#%@';
4
+
5
+ /**
6
+ * Renders a 3D sphere in the terminal using ray-traced shading with true color.
7
+ * @param {object} opts
8
+ * @param {number} [opts.radius=10] - Sphere radius in character cells
9
+ * @param {number[]} [opts.color=[37,99,235]] - RGB base color
10
+ * @param {string} [opts.label] - Text label below the orb
11
+ * @param {string} [opts.status] - Status text below label
12
+ * @param {boolean} [opts.glow=true] - Enable glow effect
13
+ * @returns {string} The rendered orb as a string
14
+ */
15
+ export function renderOrb(opts = {}) {
16
+ const radius = opts.radius || 10;
17
+ const [baseR, baseG, baseB] = opts.color || [37, 99, 235];
18
+ const label = opts.label || '';
19
+ const status = opts.status || '';
20
+ const glow = opts.glow !== false;
21
+
22
+ const width = radius * 2 + 4;
23
+ const height = radius + 2;
24
+ const lines = [];
25
+
26
+ const lightX = -0.4;
27
+ const lightY = -0.5;
28
+ const lightZ = 0.8;
29
+ const lightLen = Math.sqrt(lightX * lightX + lightY * lightY + lightZ * lightZ);
30
+ const lx = lightX / lightLen;
31
+ const ly = lightY / lightLen;
32
+ const lz = lightZ / lightLen;
33
+
34
+ for (let y = -radius; y <= radius; y++) {
35
+ let line = '';
36
+ const rowY = y / radius;
37
+
38
+ for (let x = -width / 2; x <= width / 2; x++) {
39
+ const rowX = x / (width / 2);
40
+ const distSq = rowX * rowX + rowY * rowY;
41
+
42
+ if (distSq > 1.0) {
43
+ if (glow && distSq < 1.5) {
44
+ const glowIntensity = 1.0 - (distSq - 1.0) / 0.5;
45
+ const gr = Math.round(baseR * glowIntensity * 0.3);
46
+ const gg = Math.round(baseG * glowIntensity * 0.3);
47
+ const gb = Math.round(baseB * glowIntensity * 0.3);
48
+ line += chalk.rgb(gr, gg, gb)('·');
49
+ } else {
50
+ line += ' ';
51
+ }
52
+ continue;
53
+ }
54
+
55
+ const nz = Math.sqrt(1 - distSq);
56
+ const nx = rowX;
57
+ const ny = rowY;
58
+
59
+ const diffuse = Math.max(0, nx * lx + ny * ly + nz * lz);
60
+
61
+ const rx = 2 * (nx * lx + ny * ly + nz * lz) * nx - lx;
62
+ const ry = 2 * (nx * lx + ny * ly + nz * lz) * ny - ly;
63
+ const rz = 2 * (nx * lx + ny * ly + nz * lz) * nz - lz;
64
+ const viewZ = 1.0;
65
+ const specular = Math.pow(Math.max(0, rz / Math.sqrt(rx * rx + ry * ry + rz * rz) * viewZ), 32);
66
+
67
+ const ambient = 0.12;
68
+ const intensity = Math.min(1.0, ambient + diffuse * 0.75 + specular * 0.5);
69
+
70
+ const r = Math.min(255, Math.round(baseR * intensity + specular * 180));
71
+ const g = Math.min(255, Math.round(baseG * intensity + specular * 180));
72
+ const b = Math.min(255, Math.round(baseB * intensity + specular * 180));
73
+
74
+ const charIdx = Math.min(SHADING_CHARS.length - 1, Math.round(intensity * (SHADING_CHARS.length - 1)));
75
+ const ch = SHADING_CHARS[charIdx];
76
+
77
+ line += chalk.rgb(r, g, b)(ch || ' ');
78
+ }
79
+ lines.push(line);
80
+ }
81
+
82
+ if (label) {
83
+ lines.push('');
84
+ const padded = label.length < width * 2
85
+ ? ' '.repeat(Math.max(0, Math.floor((width - label.length / 2)))) + label
86
+ : label;
87
+ lines.push(chalk.bold.white(padded));
88
+ }
89
+
90
+ if (status) {
91
+ const padded = status.length < width * 2
92
+ ? ' '.repeat(Math.max(0, Math.floor((width - status.length / 2)))) + status
93
+ : status;
94
+ lines.push(chalk.gray(padded));
95
+ }
96
+
97
+ return lines.join('\n');
98
+ }
99
+
100
+ /**
101
+ * Animated spinning orb in the terminal.
102
+ * @param {object} opts
103
+ * @param {number[]} [opts.color=[37,99,235]]
104
+ * @param {string} [opts.label]
105
+ * @param {string} [opts.status]
106
+ * @param {number} [opts.radius=10]
107
+ * @param {number} [opts.fps=12]
108
+ * @returns {{ update(opts), stop() }}
109
+ */
110
+ export function animateOrb(opts = {}) {
111
+ const radius = opts.radius || 10;
112
+ const fps = opts.fps || 12;
113
+ let color = opts.color || [37, 99, 235];
114
+ let label = opts.label || '';
115
+ let statusText = opts.status || '';
116
+ let frame = 0;
117
+ let running = true;
118
+ const totalLines = (radius * 2 + 3) + (label ? 1 : 0) + (statusText ? 1 : 0) + 1;
119
+
120
+ function draw() {
121
+ const angle = (frame * 0.05);
122
+ const lightX = Math.cos(angle) * 0.6;
123
+ const lightY = -0.5;
124
+ const lightZ = Math.sin(angle) * 0.4 + 0.6;
125
+ const lightLen = Math.sqrt(lightX * lightX + lightY * lightY + lightZ * lightZ);
126
+
127
+ const width = radius * 2 + 4;
128
+ const lines = [];
129
+
130
+ for (let y = -radius; y <= radius; y++) {
131
+ let line = ' ';
132
+ const rowY = y / radius;
133
+
134
+ for (let x = -width / 2; x <= width / 2; x++) {
135
+ const rowX = x / (width / 2);
136
+ const distSq = rowX * rowX + rowY * rowY;
137
+
138
+ if (distSq > 1.0) {
139
+ if (distSq < 1.4) {
140
+ const glowIntensity = 1.0 - (distSq - 1.0) / 0.4;
141
+ const pulse = 0.3 + Math.sin(frame * 0.15) * 0.1;
142
+ const gr = Math.round(color[0] * glowIntensity * pulse);
143
+ const gg = Math.round(color[1] * glowIntensity * pulse);
144
+ const gb = Math.round(color[2] * glowIntensity * pulse);
145
+ line += chalk.rgb(gr, gg, gb)('·');
146
+ } else {
147
+ line += ' ';
148
+ }
149
+ continue;
150
+ }
151
+
152
+ const nz = Math.sqrt(1 - distSq);
153
+ const nx = rowX;
154
+ const ny = rowY;
155
+
156
+ const lx = lightX / lightLen;
157
+ const ly = lightY / lightLen;
158
+ const lz = lightZ / lightLen;
159
+
160
+ const diffuse = Math.max(0, nx * lx + ny * ly + nz * lz);
161
+ const dot = nx * lx + ny * ly + nz * lz;
162
+ const rz = 2 * dot * nz - lz;
163
+ const specular = Math.pow(Math.max(0, rz), 40);
164
+
165
+ const ambient = 0.08;
166
+ const intensity = Math.min(1.0, ambient + diffuse * 0.7 + specular * 0.6);
167
+
168
+ const r = Math.min(255, Math.round(color[0] * intensity + specular * 200));
169
+ const g = Math.min(255, Math.round(color[1] * intensity + specular * 200));
170
+ const b = Math.min(255, Math.round(color[2] * intensity + specular * 200));
171
+
172
+ const charIdx = Math.min(SHADING_CHARS.length - 1, Math.round(intensity * (SHADING_CHARS.length - 1)));
173
+ line += chalk.rgb(r, g, b)(SHADING_CHARS[charIdx] || ' ');
174
+ }
175
+ lines.push(line);
176
+ }
177
+
178
+ if (label) {
179
+ lines.push('');
180
+ const pad = Math.max(0, Math.floor((width + 2 - label.length) / 2));
181
+ lines.push(' '.repeat(pad) + chalk.bold.white(label));
182
+ }
183
+ if (statusText) {
184
+ const pad = Math.max(0, Math.floor((width + 2 - statusText.length) / 2));
185
+ lines.push(' '.repeat(pad) + chalk.gray(statusText));
186
+ }
187
+
188
+ return lines.join('\n');
189
+ }
190
+
191
+ let prevLineCount = 0;
192
+
193
+ const interval = setInterval(() => {
194
+ if (!running) return;
195
+ frame++;
196
+
197
+ if (prevLineCount > 0) {
198
+ process.stdout.write(`\x1b[${prevLineCount}A\x1b[J`);
199
+ }
200
+
201
+ const output = draw();
202
+ process.stdout.write(output + '\n');
203
+ prevLineCount = output.split('\n').length;
204
+ }, 1000 / fps);
205
+
206
+ const output = draw();
207
+ process.stdout.write(output + '\n');
208
+ prevLineCount = output.split('\n').length;
209
+
210
+ return {
211
+ update(newOpts) {
212
+ if (newOpts.color) color = newOpts.color;
213
+ if (newOpts.label !== undefined) label = newOpts.label;
214
+ if (newOpts.status !== undefined) statusText = newOpts.status;
215
+ },
216
+ stop() {
217
+ running = false;
218
+ clearInterval(interval);
219
+ },
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Prints a static orb once.
225
+ */
226
+ export function printOrb(opts = {}) {
227
+ console.log('');
228
+ console.log(renderOrb(opts));
229
+ console.log('');
230
+ }
231
+
232
+ export default { renderOrb, animateOrb, printOrb };