@xaidenlabs/uso 1.1.67 → 1.1.68

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.
@@ -1,240 +1,415 @@
1
- const shell = require('shelljs');
2
- const { log, spinner } = require('../utils/logger');
3
- const readline = require('readline');
4
- const fs = require('fs');
5
- const path = require('path');
6
- const os = require('os');
1
+ const shell = require("shelljs");
2
+ const { log, spinner } = require("../utils/logger");
3
+ const readline = require("readline");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const os = require("os");
7
+ const { isStealthMode } = require("../utils/stealth");
8
+ const { runWsl } = require("../utils/wsl-bridge");
7
9
 
8
10
  const askQuestion = (query) => {
9
- const rl = readline.createInterface({
10
- input: process.stdin,
11
- output: process.stdout,
12
- });
13
- return new Promise(resolve => rl.question(query, ans => {
14
- rl.close();
15
- resolve(ans);
16
- }));
11
+ const rl = readline.createInterface({
12
+ input: process.stdin,
13
+ output: process.stdout,
14
+ });
15
+ return new Promise((resolve) =>
16
+ rl.question(query, (ans) => {
17
+ rl.close();
18
+ resolve(ans);
19
+ }),
20
+ );
21
+ };
22
+
23
+ const getStealthContext = () => {
24
+ if (os.platform() !== "win32") return { enabled: false, distro: "Ubuntu" };
25
+ return isStealthMode();
26
+ };
27
+
28
+ const runInStealth = (command, stealth, silent = true) => {
29
+ const envSetup =
30
+ 'source $HOME/.cargo/env 2>/dev/null; export PATH="$HOME/.local/share/solana/install/active_release/bin:$HOME/.avm/bin:$PATH"';
31
+ return runWsl(`${envSetup} && ${command}`, {
32
+ distro: stealth.distro,
33
+ execOpts: { silent },
34
+ });
35
+ };
36
+
37
+ const commandExists = (command, stealth) => {
38
+ if (stealth.enabled) {
39
+ const result = runInStealth(`command -v ${command}`, stealth, true);
40
+ return result.code === 0;
41
+ }
42
+ return !!shell.which(command);
43
+ };
44
+
45
+ const wslPathExists = (wslPath, stealth) => {
46
+ const result = runInStealth(`[ -e "${wslPath}" ]`, stealth, true);
47
+ return result.code === 0;
17
48
  };
18
49
 
19
50
  /**
20
51
  * Runs a command and attempts to elevate privileges if it fails with a permission error.
21
52
  */
22
- const runOrElevate = (command, description) => {
23
- // We run without silent:true initially to let the user see output,
24
- // but detecting the error code is what matters.
25
- // actually, to detect the specific string "os error 1314", we need to capture output.
26
- // So we run silently first? Or we just run and if it fails, we assume it *might* be elevation if on Windows?
27
- // Let's run synchronously and capture output.
28
-
29
- const result = shell.exec(command, { silent: true });
53
+ const runOrElevate = (
54
+ command,
55
+ description,
56
+ stealth = { enabled: false, distro: "Ubuntu" },
57
+ ) => {
58
+ if (stealth.enabled) {
59
+ const result = runInStealth(command, stealth, true);
30
60
 
31
61
  if (result.code === 0) {
32
- console.log(result.stdout);
33
- return true;
62
+ if (result.stdout) console.log(result.stdout);
63
+ return true;
34
64
  }
35
65
 
36
- // Print the error output to the user
37
- console.log(result.stdout);
38
- console.error(result.stderr);
39
-
40
- const output = result.stderr + result.stdout;
41
-
42
- // Check for common permission errors
43
- // "os error 1314" is specific to Windows symlink privilege
44
- if ((output.includes("os error 1314") || output.includes("EPERM") || output.includes("permission denied")) && os.platform() === 'win32') {
45
- log.warn(`⚠️ Permission denied during: ${description}`);
46
- log.info("🛡️ Triggering Run as Administrator (UAC) to retry...");
66
+ if (result.stdout) console.log(result.stdout);
67
+ if (result.stderr) console.error(result.stderr);
68
+ log.error(`❌ Command failed in WSL: ${description}`);
69
+ return false;
70
+ }
47
71
 
48
- // Construct PowerShell command to run cmd /c <command> as admin
49
- // We need to be careful with quoting.
50
- const escapedCommand = command.replace(/'/g, "''"); // Basic PowerShell escaping for single quotes
51
- const elevateCmd = `powershell -Command "Start-Process -FilePath 'cmd.exe' -ArgumentList '/c ${escapedCommand}' -Verb RunAs -Wait"`;
72
+ // We run without silent:true initially to let the user see output,
73
+ // but detecting the error code is what matters.
74
+ // actually, to detect the specific string "os error 1314", we need to capture output.
75
+ // So we run silently first? Or we just run and if it fails, we assume it *might* be elevation if on Windows?
76
+ // Let's run synchronously and capture output.
52
77
 
53
- const elevatedRun = shell.exec(elevateCmd);
78
+ const result = shell.exec(command, { silent: true });
54
79
 
55
- if (elevatedRun.code === 0) {
56
- log.success(`✅ ${description} completed (Elevated).`);
57
- return true;
58
- } else {
59
- log.error(`❌ Elevated execution failed for: ${description}`);
60
- return false;
61
- }
80
+ if (result.code === 0) {
81
+ console.log(result.stdout);
82
+ return true;
83
+ }
84
+
85
+ // Print the error output to the user
86
+ console.log(result.stdout);
87
+ console.error(result.stderr);
88
+
89
+ const output = result.stderr + result.stdout;
90
+
91
+ // Check for common permission errors
92
+ // "os error 1314" is specific to Windows symlink privilege
93
+ if (
94
+ (output.includes("os error 1314") ||
95
+ output.includes("EPERM") ||
96
+ output.includes("permission denied")) &&
97
+ os.platform() === "win32"
98
+ ) {
99
+ log.warn(`⚠️ Permission denied during: ${description}`);
100
+ log.info("🛡️ Triggering Run as Administrator (UAC) to retry...");
101
+
102
+ // Construct PowerShell command to run cmd /c <command> as admin
103
+ // We need to be careful with quoting.
104
+ const escapedCommand = command.replace(/'/g, "''"); // Basic PowerShell escaping for single quotes
105
+ const elevateCmd = `powershell -Command "Start-Process -FilePath 'cmd.exe' -ArgumentList '/c ${escapedCommand}' -Verb RunAs -Wait"`;
106
+
107
+ const elevatedRun = shell.exec(elevateCmd);
108
+
109
+ if (elevatedRun.code === 0) {
110
+ log.success(`✅ ${description} completed (Elevated).`);
111
+ return true;
112
+ } else {
113
+ log.error(`❌ Elevated execution failed for: ${description}`);
114
+ return false;
62
115
  }
116
+ }
63
117
 
64
- log.error(`❌ Command failed: ${description}`);
65
- return false;
118
+ log.error(`❌ Command failed: ${description}`);
119
+ return false;
66
120
  };
67
121
 
68
122
  const uninstall = async (component) => {
69
- log.header("🗑️ USO Uninstallation & Cleanup");
70
-
71
- if (component) {
72
- component = component.toLowerCase();
73
- log.info(`🎯 Targeted uninstallation: ${component}`);
74
-
75
- if (component === 'anchor') {
76
- const anchorInstalled = shell.which('anchor');
77
- if (anchorInstalled) {
78
- log.info("Removing Anchor...");
79
- // Try avm uninstall first if available
80
- if (shell.which('avm')) {
81
- runOrElevate('avm uninstall latest', 'Uninstall Anchor (AVM)');
82
- }
83
- runOrElevate('cargo uninstall anchor-cli', 'Uninstall anchor-cli');
84
- runOrElevate('cargo uninstall avm', 'Uninstall avm');
85
- log.success("Anchor removal steps completed.");
86
- } else {
87
- log.success("✅ Anchor is not installed.");
88
- }
89
- return;
123
+ const stealth = getStealthContext();
124
+ log.header("🗑️ USO Uninstallation & Cleanup");
125
+ if (stealth.enabled) {
126
+ log.info(
127
+ `🐧 Stealth Mode detected. Uninstall targets WSL distro: ${stealth.distro}`,
128
+ );
129
+ }
130
+
131
+ if (component) {
132
+ component = component.toLowerCase();
133
+ log.info(`🎯 Targeted uninstallation: ${component}`);
134
+
135
+ if (component === "anchor") {
136
+ const anchorInstalled = commandExists("anchor", stealth);
137
+ if (anchorInstalled) {
138
+ log.info("Removing Anchor...");
139
+ // Try avm uninstall first if available
140
+ if (commandExists("avm", stealth)) {
141
+ runOrElevate(
142
+ "avm uninstall latest",
143
+ "Uninstall Anchor (AVM)",
144
+ stealth,
145
+ );
90
146
  }
147
+ runOrElevate(
148
+ "cargo uninstall anchor-cli",
149
+ "Uninstall anchor-cli",
150
+ stealth,
151
+ );
152
+ runOrElevate("cargo uninstall avm", "Uninstall avm", stealth);
153
+ log.success("Anchor removal steps completed.");
154
+ } else {
155
+ log.success("✅ Anchor is not installed.");
156
+ }
157
+ return;
158
+ }
91
159
 
92
- if (component === 'solana') {
93
- // Check PATH first
94
- let solanaInstalled = shell.which('solana');
95
- const localShareSolana = path.join(os.homedir(), '.local', 'share', 'solana');
96
-
97
- // If not found in PATH, check default location
98
- if (!solanaInstalled && fs.existsSync(localShareSolana)) {
99
- solanaInstalled = true;
100
- }
101
-
102
- if (solanaInstalled) {
103
- log.info("Removing Solana CLI...");
104
-
105
- if (fs.existsSync(localShareSolana)) {
106
- try {
107
- fs.rmSync(localShareSolana, { recursive: true, force: true });
108
- log.success(`Removed ${localShareSolana}`);
109
- } catch (err) {
110
- log.warn(`Failed to remove ${localShareSolana} directly: ${err.message}`);
111
- log.info("Trying to remove via elevated command...");
112
- runOrElevate(`rmdir /s /q "${localShareSolana}"`, `Remove folder ${localShareSolana}`);
113
- }
114
- } else {
115
- log.warn(`Could not find Solana folder at ${localShareSolana}. It might be removed already.`);
116
- }
117
- } else {
118
- log.success("✅ Solana CLI is not installed.");
119
- }
120
- return;
160
+ if (component === "solana") {
161
+ // Check PATH first
162
+ let solanaInstalled = commandExists("solana", stealth);
163
+ const localShareSolana = path.join(
164
+ os.homedir(),
165
+ ".local",
166
+ "share",
167
+ "solana",
168
+ );
169
+ const wslSolanaPath = "$HOME/.local/share/solana";
170
+
171
+ // If not found in PATH, check default location
172
+ if (!solanaInstalled) {
173
+ if (stealth.enabled) {
174
+ solanaInstalled = wslPathExists(wslSolanaPath, stealth);
175
+ } else if (fs.existsSync(localShareSolana)) {
176
+ solanaInstalled = true;
121
177
  }
122
-
123
- if (component === 'rust') {
124
- const rustInstalled = shell.which('rustc');
125
- if (rustInstalled) {
126
- log.info("Running rustup self uninstall...");
127
- runOrElevate('rustup self uninstall -y', 'Uninstall Rust');
128
- } else {
129
- log.success("✅ Rust is not installed.");
178
+ }
179
+
180
+ if (solanaInstalled) {
181
+ log.info("Removing Solana CLI...");
182
+
183
+ if (stealth.enabled) {
184
+ runOrElevate(
185
+ `rm -rf "${wslSolanaPath}"`,
186
+ `Remove folder ${wslSolanaPath}`,
187
+ stealth,
188
+ );
189
+ log.success(`Removed ${wslSolanaPath}`);
190
+ } else {
191
+ if (fs.existsSync(localShareSolana)) {
192
+ try {
193
+ fs.rmSync(localShareSolana, { recursive: true, force: true });
194
+ log.success(`Removed ${localShareSolana}`);
195
+ } catch (err) {
196
+ log.warn(
197
+ `Failed to remove ${localShareSolana} directly: ${err.message}`,
198
+ );
199
+ log.info("Trying to remove via elevated command...");
200
+ runOrElevate(
201
+ `rmdir /s /q "${localShareSolana}"`,
202
+ `Remove folder ${localShareSolana}`,
203
+ stealth,
204
+ );
130
205
  }
131
- return;
206
+ } else {
207
+ log.warn(
208
+ `Could not find Solana folder at ${localShareSolana}. It might be removed already.`,
209
+ );
210
+ }
132
211
  }
133
-
134
- log.error(`❌ Unknown component: ${component}. Available: rust, solana, anchor`);
135
- return;
212
+ } else {
213
+ log.success("✅ Solana CLI is not installed.");
214
+ }
215
+ return;
136
216
  }
137
217
 
138
- // --- FULL INTERACTIVE UNINSTALL ---
139
-
140
- log.warn("This process allows you to remove components installed by uso.");
141
- log.warn("Please be careful, especially with wallet removal!");
142
-
143
- const proceed = await askQuestion("👉 Do you want to proceed with uninstallation? (y/N): ");
144
- if (proceed.toLowerCase() !== 'y') {
145
- log.info("Operation cancelled.");
146
- return;
218
+ if (component === "rust") {
219
+ const rustInstalled = commandExists("rustc", stealth);
220
+ if (rustInstalled) {
221
+ log.info("Running rustup self uninstall...");
222
+ runOrElevate("rustup self uninstall -y", "Uninstall Rust", stealth);
223
+ } else {
224
+ log.success("✅ Rust is not installed.");
225
+ }
226
+ return;
147
227
  }
148
228
 
149
- // 1. Uninstall Anchor
150
- const anchorInstalled = shell.which('anchor');
151
- if (anchorInstalled) {
152
- const removeAnchor = await askQuestion("\n⚓ Remove Anchor Framework? (y/N): ");
153
- if (removeAnchor.toLowerCase() === 'y') {
154
- log.info("Removing Anchor...");
155
- // Try avm uninstall first if available
156
- if (shell.which('avm')) {
157
- runOrElevate('avm uninstall latest', 'Uninstall Anchor (AVM)');
158
- }
159
- runOrElevate('cargo uninstall anchor-cli', 'Uninstall anchor-cli');
160
- runOrElevate('cargo uninstall avm', 'Uninstall avm');
161
- log.success("Anchor removal steps completed.");
229
+ log.error(
230
+ `❌ Unknown component: ${component}. Available: rust, solana, anchor`,
231
+ );
232
+ return;
233
+ }
234
+
235
+ // --- FULL INTERACTIVE UNINSTALL ---
236
+
237
+ log.warn("This process allows you to remove components installed by uso.");
238
+ log.warn("Please be careful, especially with wallet removal!");
239
+
240
+ const proceed = await askQuestion(
241
+ "👉 Do you want to proceed with uninstallation? (y/N): ",
242
+ );
243
+ if (proceed.toLowerCase() !== "y") {
244
+ log.info("Operation cancelled.");
245
+ return;
246
+ }
247
+
248
+ // 1. Uninstall Anchor
249
+ const anchorInstalled = commandExists("anchor", stealth);
250
+ if (anchorInstalled) {
251
+ const removeAnchor = await askQuestion(
252
+ "\n⚓ Remove Anchor Framework? (y/N): ",
253
+ );
254
+ if (removeAnchor.toLowerCase() === "y") {
255
+ log.info("Removing Anchor...");
256
+ // Try avm uninstall first if available
257
+ if (commandExists("avm", stealth)) {
258
+ runOrElevate("avm uninstall latest", "Uninstall Anchor (AVM)", stealth);
259
+ }
260
+ runOrElevate(
261
+ "cargo uninstall anchor-cli",
262
+ "Uninstall anchor-cli",
263
+ stealth,
264
+ );
265
+ runOrElevate("cargo uninstall avm", "Uninstall avm", stealth);
266
+ log.success("Anchor removal steps completed.");
267
+ }
268
+ }
269
+
270
+ // 2. Uninstall Solana
271
+ let solanaInstalled = commandExists("solana", stealth);
272
+ const localShareSolana = path.join(os.homedir(), ".local", "share", "solana");
273
+ const wslSolanaPath = "$HOME/.local/share/solana";
274
+
275
+ // If not found in PATH, check default location (like doctor does)
276
+ if (!solanaInstalled) {
277
+ if (stealth.enabled) {
278
+ solanaInstalled = wslPathExists(wslSolanaPath, stealth);
279
+ } else if (fs.existsSync(localShareSolana)) {
280
+ solanaInstalled = true;
281
+ }
282
+ }
283
+
284
+ if (solanaInstalled) {
285
+ const removeSolana = await askQuestion("\n☀️ Remove Solana CLI? (y/N): ");
286
+ if (removeSolana.toLowerCase() === "y") {
287
+ log.info("Removing Solana CLI...");
288
+
289
+ // Default locations
290
+ // const localShareSolana = path.join(os.homedir(), '.local', 'share', 'solana'); // Already defined
291
+
292
+ if (stealth.enabled) {
293
+ runOrElevate(
294
+ `rm -rf "${wslSolanaPath}"`,
295
+ `Remove folder ${wslSolanaPath}`,
296
+ stealth,
297
+ );
298
+ log.success(`Removed ${wslSolanaPath}`);
299
+ } else {
300
+ if (fs.existsSync(localShareSolana)) {
301
+ try {
302
+ fs.rmSync(localShareSolana, { recursive: true, force: true });
303
+ log.success(`Removed ${localShareSolana}`);
304
+ } catch (err) {
305
+ log.warn(
306
+ `Failed to remove ${localShareSolana} directly: ${err.message}`,
307
+ );
308
+ log.info("Trying to remove via elevated command...");
309
+ runOrElevate(
310
+ `rmdir /s /q "${localShareSolana}"`,
311
+ `Remove folder ${localShareSolana}`,
312
+ stealth,
313
+ );
314
+ }
315
+ } else {
316
+ log.warn(
317
+ `Could not find Solana at ${localShareSolana}. It might be already removed.`,
318
+ );
162
319
  }
320
+ }
163
321
  }
164
-
165
- // 2. Uninstall Solana
166
- let solanaInstalled = shell.which('solana');
167
- const localShareSolana = path.join(os.homedir(), '.local', 'share', 'solana');
168
-
169
- // If not found in PATH, check default location (like doctor does)
170
- if (!solanaInstalled && fs.existsSync(localShareSolana)) {
171
- solanaInstalled = true;
322
+ }
323
+
324
+ // 3. Uninstall Rust
325
+ const rustInstalled = commandExists("rustc", stealth);
326
+ if (rustInstalled) {
327
+ const removeRust = await askQuestion("\n🦀 Remove Rust? (y/N): ");
328
+ if (removeRust.toLowerCase() === "y") {
329
+ log.info("Running rustup self uninstall...");
330
+ runOrElevate("rustup self uninstall -y", "Uninstall Rust", stealth);
172
331
  }
173
-
174
- if (solanaInstalled) {
175
- const removeSolana = await askQuestion("\n☀️ Remove Solana CLI? (y/N): ");
176
- if (removeSolana.toLowerCase() === 'y') {
177
- log.info("Removing Solana CLI...");
178
-
179
- // Default locations
180
- // const localShareSolana = path.join(os.homedir(), '.local', 'share', 'solana'); // Already defined
181
-
182
- if (fs.existsSync(localShareSolana)) {
183
- try {
184
- fs.rmSync(localShareSolana, { recursive: true, force: true });
185
- log.success(`Removed ${localShareSolana}`);
186
- } catch (err) {
187
- log.warn(`Failed to remove ${localShareSolana} directly: ${err.message}`);
188
- log.info("Trying to remove via elevated command...");
189
- runOrElevate(`rmdir /s /q "${localShareSolana}"`, `Remove folder ${localShareSolana}`);
190
- }
191
- } else {
192
- log.warn(`Could not find Solana at ${localShareSolana}. It might be already removed.`);
332
+ }
333
+
334
+ // 4. WALLET REMOVAL (DANGER)
335
+ const walletPath = path.join(os.homedir(), ".config", "solana", "id.json");
336
+ const wslWalletPath = "$HOME/.config/solana/id.json";
337
+ const hasNativeWallet = fs.existsSync(walletPath);
338
+ const hasWslWallet = stealth.enabled
339
+ ? wslPathExists(wslWalletPath, stealth)
340
+ : false;
341
+
342
+ if (hasNativeWallet || hasWslWallet) {
343
+ log.error("\n⚠️ DANGER ZONE ⚠️");
344
+ if (hasNativeWallet) log.warn(`Found a Solana wallet at: ${walletPath}`);
345
+ if (hasWslWallet)
346
+ log.warn(`Found a Solana wallet in WSL at: ${wslWalletPath}`);
347
+ log.warn(
348
+ "If you delete this without a backup, your funds will be LOST FOREVER.",
349
+ );
350
+
351
+ const removeWallet = await askQuestion(
352
+ "💥 Do you REALLY want to delete this wallet? (type 'DELETE' to confirm): ",
353
+ );
354
+ if (removeWallet === "DELETE") {
355
+ if (hasNativeWallet) {
356
+ try {
357
+ fs.unlinkSync(walletPath);
358
+ log.success("Native wallet deleted.");
359
+
360
+ // Clean up parent config dir if empty
361
+ const configDir = path.dirname(walletPath);
362
+ try {
363
+ if (fs.readdirSync(configDir).length === 0) {
364
+ fs.rmSync(configDir, { recursive: true, force: true });
193
365
  }
366
+ } catch (e) {}
367
+ } catch (err) {
368
+ log.error(`Failed to delete native wallet: ${err.message}`);
194
369
  }
195
- }
196
-
197
- // 3. Uninstall Rust
198
- const rustInstalled = shell.which('rustc');
199
- if (rustInstalled) {
200
- const removeRust = await askQuestion("\n🦀 Remove Rust? (y/N): ");
201
- if (removeRust.toLowerCase() === 'y') {
202
- log.info("Running rustup self uninstall...");
203
- runOrElevate('rustup self uninstall -y', 'Uninstall Rust');
370
+ }
371
+
372
+ if (hasWslWallet) {
373
+ const deleted = runOrElevate(
374
+ `rm -f "${wslWalletPath}"`,
375
+ "Delete WSL wallet",
376
+ stealth,
377
+ );
378
+ if (deleted) {
379
+ // Best effort cleanup of config dir if empty.
380
+ runOrElevate(
381
+ 'rmdir "$HOME/.config/solana" 2>/dev/null || true',
382
+ "Cleanup WSL wallet config directory",
383
+ stealth,
384
+ );
385
+ log.success("WSL wallet deleted.");
204
386
  }
387
+ }
388
+ } else {
389
+ log.info("Skipping wallet deletion.");
205
390
  }
206
-
207
- // 4. WALLET REMOVAL (DANGER)
208
- const walletPath = path.join(os.homedir(), '.config', 'solana', 'id.json');
209
- if (fs.existsSync(walletPath)) {
210
- log.error("\n⚠️ DANGER ZONE ⚠️");
211
- log.warn(`Found a Solana wallet at: ${walletPath}`);
212
- log.warn("If you delete this without a backup, your funds will be LOST FOREVER.");
213
-
214
- const removeWallet = await askQuestion("💥 Do you REALLY want to delete this wallet? (type 'DELETE' to confirm): ");
215
- if (removeWallet === 'DELETE') {
216
- try {
217
- fs.unlinkSync(walletPath);
218
- log.success("Wallet deleted.");
219
-
220
- // Clean up parent config dir if empty
221
- const configDir = path.dirname(walletPath);
222
- try {
223
- if (fs.readdirSync(configDir).length === 0) {
224
- fs.rmSync(configDir, { recursive: true, force: true });
225
- }
226
- } catch (e) { }
227
- } catch (err) {
228
- log.error(`Failed to delete wallet: ${err.message}`);
229
- }
230
- } else {
231
- log.info("Skipping wallet deletion.");
391
+ }
392
+
393
+ if (stealth.enabled) {
394
+ const configPath = path.join(os.homedir(), ".uso-config.json");
395
+ if (fs.existsSync(configPath)) {
396
+ const disableStealth = await askQuestion(
397
+ "\n🐧 Disable Stealth WSL mode for USO? (y/N): ",
398
+ );
399
+ if (disableStealth.toLowerCase() === "y") {
400
+ try {
401
+ fs.rmSync(configPath, { force: true });
402
+ log.success("Stealth mode config removed.");
403
+ } catch (err) {
404
+ log.warn(`Failed to remove stealth config: ${err.message}`);
232
405
  }
406
+ }
233
407
  }
408
+ }
234
409
 
235
- log.header("\n✅ Cleanup complete.");
236
- log.info("To remove the 'uso' tool itself, run:");
237
- log.info(" npm uninstall -g uso");
410
+ log.header("\n✅ Cleanup complete.");
411
+ log.info("To remove the 'uso' tool itself, run:");
412
+ log.info(" npm uninstall -g uso");
238
413
  };
239
414
 
240
415
  module.exports = { uninstall };