agy-cli-usage 0.4.4 → 0.4.5
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/CHANGELOG.md +8 -0
- package/dist/src/api.d.ts +2 -0
- package/dist/src/api.js +7 -1
- package/dist/src/credentials.d.ts +1 -0
- package/dist/src/credentials.js +4 -0
- package/dist/src/main.js +9 -0
- package/dist/src/render.d.ts +2 -2
- package/dist/src/render.js +29 -3
- package/dist/src/update.d.ts +8 -1
- package/dist/src/update.js +24 -8
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
|
|
11
11
|
* use plain v* tags in release-please ([#12](https://github.com/abruption/agy-cli-usage/issues/12)) ([74b648d](https://github.com/abruption/agy-cli-usage/commit/74b648df24967f71a43095a80e7340a6b5ac2e39)), closes [#9](https://github.com/abruption/agy-cli-usage/issues/9)
|
|
12
12
|
|
|
13
|
+
## [0.4.5](https://github.com/abruption/agy-cli-usage/compare/v0.4.4...v0.4.5) (2026-07-03)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* derive UA from package version, name error classes, bound update-check timeouts, document env vars in --help, show cache freshness in watch mode ([62becdc](https://github.com/abruption/agy-cli-usage/commit/62becdcf4175d2cda3cec8dc3f5bba13005c0d59)), closes [#32](https://github.com/abruption/agy-cli-usage/issues/32)
|
|
19
|
+
* UA version, error class names, update-check timeouts, --help env vars, watch/cache indicator ([#37](https://github.com/abruption/agy-cli-usage/issues/37)) ([62becdc](https://github.com/abruption/agy-cli-usage/commit/62becdcf4175d2cda3cec8dc3f5bba13005c0d59))
|
|
20
|
+
|
|
13
21
|
## [0.4.4](https://github.com/abruption/agy-cli-usage/compare/v0.4.3...v0.4.4) (2026-07-03)
|
|
14
22
|
|
|
15
23
|
|
package/dist/src/api.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { FetchResult } from './types.js';
|
|
2
|
+
/** Exported for direct unit testing — keeps the UA tied to the real package name/version. */
|
|
3
|
+
export declare function buildUserAgent(): string;
|
|
2
4
|
declare class ApiError extends Error {
|
|
3
5
|
status: number;
|
|
4
6
|
constructor(message: string, status: number);
|
package/dist/src/api.js
CHANGED
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
//
|
|
9
9
|
// Captured from live agy traffic (mitmproxy). The internal endpoint is undocumented;
|
|
10
10
|
// the PTY fallback exists for when it changes.
|
|
11
|
-
|
|
11
|
+
import { currentVersion } from './update.js';
|
|
12
|
+
/** Exported for direct unit testing — keeps the UA tied to the real package name/version. */
|
|
13
|
+
export function buildUserAgent() {
|
|
14
|
+
return `agy-cli-usage/${currentVersion()} ${process.platform}/${process.arch}`;
|
|
15
|
+
}
|
|
16
|
+
const UA = buildUserAgent();
|
|
12
17
|
// Antigravity ships against the "daily" Cloud Code host; stable builds use the
|
|
13
18
|
// plain host. Try daily first (matches current CLI), fall back to prod.
|
|
14
19
|
const HOSTS = ['daily-cloudcode-pa.googleapis.com', 'cloudcode-pa.googleapis.com'];
|
|
@@ -16,6 +21,7 @@ class ApiError extends Error {
|
|
|
16
21
|
status;
|
|
17
22
|
constructor(message, status) {
|
|
18
23
|
super(message);
|
|
24
|
+
this.name = 'ApiError';
|
|
19
25
|
this.status = status;
|
|
20
26
|
}
|
|
21
27
|
}
|
package/dist/src/credentials.js
CHANGED
|
@@ -35,6 +35,10 @@ const KEYRING_SERVICE = 'gemini';
|
|
|
35
35
|
const KEYRING_ACCOUNT = 'antigravity';
|
|
36
36
|
const B64_PREFIX = 'go-keyring-base64:';
|
|
37
37
|
class CredentialError extends Error {
|
|
38
|
+
constructor(message) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = 'CredentialError';
|
|
41
|
+
}
|
|
38
42
|
}
|
|
39
43
|
// --- raw keyring read --------------------------------------------------------
|
|
40
44
|
async function readViaNapiEsm() {
|
package/dist/src/main.js
CHANGED
|
@@ -82,6 +82,15 @@ const HELP = `agy-cli-usage — Antigravity CLI (agy) usage/quota monitor
|
|
|
82
82
|
agy-cli-usage --no-cache | --refresh
|
|
83
83
|
agy-cli-usage update [--check] self-update via npm (--check: report only)
|
|
84
84
|
agy-cli-usage --version | -v
|
|
85
|
+
|
|
86
|
+
Environment variables:
|
|
87
|
+
AGY_OAUTH_TOKEN_FILE Override the token file path (headless fallback).
|
|
88
|
+
AGY_BIN Path to the agy binary (PTY source). Else resolved from
|
|
89
|
+
PATH, then ~/.local/bin.
|
|
90
|
+
XDG_CACHE_HOME Cache base dir (cache lives at
|
|
91
|
+
<base>/agy-usage/quota.json; default ~/.cache).
|
|
92
|
+
NO_COLOR Disable ANSI color in the rendered panel.
|
|
93
|
+
PORT / HOST HTTP server bind (server mode only).
|
|
85
94
|
`;
|
|
86
95
|
/** Exported for direct unit testing via an injected `cacheFile` — not part of the CLI's public surface. */
|
|
87
96
|
export function readCache(source, channel, cacheFile = CACHE_FILE) {
|
package/dist/src/render.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Snapshot } from './types.js';
|
|
2
|
-
/** Returns the full panel as a string. */
|
|
3
|
-
export declare function renderPanel(snap: Snapshot): string;
|
|
2
|
+
/** Returns the full panel as a string. `nowMs` is injectable for testing. */
|
|
3
|
+
export declare function renderPanel(snap: Snapshot, nowMs?: number): string;
|
package/dist/src/render.js
CHANGED
|
@@ -46,14 +46,40 @@ function bucketLine(b) {
|
|
|
46
46
|
}
|
|
47
47
|
return lines.join('\n');
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
// `--watch`'s default interval (60s) is faster than the 5-minute quota cache
|
|
50
|
+
// TTL, so most refreshes just re-render a cached snapshot with no visual
|
|
51
|
+
// sign the fetch didn't actually happen. `fetchedAt` is set once, when the
|
|
52
|
+
// data was truly fetched (see quota.ts), and carried through untouched on a
|
|
53
|
+
// cache hit — so comparing it to "now" at render time is enough to tell the
|
|
54
|
+
// two cases apart without threading extra state through Snapshot/the cache.
|
|
55
|
+
// A small skew is tolerated so a genuinely fresh fetch isn't mislabeled
|
|
56
|
+
// "cached" just because rendering happened slightly after fetching.
|
|
57
|
+
const FRESHNESS_SKEW_MS = 3_000;
|
|
58
|
+
function formatAge(totalSeconds) {
|
|
59
|
+
const s = Math.max(0, Math.round(totalSeconds));
|
|
60
|
+
if (s < 60)
|
|
61
|
+
return `${s}s`;
|
|
62
|
+
const h = Math.floor(s / 3600);
|
|
63
|
+
const m = Math.floor((s % 3600) / 60);
|
|
64
|
+
if (h > 0)
|
|
65
|
+
return `${h}h ${m}m`;
|
|
66
|
+
return `${m}m ${s % 60}s`;
|
|
67
|
+
}
|
|
68
|
+
function freshnessSuffix(fetchedAt, nowMs) {
|
|
69
|
+
const ageMs = nowMs - new Date(fetchedAt).getTime();
|
|
70
|
+
if (!Number.isFinite(ageMs) || ageMs < FRESHNESS_SKEW_MS)
|
|
71
|
+
return '';
|
|
72
|
+
return dim(` (cached, refreshed ${formatAge(ageMs / 1000)} ago)`);
|
|
73
|
+
}
|
|
74
|
+
/** Returns the full panel as a string. `nowMs` is injectable for testing. */
|
|
75
|
+
export function renderPanel(snap, nowMs = Date.now()) {
|
|
51
76
|
const out = [];
|
|
52
77
|
out.push('');
|
|
53
78
|
out.push(bold(' Models & Quota'));
|
|
54
79
|
if (snap.account)
|
|
55
80
|
out.push(` ${dim('Account:')} ${snap.account}`);
|
|
56
|
-
out.push(` ${dim(`source: ${snap.source}${snap.host ? ` · ${snap.host}` : ''} · ${snap.fetchedAt}`)}`
|
|
81
|
+
out.push(` ${dim(`source: ${snap.source}${snap.host ? ` · ${snap.host}` : ''} · ${snap.fetchedAt}`)}` +
|
|
82
|
+
freshnessSuffix(snap.fetchedAt, nowMs));
|
|
57
83
|
out.push('');
|
|
58
84
|
for (const g of snap.groups) {
|
|
59
85
|
out.push(bold(` ${g.name.toUpperCase()}`));
|
package/dist/src/update.d.ts
CHANGED
|
@@ -9,8 +9,15 @@ export declare function currentVersion(): string;
|
|
|
9
9
|
* Returns negative if a<b, 0 if equal, positive if a>b.
|
|
10
10
|
*/
|
|
11
11
|
export declare function semverCompare(a: string, b: string): number;
|
|
12
|
+
export declare const NPM_VIEW_TIMEOUT_MS = 8000;
|
|
13
|
+
export declare const REGISTRY_FETCH_TIMEOUT_MS = 8000;
|
|
14
|
+
/** Exported for direct unit testing via injection — not part of the public surface. */
|
|
15
|
+
export interface LatestVersionDeps {
|
|
16
|
+
execNpmView?: (pkgName: string, timeoutMs: number) => string;
|
|
17
|
+
fetchRegistry?: (pkgName: string, timeoutMs: number) => Promise<Response>;
|
|
18
|
+
}
|
|
12
19
|
/** Latest published version: prefer the user's configured registry (npm view), fall back to public. */
|
|
13
|
-
export declare function latestVersion(): Promise<string | null>;
|
|
20
|
+
export declare function latestVersion(deps?: LatestVersionDeps): Promise<string | null>;
|
|
14
21
|
/** Run the update flow. Returns the intended process exit code. */
|
|
15
22
|
export declare function runUpdate({ checkOnly }?: {
|
|
16
23
|
checkOnly?: boolean;
|
package/dist/src/update.js
CHANGED
|
@@ -34,26 +34,42 @@ export function semverCompare(a, b) {
|
|
|
34
34
|
}
|
|
35
35
|
return 0;
|
|
36
36
|
}
|
|
37
|
+
// Both the `npm view` child process and the registry fetch fallback are
|
|
38
|
+
// bounded so a slow/unreachable registry can't hang the update check
|
|
39
|
+
// indefinitely — a timed-out attempt is treated the same as "unavailable"
|
|
40
|
+
// and falls through to the next strategy (or to `latestVersion` returning
|
|
41
|
+
// null, which `runUpdate` reports as "could not determine the latest version").
|
|
42
|
+
export const NPM_VIEW_TIMEOUT_MS = 8_000;
|
|
43
|
+
export const REGISTRY_FETCH_TIMEOUT_MS = 8_000;
|
|
44
|
+
function defaultExecNpmView(pkgName, timeoutMs) {
|
|
45
|
+
return execFileSync('npm', ['view', pkgName, 'version'], {
|
|
46
|
+
encoding: 'utf8',
|
|
47
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
48
|
+
timeout: timeoutMs,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function defaultFetchRegistry(pkgName, timeoutMs) {
|
|
52
|
+
return fetch(`https://registry.npmjs.org/${pkgName}/latest`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
53
|
+
}
|
|
37
54
|
/** Latest published version: prefer the user's configured registry (npm view), fall back to public. */
|
|
38
|
-
export async function latestVersion() {
|
|
55
|
+
export async function latestVersion(deps = {}) {
|
|
56
|
+
const execNpmView = deps.execNpmView ?? defaultExecNpmView;
|
|
57
|
+
const fetchRegistry = deps.fetchRegistry ?? defaultFetchRegistry;
|
|
39
58
|
try {
|
|
40
|
-
const out =
|
|
41
|
-
encoding: 'utf8',
|
|
42
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
43
|
-
}).trim();
|
|
59
|
+
const out = execNpmView(PKG_NAME, NPM_VIEW_TIMEOUT_MS).trim();
|
|
44
60
|
if (out)
|
|
45
61
|
return out;
|
|
46
62
|
}
|
|
47
63
|
catch {
|
|
48
|
-
// npm missing or
|
|
64
|
+
// npm missing, offline, or timed out — try the public registry directly
|
|
49
65
|
}
|
|
50
66
|
try {
|
|
51
|
-
const res = await
|
|
67
|
+
const res = await fetchRegistry(PKG_NAME, REGISTRY_FETCH_TIMEOUT_MS);
|
|
52
68
|
if (res.ok)
|
|
53
69
|
return (await res.json()).version;
|
|
54
70
|
}
|
|
55
71
|
catch {
|
|
56
|
-
// offline
|
|
72
|
+
// offline or timed out
|
|
57
73
|
}
|
|
58
74
|
return null;
|
|
59
75
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agy-cli-usage",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Headless usage/quota monitor for the Antigravity CLI (agy) — reads Cloud Code quota directly, with a PTY fallback. No IDE required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "dist/src/main.d.ts",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"license": "MIT",
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@eslint/js": "^9.39.4",
|
|
60
|
-
"@types/node": "^
|
|
60
|
+
"@types/node": "^18.19.0",
|
|
61
61
|
"eslint": "^9.39.4",
|
|
62
62
|
"typescript": "^6.0.3",
|
|
63
63
|
"typescript-eslint": "^8.62.1"
|