coding-usage-bar 0.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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +220 -0
  3. package/README.zh-CN.md +53 -0
  4. package/THIRD_PARTY_NOTICES.md +16 -0
  5. package/assets/coding-usage-bar-limit.png +0 -0
  6. package/assets/coding-usage-bar-ok.png +0 -0
  7. package/assets/coding-usage-bar-over.png +0 -0
  8. package/assets/coding-usage-bar-under.png +0 -0
  9. package/assets/provider-claude.png +0 -0
  10. package/assets/provider-codex.png +0 -0
  11. package/assets/provider-deepseek.png +0 -0
  12. package/assets/provider-glm-dark.png +0 -0
  13. package/assets/provider-glm.png +0 -0
  14. package/assets/provider-marks.png +0 -0
  15. package/assets/provider-minimax.png +0 -0
  16. package/dist/burn.d.ts +3 -0
  17. package/dist/burn.js +164 -0
  18. package/dist/card.d.ts +4 -0
  19. package/dist/card.js +79 -0
  20. package/dist/claude.d.ts +9 -0
  21. package/dist/claude.js +141 -0
  22. package/dist/cli.d.ts +2 -0
  23. package/dist/cli.js +129 -0
  24. package/dist/codex.d.ts +6 -0
  25. package/dist/codex.js +128 -0
  26. package/dist/config.d.ts +4 -0
  27. package/dist/config.js +64 -0
  28. package/dist/daemon.d.ts +3 -0
  29. package/dist/daemon.js +16 -0
  30. package/dist/deepseek.d.ts +18 -0
  31. package/dist/deepseek.js +47 -0
  32. package/dist/doctor.d.ts +6 -0
  33. package/dist/doctor.js +225 -0
  34. package/dist/format.d.ts +9 -0
  35. package/dist/format.js +92 -0
  36. package/dist/fs-util.d.ts +6 -0
  37. package/dist/fs-util.js +39 -0
  38. package/dist/glm.d.ts +22 -0
  39. package/dist/glm.js +62 -0
  40. package/dist/install.d.ts +25 -0
  41. package/dist/install.js +432 -0
  42. package/dist/menubar.d.ts +23 -0
  43. package/dist/menubar.js +880 -0
  44. package/dist/minimax.d.ts +47 -0
  45. package/dist/minimax.js +100 -0
  46. package/dist/node-runtime.d.ts +1 -0
  47. package/dist/node-runtime.js +29 -0
  48. package/dist/notifier.d.ts +7 -0
  49. package/dist/notifier.js +121 -0
  50. package/dist/paths.d.ts +5 -0
  51. package/dist/paths.js +45 -0
  52. package/dist/runtime.d.ts +23 -0
  53. package/dist/runtime.js +244 -0
  54. package/dist/star.d.ts +15 -0
  55. package/dist/star.js +117 -0
  56. package/dist/status.d.ts +14 -0
  57. package/dist/status.js +65 -0
  58. package/dist/store.d.ts +4 -0
  59. package/dist/store.js +29 -0
  60. package/dist/types.d.ts +101 -0
  61. package/dist/types.js +1 -0
  62. package/dist/usage.d.ts +12 -0
  63. package/dist/usage.js +56 -0
  64. package/docs/assets/hero.png +0 -0
  65. package/fixtures/claude/latest.json +20 -0
  66. package/fixtures/claude/samples.jsonl +3 -0
  67. package/fixtures/codex/latest.json +20 -0
  68. package/fixtures/codex/samples.jsonl +3 -0
  69. package/fixtures/deepseek/latest.json +12 -0
  70. package/package.json +59 -0
package/dist/doctor.js ADDED
@@ -0,0 +1,225 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { readConfig } from "./config.js";
4
+ import { collectCodexUsage } from "./codex.js";
5
+ import { claudeStatusLineHasIngest, getClaudeStatusLineCommand, readClaudeSettings } from "./claude.js";
6
+ import { isDir, isFile } from "./fs-util.js";
7
+ import { buildPaths, providerLatestPath } from "./paths.js";
8
+ import { notificationBackend } from "./notifier.js";
9
+ import { isSwiftBarInstalled, swiftBarPluginPath } from "./menubar.js";
10
+ function stableClaudeIngestHint() {
11
+ const paths = buildPaths();
12
+ return `printf "%s" "$input" | node "${paths.stateDir}/app/dist/cli.js" ingest claude-statusline >/dev/null`;
13
+ }
14
+ function pathEntries() {
15
+ return (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
16
+ }
17
+ function isManagedCliShim() {
18
+ const paths = buildPaths();
19
+ try {
20
+ return fs.lstatSync(paths.cliBinFile).isSymbolicLink()
21
+ && path.resolve(path.dirname(paths.cliBinFile), fs.readlinkSync(paths.cliBinFile))
22
+ === path.join(paths.stateDir, "app", "dist", "cli.js");
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ export function runDoctor(options = {}) {
29
+ const paths = buildPaths();
30
+ const config = readConfig(paths);
31
+ const monitored = new Set(config.providers);
32
+ const checks = [];
33
+ checks.push({
34
+ name: "Runtime directory",
35
+ ok: options.dryRun ? true : isDir(paths.stateDir),
36
+ message: options.dryRun ? `[dry-run] would use ${paths.stateDir}` : paths.stateDir,
37
+ });
38
+ const cliBinInPath = pathEntries().includes(paths.cliBinDir);
39
+ checks.push({
40
+ name: "CLI command",
41
+ ok: options.dryRun ? true : isManagedCliShim() && cliBinInPath,
42
+ message: options.dryRun
43
+ ? `[dry-run] would link ${paths.cliBinFile}`
44
+ : cliBinInPath
45
+ ? `${paths.cliBinFile}`
46
+ : `${paths.cliBinFile}; add ${paths.cliBinDir} to PATH to use coding-usage-bar directly`,
47
+ });
48
+ checks.push({
49
+ name: "Configured providers",
50
+ ok: true,
51
+ message: `${config.providers.join(", ")} (${paths.configFile})`,
52
+ });
53
+ if (monitored.has("codex")) {
54
+ try {
55
+ const usage = collectCodexUsage(path.join(paths.homeDir, ".codex"));
56
+ checks.push({
57
+ name: "Codex usage",
58
+ ok: true,
59
+ message: `latest rate_limits observed at ${usage.observedAt}`,
60
+ });
61
+ }
62
+ catch (error) {
63
+ checks.push({
64
+ name: "Codex usage",
65
+ ok: false,
66
+ message: error instanceof Error ? error.message : String(error),
67
+ });
68
+ }
69
+ }
70
+ else {
71
+ checks.push({
72
+ name: "Codex usage",
73
+ ok: true,
74
+ message: "disabled by config",
75
+ });
76
+ }
77
+ const claudeLatest = providerLatestPath(paths, "claude");
78
+ if (monitored.has("claude")) {
79
+ checks.push({
80
+ name: "Claude usage cache",
81
+ ok: isFile(claudeLatest),
82
+ message: isFile(claudeLatest)
83
+ ? `found ${claudeLatest}`
84
+ : `missing ${claudeLatest}; add Claude status line ingest, e.g. ${stableClaudeIngestHint()}`,
85
+ });
86
+ const settings = readClaudeSettings(paths.claudeSettingsFile);
87
+ const command = getClaudeStatusLineCommand(settings);
88
+ checks.push({
89
+ name: "Claude status line",
90
+ ok: claudeStatusLineHasIngest(command, {
91
+ appCliPath: path.join(paths.stateDir, "app", "dist", "cli.js"),
92
+ managedScriptPath: paths.claudeStatusLineScript,
93
+ }),
94
+ message: command
95
+ ? `configured: ${command}`
96
+ : "not configured; coding-usage-bar install can create a minimal collector if no status line exists",
97
+ });
98
+ }
99
+ else {
100
+ checks.push({
101
+ name: "Claude usage cache",
102
+ ok: true,
103
+ message: "disabled by config",
104
+ });
105
+ }
106
+ if (monitored.has("glm")) {
107
+ const hasApiKey = Boolean(config.glm?.apiKey);
108
+ checks.push({
109
+ name: "GLM API key",
110
+ ok: hasApiKey,
111
+ message: hasApiKey
112
+ ? "configured"
113
+ : "not set; edit ~/.coding-usage-bar/config.json to set glm.apiKey",
114
+ });
115
+ const glmLatest = providerLatestPath(paths, "glm");
116
+ checks.push({
117
+ name: "GLM usage cache",
118
+ ok: !hasApiKey || isFile(glmLatest),
119
+ message: isFile(glmLatest)
120
+ ? `found ${glmLatest}`
121
+ : hasApiKey
122
+ ? `missing ${glmLatest}; run coding-usage-bar daemon --once to collect`
123
+ : "skipped (API key not set)",
124
+ });
125
+ }
126
+ else {
127
+ checks.push({
128
+ name: "GLM",
129
+ ok: true,
130
+ message: "disabled by config",
131
+ });
132
+ }
133
+ if (monitored.has("deepseek")) {
134
+ const hasApiKey = Boolean(config.deepseek?.apiKey);
135
+ checks.push({
136
+ name: "DeepSeek API key",
137
+ ok: hasApiKey,
138
+ message: hasApiKey
139
+ ? "configured"
140
+ : "not set; edit ~/.coding-usage-bar/config.json to set deepseek.apiKey",
141
+ });
142
+ const deepseekLatest = providerLatestPath(paths, "deepseek");
143
+ checks.push({
144
+ name: "DeepSeek balance cache",
145
+ ok: !hasApiKey || isFile(deepseekLatest),
146
+ message: isFile(deepseekLatest)
147
+ ? `found ${deepseekLatest}`
148
+ : hasApiKey
149
+ ? `missing ${deepseekLatest}; run coding-usage-bar daemon --once to collect`
150
+ : "skipped (API key not set)",
151
+ });
152
+ }
153
+ else {
154
+ checks.push({
155
+ name: "DeepSeek",
156
+ ok: true,
157
+ message: "disabled by config",
158
+ });
159
+ }
160
+ if (monitored.has("minimax")) {
161
+ const hasApiKey = Boolean(config.minimax?.apiKey);
162
+ checks.push({
163
+ name: "MiniMax API key",
164
+ ok: hasApiKey,
165
+ message: hasApiKey
166
+ ? `configured (region: ${config.minimax?.region ?? "cn"})`
167
+ : "not set; edit ~/.coding-usage-bar/config.json to set minimax.apiKey",
168
+ });
169
+ const minimaxLatest = providerLatestPath(paths, "minimax");
170
+ checks.push({
171
+ name: "MiniMax usage cache",
172
+ ok: !hasApiKey || isFile(minimaxLatest),
173
+ message: isFile(minimaxLatest)
174
+ ? `found ${minimaxLatest}`
175
+ : hasApiKey
176
+ ? `missing ${minimaxLatest}; run coding-usage-bar daemon --once to collect`
177
+ : "skipped (API key not set)",
178
+ });
179
+ }
180
+ else {
181
+ checks.push({
182
+ name: "MiniMax",
183
+ ok: true,
184
+ message: "disabled by config",
185
+ });
186
+ }
187
+ const backend = notificationBackend();
188
+ const notificationOk = backend !== "unsupported" && backend !== "burnt-toast";
189
+ checks.push({
190
+ name: "Notification",
191
+ ok: notificationOk,
192
+ message: backend === "burnt-toast"
193
+ ? "Windows design target: install PowerShell module BurntToast with Install-Module BurntToast -Scope CurrentUser"
194
+ : `backend=${backend}`,
195
+ });
196
+ checks.push({
197
+ name: "Daemon",
198
+ ok: process.platform === "darwin" ? fs.existsSync(paths.launchAgentFile) || Boolean(options.dryRun) : false,
199
+ message: process.platform === "darwin"
200
+ ? options.dryRun
201
+ ? `[dry-run] would use ${paths.launchAgentFile}`
202
+ : paths.launchAgentFile
203
+ : "daemon install is only implemented for macOS launchd in v1",
204
+ });
205
+ const swiftBarInstalled = isSwiftBarInstalled();
206
+ const swiftBarPlugin = swiftBarPluginPath(paths);
207
+ checks.push({
208
+ name: "Menu bar",
209
+ ok: swiftBarInstalled && isFile(swiftBarPlugin.file),
210
+ message: swiftBarInstalled
211
+ ? isFile(swiftBarPlugin.file)
212
+ ? `SwiftBar plugin installed: ${swiftBarPlugin.file}`
213
+ : `SwiftBar found; run coding-usage-bar menubar install`
214
+ : "SwiftBar not found; install SwiftBar, then run coding-usage-bar menubar install",
215
+ });
216
+ return checks;
217
+ }
218
+ export function formatDoctor(checks) {
219
+ return checks
220
+ .map((check) => `${check.ok ? "OK" : "MISSING"} ${check.name}: ${check.message}`)
221
+ .join("\n");
222
+ }
223
+ export function doctorHasFailures(checks) {
224
+ return checks.some((check) => !check.ok);
225
+ }
@@ -0,0 +1,9 @@
1
+ import { BurnAnalysis, StatusIssue, ProviderUsage, UsageWindow, StatusSnapshot } from "./types.js";
2
+ export declare function formatDurationUntil(iso: string, now?: Date): string;
3
+ export declare function formatProviderLabel(provider: string): "Claude" | "GLM" | "DeepSeek" | "MiniMax" | "Codex";
4
+ export declare function formatWindowLabel(window: UsageWindow): "5h" | "7d";
5
+ export declare function usageBar(usedPercent: number, width?: number): string;
6
+ export declare function formatStatusRows(usages: ProviderUsage[], analyses: BurnAnalysis[]): string;
7
+ export declare function formatAnalysisDetail(analysis: BurnAnalysis): string;
8
+ export declare function formatIssues(issues: StatusIssue[]): string;
9
+ export declare function formatProviderMeta(snapshot: StatusSnapshot): string;
package/dist/format.js ADDED
@@ -0,0 +1,92 @@
1
+ export function formatDurationUntil(iso, now = new Date()) {
2
+ const minutes = Math.max(0, Math.round((Date.parse(iso) - now.getTime()) / 60_000));
3
+ const days = Math.floor(minutes / 1440);
4
+ const hours = Math.floor((minutes % 1440) / 60);
5
+ const mins = minutes % 60;
6
+ if (days > 0) {
7
+ return `${days}d ${hours}h`;
8
+ }
9
+ if (hours > 0) {
10
+ return `${hours}h ${mins}m`;
11
+ }
12
+ return `${mins}m`;
13
+ }
14
+ function windowLabel(window) {
15
+ return window.name === "five_hour" ? "5h" : "7d";
16
+ }
17
+ function providerLabel(provider) {
18
+ if (provider === "claude")
19
+ return "Claude";
20
+ if (provider === "glm")
21
+ return "GLM";
22
+ if (provider === "deepseek")
23
+ return "DeepSeek";
24
+ if (provider === "minimax")
25
+ return "MiniMax";
26
+ return "Codex";
27
+ }
28
+ export function formatProviderLabel(provider) {
29
+ return providerLabel(provider);
30
+ }
31
+ export function formatWindowLabel(window) {
32
+ return windowLabel(window);
33
+ }
34
+ export function usageBar(usedPercent, width = 12) {
35
+ const usedCells = Math.max(0, Math.min(width, Math.round((usedPercent / 100) * width)));
36
+ return `[${"#".repeat(usedCells)}${"-".repeat(width - usedCells)}] ${Math.round(usedPercent)}%`;
37
+ }
38
+ export function formatStatusRows(usages, analyses) {
39
+ const rows = ["Provider Period Usage Reset State"];
40
+ for (const usage of usages) {
41
+ const analysis = analyses.find((item) => item.provider === usage.provider);
42
+ if (usage.balance && usage.windows.length === 0) {
43
+ const currency = usage.balance.currency === "CNY" ? "¥" : "$";
44
+ rows.push([
45
+ providerLabel(usage.provider).padEnd(8),
46
+ "balance".padEnd(8),
47
+ `${currency}${usage.balance.total}`.padEnd(19),
48
+ "".padEnd(10),
49
+ analysis?.state ?? "RAW",
50
+ ].join(""));
51
+ continue;
52
+ }
53
+ for (const window of usage.windows) {
54
+ rows.push([
55
+ providerLabel(usage.provider).padEnd(8),
56
+ windowLabel(window).padEnd(8),
57
+ usageBar(window.usedPercent).padEnd(19),
58
+ formatDurationUntil(window.resetsAt).padEnd(10),
59
+ analysis?.state ?? "RAW",
60
+ ].join(""));
61
+ }
62
+ }
63
+ return rows.join("\n");
64
+ }
65
+ export function formatAnalysisDetail(analysis) {
66
+ if (!analysis.target) {
67
+ return analysis.message;
68
+ }
69
+ return `${analysis.message} k=${analysis.target.conversionRate.toFixed(3)}, recommended=${analysis.target.recommendedPercent.toFixed(1)}%.`;
70
+ }
71
+ export function formatIssues(issues) {
72
+ if (issues.length === 0) {
73
+ return "";
74
+ }
75
+ return issues
76
+ .map((issue) => {
77
+ const provider = issue.provider ? `${providerLabel(issue.provider)} ` : "";
78
+ return `${issue.severity.toUpperCase()} ${provider}${issue.code}: ${issue.message}`;
79
+ })
80
+ .join("\n");
81
+ }
82
+ export function formatProviderMeta(snapshot) {
83
+ if (snapshot.providers.length === 0) {
84
+ return "";
85
+ }
86
+ return snapshot.providers
87
+ .map(({ usage, meta }) => {
88
+ const stale = meta.stale ? "stale" : "fresh";
89
+ return `${providerLabel(usage.provider)} source=${meta.source} age=${meta.ageSeconds}s ${stale}`;
90
+ })
91
+ .join("\n");
92
+ }
@@ -0,0 +1,6 @@
1
+ export declare function ensureDir(dir: string): void;
2
+ export declare function readJsonFile<T>(file: string): T | null;
3
+ export declare function writeJsonAtomic(file: string, value: unknown): void;
4
+ export declare function appendJsonLine(file: string, value: unknown): void;
5
+ export declare function isFile(file: string): boolean;
6
+ export declare function isDir(dir: string): boolean;
@@ -0,0 +1,39 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export function ensureDir(dir) {
4
+ fs.mkdirSync(dir, { recursive: true });
5
+ }
6
+ export function readJsonFile(file) {
7
+ try {
8
+ return JSON.parse(fs.readFileSync(file, "utf8"));
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ }
14
+ export function writeJsonAtomic(file, value) {
15
+ ensureDir(path.dirname(file));
16
+ const tmp = `${file}.${process.pid}.tmp`;
17
+ fs.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, "utf8");
18
+ fs.renameSync(tmp, file);
19
+ }
20
+ export function appendJsonLine(file, value) {
21
+ ensureDir(path.dirname(file));
22
+ fs.appendFileSync(file, `${JSON.stringify(value)}\n`, "utf8");
23
+ }
24
+ export function isFile(file) {
25
+ try {
26
+ return fs.statSync(file).isFile();
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ export function isDir(dir) {
33
+ try {
34
+ return fs.statSync(dir).isDirectory();
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
package/dist/glm.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { GlmConfig, ProviderUsage } from "./types.js";
2
+ interface GlmLimit {
3
+ type: string;
4
+ unit?: number;
5
+ number?: number;
6
+ percentage: number;
7
+ nextResetTime: number;
8
+ }
9
+ interface GlmQuotaResponse {
10
+ success: boolean;
11
+ data?: {
12
+ limits: GlmLimit[];
13
+ level: string;
14
+ };
15
+ }
16
+ export declare function fetchGlmQuota(config: GlmConfig): Promise<GlmQuotaResponse>;
17
+ export declare function usageFromGlmQuota(quota: GlmQuotaResponse, options: {
18
+ observedAt?: string;
19
+ source: string;
20
+ }): ProviderUsage | null;
21
+ export declare function collectGlmUsage(config: GlmConfig): Promise<ProviderUsage>;
22
+ export {};
package/dist/glm.js ADDED
@@ -0,0 +1,62 @@
1
+ import { makeProviderUsage, normalizeWindow } from "./usage.js";
2
+ const DEFAULT_BASE_URL = "https://open.bigmodel.cn";
3
+ export async function fetchGlmQuota(config) {
4
+ const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
5
+ const url = `${baseUrl}/api/monitor/usage/quota/limit`;
6
+ const response = await fetch(url, {
7
+ method: "GET",
8
+ headers: {
9
+ "Authorization": `Bearer ${config.apiKey}`,
10
+ "Content-Type": "application/json",
11
+ },
12
+ signal: AbortSignal.timeout(15_000),
13
+ });
14
+ if (!response.ok) {
15
+ throw new Error(`GLM quota API returned HTTP ${response.status}`);
16
+ }
17
+ return (await response.json());
18
+ }
19
+ export function usageFromGlmQuota(quota, options) {
20
+ if (!quota.success || !quota.data) {
21
+ return null;
22
+ }
23
+ const tokenLimits = quota.data.limits.filter((limit) => limit.type === "TOKENS_LIMIT");
24
+ if (tokenLimits.length < 2) {
25
+ return null;
26
+ }
27
+ const sortedByReset = [...tokenLimits].sort((a, b) => a.nextResetTime - b.nextResetTime);
28
+ const fiveHourRaw = sortedByReset[0];
29
+ const sevenDayRaw = sortedByReset[1];
30
+ const fiveHour = normalizeWindow("five_hour", {
31
+ used_percent: fiveHourRaw.percentage,
32
+ resets_at: new Date(fiveHourRaw.nextResetTime).toISOString(),
33
+ }, 300);
34
+ const sevenDay = normalizeWindow("seven_day", {
35
+ used_percent: sevenDayRaw.percentage,
36
+ resets_at: new Date(sevenDayRaw.nextResetTime).toISOString(),
37
+ }, 10080);
38
+ if (!fiveHour || !sevenDay) {
39
+ return null;
40
+ }
41
+ return makeProviderUsage({
42
+ provider: "glm",
43
+ source: options.source,
44
+ observedAt: options.observedAt,
45
+ planType: quota.data.level ?? null,
46
+ fiveHour,
47
+ sevenDay,
48
+ });
49
+ }
50
+ export async function collectGlmUsage(config) {
51
+ if (!config.apiKey) {
52
+ throw new Error("GLM API key not configured. Edit ~/.coding-usage-bar/config.json to set glm.apiKey.");
53
+ }
54
+ const quota = await fetchGlmQuota(config);
55
+ const usage = usageFromGlmQuota(quota, {
56
+ source: `${config.baseUrl || DEFAULT_BASE_URL}/api/monitor/usage/quota/limit`,
57
+ });
58
+ if (!usage) {
59
+ throw new Error("GLM quota response did not contain expected token limit data");
60
+ }
61
+ return usage;
62
+ }
@@ -0,0 +1,25 @@
1
+ import { RuntimePaths } from "./types.js";
2
+ export declare const LAUNCHD_LABEL = "com.duying.coding-usage-bar";
3
+ interface StatusLineUpdateContext {
4
+ existingCommand: string;
5
+ wrapperScript: string;
6
+ originalCommandFile: string;
7
+ stableIngestCommand: string;
8
+ }
9
+ interface ClaudeStatusLineInstallOptions {
10
+ dryRun: boolean;
11
+ confirmStatusLineUpdate?: (context: StatusLineUpdateContext) => boolean;
12
+ }
13
+ export declare function isSameDirectory(left: string, right: string): boolean;
14
+ type LaunchctlRunner = (file: string, args: string[], options?: {
15
+ stdio?: "ignore";
16
+ }) => unknown;
17
+ export declare function stopLaunchAgent(plistFile: string, run?: LaunchctlRunner): void;
18
+ export declare function installClaudeStatusLine(paths: RuntimePaths, options: ClaudeStatusLineInstallOptions): string[];
19
+ export declare function install(options?: {
20
+ dryRun?: boolean;
21
+ }): string[];
22
+ export declare function uninstall(options?: {
23
+ dryRun?: boolean;
24
+ }): string[];
25
+ export {};