agy-cli-usage 0.4.5 → 0.4.6
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 +7 -0
- package/README.md +2 -0
- package/dist/src/api.d.ts +13 -1
- package/dist/src/api.js +21 -6
- package/dist/src/credentials.js +8 -0
- package/dist/src/main.js +13 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,13 @@
|
|
|
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.6](https://github.com/abruption/agy-cli-usage/compare/v0.4.5...v0.4.6) (2026-09-07)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* restore the "antigravity" User-Agent the quota API gates on ([#48](https://github.com/abruption/agy-cli-usage/issues/48)) ([c375d5a](https://github.com/abruption/agy-cli-usage/commit/c375d5a3e9c1698cbb1dfe998be6e7c388363986)), closes [#47](https://github.com/abruption/agy-cli-usage/issues/47)
|
|
19
|
+
|
|
13
20
|
## [0.4.5](https://github.com/abruption/agy-cli-usage/compare/v0.4.4...v0.4.5) (2026-07-03)
|
|
14
21
|
|
|
15
22
|
|
package/README.md
CHANGED
|
@@ -107,6 +107,8 @@ The token is **read only** from wherever `agy` stored it. Handled per platform a
|
|
|
107
107
|
|
|
108
108
|
Read order: `keyring → OS CLI → Windows credman → token file → PTY`. Override the file path with `AGY_OAUTH_TOKEN_FILE`.
|
|
109
109
|
|
|
110
|
+
Token files are tried in order: `AGY_OAUTH_TOKEN_FILE`, then `~/.gemini/antigravity-cli/antigravity-oauth-token`, then `~/.gemini/jetski-standalone-oauth-token`. The last one is checked only after every keyring backend has failed — on macOS it sits beside the Keychain entry and can hold an older grant from a different session, so it must not win over a working one.
|
|
111
|
+
|
|
110
112
|
## HTTP endpoint (optional)
|
|
111
113
|
|
|
112
114
|
```bash
|
package/dist/src/api.d.ts
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import type { FetchResult } from './types.js';
|
|
2
2
|
/** Exported for direct unit testing — keeps the UA tied to the real package name/version. */
|
|
3
3
|
export declare function buildUserAgent(): string;
|
|
4
|
+
/**
|
|
5
|
+
* Why these are distinct: the endpoint uses 401 and 403 for unrelated problems
|
|
6
|
+
* and only one of them is fixed by signing in again.
|
|
7
|
+
* unauthorized — the token was rejected (expired/revoked): re-authenticate.
|
|
8
|
+
* not-entitled — the token is fine but the account has no Antigravity
|
|
9
|
+
* license ("You do not have a valid license of this
|
|
10
|
+
* product…"): signing in again changes nothing.
|
|
11
|
+
* no-project — HTTP 200 without `cloudaicompanionProject`.
|
|
12
|
+
* http — anything else (wrong host, 5xx, …).
|
|
13
|
+
*/
|
|
14
|
+
export type ApiErrorKind = 'unauthorized' | 'not-entitled' | 'no-project' | 'http';
|
|
4
15
|
declare class ApiError extends Error {
|
|
5
16
|
status: number;
|
|
6
|
-
|
|
17
|
+
kind: ApiErrorKind;
|
|
18
|
+
constructor(message: string, status: number, kind?: ApiErrorKind);
|
|
7
19
|
}
|
|
8
20
|
export interface FetchOptions {
|
|
9
21
|
host?: string;
|
package/dist/src/api.js
CHANGED
|
@@ -9,9 +9,16 @@
|
|
|
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
|
+
// The UA is load-bearing, not cosmetic: Cloud Code picks the product from the
|
|
13
|
+
// User-Agent, not from the request body. Without an `antigravity` substring
|
|
14
|
+
// (case-insensitive, position and version format irrelevant) `loadCodeAssist`
|
|
15
|
+
// answers HTTP 200 but omits `cloudaicompanionProject`, so the quota call can
|
|
16
|
+
// never run — sending `ideType: "ANTIGRAVITY"` does not compensate. Dropping
|
|
17
|
+
// that substring is what broke v0.4.5 (#47). Verified against the live daily
|
|
18
|
+
// host on 2026-09-07.
|
|
12
19
|
/** Exported for direct unit testing — keeps the UA tied to the real package name/version. */
|
|
13
20
|
export function buildUserAgent() {
|
|
14
|
-
return `agy-cli-usage/${currentVersion()} ${process.platform}/${process.arch}`;
|
|
21
|
+
return `antigravity-agy-cli-usage/${currentVersion()} ${process.platform}/${process.arch}`;
|
|
15
22
|
}
|
|
16
23
|
const UA = buildUserAgent();
|
|
17
24
|
// Antigravity ships against the "daily" Cloud Code host; stable builds use the
|
|
@@ -19,10 +26,12 @@ const UA = buildUserAgent();
|
|
|
19
26
|
const HOSTS = ['daily-cloudcode-pa.googleapis.com', 'cloudcode-pa.googleapis.com'];
|
|
20
27
|
class ApiError extends Error {
|
|
21
28
|
status;
|
|
22
|
-
|
|
29
|
+
kind;
|
|
30
|
+
constructor(message, status, kind) {
|
|
23
31
|
super(message);
|
|
24
32
|
this.name = 'ApiError';
|
|
25
33
|
this.status = status;
|
|
34
|
+
this.kind = kind ?? (status === 401 ? 'unauthorized' : status === 403 ? 'not-entitled' : 'http');
|
|
26
35
|
}
|
|
27
36
|
}
|
|
28
37
|
// Known limitation: this is the only email source `loadCodeAssist` exposes —
|
|
@@ -77,8 +86,11 @@ export async function fetchQuotaSummary(accessToken, opts = {}) {
|
|
|
77
86
|
metadata: { ideType: 'ANTIGRAVITY' },
|
|
78
87
|
});
|
|
79
88
|
const project = lca.cloudaicompanionProject;
|
|
80
|
-
if (!project)
|
|
81
|
-
throw new ApiError(
|
|
89
|
+
if (!project) {
|
|
90
|
+
throw new ApiError(`loadCodeAssist on ${host} returned HTTP 200 without cloudaicompanionProject — either the ` +
|
|
91
|
+
`request was not recognized as Antigravity (the API reads the product off the User-Agent, ` +
|
|
92
|
+
`which must contain "antigravity") or the signed-in account has no Antigravity entitlement`, 0, 'no-project');
|
|
93
|
+
}
|
|
82
94
|
const raw = await postInternal(host, accessToken, 'retrieveUserQuotaSummary', { project });
|
|
83
95
|
return {
|
|
84
96
|
raw,
|
|
@@ -89,8 +101,11 @@ export async function fetchQuotaSummary(accessToken, opts = {}) {
|
|
|
89
101
|
}
|
|
90
102
|
catch (err) {
|
|
91
103
|
lastErr = err;
|
|
92
|
-
// 404 / wrong-host -> try next candidate
|
|
93
|
-
|
|
104
|
+
// 404 / wrong-host -> try next candidate. A rejected token or a missing
|
|
105
|
+
// license is account-level, identical on every host: stop early.
|
|
106
|
+
// `no-project` deliberately keeps going — an account can be entitled on
|
|
107
|
+
// one channel and not the other, and one extra round trip is cheap.
|
|
108
|
+
if (err instanceof ApiError && (err.kind === 'unauthorized' || err.kind === 'not-entitled'))
|
|
94
109
|
throw err;
|
|
95
110
|
}
|
|
96
111
|
}
|
package/dist/src/credentials.js
CHANGED
|
@@ -127,10 +127,18 @@ function readViaWindowsCredman() {
|
|
|
127
127
|
}
|
|
128
128
|
// On headless Linux (no Secret Service) agy persists the token to a plain-JSON
|
|
129
129
|
// file instead of the keyring. Same payload shape, no `go-keyring-base64:` prefix.
|
|
130
|
+
//
|
|
131
|
+
// The jetski path is last on purpose. macOS keeps that file around next to the
|
|
132
|
+
// Keychain entry, and the two can hold *different* grants — an observed one was
|
|
133
|
+
// three days stale and belonged to a session with no Antigravity license, which
|
|
134
|
+
// still refreshes fine and only fails later at the quota call. Every keyring
|
|
135
|
+
// backend is tried first, so this is reached only where the alternative is no
|
|
136
|
+
// credential at all.
|
|
130
137
|
function readViaFile() {
|
|
131
138
|
const candidates = [
|
|
132
139
|
process.env.AGY_OAUTH_TOKEN_FILE,
|
|
133
140
|
join(homedir(), '.gemini', 'antigravity-cli', 'antigravity-oauth-token'),
|
|
141
|
+
join(homedir(), '.gemini', 'jetski-standalone-oauth-token'),
|
|
134
142
|
].filter((p) => Boolean(p));
|
|
135
143
|
for (const path of candidates) {
|
|
136
144
|
try {
|
package/dist/src/main.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// agy-cli-usage update [--check] self-update via npm
|
|
13
13
|
// agy-cli-usage --version | -v print the installed version
|
|
14
14
|
import { getAccessToken, CredentialError } from './credentials.js';
|
|
15
|
-
import { fetchQuotaSummary } from './api.js';
|
|
15
|
+
import { fetchQuotaSummary, ApiError } from './api.js';
|
|
16
16
|
import { captureUsageViaPty } from './pty-fallback.js';
|
|
17
17
|
import { fromApi, fromPty } from './quota.js';
|
|
18
18
|
import { renderPanel } from './render.js';
|
|
@@ -24,6 +24,15 @@ import { fileURLToPath } from 'node:url';
|
|
|
24
24
|
const CACHE_DIR = join(process.env.XDG_CACHE_HOME || join(homedir(), '.cache'), 'agy-usage');
|
|
25
25
|
const CACHE_FILE = join(CACHE_DIR, 'quota.json');
|
|
26
26
|
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
27
|
+
// The API path fails in ways that look alike on the wire but need different
|
|
28
|
+
// things from the user — see ApiErrorKind in api.ts. Printing "sign in again"
|
|
29
|
+
// for a missing license just sends people round a loop that cannot help.
|
|
30
|
+
const API_ERROR_HINTS = {
|
|
31
|
+
unauthorized: 'hint: the stored token was rejected — run `agy` and sign in again.',
|
|
32
|
+
'not-entitled': 'hint: the token is valid but this Google account has no Antigravity license. Signing in again ' +
|
|
33
|
+
'will not change that — check which account `agy` is using, or its subscription.',
|
|
34
|
+
'no-project': 'hint: `--source pty` reads the panel from `agy` itself and does not need this endpoint.',
|
|
35
|
+
};
|
|
27
36
|
const VALID_SOURCES = ['auto', 'api', 'pty'];
|
|
28
37
|
const VALID_CHANNELS = ['auto', 'daily', 'prod'];
|
|
29
38
|
const errMessage = (e) => (e instanceof Error ? e.message : String(e));
|
|
@@ -206,6 +215,9 @@ if (isMainModule()) {
|
|
|
206
215
|
}
|
|
207
216
|
else {
|
|
208
217
|
process.stderr.write(`error: ${errMessage(err)}\n`);
|
|
218
|
+
const hint = err instanceof ApiError ? API_ERROR_HINTS[err.kind] : undefined;
|
|
219
|
+
if (hint)
|
|
220
|
+
process.stderr.write(`${hint}\n`);
|
|
209
221
|
}
|
|
210
222
|
process.exit(1);
|
|
211
223
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agy-cli-usage",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
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",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
},
|
|
57
57
|
"license": "MIT",
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@eslint/js": "^
|
|
60
|
-
"@types/node": "^
|
|
61
|
-
"eslint": "^
|
|
59
|
+
"@eslint/js": "^10.0.1",
|
|
60
|
+
"@types/node": "^26.1.2",
|
|
61
|
+
"eslint": "^10.6.0",
|
|
62
62
|
"typescript": "^6.0.3",
|
|
63
63
|
"typescript-eslint": "^8.62.1"
|
|
64
64
|
}
|