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.
- package/LICENSE +21 -0
- package/README.md +220 -0
- package/README.zh-CN.md +53 -0
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/assets/coding-usage-bar-limit.png +0 -0
- package/assets/coding-usage-bar-ok.png +0 -0
- package/assets/coding-usage-bar-over.png +0 -0
- package/assets/coding-usage-bar-under.png +0 -0
- package/assets/provider-claude.png +0 -0
- package/assets/provider-codex.png +0 -0
- package/assets/provider-deepseek.png +0 -0
- package/assets/provider-glm-dark.png +0 -0
- package/assets/provider-glm.png +0 -0
- package/assets/provider-marks.png +0 -0
- package/assets/provider-minimax.png +0 -0
- package/dist/burn.d.ts +3 -0
- package/dist/burn.js +164 -0
- package/dist/card.d.ts +4 -0
- package/dist/card.js +79 -0
- package/dist/claude.d.ts +9 -0
- package/dist/claude.js +141 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +129 -0
- package/dist/codex.d.ts +6 -0
- package/dist/codex.js +128 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +64 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +16 -0
- package/dist/deepseek.d.ts +18 -0
- package/dist/deepseek.js +47 -0
- package/dist/doctor.d.ts +6 -0
- package/dist/doctor.js +225 -0
- package/dist/format.d.ts +9 -0
- package/dist/format.js +92 -0
- package/dist/fs-util.d.ts +6 -0
- package/dist/fs-util.js +39 -0
- package/dist/glm.d.ts +22 -0
- package/dist/glm.js +62 -0
- package/dist/install.d.ts +25 -0
- package/dist/install.js +432 -0
- package/dist/menubar.d.ts +23 -0
- package/dist/menubar.js +880 -0
- package/dist/minimax.d.ts +47 -0
- package/dist/minimax.js +100 -0
- package/dist/node-runtime.d.ts +1 -0
- package/dist/node-runtime.js +29 -0
- package/dist/notifier.d.ts +7 -0
- package/dist/notifier.js +121 -0
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +45 -0
- package/dist/runtime.d.ts +23 -0
- package/dist/runtime.js +244 -0
- package/dist/star.d.ts +15 -0
- package/dist/star.js +117 -0
- package/dist/status.d.ts +14 -0
- package/dist/status.js +65 -0
- package/dist/store.d.ts +4 -0
- package/dist/store.js +29 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.js +1 -0
- package/dist/usage.d.ts +12 -0
- package/dist/usage.js +56 -0
- package/docs/assets/hero.png +0 -0
- package/fixtures/claude/latest.json +20 -0
- package/fixtures/claude/samples.jsonl +3 -0
- package/fixtures/codex/latest.json +20 -0
- package/fixtures/codex/samples.jsonl +3 -0
- package/fixtures/deepseek/latest.json +12 -0
- package/package.json +59 -0
package/dist/install.js
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { claudeStatusLineHasIngest, getClaudeStatusLineCommand, readClaudeSettings } from "./claude.js";
|
|
6
|
+
import { ensureConfig, readConfig } from "./config.js";
|
|
7
|
+
import { ensureDir, isFile, readJsonFile, writeJsonAtomic } from "./fs-util.js";
|
|
8
|
+
import { addSwiftBarToLoginItems, ensureSwiftBarInstalled, installMenuBar, openSwiftBar, uninstallMenuBar } from "./menubar.js";
|
|
9
|
+
import { buildPaths } from "./paths.js";
|
|
10
|
+
import { stableNodeExecutable } from "./node-runtime.js";
|
|
11
|
+
export const LAUNCHD_LABEL = "com.duying.coding-usage-bar";
|
|
12
|
+
const MARKER = "coding-usage-bar managed";
|
|
13
|
+
function appInstallDir(paths) {
|
|
14
|
+
return path.join(paths.stateDir, "app");
|
|
15
|
+
}
|
|
16
|
+
function appCliPath(paths) {
|
|
17
|
+
return path.join(appInstallDir(paths), "dist", "cli.js");
|
|
18
|
+
}
|
|
19
|
+
function shellSingleQuote(value) {
|
|
20
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
21
|
+
}
|
|
22
|
+
function stableIngestCommand(paths) {
|
|
23
|
+
return `${shellSingleQuote(stableNodeExecutable())} ${shellSingleQuote(appCliPath(paths))} ingest claude-statusline`;
|
|
24
|
+
}
|
|
25
|
+
function originalStatusLineFile(paths) {
|
|
26
|
+
return path.join(paths.claudeDir, "statusline-original.json");
|
|
27
|
+
}
|
|
28
|
+
function originalStatusLineFromMetadata(paths) {
|
|
29
|
+
const metadata = readJsonFile(originalStatusLineFile(paths));
|
|
30
|
+
const statusLine = metadata?.statusLine;
|
|
31
|
+
if (statusLine && typeof statusLine === "object") {
|
|
32
|
+
const command = statusLine.command;
|
|
33
|
+
if (typeof command === "string") {
|
|
34
|
+
return statusLine;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
function currentPackageRoot() {
|
|
40
|
+
const entry = fs.realpathSync(process.argv[1]);
|
|
41
|
+
return path.resolve(path.dirname(entry), "..");
|
|
42
|
+
}
|
|
43
|
+
export function isSameDirectory(left, right) {
|
|
44
|
+
if (!fs.existsSync(left) || !fs.existsSync(right)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const leftStat = fs.statSync(left);
|
|
48
|
+
const rightStat = fs.statSync(right);
|
|
49
|
+
return leftStat.isDirectory()
|
|
50
|
+
&& rightStat.isDirectory()
|
|
51
|
+
&& leftStat.dev === rightStat.dev
|
|
52
|
+
&& leftStat.ino === rightStat.ino;
|
|
53
|
+
}
|
|
54
|
+
function installRuntimeApp(paths, dryRun) {
|
|
55
|
+
const packageRoot = currentPackageRoot();
|
|
56
|
+
const sourceDist = path.join(packageRoot, "dist");
|
|
57
|
+
const sourceAssets = path.join(packageRoot, "assets");
|
|
58
|
+
const sourceFixtures = path.join(packageRoot, "fixtures");
|
|
59
|
+
const target = appInstallDir(paths);
|
|
60
|
+
const tmpTarget = path.join(paths.stateDir, `app.${process.pid}.tmp`);
|
|
61
|
+
if (dryRun) {
|
|
62
|
+
return [`[dry-run] would copy ${sourceDist}, ${sourceAssets}, and ${sourceFixtures} -> ${target}`];
|
|
63
|
+
}
|
|
64
|
+
if (isSameDirectory(packageRoot, target)) {
|
|
65
|
+
return [`Runtime app already installed: ${target}`];
|
|
66
|
+
}
|
|
67
|
+
if (!fs.existsSync(sourceDist)) {
|
|
68
|
+
throw new Error(`Cannot install runtime app: missing built dist at ${sourceDist}`);
|
|
69
|
+
}
|
|
70
|
+
fs.rmSync(tmpTarget, { recursive: true, force: true });
|
|
71
|
+
ensureDir(tmpTarget);
|
|
72
|
+
fs.cpSync(sourceDist, path.join(tmpTarget, "dist"), { recursive: true });
|
|
73
|
+
if (fs.existsSync(sourceAssets)) {
|
|
74
|
+
fs.cpSync(sourceAssets, path.join(tmpTarget, "assets"), { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
if (fs.existsSync(sourceFixtures)) {
|
|
77
|
+
fs.cpSync(sourceFixtures, path.join(tmpTarget, "fixtures"), { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
fs.copyFileSync(path.join(packageRoot, "package.json"), path.join(tmpTarget, "package.json"));
|
|
80
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
81
|
+
fs.renameSync(tmpTarget, target);
|
|
82
|
+
return [`Installed runtime app: ${target}`];
|
|
83
|
+
}
|
|
84
|
+
function installedCliCommand(paths) {
|
|
85
|
+
return [stableNodeExecutable(), appCliPath(paths)];
|
|
86
|
+
}
|
|
87
|
+
function installedCliShimTarget(paths) {
|
|
88
|
+
return appCliPath(paths);
|
|
89
|
+
}
|
|
90
|
+
function pathEntries() {
|
|
91
|
+
return (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
|
|
92
|
+
}
|
|
93
|
+
function isManagedCliShim(paths) {
|
|
94
|
+
try {
|
|
95
|
+
return fs.lstatSync(paths.cliBinFile).isSymbolicLink()
|
|
96
|
+
&& path.resolve(path.dirname(paths.cliBinFile), fs.readlinkSync(paths.cliBinFile)) === installedCliShimTarget(paths);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function pathExists(file) {
|
|
103
|
+
try {
|
|
104
|
+
fs.lstatSync(file);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function installCliShim(paths, dryRun) {
|
|
112
|
+
const target = installedCliShimTarget(paths);
|
|
113
|
+
const binInPath = pathEntries().includes(paths.cliBinDir);
|
|
114
|
+
if (dryRun) {
|
|
115
|
+
const pathMessage = binInPath
|
|
116
|
+
? `${paths.cliBinDir} is already in PATH`
|
|
117
|
+
: `${paths.cliBinDir} is not in PATH; add it to use coding-usage-bar directly`;
|
|
118
|
+
return [`[dry-run] would link ${paths.cliBinFile} -> ${target}`, `[dry-run] ${pathMessage}`];
|
|
119
|
+
}
|
|
120
|
+
ensureDir(paths.cliBinDir);
|
|
121
|
+
if (pathExists(paths.cliBinFile)) {
|
|
122
|
+
if (isManagedCliShim(paths)) {
|
|
123
|
+
fs.rmSync(paths.cliBinFile, { force: true });
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
return [`CLI shim already exists and is not managed by coding-usage-bar: ${paths.cliBinFile}`];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
fs.symlinkSync(target, paths.cliBinFile);
|
|
130
|
+
const messages = [`Installed CLI shim: ${paths.cliBinFile} -> ${target}`];
|
|
131
|
+
if (!binInPath) {
|
|
132
|
+
messages.push(`Add ${paths.cliBinDir} to PATH to use coding-usage-bar directly.`);
|
|
133
|
+
}
|
|
134
|
+
return messages;
|
|
135
|
+
}
|
|
136
|
+
function plistXml(programArgs) {
|
|
137
|
+
const args = programArgs
|
|
138
|
+
.map((arg) => ` <string>${arg.replaceAll("&", "&").replaceAll("<", "<")}</string>`)
|
|
139
|
+
.join("\n");
|
|
140
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
141
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
142
|
+
<plist version="1.0">
|
|
143
|
+
<dict>
|
|
144
|
+
<key>Label</key>
|
|
145
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
146
|
+
<key>ProgramArguments</key>
|
|
147
|
+
<array>
|
|
148
|
+
${args}
|
|
149
|
+
<string>daemon</string>
|
|
150
|
+
<string>--once</string>
|
|
151
|
+
</array>
|
|
152
|
+
<key>StartInterval</key>
|
|
153
|
+
<integer>300</integer>
|
|
154
|
+
<key>StandardOutPath</key>
|
|
155
|
+
<string>${path.join(os.homedir(), ".coding-usage-bar", "daemon.log")}</string>
|
|
156
|
+
<key>StandardErrorPath</key>
|
|
157
|
+
<string>${path.join(os.homedir(), ".coding-usage-bar", "daemon.err.log")}</string>
|
|
158
|
+
</dict>
|
|
159
|
+
</plist>
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
function restartLaunchAgent(plistFile) {
|
|
163
|
+
const domain = `gui/${process.getuid?.() ?? ""}`;
|
|
164
|
+
try {
|
|
165
|
+
execFileSync("launchctl", ["bootout", domain, plistFile], { stdio: "ignore" });
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Not loaded yet or old launchctl behavior.
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
execFileSync("launchctl", ["bootstrap", domain, plistFile]);
|
|
172
|
+
execFileSync("launchctl", ["kickstart", "-k", `${domain}/${LAUNCHD_LABEL}`]);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Fall back to the older interface used on older macOS versions.
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
execFileSync("launchctl", ["unload", plistFile], { stdio: "ignore" });
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// Not loaded yet.
|
|
183
|
+
}
|
|
184
|
+
execFileSync("launchctl", ["load", plistFile]);
|
|
185
|
+
}
|
|
186
|
+
export function stopLaunchAgent(plistFile, run = execFileSync) {
|
|
187
|
+
const domain = `gui/${process.getuid?.() ?? ""}`;
|
|
188
|
+
let bootoutError;
|
|
189
|
+
try {
|
|
190
|
+
run("launchctl", ["bootout", domain, plistFile], { stdio: "ignore" });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
bootoutError = error;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
run("launchctl", ["unload", plistFile], { stdio: "ignore" });
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Verify below whether a job still exists before deciding the failure is safe.
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
run("launchctl", ["print", `${domain}/${LAUNCHD_LABEL}`], { stdio: "ignore" });
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
throw new Error(`Could not stop launchd agent ${LAUNCHD_LABEL}: ${bootoutError instanceof Error ? bootoutError.message : String(bootoutError)}`);
|
|
209
|
+
}
|
|
210
|
+
function wrapperStatusLineScript(existingCommand, stableIngestCommand) {
|
|
211
|
+
return [
|
|
212
|
+
"#!/usr/bin/env bash",
|
|
213
|
+
`# ${MARKER} Claude status line wrapper`,
|
|
214
|
+
`ORIGINAL_COMMAND=${shellSingleQuote(existingCommand)}`,
|
|
215
|
+
'input="$(cat)"',
|
|
216
|
+
`printf "%s" "$input" | ${stableIngestCommand} >/dev/null 2>&1 || true`,
|
|
217
|
+
'printf "%s" "$input" | /bin/sh -c "$ORIGINAL_COMMAND"',
|
|
218
|
+
"",
|
|
219
|
+
].join("\n");
|
|
220
|
+
}
|
|
221
|
+
function manualClaudeStatusLineMessages(stableIngestCommand) {
|
|
222
|
+
return [
|
|
223
|
+
"Add this near the top of your existing status line script:",
|
|
224
|
+
' input="$(cat)"',
|
|
225
|
+
` printf "%s" "$input" | ${stableIngestCommand} >/dev/null`,
|
|
226
|
+
"Then make the rest of your script read from $input instead of stdin.",
|
|
227
|
+
"Without this, Claude usage will stay unavailable in Coding Usage Bar; Claude burn-rate analysis, Claude notifications, and Claude menu bar data will show CLAUDE_INGEST_MISSING. Codex usage is unaffected.",
|
|
228
|
+
];
|
|
229
|
+
}
|
|
230
|
+
function defaultConfirmStatusLineUpdate(context) {
|
|
231
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
const prompt = [
|
|
235
|
+
"Coding Usage Bar detected an existing Claude status line command.",
|
|
236
|
+
`Existing command: ${context.existingCommand}`,
|
|
237
|
+
"If you continue, Coding Usage Bar will:",
|
|
238
|
+
` - write a wrapper script: ${context.wrapperScript}`,
|
|
239
|
+
` - save the original command metadata: ${context.originalCommandFile}`,
|
|
240
|
+
" - update Claude settings so the wrapper runs first",
|
|
241
|
+
" - collect Claude usage, then forward the same input to your existing command",
|
|
242
|
+
"Continue? [y/N] ",
|
|
243
|
+
].join("\n");
|
|
244
|
+
try {
|
|
245
|
+
const answer = execFileSync("/bin/sh", [
|
|
246
|
+
"-c",
|
|
247
|
+
'printf "%s" "$1" > /dev/tty; IFS= read -r answer < /dev/tty; printf "%s" "$answer"',
|
|
248
|
+
"coding-usage-bar-prompt",
|
|
249
|
+
prompt,
|
|
250
|
+
], { encoding: "utf8" }).trim().toLowerCase();
|
|
251
|
+
return answer === "y" || answer === "yes";
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
export function installClaudeStatusLine(paths, options) {
|
|
258
|
+
const dryRun = options.dryRun;
|
|
259
|
+
const settings = readClaudeSettings(paths.claudeSettingsFile);
|
|
260
|
+
const existing = getClaudeStatusLineCommand(settings);
|
|
261
|
+
const ingestCommand = stableIngestCommand(paths);
|
|
262
|
+
if (claudeStatusLineHasIngest(existing, {
|
|
263
|
+
appCliPath: appCliPath(paths),
|
|
264
|
+
managedScriptPath: paths.claudeStatusLineScript,
|
|
265
|
+
})) {
|
|
266
|
+
return ["Claude status line already includes coding-usage-bar ingest."];
|
|
267
|
+
}
|
|
268
|
+
if (existing) {
|
|
269
|
+
const context = {
|
|
270
|
+
existingCommand: existing,
|
|
271
|
+
wrapperScript: paths.claudeStatusLineScript,
|
|
272
|
+
originalCommandFile: originalStatusLineFile(paths),
|
|
273
|
+
stableIngestCommand: ingestCommand,
|
|
274
|
+
};
|
|
275
|
+
if (dryRun) {
|
|
276
|
+
return [
|
|
277
|
+
"Claude status line already exists.",
|
|
278
|
+
`[dry-run] would ask whether to wrap the existing command: ${existing}`,
|
|
279
|
+
`[dry-run] would write wrapper script: ${paths.claudeStatusLineScript}`,
|
|
280
|
+
`[dry-run] would save original command metadata: ${context.originalCommandFile}`,
|
|
281
|
+
`[dry-run] would update statusLine.command in ${paths.claudeSettingsFile}`,
|
|
282
|
+
...manualClaudeStatusLineMessages(ingestCommand),
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
const confirmed = (options.confirmStatusLineUpdate ?? defaultConfirmStatusLineUpdate)(context);
|
|
286
|
+
if (!confirmed) {
|
|
287
|
+
return [
|
|
288
|
+
"Skipped Claude status line update.",
|
|
289
|
+
...manualClaudeStatusLineMessages(ingestCommand),
|
|
290
|
+
];
|
|
291
|
+
}
|
|
292
|
+
ensureDir(path.dirname(paths.claudeStatusLineScript));
|
|
293
|
+
writeJsonAtomic(context.originalCommandFile, {
|
|
294
|
+
statusLine: settings?.statusLine ?? { type: "command", command: existing },
|
|
295
|
+
});
|
|
296
|
+
fs.writeFileSync(paths.claudeStatusLineScript, wrapperStatusLineScript(existing, ingestCommand), { mode: 0o755 });
|
|
297
|
+
writeJsonAtomic(paths.claudeSettingsFile, {
|
|
298
|
+
...(settings ?? {}),
|
|
299
|
+
statusLine: {
|
|
300
|
+
...((settings?.statusLine && typeof settings.statusLine === "object") ? settings.statusLine : {}),
|
|
301
|
+
type: "command",
|
|
302
|
+
command: paths.claudeStatusLineScript,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
return [`Updated Claude status line script: ${paths.claudeStatusLineScript}`];
|
|
306
|
+
}
|
|
307
|
+
const lines = [`# ${MARKER}`, 'input="$(cat)"', `printf "%s" "$input" | ${ingestCommand} >/dev/null`, 'printf "Coding Usage Bar ready"'];
|
|
308
|
+
const script = `${lines.join("\n")}\n`;
|
|
309
|
+
const newSettings = {
|
|
310
|
+
...(settings ?? {}),
|
|
311
|
+
statusLine: {
|
|
312
|
+
type: "command",
|
|
313
|
+
command: paths.claudeStatusLineScript,
|
|
314
|
+
padding: 0,
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
if (dryRun) {
|
|
318
|
+
return [
|
|
319
|
+
`[dry-run] would create ${paths.claudeStatusLineScript}`,
|
|
320
|
+
`[dry-run] would write statusLine.command to ${paths.claudeSettingsFile}`,
|
|
321
|
+
];
|
|
322
|
+
}
|
|
323
|
+
ensureDir(path.dirname(paths.claudeStatusLineScript));
|
|
324
|
+
fs.writeFileSync(paths.claudeStatusLineScript, script, { mode: 0o755 });
|
|
325
|
+
writeJsonAtomic(paths.claudeSettingsFile, newSettings);
|
|
326
|
+
return [`Created Claude status line collector: ${paths.claudeStatusLineScript}`];
|
|
327
|
+
}
|
|
328
|
+
export function install(options = {}) {
|
|
329
|
+
const dryRun = options.dryRun ?? false;
|
|
330
|
+
const paths = buildPaths();
|
|
331
|
+
const messages = [];
|
|
332
|
+
if (process.platform !== "darwin") {
|
|
333
|
+
messages.push("macOS launchd install is only implemented on macOS. Windows support is designed but not implemented in v1.");
|
|
334
|
+
return messages;
|
|
335
|
+
}
|
|
336
|
+
if (!dryRun) {
|
|
337
|
+
ensureDir(paths.stateDir);
|
|
338
|
+
ensureDir(path.dirname(paths.launchAgentFile));
|
|
339
|
+
}
|
|
340
|
+
messages.push(...installRuntimeApp(paths, dryRun));
|
|
341
|
+
messages.push(...installCliShim(paths, dryRun));
|
|
342
|
+
if (dryRun) {
|
|
343
|
+
messages.push(`[dry-run] would ensure config file: ${paths.configFile}`);
|
|
344
|
+
}
|
|
345
|
+
else if (ensureConfig(paths)) {
|
|
346
|
+
messages.push(`Created config file: ${paths.configFile}`);
|
|
347
|
+
}
|
|
348
|
+
if (!dryRun) {
|
|
349
|
+
const config = readConfig(paths);
|
|
350
|
+
if (!config.glm?.apiKey) {
|
|
351
|
+
messages.push("");
|
|
352
|
+
messages.push("To enable GLM Coding Plan monitoring:");
|
|
353
|
+
messages.push(" Edit ~/.coding-usage-bar/config.json and set glm.apiKey to your Zhipu API key.");
|
|
354
|
+
messages.push(" Get your key at: https://open.bigmodel.cn");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
messages.push(...ensureSwiftBarInstalled({ dryRun }));
|
|
358
|
+
messages.push(...installMenuBar({ dryRun }));
|
|
359
|
+
if (!dryRun) {
|
|
360
|
+
messages.push(...openSwiftBar());
|
|
361
|
+
}
|
|
362
|
+
messages.push(...addSwiftBarToLoginItems({ dryRun }));
|
|
363
|
+
messages.push(...installClaudeStatusLine(paths, { dryRun }));
|
|
364
|
+
const args = installedCliCommand(paths);
|
|
365
|
+
const xml = plistXml(args);
|
|
366
|
+
if (dryRun) {
|
|
367
|
+
messages.push(`[dry-run] would write launchd plist: ${paths.launchAgentFile}`);
|
|
368
|
+
messages.push(`[dry-run] ProgramArguments: ${[...args, "daemon", "--once"].join(" ")}`);
|
|
369
|
+
return messages;
|
|
370
|
+
}
|
|
371
|
+
fs.writeFileSync(paths.launchAgentFile, xml, "utf8");
|
|
372
|
+
restartLaunchAgent(paths.launchAgentFile);
|
|
373
|
+
messages.push(`Installed and restarted launchd agent: ${paths.launchAgentFile}`);
|
|
374
|
+
return messages;
|
|
375
|
+
}
|
|
376
|
+
export function uninstall(options = {}) {
|
|
377
|
+
const dryRun = options.dryRun ?? false;
|
|
378
|
+
const paths = buildPaths();
|
|
379
|
+
const messages = [];
|
|
380
|
+
if (process.platform === "darwin" && isFile(paths.launchAgentFile)) {
|
|
381
|
+
if (dryRun) {
|
|
382
|
+
messages.push(`[dry-run] would unload and remove ${paths.launchAgentFile}`);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
stopLaunchAgent(paths.launchAgentFile);
|
|
386
|
+
fs.rmSync(paths.launchAgentFile, { force: true });
|
|
387
|
+
messages.push(`Removed launchd agent: ${paths.launchAgentFile}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (isManagedCliShim(paths)) {
|
|
391
|
+
if (dryRun) {
|
|
392
|
+
messages.push(`[dry-run] would remove CLI shim ${paths.cliBinFile}`);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
fs.rmSync(paths.cliBinFile, { force: true });
|
|
396
|
+
messages.push(`Removed CLI shim: ${paths.cliBinFile}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
else if (isFile(paths.cliBinFile)) {
|
|
400
|
+
messages.push(`CLI shim is user-managed; leaving it unchanged: ${paths.cliBinFile}`);
|
|
401
|
+
}
|
|
402
|
+
messages.push(...uninstallMenuBar({ dryRun }));
|
|
403
|
+
const settings = readClaudeSettings(paths.claudeSettingsFile);
|
|
404
|
+
const existing = getClaudeStatusLineCommand(settings);
|
|
405
|
+
if (existing === paths.claudeStatusLineScript && settings) {
|
|
406
|
+
const originalStatusLine = originalStatusLineFromMetadata(paths);
|
|
407
|
+
if (dryRun) {
|
|
408
|
+
if (originalStatusLine) {
|
|
409
|
+
messages.push(`[dry-run] would restore user-managed statusLine.command in ${paths.claudeSettingsFile}`);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
messages.push(`[dry-run] would remove coding-usage-bar statusLine from ${paths.claudeSettingsFile}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
if (originalStatusLine) {
|
|
417
|
+
settings.statusLine = originalStatusLine;
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
delete settings.statusLine;
|
|
421
|
+
}
|
|
422
|
+
writeJsonAtomic(paths.claudeSettingsFile, settings);
|
|
423
|
+
fs.rmSync(paths.claudeStatusLineScript, { force: true });
|
|
424
|
+
fs.rmSync(originalStatusLineFile(paths), { force: true });
|
|
425
|
+
messages.push(originalStatusLine ? "Restored user-managed Claude status line." : "Removed coding-usage-bar managed Claude status line.");
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else if (existing) {
|
|
429
|
+
messages.push("Claude status line is user-managed; leaving it unchanged.");
|
|
430
|
+
}
|
|
431
|
+
return messages.length ? messages : ["Nothing to uninstall."];
|
|
432
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RuntimePaths, StatusSnapshot } from "./types.js";
|
|
2
|
+
export declare function readCompactMode(paths?: RuntimePaths): boolean;
|
|
3
|
+
export declare function toggleCompactMode(paths?: RuntimePaths): boolean;
|
|
4
|
+
export declare function swiftBarPluginPath(paths?: RuntimePaths): {
|
|
5
|
+
dir: string;
|
|
6
|
+
file: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function isSwiftBarInstalled(): boolean;
|
|
9
|
+
export declare function ensureSwiftBarInstalled(options?: {
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
}): string[];
|
|
12
|
+
export declare function openSwiftBar(): string[];
|
|
13
|
+
export declare function addSwiftBarToLoginItems(options?: {
|
|
14
|
+
dryRun?: boolean;
|
|
15
|
+
}): string[];
|
|
16
|
+
export declare function renderMenuBar(snapshot?: StatusSnapshot, paths?: RuntimePaths): string;
|
|
17
|
+
export declare function swiftBarStatusItemVisibilityKeys(defaultsOutput: string): string[];
|
|
18
|
+
export declare function installMenuBar(options?: {
|
|
19
|
+
dryRun?: boolean;
|
|
20
|
+
}): string[];
|
|
21
|
+
export declare function uninstallMenuBar(options?: {
|
|
22
|
+
dryRun?: boolean;
|
|
23
|
+
}): string[];
|