@modelprofile.com/authswitch 10.0.1 → 10.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/accounts.d.ts +0 -17
- package/dist_ts/accounts.js +2 -82
- package/dist_ts/classes.authoritycli.js +3 -2
- package/dist_ts/classes.cli.js +3 -2
- package/dist_ts/classes.codexpreuse.js +1 -1
- package/dist_ts/classes.limits.js +3 -2
- package/dist_ts/classes.listrenderer.js +3 -2
- package/dist_ts/classes.tui.js +3 -2
- package/dist_ts/classes.watch.js +3 -2
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/preuse-text.d.ts +18 -0
- package/dist_ts/preuse-text.js +28 -0
- package/dist_ts/preuse.d.ts +9 -36
- package/dist_ts/preuse.js +3 -27
- package/dist_ts/timetext.d.ts +23 -0
- package/dist_ts/timetext.js +88 -0
- package/dist_ts/watchpolicy.js +3 -2
- package/package.json +5 -1
- package/readme.md +22 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/accounts.ts +1 -69
- package/ts/classes.authoritycli.ts +2 -1
- package/ts/classes.cli.ts +2 -1
- package/ts/classes.codexpreuse.ts +3 -3
- package/ts/classes.limits.ts +2 -1
- package/ts/classes.listrenderer.ts +2 -1
- package/ts/classes.tui.ts +2 -1
- package/ts/classes.watch.ts +2 -1
- package/ts/index.ts +1 -0
- package/ts/preuse-text.ts +40 -0
- package/ts/preuse.ts +9 -55
- package/ts/timetext.ts +76 -0
- package/ts/watchpolicy.ts +2 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human time text: countdowns to a deadline and timestamps in local calendar time.
|
|
3
|
+
*
|
|
4
|
+
* It imports nothing, so the browser-safe `./preuse-text` entry point can render a reset exactly as the
|
|
5
|
+
* command line does without pulling a Node built-in into a bundle.
|
|
6
|
+
*/
|
|
7
|
+
/** A countdown is a snapshot of the provider's deadline, not proof a quota has reset. */
|
|
8
|
+
export const until = (timestampArg, nowArg) => {
|
|
9
|
+
if (timestampArg === null)
|
|
10
|
+
return 'Not scheduled';
|
|
11
|
+
const remaining = Date.parse(timestampArg) - nowArg;
|
|
12
|
+
if (!Number.isFinite(remaining))
|
|
13
|
+
return 'Unavailable';
|
|
14
|
+
if (remaining <= 0)
|
|
15
|
+
return 'due';
|
|
16
|
+
const minutes = Math.floor(remaining / 60000);
|
|
17
|
+
if (!minutes)
|
|
18
|
+
return '<1min';
|
|
19
|
+
const days = Math.floor(minutes / 1440);
|
|
20
|
+
const hours = Math.floor(minutes % 1440 / 60);
|
|
21
|
+
return [days ? `${days}d` : '', hours ? `${hours}h` : '', minutes % 60 ? `${minutes % 60}min` : ''].filter(Boolean).join(' ');
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* A two-unit countdown for condensed tables: `2h 13m`, `3d 4h`, `45m`.
|
|
25
|
+
*
|
|
26
|
+
* `until` stays the long form used by the list view and the TUI. This one trades the third unit for
|
|
27
|
+
* a column that never wraps, and it never invents a deadline: an unknown or unparseable timestamp is
|
|
28
|
+
* reported as such rather than as zero time remaining.
|
|
29
|
+
*/
|
|
30
|
+
const twoUnits = (millisecondsArg) => {
|
|
31
|
+
const minutes = Math.floor(millisecondsArg / 60000);
|
|
32
|
+
const days = Math.floor(minutes / 1440);
|
|
33
|
+
const hours = Math.floor(minutes % 1440 / 60);
|
|
34
|
+
if (days)
|
|
35
|
+
return hours ? `${days}d ${hours}h` : `${days}d`;
|
|
36
|
+
if (hours)
|
|
37
|
+
return minutes % 60 ? `${hours}h ${minutes % 60}m` : `${hours}h`;
|
|
38
|
+
return `${minutes}m`;
|
|
39
|
+
};
|
|
40
|
+
export const compactUntil = (timestampArg, nowArg, unavailableArg = 'n/a') => {
|
|
41
|
+
if (timestampArg === null)
|
|
42
|
+
return 'not scheduled';
|
|
43
|
+
const remaining = Date.parse(timestampArg) - nowArg;
|
|
44
|
+
if (!Number.isFinite(remaining))
|
|
45
|
+
return unavailableArg;
|
|
46
|
+
if (remaining <= 0)
|
|
47
|
+
return 'due';
|
|
48
|
+
return remaining < 60000 ? '<1m' : twoUnits(remaining);
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The same two-unit form for a past timestamp, for "saved 3d 4h ago".
|
|
52
|
+
*
|
|
53
|
+
* The result is always a bare duration or the caller's unavailable marker, never a phrase: callers
|
|
54
|
+
* suffix it ("... ago"), and a phrase would read as "just now ago". A span under a minute is `<1m`,
|
|
55
|
+
* exactly as `compactUntil` reports one.
|
|
56
|
+
*/
|
|
57
|
+
export const compactSince = (timestampArg, nowArg, unavailableArg = 'n/a') => {
|
|
58
|
+
if (timestampArg === null)
|
|
59
|
+
return unavailableArg;
|
|
60
|
+
const elapsed = nowArg - Date.parse(timestampArg);
|
|
61
|
+
if (!Number.isFinite(elapsed))
|
|
62
|
+
return unavailableArg;
|
|
63
|
+
return elapsed < 60000 ? '<1m' : twoUnits(elapsed);
|
|
64
|
+
};
|
|
65
|
+
const twoDigits = (valueArg) => String(valueArg).padStart(2, '0');
|
|
66
|
+
/**
|
|
67
|
+
* A timestamp in local calendar time: `2026-10-08`, or `2026-10-08 23:14` with the time. Human output never prints
|
|
68
|
+
* a raw ISO timestamp; an unknown or unparseable one reads as the caller's unavailable marker.
|
|
69
|
+
*/
|
|
70
|
+
const localTimestamp = (timestampArg, withTimeArg, unavailableArg) => {
|
|
71
|
+
const time = timestampArg === null ? Number.NaN : Date.parse(timestampArg);
|
|
72
|
+
if (!Number.isFinite(time))
|
|
73
|
+
return unavailableArg;
|
|
74
|
+
const date = new Date(time);
|
|
75
|
+
const day = `${date.getFullYear()}-${twoDigits(date.getMonth() + 1)}-${twoDigits(date.getDate())}`;
|
|
76
|
+
return withTimeArg ? `${day} ${twoDigits(date.getHours())}:${twoDigits(date.getMinutes())}` : day;
|
|
77
|
+
};
|
|
78
|
+
export const localDate = (timestampArg, unavailableArg = 'n/a') => localTimestamp(timestampArg, false, unavailableArg);
|
|
79
|
+
export const localDateTime = (timestampArg, unavailableArg = 'n/a') => localTimestamp(timestampArg, true, unavailableArg);
|
|
80
|
+
/** A moment's local time of day with seconds, `14:02:07`, for a log line whose date is implied. */
|
|
81
|
+
export const localTime = (timeArg) => {
|
|
82
|
+
const date = new Date(timeArg);
|
|
83
|
+
return [date.getHours(), date.getMinutes(), date.getSeconds()].map(twoDigits).join(':');
|
|
84
|
+
};
|
|
85
|
+
const ISO_TIMESTAMP = /\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})/g;
|
|
86
|
+
/** Provider text with every ISO 8601 timestamp in it rewritten as local date and time. */
|
|
87
|
+
export const localiseTimestamps = (textArg) => textArg.replace(ISO_TIMESTAMP, match => localDateTime(match, match));
|
|
88
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGltZXRleHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy90aW1ldGV4dC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7R0FLRztBQUVILHlGQUF5RjtBQUN6RixNQUFNLENBQUMsTUFBTSxLQUFLLEdBQUcsQ0FBQyxZQUEyQixFQUFFLE1BQWMsRUFBVSxFQUFFO0lBQzNFLElBQUksWUFBWSxLQUFLLElBQUk7UUFBRSxPQUFPLGVBQWUsQ0FBQztJQUNsRCxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxHQUFHLE1BQU0sQ0FBQztJQUNwRCxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxTQUFTLENBQUM7UUFBRSxPQUFPLGFBQWEsQ0FBQztJQUN0RCxJQUFJLFNBQVMsSUFBSSxDQUFDO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDakMsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDLENBQUM7SUFDOUMsSUFBSSxDQUFDLE9BQU87UUFBRSxPQUFPLE9BQU8sQ0FBQztJQUM3QixNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUMsQ0FBQztJQUN4QyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sR0FBRyxJQUFJLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDOUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLE9BQU8sR0FBRyxFQUFFLENBQUMsQ0FBQyxDQUFDLEdBQUcsT0FBTyxHQUFHLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0FBQ2hJLENBQUMsQ0FBQztBQUNGOzs7Ozs7R0FNRztBQUNILE1BQU0sUUFBUSxHQUFHLENBQUMsZUFBdUIsRUFBVSxFQUFFO0lBQ25ELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsZUFBZSxHQUFHLEtBQUssQ0FBQyxDQUFDO0lBQ3BELE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQyxDQUFDO0lBQ3hDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxHQUFHLElBQUksR0FBRyxFQUFFLENBQUMsQ0FBQztJQUM5QyxJQUFJLElBQUk7UUFBRSxPQUFPLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxJQUFJLEtBQUssS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsSUFBSSxHQUFHLENBQUM7SUFDM0QsSUFBSSxLQUFLO1FBQUUsT0FBTyxPQUFPLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxHQUFHLEtBQUssS0FBSyxPQUFPLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsS0FBSyxHQUFHLENBQUM7SUFDNUUsT0FBTyxHQUFHLE9BQU8sR0FBRyxDQUFDO0FBQ3ZCLENBQUMsQ0FBQztBQUNGLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxDQUFDLFlBQTJCLEVBQUUsTUFBYyxFQUFFLGNBQWMsR0FBRyxLQUFLLEVBQVUsRUFBRTtJQUMxRyxJQUFJLFlBQVksS0FBSyxJQUFJO1FBQUUsT0FBTyxlQUFlLENBQUM7SUFDbEQsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsR0FBRyxNQUFNLENBQUM7SUFDcEQsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDO1FBQUUsT0FBTyxjQUFjLENBQUM7SUFDdkQsSUFBSSxTQUFTLElBQUksQ0FBQztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ2pDLE9BQU8sU0FBUyxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDLENBQUM7QUFDekQsQ0FBQyxDQUFDO0FBQ0Y7Ozs7OztHQU1HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sWUFBWSxHQUFHLENBQUMsWUFBMkIsRUFBRSxNQUFjLEVBQUUsY0FBYyxHQUFHLEtBQUssRUFBVSxFQUFFO0lBQzFHLElBQUksWUFBWSxLQUFLLElBQUk7UUFBRSxPQUFPLGNBQWMsQ0FBQztJQUNqRCxNQUFNLE9BQU8sR0FBRyxNQUFNLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUNsRCxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUM7UUFBRSxPQUFPLGNBQWMsQ0FBQztJQUNyRCxPQUFPLE9BQU8sR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0FBQ3JELENBQUMsQ0FBQztBQUNGLE1BQU0sU0FBUyxHQUFHLENBQUMsUUFBZ0IsRUFBVSxFQUFFLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUM7QUFDbEY7OztHQUdHO0FBQ0gsTUFBTSxjQUFjLEdBQUcsQ0FBQyxZQUEyQixFQUFFLFdBQW9CLEVBQUUsY0FBc0IsRUFBVSxFQUFFO0lBQzNHLE1BQU0sSUFBSSxHQUFHLFlBQVksS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsWUFBWSxDQUFDLENBQUM7SUFDM0UsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDO1FBQUUsT0FBTyxjQUFjLENBQUM7SUFDbEQsTUFBTSxJQUFJLEdBQUcsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDNUIsTUFBTSxHQUFHLEdBQUcsR0FBRyxJQUFJLENBQUMsV0FBVyxFQUFFLElBQUksU0FBUyxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsR0FBRyxDQUFDLENBQUMsSUFBSSxTQUFTLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDLEVBQUUsQ0FBQztJQUNuRyxPQUFPLFdBQVcsQ0FBQyxDQUFDLENBQUMsR0FBRyxHQUFHLElBQUksU0FBUyxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxJQUFJLFNBQVMsQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUM7QUFDcEcsQ0FBQyxDQUFDO0FBQ0YsTUFBTSxDQUFDLE1BQU0sU0FBUyxHQUFHLENBQUMsWUFBMkIsRUFBRSxjQUFjLEdBQUcsS0FBSyxFQUFVLEVBQUUsQ0FBQyxjQUFjLENBQUMsWUFBWSxFQUFFLEtBQUssRUFBRSxjQUFjLENBQUMsQ0FBQztBQUM5SSxNQUFNLENBQUMsTUFBTSxhQUFhLEdBQUcsQ0FBQyxZQUEyQixFQUFFLGNBQWMsR0FBRyxLQUFLLEVBQVUsRUFBRSxDQUFDLGNBQWMsQ0FBQyxZQUFZLEVBQUUsSUFBSSxFQUFFLGNBQWMsQ0FBQyxDQUFDO0FBQ2pKLG1HQUFtRztBQUNuRyxNQUFNLENBQUMsTUFBTSxTQUFTLEdBQUcsQ0FBQyxPQUFlLEVBQVUsRUFBRTtJQUNuRCxNQUFNLElBQUksR0FBRyxJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUMvQixPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxFQUFFLElBQUksQ0FBQyxVQUFVLEVBQUUsRUFBRSxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0FBQzFGLENBQUMsQ0FBQztBQUNGLE1BQU0sYUFBYSxHQUFHLDZFQUE2RSxDQUFDO0FBQ3BHLDBGQUEwRjtBQUMxRixNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLE9BQWUsRUFBVSxFQUFFLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxhQUFhLEVBQUUsS0FBSyxDQUFDLEVBQUUsQ0FBQyxhQUFhLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUMifQ==
|
package/dist_ts/watchpolicy.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { usagePercentText } from './accounts.js';
|
|
2
|
+
import { compactUntil } from './timetext.js';
|
|
2
3
|
import { plainText } from './formatting.js';
|
|
3
4
|
/**
|
|
4
5
|
* How far below the threshold a switch target must be on every limit, so that a switch never lands on an account that
|
|
@@ -79,4 +80,4 @@ export const decideWatchAction = (inputArg) => {
|
|
|
79
80
|
}
|
|
80
81
|
return stay(true, `every limit below ${threshold}%`);
|
|
81
82
|
};
|
|
82
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
83
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2F0Y2hwb2xpY3kuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy93YXRjaHBvbGljeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDakQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUM3QyxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUE4QjVDOzs7R0FHRztBQUNILE1BQU0sZ0JBQWdCLEdBQUcsRUFBRSxDQUFDO0FBRTVCLE1BQU0sY0FBYyxHQUFHLENBQUMsVUFBeUIsRUFBeUIsRUFBRSxDQUFDLENBQUMsVUFBVSxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsS0FBSyxLQUFLLFNBQVMsQ0FBQyxDQUFDO0FBQ3JKLE1BQU0sY0FBYyxHQUFHLENBQUMsVUFBeUIsRUFBeUIsRUFBRSxDQUFDLENBQUMsVUFBVSxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsS0FBSyxLQUFLLFNBQVMsQ0FBQyxDQUFDO0FBRXJKLDZIQUE2SDtBQUM3SCxNQUFNLE9BQU8sR0FBRyxDQUFDLFVBQTBDLEVBQUUsWUFBb0IsRUFBbUMsRUFBRSxDQUNwSCxVQUFVLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLFdBQVcsSUFBSSxZQUFZLENBQUM7S0FDNUQsSUFBSSxDQUFDLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUMsS0FBSyxDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUMsV0FBVyxJQUFJLEtBQUssQ0FBQyxlQUFlLEdBQUcsSUFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBRXBILE1BQU0sU0FBUyxH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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelprofile.com/authswitch",
|
|
3
|
-
"version": "10.0
|
|
3
|
+
"version": "10.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Manage Codex, OpenCode and Claude Code accounts with guided switching and live usage status",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
"types": "./dist_ts/mutation.d.ts",
|
|
15
15
|
"import": "./dist_ts/mutation.js"
|
|
16
16
|
},
|
|
17
|
+
"./preuse-text": {
|
|
18
|
+
"types": "./dist_ts/preuse-text.d.ts",
|
|
19
|
+
"import": "./dist_ts/preuse-text.js"
|
|
20
|
+
},
|
|
17
21
|
"./authority-contract": {
|
|
18
22
|
"types": "./dist_ts/authority-contract.d.ts",
|
|
19
23
|
"import": "./dist_ts/authority-contract.js"
|
package/readme.md
CHANGED
|
@@ -291,6 +291,26 @@ reason is `outcome_unknown` with problem `request_uncertain` and carries its cau
|
|
|
291
291
|
(`{ kind, status, providerCode }`, for example `{ "kind": "http", "status": 503, "providerCode": null }`),
|
|
292
292
|
never the provider's text. Human output prints both as sentences.
|
|
293
293
|
|
|
294
|
+
A host that shows receipts in a browser words them the same way through
|
|
295
|
+
`@modelprofile.com/authswitch/preuse-text`, the browser-safe entry point for those sentences:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
import { describePreuseFailure, describePreuseLimit, localDateTime } from '@modelprofile.com/authswitch/preuse-text';
|
|
299
|
+
import type { IAuthSwitchPreuseOperation } from '@modelprofile.com/authswitch/authority-contract';
|
|
300
|
+
|
|
301
|
+
const lines = (operation: IAuthSwitchPreuseOperation): string[] => [
|
|
302
|
+
...(operation.limit ? [describePreuseLimit(operation.limit, Date.now())] : []),
|
|
303
|
+
...(operation.failure ? [`Cause: ${describePreuseFailure(operation.failure)}`] : []),
|
|
304
|
+
];
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`describePreuseLimit(limit, now)` names the window and the limit, and a stated reset as a countdown from
|
|
308
|
+
`now` with the local time (`Weekly usage limit reached, resets in 16h 46min (2026-09-27 05:12)`, or `due
|
|
309
|
+
to reset now` once it has passed); `describePreuseFailure(failure)` names the cause as the tail of a
|
|
310
|
+
sentence (`the provider answered HTTP 503`). `localDateTime(timestamp)` is the local time both use,
|
|
311
|
+
`2026-09-27 05:12`. The module loads nothing but that time text, which imports nothing either; the package
|
|
312
|
+
root exports the same three functions from the same source.
|
|
313
|
+
|
|
294
314
|
For `account add`, `account reauth` and `account operation resume`, `--json` writes JSON Lines:
|
|
295
315
|
each line is one complete operation receipt, and unchanged revisions are omitted. `account
|
|
296
316
|
operation get --json` and `account operation cancel --json` each write one JSON object followed by
|
|
@@ -784,8 +804,8 @@ such as AGL all read this one rule; `remove` and a save that keeps the login
|
|
|
784
804
|
active leave the loaded login in place.
|
|
785
805
|
|
|
786
806
|
That rule, `assertAuthSwitchMutation` and the `TAuthSwitchMutation` command are also
|
|
787
|
-
published on their own at `@modelprofile.com/authswitch/mutation`, which
|
|
788
|
-
|
|
807
|
+
published on their own at `@modelprofile.com/authswitch/mutation`, which a browser can
|
|
808
|
+
bundle, as it can the preuse sentences at `./preuse-text`:
|
|
789
809
|
|
|
790
810
|
```ts
|
|
791
811
|
import { authSwitchMutationReplacesLogin, type TAuthSwitchMutation } from '@modelprofile.com/authswitch/mutation';
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/accounts.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IAccountType, IAuthHarness, IHarnessAccount, IHarnessAccountStatus, IHarnessCredentialDrift, IHarnessState, IHarnessStatusOptions, IHarnessUsageWindow } from './interfaces.harness.js';
|
|
2
2
|
import { plainText } from './formatting.js';
|
|
3
|
+
import { compactUntil, localDateTime, localiseTimestamps, until } from './timetext.js';
|
|
3
4
|
|
|
4
5
|
export interface IAccountRow {
|
|
5
6
|
harness: IAuthHarness;
|
|
@@ -23,75 +24,6 @@ const WINDOW_PERIODS: ReadonlyMap<number, string> = new Map([[18000, 'five-hour'
|
|
|
23
24
|
* The narrow table cells stay on the compact form throughout (`5h 37% · 2h 13m`); a column is not a label.
|
|
24
25
|
*/
|
|
25
26
|
export const usageWindowPeriod = (secondsArg: number): string => WINDOW_PERIODS.get(secondsArg) ?? duration(secondsArg);
|
|
26
|
-
/** A countdown is a snapshot of the provider's deadline, not proof a quota has reset. */
|
|
27
|
-
export const until = (timestampArg: string | null, nowArg: number): string => {
|
|
28
|
-
if (timestampArg === null) return 'Not scheduled';
|
|
29
|
-
const remaining = Date.parse(timestampArg) - nowArg;
|
|
30
|
-
if (!Number.isFinite(remaining)) return 'Unavailable';
|
|
31
|
-
if (remaining <= 0) return 'due';
|
|
32
|
-
const minutes = Math.floor(remaining / 60000);
|
|
33
|
-
if (!minutes) return '<1min';
|
|
34
|
-
const days = Math.floor(minutes / 1440);
|
|
35
|
-
const hours = Math.floor(minutes % 1440 / 60);
|
|
36
|
-
return [days ? `${days}d` : '', hours ? `${hours}h` : '', minutes % 60 ? `${minutes % 60}min` : ''].filter(Boolean).join(' ');
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* A two-unit countdown for condensed tables: `2h 13m`, `3d 4h`, `45m`.
|
|
40
|
-
*
|
|
41
|
-
* `until` stays the long form used by the list view and the TUI. This one trades the third unit for
|
|
42
|
-
* a column that never wraps, and it never invents a deadline: an unknown or unparseable timestamp is
|
|
43
|
-
* reported as such rather than as zero time remaining.
|
|
44
|
-
*/
|
|
45
|
-
const twoUnits = (millisecondsArg: number): string => {
|
|
46
|
-
const minutes = Math.floor(millisecondsArg / 60000);
|
|
47
|
-
const days = Math.floor(minutes / 1440);
|
|
48
|
-
const hours = Math.floor(minutes % 1440 / 60);
|
|
49
|
-
if (days) return hours ? `${days}d ${hours}h` : `${days}d`;
|
|
50
|
-
if (hours) return minutes % 60 ? `${hours}h ${minutes % 60}m` : `${hours}h`;
|
|
51
|
-
return `${minutes}m`;
|
|
52
|
-
};
|
|
53
|
-
export const compactUntil = (timestampArg: string | null, nowArg: number, unavailableArg = 'n/a'): string => {
|
|
54
|
-
if (timestampArg === null) return 'not scheduled';
|
|
55
|
-
const remaining = Date.parse(timestampArg) - nowArg;
|
|
56
|
-
if (!Number.isFinite(remaining)) return unavailableArg;
|
|
57
|
-
if (remaining <= 0) return 'due';
|
|
58
|
-
return remaining < 60000 ? '<1m' : twoUnits(remaining);
|
|
59
|
-
};
|
|
60
|
-
/**
|
|
61
|
-
* The same two-unit form for a past timestamp, for "saved 3d 4h ago".
|
|
62
|
-
*
|
|
63
|
-
* The result is always a bare duration or the caller's unavailable marker, never a phrase: callers
|
|
64
|
-
* suffix it ("... ago"), and a phrase would read as "just now ago". A span under a minute is `<1m`,
|
|
65
|
-
* exactly as `compactUntil` reports one.
|
|
66
|
-
*/
|
|
67
|
-
export const compactSince = (timestampArg: string | null, nowArg: number, unavailableArg = 'n/a'): string => {
|
|
68
|
-
if (timestampArg === null) return unavailableArg;
|
|
69
|
-
const elapsed = nowArg - Date.parse(timestampArg);
|
|
70
|
-
if (!Number.isFinite(elapsed)) return unavailableArg;
|
|
71
|
-
return elapsed < 60000 ? '<1m' : twoUnits(elapsed);
|
|
72
|
-
};
|
|
73
|
-
const twoDigits = (valueArg: number): string => String(valueArg).padStart(2, '0');
|
|
74
|
-
/**
|
|
75
|
-
* A timestamp in local calendar time: `2026-10-08`, or `2026-10-08 23:14` with the time. Human output never prints
|
|
76
|
-
* a raw ISO timestamp; an unknown or unparseable one reads as the caller's unavailable marker.
|
|
77
|
-
*/
|
|
78
|
-
const localTimestamp = (timestampArg: string | null, withTimeArg: boolean, unavailableArg: string): string => {
|
|
79
|
-
const time = timestampArg === null ? Number.NaN : Date.parse(timestampArg);
|
|
80
|
-
if (!Number.isFinite(time)) return unavailableArg;
|
|
81
|
-
const date = new Date(time);
|
|
82
|
-
const day = `${date.getFullYear()}-${twoDigits(date.getMonth() + 1)}-${twoDigits(date.getDate())}`;
|
|
83
|
-
return withTimeArg ? `${day} ${twoDigits(date.getHours())}:${twoDigits(date.getMinutes())}` : day;
|
|
84
|
-
};
|
|
85
|
-
export const localDate = (timestampArg: string | null, unavailableArg = 'n/a'): string => localTimestamp(timestampArg, false, unavailableArg);
|
|
86
|
-
export const localDateTime = (timestampArg: string | null, unavailableArg = 'n/a'): string => localTimestamp(timestampArg, true, unavailableArg);
|
|
87
|
-
/** A moment's local time of day with seconds, `14:02:07`, for a log line whose date is implied. */
|
|
88
|
-
export const localTime = (timeArg: number): string => {
|
|
89
|
-
const date = new Date(timeArg);
|
|
90
|
-
return [date.getHours(), date.getMinutes(), date.getSeconds()].map(twoDigits).join(':');
|
|
91
|
-
};
|
|
92
|
-
const ISO_TIMESTAMP = /\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})/g;
|
|
93
|
-
/** Provider text with every ISO 8601 timestamp in it rewritten as local date and time. */
|
|
94
|
-
export const localiseTimestamps = (textArg: string): string => textArg.replace(ISO_TIMESTAMP, match => localDateTime(match, match));
|
|
95
27
|
|
|
96
28
|
/**
|
|
97
29
|
* One sentence for a credential file that no longer holds the account the last switch wrote.
|
|
@@ -6,7 +6,8 @@ import type { IAuthSwitchImportInventory, IAuthSwitchImportResult, IAuthSwitchIm
|
|
|
6
6
|
IReq_AuthSwitchImportStatus, TAuthSwitchImportAction } from './authority-import-contract.js';
|
|
7
7
|
import { AuthSwitchClient } from './classes.authorityclient.js';
|
|
8
8
|
import { resolveAuthSwitchAuthorityPaths } from './classes.authorityservice.js';
|
|
9
|
-
import { defaultPreusePrompt,
|
|
9
|
+
import { defaultPreusePrompt, validatePreuseOptions } from './preuse.js';
|
|
10
|
+
import { describePreuseFailure, describePreuseLimit } from './preuse-text.js';
|
|
10
11
|
import { plainText } from './formatting.js';
|
|
11
12
|
import * as plugins from './plugins.js';
|
|
12
13
|
|
package/ts/classes.cli.ts
CHANGED
|
@@ -9,7 +9,8 @@ import { readAccountList } from './classes.accountlist.js';
|
|
|
9
9
|
import { AccountListRenderer } from './classes.listrenderer.js';
|
|
10
10
|
import { accountLimits, activeAccounts, CondensedRenderer } from './classes.limits.js';
|
|
11
11
|
import { consoleHeading, consoleTable } from './consoletable.js';
|
|
12
|
-
import { accountName, credentialDriftNote, readAccountBadges,
|
|
12
|
+
import { accountName, credentialDriftNote, readAccountBadges, usagePercentText } from './accounts.js';
|
|
13
|
+
import { until } from './timetext.js';
|
|
13
14
|
import { describeHarnessProcesses, describeStopOutcome } from './classes.harnessprocesses.js';
|
|
14
15
|
import { defaultPreusePrompt, preuseAccounts, preuseCountsText, PreuseError, preuseTargets, validatePreuseOptions,
|
|
15
16
|
type IPreuseRunSummary, type TPreuseAccountResult, type TPreuseOutcome, type TPreuseRunEvent } from './preuse.js';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import type { ICodexIdentity } from './interfaces.js';
|
|
3
3
|
import type { IHarnessPreuseOptions, IHarnessPreuseResult } from './interfaces.harness.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import type { IAuthSwitchPreuseLimit } from './authority-contract.js';
|
|
5
|
+
import { PreuseError, PreuseFailureError, PreuseLimitError, preuseMayHaveConsumed, validatePreuseOptions } from './preuse.js';
|
|
6
6
|
import { parseOpenAiTokenClaims } from './openaiauth.js';
|
|
7
7
|
import { OpenAiBackendError, readOpenAiAccountUsage, type IOpenAiAccountUsage } from './openaiusage.js';
|
|
8
8
|
import { usageWindowPeriod } from './accounts.js';
|
|
@@ -14,7 +14,7 @@ const PREUSE_TIMEOUT_MS = 180_000;
|
|
|
14
14
|
* The limit the account's own usage reading says is reached, or null. Every window at its limit must reset
|
|
15
15
|
* before a prompt can pass, so the one that resets last is named.
|
|
16
16
|
*/
|
|
17
|
-
const reachedLimit = (usage: IOpenAiAccountUsage):
|
|
17
|
+
const reachedLimit = (usage: IOpenAiAccountUsage): IAuthSwitchPreuseLimit | null => {
|
|
18
18
|
const limit = usage.rateLimits.rateLimit;
|
|
19
19
|
if (!limit || (limit.allowed && !limit.limitReached)) return null;
|
|
20
20
|
const exhausted = [limit.primaryWindow, limit.secondaryWindow]
|
package/ts/classes.limits.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
|
-
import { accountTypeText,
|
|
2
|
+
import { accountTypeText, credentialDriftNote, orderedUsageWindows, usagePercentText } from './accounts.js';
|
|
3
|
+
import { compactSince, compactUntil, localDate } from './timetext.js';
|
|
3
4
|
import { consoleHeading, consoleNote, consoleTable } from './consoletable.js';
|
|
4
5
|
import type { IAccountList, IAccountLimitRow, IAccountLimits, IActiveAccountDrift, IActiveAccountRow, IActiveAccounts, IHarnessAccountList } from './interfaces.list.js';
|
|
5
6
|
import type { IAccountType, IAuthHarness, IHarnessBilling, IHarnessCredentialDrift, TUsageSeverity } from './interfaces.harness.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
|
-
import { accountUsageWindows, compactUsageLine, credentialDriftProblem
|
|
2
|
+
import { accountUsageWindows, compactUsageLine, credentialDriftProblem } from './accounts.js';
|
|
3
|
+
import { localDate, localDateTime, localiseTimestamps } from './timetext.js';
|
|
3
4
|
import { accountBilling, accountCell, accountLimits, accountType, CondensedRenderer, nextRenewal, renewalCell, UNAVAILABLE, usageColor, type THarnessTraits } from './classes.limits.js';
|
|
4
5
|
import { consoleHeading, consoleNote, consoleTable, consoleWidth } from './consoletable.js';
|
|
5
6
|
import type { IHarnessStatusFact, IHarnessStatusSummary } from './interfaces.harness.js';
|
package/ts/classes.tui.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import type { IAuthHarness, IHarnessAccount, IHarnessOutcome, IHarnessProcess, IHarnessState, IHarnessStopOutcome } from './interfaces.harness.js';
|
|
3
|
-
import { accountBadge, accountDetails, accountPlan, accountQuotaSummary, accountResets, accountState, accountUsageWindows, usageWindowName,
|
|
3
|
+
import { accountBadge, accountDetails, accountPlan, accountQuotaSummary, accountResets, accountState, accountUsageWindows, usageWindowName, readAccountRows, type IAccountRow } from './accounts.js';
|
|
4
|
+
import { until } from './timetext.js';
|
|
4
5
|
import { defaultPreusePrompt, preuseAccounts, preuseCountsText, preuseTargets, type TPreuseAccountResult } from './preuse.js';
|
|
5
6
|
import { plainText } from './formatting.js';
|
|
6
7
|
import { AuthSwitchOperations, authSwitchMutationReplacesLogin, type TAuthSwitchMutation } from './classes.operations.js';
|
package/ts/classes.watch.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { accountBadge, accountName, durationText,
|
|
1
|
+
import { accountBadge, accountName, durationText, readAccountRows, type IAccountRow } from './accounts.js';
|
|
2
|
+
import { localTime } from './timetext.js';
|
|
2
3
|
import { pause } from './helpers.js';
|
|
3
4
|
import { bold, dim, green, orange, plainText, red } from './formatting.js';
|
|
4
5
|
import { decideWatchAction, type IWatchReading } from './watchpolicy.js';
|
package/ts/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from './classes.claudestatus.js';
|
|
|
11
11
|
export * from './classes.codexstatus.js';
|
|
12
12
|
export * from './classes.codexpreuse.js';
|
|
13
13
|
export * from './preuse.js';
|
|
14
|
+
export * from './preuse-text.js';
|
|
14
15
|
export * from './helpers.js';
|
|
15
16
|
export * from './classes.codexhome.js';
|
|
16
17
|
export * from './classes.codexauth.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sentences that name what stopped a preuse, with no Node dependency.
|
|
3
|
+
*
|
|
4
|
+
* A preuse receipt carries its limit and its cause as data (`IAuthSwitchPreuseOperation.limit` and `.failure`);
|
|
5
|
+
* the command line and a host that shows receipts, AGL's web client for one, must word them alike. This module
|
|
6
|
+
* therefore loads nothing but the time text it renders a reset with, which imports nothing either, so that it can
|
|
7
|
+
* be bundled for a browser through the `@modelprofile.com/authswitch/preuse-text` entry point. The receipt types
|
|
8
|
+
* are type-only imports, which the emitted JavaScript drops.
|
|
9
|
+
*/
|
|
10
|
+
import type { IAuthSwitchPreuseFailure, IAuthSwitchPreuseLimit } from './authority-contract.js';
|
|
11
|
+
import { localDateTime, until } from './timetext.js';
|
|
12
|
+
|
|
13
|
+
export { localDateTime } from './timetext.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `Weekly usage limit reached, resets in 16h 46min (2026-09-27 05:12)`: human text, so the reset reads in local time.
|
|
17
|
+
* The limit itself keeps its ISO `resetsAt` for receipts and JSON output.
|
|
18
|
+
*/
|
|
19
|
+
export const describePreuseLimit = (limitArg: IAuthSwitchPreuseLimit, nowArg: number): string => {
|
|
20
|
+
const name = `${limitArg.window === null ? '' : `${limitArg.window} `}${limitArg.kind === 'usage_limit' ? 'usage' : 'rate'} limit reached`;
|
|
21
|
+
const countdown = limitArg.resetsAt === null ? null : until(limitArg.resetsAt, nowArg);
|
|
22
|
+
const at = localDateTime(limitArg.resetsAt);
|
|
23
|
+
const reset = limitArg.resetsAt === null ? ''
|
|
24
|
+
: countdown === 'due' ? `, due to reset now (${at})` : `, resets in ${countdown} (${at})`;
|
|
25
|
+
return `${name.charAt(0).toUpperCase()}${name.slice(1)}${reset}`;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** `the provider answered HTTP 503`: the cause of a failed preuse, as the tail of a sentence. */
|
|
29
|
+
export const describePreuseFailure = (failureArg: IAuthSwitchPreuseFailure): string => {
|
|
30
|
+
const status = failureArg.status === null ? null : `HTTP ${failureArg.status}`;
|
|
31
|
+
switch (failureArg.kind) {
|
|
32
|
+
case 'http': return `the provider answered ${status ?? 'with an HTTP error'}`;
|
|
33
|
+
case 'stream':
|
|
34
|
+
return `the answer stream reported an error${failureArg.providerCode === null ? '' : ` (code ${failureArg.providerCode})`}`;
|
|
35
|
+
case 'response': return `the provider's answer${status ? ` (${status})` : ''} could not be read`;
|
|
36
|
+
case 'network': return 'no answer arrived: the connection failed';
|
|
37
|
+
case 'aborted': return 'the request was aborted';
|
|
38
|
+
case 'unknown': return 'the request ended in an error that names no cause';
|
|
39
|
+
}
|
|
40
|
+
};
|
package/ts/preuse.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { IAuthSwitchPreuseFailure, IAuthSwitchPreuseLimit } from './authority-contract.js';
|
|
2
|
+
import { orderedUsageWindows } from './accounts.js';
|
|
3
|
+
import { describePreuseFailure, describePreuseLimit } from './preuse-text.js';
|
|
2
4
|
import type { IAuthHarness, IHarnessAccount, IHarnessPreuseOptions, IHarnessPreuseResult, IHarnessUsageWindow } from './interfaces.harness.js';
|
|
3
5
|
|
|
4
6
|
export const defaultPreusePrompt = 'Write 2000 words about strawberries.';
|
|
@@ -18,31 +20,8 @@ export class PreuseError extends Error {
|
|
|
18
20
|
constructor(messageArg: string, public readonly requestStarted = false) { super(messageArg); }
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
*
|
|
24
|
-
* `usage_limit` is the account's quota, spent until it resets; `rate_limit` a short throttle. `window` names the
|
|
25
|
-
* window by its length (`weekly`, `five-hour`) and `resetsAt` is an ISO time; either is null when the account's
|
|
26
|
-
* usage reading or the provider's refusal did not state it.
|
|
27
|
-
*/
|
|
28
|
-
export interface IPreuseLimit {
|
|
29
|
-
kind: 'usage_limit' | 'rate_limit';
|
|
30
|
-
window: string | null;
|
|
31
|
-
resetsAt: string | null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* `Weekly usage limit reached, resets in 16h 46min (2026-09-27 05:12)`: human text, so the reset reads in local time.
|
|
36
|
-
* The limit itself keeps its ISO `resetsAt` for receipts and JSON output.
|
|
37
|
-
*/
|
|
38
|
-
export const describePreuseLimit = (limitArg: IPreuseLimit, nowArg: number): string => {
|
|
39
|
-
const name = `${limitArg.window === null ? '' : `${limitArg.window} `}${limitArg.kind === 'usage_limit' ? 'usage' : 'rate'} limit reached`;
|
|
40
|
-
const countdown = limitArg.resetsAt === null ? null : until(limitArg.resetsAt, nowArg);
|
|
41
|
-
const at = localDateTime(limitArg.resetsAt);
|
|
42
|
-
const reset = limitArg.resetsAt === null ? ''
|
|
43
|
-
: countdown === 'due' ? `, due to reset now (${at})` : `, resets in ${countdown} (${at})`;
|
|
44
|
-
return `${name.charAt(0).toUpperCase()}${name.slice(1)}${reset}`;
|
|
45
|
-
};
|
|
23
|
+
/** The authority's `IAuthSwitchPreuseLimit`, under the name the package root has exported since 10.0.0. */
|
|
24
|
+
export type IPreuseLimit = IAuthSwitchPreuseLimit;
|
|
46
25
|
|
|
47
26
|
/**
|
|
48
27
|
* A preuse a limit stopped. `usage` is the account's own usage reading before anything was sent; `provider`
|
|
@@ -50,7 +29,7 @@ export const describePreuseLimit = (limitArg: IPreuseLimit, nowArg: number): str
|
|
|
50
29
|
* answer: a refusal before its answer consumed nothing.
|
|
51
30
|
*/
|
|
52
31
|
export class PreuseLimitError extends PreuseError {
|
|
53
|
-
constructor(public readonly limit:
|
|
32
|
+
constructor(public readonly limit: IAuthSwitchPreuseLimit, public readonly source: 'usage' | 'provider',
|
|
54
33
|
public readonly generationStarted: boolean, nowArg: number) {
|
|
55
34
|
const described = describePreuseLimit(limit, nowArg);
|
|
56
35
|
super(source === 'usage' ? `${described}. No preuse prompt was sent.`
|
|
@@ -61,37 +40,12 @@ export class PreuseLimitError extends PreuseError {
|
|
|
61
40
|
}
|
|
62
41
|
}
|
|
63
42
|
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
*
|
|
67
|
-
* `http` is an answer with an HTTP error status; `stream` an error the answer stream reported after it began,
|
|
68
|
-
* with the machine-readable code the provider stated and no status, since its answer was a success; `response` an answer that could not be read;
|
|
69
|
-
* `network` a connection that failed before any answer; `aborted` an abort that was neither the caller's nor the
|
|
70
|
-
* deadline; `unknown` anything else. `status` and `providerCode` are null when the failure did not state them.
|
|
71
|
-
*/
|
|
72
|
-
export interface IPreuseFailure {
|
|
73
|
-
kind: 'http' | 'stream' | 'response' | 'network' | 'aborted' | 'unknown';
|
|
74
|
-
status: number | null;
|
|
75
|
-
providerCode: string | null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** `the provider answered HTTP 503`: the cause of a failed preuse, as the tail of a sentence. */
|
|
79
|
-
export const describePreuseFailure = (failureArg: IPreuseFailure): string => {
|
|
80
|
-
const status = failureArg.status === null ? null : `HTTP ${failureArg.status}`;
|
|
81
|
-
switch (failureArg.kind) {
|
|
82
|
-
case 'http': return `the provider answered ${status ?? 'with an HTTP error'}`;
|
|
83
|
-
case 'stream':
|
|
84
|
-
return `the answer stream reported an error${failureArg.providerCode === null ? '' : ` (code ${failureArg.providerCode})`}`;
|
|
85
|
-
case 'response': return `the provider's answer${status ? ` (${status})` : ''} could not be read`;
|
|
86
|
-
case 'network': return 'no answer arrived: the connection failed';
|
|
87
|
-
case 'aborted': return 'the request was aborted';
|
|
88
|
-
case 'unknown': return 'the request ended in an error that names no cause';
|
|
89
|
-
}
|
|
90
|
-
};
|
|
43
|
+
/** The authority's `IAuthSwitchPreuseFailure`, under the name the package root has exported since 10.0.0. */
|
|
44
|
+
export type IPreuseFailure = IAuthSwitchPreuseFailure;
|
|
91
45
|
|
|
92
46
|
/** A sent preuse prompt that failed for the stated cause; its tokens may have been consumed. */
|
|
93
47
|
export class PreuseFailureError extends PreuseError {
|
|
94
|
-
constructor(public readonly failure:
|
|
48
|
+
constructor(public readonly failure: IAuthSwitchPreuseFailure) {
|
|
95
49
|
super(`Preuse did not complete: ${describePreuseFailure(failure)}. ${preuseMayHaveConsumed}`, true);
|
|
96
50
|
}
|
|
97
51
|
}
|
package/ts/timetext.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human time text: countdowns to a deadline and timestamps in local calendar time.
|
|
3
|
+
*
|
|
4
|
+
* It imports nothing, so the browser-safe `./preuse-text` entry point can render a reset exactly as the
|
|
5
|
+
* command line does without pulling a Node built-in into a bundle.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** A countdown is a snapshot of the provider's deadline, not proof a quota has reset. */
|
|
9
|
+
export const until = (timestampArg: string | null, nowArg: number): string => {
|
|
10
|
+
if (timestampArg === null) return 'Not scheduled';
|
|
11
|
+
const remaining = Date.parse(timestampArg) - nowArg;
|
|
12
|
+
if (!Number.isFinite(remaining)) return 'Unavailable';
|
|
13
|
+
if (remaining <= 0) return 'due';
|
|
14
|
+
const minutes = Math.floor(remaining / 60000);
|
|
15
|
+
if (!minutes) return '<1min';
|
|
16
|
+
const days = Math.floor(minutes / 1440);
|
|
17
|
+
const hours = Math.floor(minutes % 1440 / 60);
|
|
18
|
+
return [days ? `${days}d` : '', hours ? `${hours}h` : '', minutes % 60 ? `${minutes % 60}min` : ''].filter(Boolean).join(' ');
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A two-unit countdown for condensed tables: `2h 13m`, `3d 4h`, `45m`.
|
|
22
|
+
*
|
|
23
|
+
* `until` stays the long form used by the list view and the TUI. This one trades the third unit for
|
|
24
|
+
* a column that never wraps, and it never invents a deadline: an unknown or unparseable timestamp is
|
|
25
|
+
* reported as such rather than as zero time remaining.
|
|
26
|
+
*/
|
|
27
|
+
const twoUnits = (millisecondsArg: number): string => {
|
|
28
|
+
const minutes = Math.floor(millisecondsArg / 60000);
|
|
29
|
+
const days = Math.floor(minutes / 1440);
|
|
30
|
+
const hours = Math.floor(minutes % 1440 / 60);
|
|
31
|
+
if (days) return hours ? `${days}d ${hours}h` : `${days}d`;
|
|
32
|
+
if (hours) return minutes % 60 ? `${hours}h ${minutes % 60}m` : `${hours}h`;
|
|
33
|
+
return `${minutes}m`;
|
|
34
|
+
};
|
|
35
|
+
export const compactUntil = (timestampArg: string | null, nowArg: number, unavailableArg = 'n/a'): string => {
|
|
36
|
+
if (timestampArg === null) return 'not scheduled';
|
|
37
|
+
const remaining = Date.parse(timestampArg) - nowArg;
|
|
38
|
+
if (!Number.isFinite(remaining)) return unavailableArg;
|
|
39
|
+
if (remaining <= 0) return 'due';
|
|
40
|
+
return remaining < 60000 ? '<1m' : twoUnits(remaining);
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The same two-unit form for a past timestamp, for "saved 3d 4h ago".
|
|
44
|
+
*
|
|
45
|
+
* The result is always a bare duration or the caller's unavailable marker, never a phrase: callers
|
|
46
|
+
* suffix it ("... ago"), and a phrase would read as "just now ago". A span under a minute is `<1m`,
|
|
47
|
+
* exactly as `compactUntil` reports one.
|
|
48
|
+
*/
|
|
49
|
+
export const compactSince = (timestampArg: string | null, nowArg: number, unavailableArg = 'n/a'): string => {
|
|
50
|
+
if (timestampArg === null) return unavailableArg;
|
|
51
|
+
const elapsed = nowArg - Date.parse(timestampArg);
|
|
52
|
+
if (!Number.isFinite(elapsed)) return unavailableArg;
|
|
53
|
+
return elapsed < 60000 ? '<1m' : twoUnits(elapsed);
|
|
54
|
+
};
|
|
55
|
+
const twoDigits = (valueArg: number): string => String(valueArg).padStart(2, '0');
|
|
56
|
+
/**
|
|
57
|
+
* A timestamp in local calendar time: `2026-10-08`, or `2026-10-08 23:14` with the time. Human output never prints
|
|
58
|
+
* a raw ISO timestamp; an unknown or unparseable one reads as the caller's unavailable marker.
|
|
59
|
+
*/
|
|
60
|
+
const localTimestamp = (timestampArg: string | null, withTimeArg: boolean, unavailableArg: string): string => {
|
|
61
|
+
const time = timestampArg === null ? Number.NaN : Date.parse(timestampArg);
|
|
62
|
+
if (!Number.isFinite(time)) return unavailableArg;
|
|
63
|
+
const date = new Date(time);
|
|
64
|
+
const day = `${date.getFullYear()}-${twoDigits(date.getMonth() + 1)}-${twoDigits(date.getDate())}`;
|
|
65
|
+
return withTimeArg ? `${day} ${twoDigits(date.getHours())}:${twoDigits(date.getMinutes())}` : day;
|
|
66
|
+
};
|
|
67
|
+
export const localDate = (timestampArg: string | null, unavailableArg = 'n/a'): string => localTimestamp(timestampArg, false, unavailableArg);
|
|
68
|
+
export const localDateTime = (timestampArg: string | null, unavailableArg = 'n/a'): string => localTimestamp(timestampArg, true, unavailableArg);
|
|
69
|
+
/** A moment's local time of day with seconds, `14:02:07`, for a log line whose date is implied. */
|
|
70
|
+
export const localTime = (timeArg: number): string => {
|
|
71
|
+
const date = new Date(timeArg);
|
|
72
|
+
return [date.getHours(), date.getMinutes(), date.getSeconds()].map(twoDigits).join(':');
|
|
73
|
+
};
|
|
74
|
+
const ISO_TIMESTAMP = /\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})/g;
|
|
75
|
+
/** Provider text with every ISO 8601 timestamp in it rewritten as local date and time. */
|
|
76
|
+
export const localiseTimestamps = (textArg: string): string => textArg.replace(ISO_TIMESTAMP, match => localDateTime(match, match));
|
package/ts/watchpolicy.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { usagePercentText } from './accounts.js';
|
|
2
|
+
import { compactUntil } from './timetext.js';
|
|
2
3
|
import { plainText } from './formatting.js';
|
|
3
4
|
import type { IHarnessUsageWindow } from './interfaces.harness.js';
|
|
4
5
|
|