@usagefleet/cli 1.2.72 → 1.2.74
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/README.md +5 -5
- package/dist/completion.js +92 -8
- package/dist/index.js +19 -4
- package/dist/notifier.js +1 -7
- package/dist/release.js +1 -1
- package/dist/ui.js +0 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,12 +43,12 @@ usagefleet uninstall # remove it
|
|
|
43
43
|
usagefleet completion zsh # print a shell completion script (zsh, fish)
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
`install` sets up completions for you, for each shell you actually use — zsh
|
|
47
|
+
gets `~/.zsh/completions/_usagefleet` plus an `fpath` block appended to
|
|
48
|
+
`.zshrc`, fish gets `~/.config/fish/completions/usagefleet.fish`. Restart the
|
|
49
|
+
shell once. `uninstall` removes both again, and self-update keeps them current.
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
usagefleet completion zsh > ~/.zsh/completions/_usagefleet # a dir on your fpath
|
|
50
|
-
usagefleet completion fish > ~/.config/fish/completions/usagefleet.fish
|
|
51
|
-
```
|
|
51
|
+
`completion` stays for piping a script somewhere else yourself.
|
|
52
52
|
|
|
53
53
|
## What it collects
|
|
54
54
|
|
package/dist/completion.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { writeFileAtomic } from './atomic-write.js';
|
|
5
|
+
/** The user-facing command list, shared by `help` and the generated completion
|
|
6
|
+
* scripts, so a command can't land in one and be missing from the other. `args`
|
|
7
|
+
* is the hint `help` prints after the name; completion only needs the bare name.
|
|
8
|
+
*
|
|
9
|
+
* `watch` is deliberately absent: it is the entrypoint the installed service
|
|
10
|
+
* runs, not something to type. It still dispatches — every plist and unit on
|
|
11
|
+
* disk names it — it just isn't advertised. */
|
|
4
12
|
export const commands = [
|
|
5
13
|
{ name: 'run', meaning: 'scan once, upload usage + report limits' },
|
|
6
|
-
{ name: 'watch', args: '[--interval s]', meaning: 'poll continuously (default 15s)' },
|
|
7
14
|
{ name: 'limits', meaning: 'report only your real 5h/weekly usage' },
|
|
8
15
|
{ name: 'guard', meaning: 'exit 2 when the group is over a blocking limit' },
|
|
9
16
|
{ name: 'update', meaning: 'update to the latest release now' },
|
|
@@ -11,17 +18,94 @@ export const commands = [
|
|
|
11
18
|
{ name: 'status', meaning: 'service health, limits, resolved config' },
|
|
12
19
|
{ name: 'config', meaning: 'config file location and env overrides' },
|
|
13
20
|
{ name: 'completion', args: '<zsh|fish>', meaning: 'print a shell completion script' },
|
|
14
|
-
{ name: 'version', meaning: 'print the release version' },
|
|
15
21
|
{ name: 'install', args: '--token <t>', meaning: 'configure + install the service and prompt guard' },
|
|
16
22
|
{ name: 'uninstall', meaning: 'remove the service and the guard' },
|
|
17
23
|
];
|
|
18
24
|
export const shells = ['zsh', 'fish'];
|
|
19
|
-
/** A completion script for `shell`, on stdout
|
|
20
|
-
*
|
|
21
|
-
* Writing those files ourselves would mean guessing at fpath and rc files. */
|
|
25
|
+
/** A completion script for `shell`, on stdout. `install` writes these to the
|
|
26
|
+
* right place automatically; this command stays for piping one somewhere else. */
|
|
22
27
|
export function completionScript(shell) {
|
|
23
28
|
return shell === 'zsh' ? zsh() : fish();
|
|
24
29
|
}
|
|
30
|
+
/** Sentinels around our .zshrc block, so `uninstall` removes exactly what we
|
|
31
|
+
* added even after the user has edited around it. */
|
|
32
|
+
const RC_START = '# >>> usagefleet completions >>>';
|
|
33
|
+
const RC_END = '# <<< usagefleet completions <<<';
|
|
34
|
+
// Resolved per call, not once at import: homedir() is what the tests vary, and
|
|
35
|
+
// service.ts derives its paths the same way.
|
|
36
|
+
const zshDir = () => join(homedir(), '.zsh', 'completions');
|
|
37
|
+
const zshrc = () => join(homedir(), '.zshrc');
|
|
38
|
+
/** Where each shell loads completions from. fish scans its directory with no
|
|
39
|
+
* further setup; zsh only scans what is on fpath, which is why zsh also needs
|
|
40
|
+
* the .zshrc block. */
|
|
41
|
+
function completionPath(shell) {
|
|
42
|
+
return shell === 'fish'
|
|
43
|
+
? join(homedir(), '.config', 'fish', 'completions', 'usagefleet.fish')
|
|
44
|
+
: join(zshDir(), '_usagefleet');
|
|
45
|
+
}
|
|
46
|
+
/** Only touch a shell the user actually runs — as the login shell or by already
|
|
47
|
+
* having its config on disk. Installing should not scatter dotfiles for shells
|
|
48
|
+
* that were never used. */
|
|
49
|
+
function shellInUse(shell) {
|
|
50
|
+
if ((process.env.SHELL ?? '').includes(shell)) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return existsSync(shell === 'fish' ? join(homedir(), '.config', 'fish') : zshrc());
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Put the completion scripts where each shell looks, and for zsh make sure that
|
|
57
|
+
* directory is on fpath. Re-running is a no-op beyond rewriting the scripts, so
|
|
58
|
+
* self-update refreshes completions for free.
|
|
59
|
+
*
|
|
60
|
+
* Returns one row per shell touched: the script path, plus the rc file when this
|
|
61
|
+
* call was the one that added the block.
|
|
62
|
+
*/
|
|
63
|
+
export function installCompletions() {
|
|
64
|
+
const done = [];
|
|
65
|
+
for (const shell of shells) {
|
|
66
|
+
if (!shellInUse(shell)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const path = completionPath(shell);
|
|
70
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
71
|
+
writeFileAtomic(path, completionScript(shell));
|
|
72
|
+
done.push(shell === 'zsh' ? { shell, path, rc: ensureZshFpath() } : { shell, path });
|
|
73
|
+
}
|
|
74
|
+
return done;
|
|
75
|
+
}
|
|
76
|
+
/** Append the fpath block to .zshrc, once. Returns the file when this call added
|
|
77
|
+
* it, undefined when it was already there. */
|
|
78
|
+
function ensureZshFpath() {
|
|
79
|
+
const rc = zshrc();
|
|
80
|
+
const current = existsSync(rc) ? readFileSync(rc, 'utf-8') : '';
|
|
81
|
+
if (current.includes(RC_START)) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
// Appended, never prepended: fpath has to be set before the compinit that
|
|
85
|
+
// reads it, and we cannot know whether the user already ran compinit earlier
|
|
86
|
+
// in the file. Adding our own after theirs is the only ordering that works
|
|
87
|
+
// from the end of the file; -C skips the dump check so the second run is cheap.
|
|
88
|
+
const block = `${RC_START}\nfpath=("${zshDir().replace(homedir(), '$HOME')}" $fpath)\nautoload -Uz compinit && compinit -C\n${RC_END}\n`;
|
|
89
|
+
writeFileAtomic(rc, current === '' || current.endsWith('\n') ? current + block : `${current}\n${block}`);
|
|
90
|
+
return rc;
|
|
91
|
+
}
|
|
92
|
+
/** Undo installCompletions: drop the scripts and strip the .zshrc block. */
|
|
93
|
+
export function removeCompletions() {
|
|
94
|
+
for (const shell of shells) {
|
|
95
|
+
rmSync(completionPath(shell), { force: true });
|
|
96
|
+
}
|
|
97
|
+
const rc = zshrc();
|
|
98
|
+
if (!existsSync(rc)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const current = readFileSync(rc, 'utf-8');
|
|
102
|
+
const start = current.indexOf(RC_START);
|
|
103
|
+
const end = current.indexOf(RC_END);
|
|
104
|
+
if (start === -1 || end < start) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
writeFileAtomic(rc, current.slice(0, start) + current.slice(end + RC_END.length).replace(/^\n/, ''));
|
|
108
|
+
}
|
|
25
109
|
function zsh() {
|
|
26
110
|
// zsh splits a _describe entry on the first colon, so any colon in the text
|
|
27
111
|
// has to be escaped or the description gets cut in half.
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { detectClaudeCreds } from './claude-creds.js';
|
|
3
3
|
import { reportLimitsOnce, runOnce } from './collector.js';
|
|
4
|
-
import { commands, completionScript, shells } from './completion.js';
|
|
4
|
+
import { commands, completionScript, installCompletions, removeCompletions, shells, } from './completion.js';
|
|
5
5
|
import { loadConfig } from './config.js';
|
|
6
6
|
import { runGuard } from './guard.js';
|
|
7
7
|
import { loadNotifyConfig } from './notifier.js';
|
|
@@ -211,6 +211,20 @@ async function cmdInstall() {
|
|
|
211
211
|
}
|
|
212
212
|
const { install } = await import('./service.js');
|
|
213
213
|
install();
|
|
214
|
+
// After the service, so a completion problem can never fail the part that
|
|
215
|
+
// matters. Self-update re-runs `install`, which keeps completions in step with
|
|
216
|
+
// new commands without the user doing anything.
|
|
217
|
+
try {
|
|
218
|
+
for (const { shell, path, rc } of installCompletions()) {
|
|
219
|
+
console.log(step(`${shell} completions`, tilde(path)));
|
|
220
|
+
if (rc) {
|
|
221
|
+
console.log(row('shell', `${tilde(rc)} updated · restart the shell to pick it up`));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
console.log(warn('completions', error.message));
|
|
227
|
+
}
|
|
214
228
|
}
|
|
215
229
|
/** Padded two-column list — name in white, meaning in gray, like the result
|
|
216
230
|
* lines. Shared by `help` and `config`. */
|
|
@@ -310,8 +324,7 @@ async function main() {
|
|
|
310
324
|
case 'completion': {
|
|
311
325
|
return cmdCompletion();
|
|
312
326
|
}
|
|
313
|
-
//
|
|
314
|
-
case 'version':
|
|
327
|
+
// Flags only: bare `usagefleet` already prints the version in its header.
|
|
315
328
|
case '--version':
|
|
316
329
|
case '-v': {
|
|
317
330
|
console.log(RELEASE_VERSION);
|
|
@@ -324,7 +337,9 @@ async function main() {
|
|
|
324
337
|
}
|
|
325
338
|
case 'uninstall': {
|
|
326
339
|
const { uninstall } = await import('./service.js');
|
|
327
|
-
|
|
340
|
+
uninstall();
|
|
341
|
+
removeCompletions();
|
|
342
|
+
return;
|
|
328
343
|
}
|
|
329
344
|
default: {
|
|
330
345
|
return help();
|
package/dist/notifier.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { sendNotification } from './notify.js';
|
|
2
|
-
import {
|
|
2
|
+
import { readStore, storePath, updateStore } from './store.js';
|
|
3
3
|
const DEFAULT_THRESHOLDS = [80, 95];
|
|
4
4
|
/** Resolve notify config from env. Enabled by default; disable with
|
|
5
5
|
* USAGEFLEET_NOTIFY=0 (also: false/off/no). Thresholds from
|
|
@@ -20,12 +20,6 @@ export function loadNotifyConfig(env = process.env) {
|
|
|
20
20
|
}
|
|
21
21
|
return { enabled, thresholds };
|
|
22
22
|
}
|
|
23
|
-
/** Per-window high-water mark so each threshold notifies at most once per
|
|
24
|
-
* window. `resetsAt` ties the mark to a specific window — when it changes the
|
|
25
|
-
* window has rolled over and the mark clears. */
|
|
26
|
-
export function emptyNotifyState() {
|
|
27
|
-
return { fiveHour: freshWindow(), sevenDay: freshWindow() };
|
|
28
|
-
}
|
|
29
23
|
/**
|
|
30
24
|
* Decide whether a window crosses a not-yet-notified threshold. Pure — no IO.
|
|
31
25
|
* Returns the threshold to fire (or null) and the next persisted state.
|
package/dist/release.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by .github/workflows/release.yml.
|
|
2
|
-
export const RELEASE_VERSION = "1.2.
|
|
2
|
+
export const RELEASE_VERSION = "1.2.74";
|
package/dist/ui.js
CHANGED
|
@@ -22,7 +22,6 @@ export const green = paint('32');
|
|
|
22
22
|
export const yellow = paint('33');
|
|
23
23
|
export const red = paint('31');
|
|
24
24
|
export const blue = paint('34');
|
|
25
|
-
export const bold = paint('1');
|
|
26
25
|
/** Width of the label column shared by every labelled line. */
|
|
27
26
|
const LABEL = 12;
|
|
28
27
|
/** "usagefleet 1.2.55 mac-arm64" — the banner an interactive command opens
|