vibeusage 0.2.21 → 0.2.22

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.
Files changed (40) hide show
  1. package/README.md +306 -173
  2. package/README.old.md +324 -0
  3. package/README.zh-CN.md +304 -188
  4. package/package.json +32 -32
  5. package/src/cli.js +41 -37
  6. package/src/commands/activate-if-needed.js +41 -0
  7. package/src/commands/diagnostics.js +8 -9
  8. package/src/commands/doctor.js +31 -26
  9. package/src/commands/init.js +285 -218
  10. package/src/commands/status.js +86 -83
  11. package/src/commands/sync.js +182 -130
  12. package/src/commands/uninstall.js +66 -62
  13. package/src/lib/activation-check.js +290 -0
  14. package/src/lib/browser-auth.js +52 -54
  15. package/src/lib/claude-config.js +25 -25
  16. package/src/lib/cli-ui.js +35 -35
  17. package/src/lib/codex-config.js +40 -36
  18. package/src/lib/debug-flags.js +2 -2
  19. package/src/lib/diagnostics.js +70 -57
  20. package/src/lib/doctor.js +139 -132
  21. package/src/lib/fs.js +17 -17
  22. package/src/lib/gemini-config.js +44 -40
  23. package/src/lib/init-flow.js +16 -22
  24. package/src/lib/insforge-client.js +10 -10
  25. package/src/lib/insforge.js +9 -3
  26. package/src/lib/openclaw-hook.js +89 -66
  27. package/src/lib/openclaw-session-plugin.js +116 -92
  28. package/src/lib/opencode-config.js +31 -32
  29. package/src/lib/opencode-usage-audit.js +34 -31
  30. package/src/lib/progress.js +12 -13
  31. package/src/lib/project-usage-purge.js +23 -17
  32. package/src/lib/prompt.js +8 -4
  33. package/src/lib/rollout.js +342 -241
  34. package/src/lib/runtime-config.js +34 -22
  35. package/src/lib/subscriptions.js +94 -92
  36. package/src/lib/tracker-paths.js +6 -6
  37. package/src/lib/upload-throttle.js +35 -16
  38. package/src/lib/uploader.js +33 -29
  39. package/src/lib/vibeusage-api.js +72 -56
  40. package/src/lib/vibeusage-public-repo.js +41 -24
@@ -1,24 +1,22 @@
1
- const http = require('node:http');
2
- const crypto = require('node:crypto');
3
- const cp = require('node:child_process');
1
+ const http = require("node:http");
2
+ const crypto = require("node:crypto");
3
+ const cp = require("node:child_process");
4
4
 
5
- const { DEFAULT_BASE_URL } = require('./runtime-config');
5
+ const { DEFAULT_BASE_URL } = require("./runtime-config");
6
6
 
7
7
  async function beginBrowserAuth({ baseUrl, dashboardUrl, timeoutMs, open }) {
8
- const nonce = crypto.randomBytes(16).toString('hex');
8
+ const nonce = crypto.randomBytes(16).toString("hex");
9
9
  const callbackPath = `/vibeusage/callback/${nonce}`;
10
- const authUrl = dashboardUrl
11
- ? new URL('/', dashboardUrl)
12
- : new URL('/auth/sign-up', baseUrl);
10
+ const authUrl = dashboardUrl ? new URL("/", dashboardUrl) : new URL("/auth/sign-up", baseUrl);
13
11
  const postAuthRedirect = resolvePostAuthRedirect({ dashboardUrl, authUrl });
14
12
  const { callbackUrl, waitForCallback } = await startLocalCallbackServer({
15
13
  callbackPath,
16
14
  timeoutMs,
17
- redirectUrl: postAuthRedirect
15
+ redirectUrl: postAuthRedirect,
18
16
  });
19
- authUrl.searchParams.set('redirect', callbackUrl);
17
+ authUrl.searchParams.set("redirect", callbackUrl);
20
18
  if (dashboardUrl && baseUrl && baseUrl !== DEFAULT_BASE_URL) {
21
- authUrl.searchParams.set('base_url', baseUrl);
19
+ authUrl.searchParams.set("base_url", baseUrl);
22
20
  }
23
21
 
24
22
  if (open !== false) openInBrowser(authUrl.toString());
@@ -38,29 +36,29 @@ async function startLocalCallbackServer({ callbackPath, timeoutMs, redirectUrl }
38
36
 
39
37
  const server = http.createServer((req, res) => {
40
38
  if (resolved) {
41
- res.writeHead(409, { 'Content-Type': 'text/plain; charset=utf-8' });
42
- res.end('Already authenticated.\n');
39
+ res.writeHead(409, { "Content-Type": "text/plain; charset=utf-8" });
40
+ res.end("Already authenticated.\n");
43
41
  return;
44
42
  }
45
43
 
46
- const method = req.method || 'GET';
47
- if (method !== 'GET') {
48
- res.writeHead(405, { 'Content-Type': 'text/plain; charset=utf-8' });
49
- res.end('Method not allowed.\n');
44
+ const method = req.method || "GET";
45
+ if (method !== "GET") {
46
+ res.writeHead(405, { "Content-Type": "text/plain; charset=utf-8" });
47
+ res.end("Method not allowed.\n");
50
48
  return;
51
49
  }
52
50
 
53
- const url = new URL(req.url || '/', 'http://127.0.0.1');
51
+ const url = new URL(req.url || "/", "http://127.0.0.1");
54
52
  if (url.pathname !== callbackPath) {
55
- res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
56
- res.end('Not found.\n');
53
+ res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
54
+ res.end("Not found.\n");
57
55
  return;
58
56
  }
59
57
 
60
- const accessToken = url.searchParams.get('access_token') || '';
58
+ const accessToken = url.searchParams.get("access_token") || "";
61
59
  if (!accessToken) {
62
- res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
63
- res.end('Missing access_token.\n');
60
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
61
+ res.end("Missing access_token.\n");
64
62
  return;
65
63
  }
66
64
 
@@ -68,50 +66,50 @@ async function startLocalCallbackServer({ callbackPath, timeoutMs, redirectUrl }
68
66
  if (redirectUrl) {
69
67
  res.writeHead(302, {
70
68
  Location: redirectUrl,
71
- 'Content-Type': 'text/html; charset=utf-8'
69
+ "Content-Type": "text/html; charset=utf-8",
72
70
  });
73
71
  res.end(
74
72
  [
75
- '<!doctype html>',
73
+ "<!doctype html>",
76
74
  '<html><head><meta charset="utf-8"><title>VibeScore</title></head>',
77
- '<body>',
78
- '<h2>Login succeeded</h2>',
75
+ "<body>",
76
+ "<h2>Login succeeded</h2>",
79
77
  `<p>Redirecting to <a href="${redirectUrl}">dashboard</a>...</p>`,
80
- '</body></html>'
81
- ].join('')
78
+ "</body></html>",
79
+ ].join(""),
82
80
  );
83
81
  } else {
84
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
82
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
85
83
  res.end(
86
84
  [
87
- '<!doctype html>',
85
+ "<!doctype html>",
88
86
  '<html><head><meta charset="utf-8"><title>VibeScore</title></head>',
89
- '<body>',
90
- '<h2>Login succeeded</h2>',
91
- '<p>You can close this tab and return to the CLI.</p>',
92
- '</body></html>'
93
- ].join('')
87
+ "<body>",
88
+ "<h2>Login succeeded</h2>",
89
+ "<p>You can close this tab and return to the CLI.</p>",
90
+ "</body></html>",
91
+ ].join(""),
94
92
  );
95
93
  }
96
94
 
97
95
  resolveResult({
98
96
  accessToken,
99
- userId: url.searchParams.get('user_id') || null,
100
- email: url.searchParams.get('email') || null,
101
- name: url.searchParams.get('name') || null
97
+ userId: url.searchParams.get("user_id") || null,
98
+ email: url.searchParams.get("email") || null,
99
+ name: url.searchParams.get("name") || null,
102
100
  });
103
101
  });
104
102
 
105
103
  await new Promise((resolve, reject) => {
106
- server.once('error', reject);
107
- server.listen(0, '127.0.0.1', resolve);
104
+ server.once("error", reject);
105
+ server.listen(0, "127.0.0.1", resolve);
108
106
  });
109
107
 
110
108
  const addr = server.address();
111
- const port = typeof addr === 'object' && addr ? addr.port : null;
109
+ const port = typeof addr === "object" && addr ? addr.port : null;
112
110
  if (!port) {
113
111
  server.close();
114
- throw new Error('Failed to bind local callback server');
112
+ throw new Error("Failed to bind local callback server");
115
113
  }
116
114
 
117
115
  const callbackUrl = `http://127.0.0.1:${port}${callbackPath}`;
@@ -119,7 +117,7 @@ async function startLocalCallbackServer({ callbackPath, timeoutMs, redirectUrl }
119
117
  const timer = setTimeout(() => {
120
118
  if (resolved) return;
121
119
  resolved = true;
122
- rejectResult(new Error('Authentication timed out'));
120
+ rejectResult(new Error("Authentication timed out"));
123
121
  server.close();
124
122
  }, timeoutMs);
125
123
 
@@ -141,19 +139,19 @@ function openInBrowser(url) {
141
139
  let cmd = null;
142
140
  let args = [];
143
141
 
144
- if (platform === 'darwin') {
145
- cmd = 'open';
142
+ if (platform === "darwin") {
143
+ cmd = "open";
146
144
  args = [url];
147
- } else if (platform === 'win32') {
148
- cmd = 'cmd';
149
- args = ['/c', 'start', '', url];
145
+ } else if (platform === "win32") {
146
+ cmd = "cmd";
147
+ args = ["/c", "start", "", url];
150
148
  } else {
151
- cmd = 'xdg-open';
149
+ cmd = "xdg-open";
152
150
  args = [url];
153
151
  }
154
152
 
155
153
  try {
156
- const child = cp.spawn(cmd, args, { stdio: 'ignore', detached: true });
154
+ const child = cp.spawn(cmd, args, { stdio: "ignore", detached: true });
157
155
  child.unref();
158
156
  } catch (_e) {}
159
157
  }
@@ -161,8 +159,8 @@ function openInBrowser(url) {
161
159
  function resolvePostAuthRedirect({ dashboardUrl, authUrl }) {
162
160
  try {
163
161
  if (dashboardUrl) {
164
- const target = new URL('/', dashboardUrl);
165
- if (target.protocol === 'http:' || target.protocol === 'https:') {
162
+ const target = new URL("/", dashboardUrl);
163
+ if (target.protocol === "http:" || target.protocol === "https:") {
166
164
  return target.toString();
167
165
  }
168
166
  return null;
@@ -175,5 +173,5 @@ function resolvePostAuthRedirect({ dashboardUrl, authUrl }) {
175
173
 
176
174
  module.exports = {
177
175
  beginBrowserAuth,
178
- openInBrowser
176
+ openInBrowser,
179
177
  };
@@ -1,9 +1,9 @@
1
- const fs = require('node:fs/promises');
2
- const path = require('node:path');
1
+ const fs = require("node:fs/promises");
2
+ const path = require("node:path");
3
3
 
4
- const { ensureDir, readJson, writeJson } = require('./fs');
4
+ const { ensureDir, readJson, writeJson } = require("./fs");
5
5
 
6
- const DEFAULT_EVENT = 'SessionEnd';
6
+ const DEFAULT_EVENT = "SessionEnd";
7
7
 
8
8
  async function upsertClaudeHook({ settingsPath, hookCommand, event = DEFAULT_EVENT }) {
9
9
  const existing = await readJson(settingsPath);
@@ -23,7 +23,7 @@ async function upsertClaudeHook({ settingsPath, hookCommand, event = DEFAULT_EVE
23
23
  return { changed: false, backupPath: null };
24
24
  }
25
25
 
26
- const nextEntries = entries.concat([{ hooks: [{ type: 'command', command: hookCommand }] }]);
26
+ const nextEntries = entries.concat([{ hooks: [{ type: "command", command: hookCommand }] }]);
27
27
  const nextHooks = { ...hooks, [event]: nextEntries };
28
28
  const nextSettings = { ...settings, hooks: nextHooks };
29
29
 
@@ -33,12 +33,12 @@ async function upsertClaudeHook({ settingsPath, hookCommand, event = DEFAULT_EVE
33
33
 
34
34
  async function removeClaudeHook({ settingsPath, hookCommand, event = DEFAULT_EVENT }) {
35
35
  const existing = await readJson(settingsPath);
36
- if (!existing) return { removed: false, skippedReason: 'settings-missing' };
36
+ if (!existing) return { removed: false, skippedReason: "settings-missing" };
37
37
 
38
38
  const settings = normalizeSettings(existing);
39
39
  const hooks = normalizeHooks(settings.hooks);
40
40
  const entries = normalizeEntries(hooks[event]);
41
- if (entries.length === 0) return { removed: false, skippedReason: 'hook-missing' };
41
+ if (entries.length === 0) return { removed: false, skippedReason: "hook-missing" };
42
42
 
43
43
  let removed = false;
44
44
  const nextEntries = [];
@@ -48,7 +48,7 @@ async function removeClaudeHook({ settingsPath, hookCommand, event = DEFAULT_EVE
48
48
  if (res.entry) nextEntries.push(res.entry);
49
49
  }
50
50
 
51
- if (!removed) return { removed: false, skippedReason: 'hook-missing' };
51
+ if (!removed) return { removed: false, skippedReason: "hook-missing" };
52
52
 
53
53
  const nextHooks = { ...hooks };
54
54
  if (nextEntries.length > 0) nextHooks[event] = nextEntries;
@@ -64,24 +64,24 @@ async function removeClaudeHook({ settingsPath, hookCommand, event = DEFAULT_EVE
64
64
 
65
65
  async function isClaudeHookConfigured({ settingsPath, hookCommand, event = DEFAULT_EVENT }) {
66
66
  const settings = await readJson(settingsPath);
67
- if (!settings || typeof settings !== 'object') return false;
67
+ if (!settings || typeof settings !== "object") return false;
68
68
  const hooks = settings.hooks;
69
- if (!hooks || typeof hooks !== 'object') return false;
69
+ if (!hooks || typeof hooks !== "object") return false;
70
70
  const entries = normalizeEntries(hooks[event]);
71
71
  return hasHook(entries, hookCommand);
72
72
  }
73
73
 
74
74
  function buildClaudeHookCommand(notifyPath) {
75
- const cmd = typeof notifyPath === 'string' ? notifyPath : '';
75
+ const cmd = typeof notifyPath === "string" ? notifyPath : "";
76
76
  return `/usr/bin/env node ${quoteArg(cmd)} --source=claude`;
77
77
  }
78
78
 
79
79
  function normalizeSettings(raw) {
80
- return raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
80
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
81
81
  }
82
82
 
83
83
  function normalizeHooks(raw) {
84
- return raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
84
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
85
85
  }
86
86
 
87
87
  function normalizeEntries(raw) {
@@ -89,14 +89,14 @@ function normalizeEntries(raw) {
89
89
  }
90
90
 
91
91
  function normalizeCommand(cmd) {
92
- if (Array.isArray(cmd)) return cmd.map((v) => String(v)).join('\u0000');
93
- if (typeof cmd === 'string') return cmd.trim();
92
+ if (Array.isArray(cmd)) return cmd.map((v) => String(v)).join("\u0000");
93
+ if (typeof cmd === "string") return cmd.trim();
94
94
  return null;
95
95
  }
96
96
 
97
97
  function hasHook(entries, hookCommand) {
98
98
  for (const entry of entries) {
99
- if (!entry || typeof entry !== 'object') continue;
99
+ if (!entry || typeof entry !== "object") continue;
100
100
  if (entry.command && commandsEqual(entry.command, hookCommand)) return true;
101
101
  const hooks = Array.isArray(entry.hooks) ? entry.hooks : [];
102
102
  for (const hook of hooks) {
@@ -107,7 +107,7 @@ function hasHook(entries, hookCommand) {
107
107
  }
108
108
 
109
109
  function stripHookFromEntry(entry, hookCommand) {
110
- if (!entry || typeof entry !== 'object') return { entry, removed: false };
110
+ if (!entry || typeof entry !== "object") return { entry, removed: false };
111
111
 
112
112
  if (entry.command) {
113
113
  if (commandsEqual(entry.command, hookCommand)) return { entry: null, removed: true };
@@ -127,11 +127,11 @@ function stripHookFromEntry(entry, hookCommand) {
127
127
  function normalizeEntriesForCommand(entries, hookCommand) {
128
128
  let changed = false;
129
129
  const nextEntries = entries.map((entry) => {
130
- if (!entry || typeof entry !== 'object') return entry;
130
+ if (!entry || typeof entry !== "object") return entry;
131
131
  if (entry.command && commandsEqual(entry.command, hookCommand)) {
132
- if (entry.type !== 'command') {
132
+ if (entry.type !== "command") {
133
133
  changed = true;
134
- return { ...entry, type: 'command' };
134
+ return { ...entry, type: "command" };
135
135
  }
136
136
  return entry;
137
137
  }
@@ -139,9 +139,9 @@ function normalizeEntriesForCommand(entries, hookCommand) {
139
139
  let hooksChanged = false;
140
140
  const nextHooks = entry.hooks.map((hook) => {
141
141
  if (hook && commandsEqual(hook.command, hookCommand)) {
142
- if (hook.type !== 'command') {
142
+ if (hook.type !== "command") {
143
143
  hooksChanged = true;
144
- return { ...hook, type: 'command' };
144
+ return { ...hook, type: "command" };
145
145
  }
146
146
  }
147
147
  return hook;
@@ -160,7 +160,7 @@ function commandsEqual(a, b) {
160
160
  }
161
161
 
162
162
  function quoteArg(value) {
163
- const v = typeof value === 'string' ? value : '';
163
+ const v = typeof value === "string" ? value : "";
164
164
  if (!v) return '""';
165
165
  if (/^[A-Za-z0-9_\-./:@]+$/.test(v)) return v;
166
166
  return `"${v.replace(/"/g, '\\"')}"`;
@@ -172,7 +172,7 @@ async function writeClaudeSettings({ settingsPath, settings }) {
172
172
  try {
173
173
  const st = await fs.stat(settingsPath);
174
174
  if (st && st.isFile()) {
175
- backupPath = `${settingsPath}.bak.${new Date().toISOString().replace(/[:.]/g, '-')}`;
175
+ backupPath = `${settingsPath}.bak.${new Date().toISOString().replace(/[:.]/g, "-")}`;
176
176
  await fs.copyFile(settingsPath, backupPath);
177
177
  }
178
178
  } catch (_e) {
@@ -186,5 +186,5 @@ module.exports = {
186
186
  upsertClaudeHook,
187
187
  removeClaudeHook,
188
188
  isClaudeHookConfigured,
189
- buildClaudeHookCommand
189
+ buildClaudeHookCommand,
190
190
  };
package/src/lib/cli-ui.js CHANGED
@@ -1,15 +1,15 @@
1
- const readline = require('node:readline');
1
+ const readline = require("node:readline");
2
2
 
3
- const RESET = '\x1b[0m';
4
- const BOLD = '\x1b[1m';
5
- const DIM = '\x1b[2m';
6
- const CYAN = '\x1b[36m';
7
- const GREEN = '\x1b[32m';
8
- const YELLOW = '\x1b[33m';
9
- const BLUE = '\x1b[34m';
10
- const UNDERLINE = '\x1b[4m';
3
+ const RESET = "\x1b[0m";
4
+ const BOLD = "\x1b[1m";
5
+ const DIM = "\x1b[2m";
6
+ const CYAN = "\x1b[36m";
7
+ const GREEN = "\x1b[32m";
8
+ const YELLOW = "\x1b[33m";
9
+ const BLUE = "\x1b[34m";
10
+ const UNDERLINE = "\x1b[4m";
11
11
 
12
- const SPINNER_FRAMES = ['|', '/', '-', '\\'];
12
+ const SPINNER_FRAMES = ["|", "/", "-", "\\"];
13
13
 
14
14
  function isInteractive() {
15
15
  return Boolean(process.stdin.isTTY && process.stdout.isTTY);
@@ -17,22 +17,22 @@ function isInteractive() {
17
17
 
18
18
  function formatLine(line, width) {
19
19
  if (!width) return line;
20
- const raw = String(line || '');
20
+ const raw = String(line || "");
21
21
  const pad = Math.max(0, width - raw.length);
22
- return raw + ' '.repeat(pad);
22
+ return raw + " ".repeat(pad);
23
23
  }
24
24
 
25
25
  function renderBox(lines, { padding = 1 } = {}) {
26
- const content = lines.map((line) => String(line || ''));
26
+ const content = lines.map((line) => String(line || ""));
27
27
  const maxLen = content.reduce((max, line) => Math.max(max, line.length), 0);
28
28
  const innerWidth = maxLen + padding * 2;
29
- const top = `+${'-'.repeat(innerWidth)}+`;
30
- const bottom = `+${'-'.repeat(innerWidth)}+`;
29
+ const top = `+${"-".repeat(innerWidth)}+`;
30
+ const bottom = `+${"-".repeat(innerWidth)}+`;
31
31
  const body = content.map((line) => {
32
- const padded = ' '.repeat(padding) + formatLine(line, maxLen) + ' '.repeat(padding);
32
+ const padded = " ".repeat(padding) + formatLine(line, maxLen) + " ".repeat(padding);
33
33
  return `|${padded}|`;
34
34
  });
35
- return [top, ...body, bottom].join('\n');
35
+ return [top, ...body, bottom].join("\n");
36
36
  }
37
37
 
38
38
  function color(text, token) {
@@ -54,7 +54,7 @@ async function promptMenu({ message, options, defaultIndex = 0 }) {
54
54
  if (!isInteractive()) return options[defaultIndex] || options[0];
55
55
 
56
56
  const safeOptions = Array.isArray(options) ? options : [];
57
- if (safeOptions.length === 0) return '';
57
+ if (safeOptions.length === 0) return "";
58
58
 
59
59
  const maxIndex = safeOptions.length - 1;
60
60
  let currentIndex = Math.min(Math.max(defaultIndex, 0), maxIndex);
@@ -64,18 +64,18 @@ async function promptMenu({ message, options, defaultIndex = 0 }) {
64
64
  const renderLines = () => {
65
65
  const lines = [promptMessage];
66
66
  safeOptions.forEach((opt, idx) => {
67
- const prefix = idx === currentIndex ? '>' : ' ';
67
+ const prefix = idx === currentIndex ? ">" : " ";
68
68
  lines.push(`${prefix} ${opt}`);
69
69
  });
70
- process.stdout.write(lines.join('\n'));
70
+ process.stdout.write(lines.join("\n"));
71
71
  };
72
72
 
73
73
  const rerender = () => {
74
74
  for (let i = 0; i < linesCount; i += 1) {
75
- process.stdout.write('\x1b[2K');
76
- if (i < linesCount - 1) process.stdout.write('\x1b[1A');
75
+ process.stdout.write("\x1b[2K");
76
+ if (i < linesCount - 1) process.stdout.write("\x1b[1A");
77
77
  }
78
- process.stdout.write('\r');
78
+ process.stdout.write("\r");
79
79
  renderLines();
80
80
  };
81
81
 
@@ -83,28 +83,28 @@ async function promptMenu({ message, options, defaultIndex = 0 }) {
83
83
 
84
84
  return await new Promise((resolve) => {
85
85
  const cleanup = () => {
86
- process.stdin.off('keypress', onKeypress);
86
+ process.stdin.off("keypress", onKeypress);
87
87
  if (process.stdin.isTTY) process.stdin.setRawMode(false);
88
88
  process.stdin.pause();
89
- process.stdout.write('\n');
89
+ process.stdout.write("\n");
90
90
  };
91
91
 
92
92
  const onKeypress = (str, key = {}) => {
93
- if (key.ctrl && key.name === 'c') {
93
+ if (key.ctrl && key.name === "c") {
94
94
  cleanup();
95
95
  return resolve(safeOptions[currentIndex]);
96
96
  }
97
- if (key.name === 'up' || str === 'k') {
97
+ if (key.name === "up" || str === "k") {
98
98
  currentIndex = currentIndex === 0 ? maxIndex : currentIndex - 1;
99
99
  rerender();
100
100
  return;
101
101
  }
102
- if (key.name === 'down' || str === 'j') {
102
+ if (key.name === "down" || str === "j") {
103
103
  currentIndex = currentIndex === maxIndex ? 0 : currentIndex + 1;
104
104
  rerender();
105
105
  return;
106
106
  }
107
- if (key.name === 'return') {
107
+ if (key.name === "return") {
108
108
  cleanup();
109
109
  return resolve(safeOptions[currentIndex]);
110
110
  }
@@ -121,7 +121,7 @@ async function promptMenu({ message, options, defaultIndex = 0 }) {
121
121
  readline.emitKeypressEvents(process.stdin);
122
122
  if (process.stdin.isTTY) process.stdin.setRawMode(true);
123
123
  process.stdin.resume();
124
- process.stdin.on('keypress', onKeypress);
124
+ process.stdin.on("keypress", onKeypress);
125
125
  });
126
126
  }
127
127
 
@@ -144,7 +144,7 @@ function createSpinner({ text, intervalMs = 80 }) {
144
144
  function stop(successText) {
145
145
  if (timer) clearInterval(timer);
146
146
  if (isInteractive()) {
147
- process.stdout.write(`\r${' '.repeat(text.length + 4)}\r`);
147
+ process.stdout.write(`\r${" ".repeat(text.length + 4)}\r`);
148
148
  }
149
149
  if (successText) process.stdout.write(`${successText}\n`);
150
150
  }
@@ -153,9 +153,9 @@ function createSpinner({ text, intervalMs = 80 }) {
153
153
  }
154
154
 
155
155
  function formatSummaryLine({ label, status, detail }) {
156
- const isSuccess = status === 'updated' || status === 'set' || status === 'installed';
157
- const bullet = isSuccess ? color('*', GREEN) : 'o';
158
- const statusLabel = isSuccess ? (detail || status) : detail ? `Skipped - ${detail}` : 'Skipped';
156
+ const isSuccess = status === "updated" || status === "set" || status === "installed";
157
+ const bullet = isSuccess ? color("*", GREEN) : "o";
158
+ const statusLabel = isSuccess ? detail || status : detail ? `Skipped - ${detail}` : "Skipped";
159
159
  const line = ` ${bullet} ${label.padEnd(22)} [${statusLabel}]`;
160
160
  return isSuccess ? line : color(line, DIM);
161
161
  }
@@ -175,5 +175,5 @@ module.exports = {
175
175
  promptMenu,
176
176
  promptEnter,
177
177
  createSpinner,
178
- formatSummaryLine
178
+ formatSummaryLine,
179
179
  };