agy-cli-usage 0.3.0 → 0.4.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/CHANGELOG.md +15 -0
- package/README.md +86 -51
- package/dist/src/api.d.ts +12 -0
- package/dist/src/api.js +84 -0
- package/dist/src/api.js.map +1 -0
- package/dist/src/credentials.d.ts +22 -0
- package/dist/src/credentials.js +233 -0
- package/dist/src/credentials.js.map +1 -0
- package/dist/src/main.d.ts +20 -0
- package/dist/src/main.js +169 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/pty-fallback.d.ts +5 -0
- package/dist/src/pty-fallback.js +221 -0
- package/dist/src/pty-fallback.js.map +1 -0
- package/dist/src/quota.d.ts +7 -0
- package/dist/src/quota.js +81 -0
- package/dist/src/quota.js.map +1 -0
- package/dist/src/render.d.ts +3 -0
- package/dist/src/render.js +82 -0
- package/dist/src/render.js.map +1 -0
- package/dist/src/server.d.ts +2 -0
- package/dist/src/server.js +43 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/types.d.ts +67 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/update.d.ts +17 -0
- package/dist/src/update.js +87 -0
- package/dist/src/update.js.map +1 -0
- package/package.json +17 -10
- package/server.js +0 -48
- package/src/api.js +0 -92
- package/src/credentials.js +0 -186
- package/src/main.js +0 -142
- package/src/pty-fallback.js +0 -211
- package/src/quota.js +0 -100
- package/src/render.js +0 -82
- package/src/update.js +0 -87
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,21 @@
|
|
|
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.0](https://github.com/abruption/agy-cli-usage/compare/v0.3.1...v0.4.0) (2026-06-24)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* ship as a compiled TypeScript build with bundled type declarations ([0e253ea](https://github.com/abruption/agy-cli-usage/commit/0e253ea353e99efe5bfd176f5b6775d76718b3a9))
|
|
19
|
+
* ship compiled TypeScript build with type declarations ([#20](https://github.com/abruption/agy-cli-usage/issues/20)) ([0e253ea](https://github.com/abruption/agy-cli-usage/commit/0e253ea353e99efe5bfd176f5b6775d76718b3a9))
|
|
20
|
+
|
|
21
|
+
## [0.3.1](https://github.com/abruption/agy-cli-usage/compare/v0.3.0...v0.3.1) (2026-06-24)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Bug Fixes
|
|
25
|
+
|
|
26
|
+
* read agy token from Windows Credential Manager ([#14](https://github.com/abruption/agy-cli-usage/issues/14)) ([e5cb274](https://github.com/abruption/agy-cli-usage/commit/e5cb274c6a0a6aa1ddf6b491d417bfc81c04916a)), closes [#13](https://github.com/abruption/agy-cli-usage/issues/13)
|
|
27
|
+
|
|
13
28
|
## [0.2.0](https://github.com/abruption/agy-cli-usage/compare/v0.1.0...v0.2.0) (2026-06-24)
|
|
14
29
|
|
|
15
30
|
### Features
|
package/README.md
CHANGED
|
@@ -1,89 +1,124 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# agy-cli-usage
|
|
2
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/agy-cli-usage)
|
|
6
|
+
[](https://www.npmjs.com/package/agy-cli-usage)
|
|
3
7
|
[](https://github.com/abruption/agy-cli-usage/actions/workflows/ci.yml)
|
|
4
|
-
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://www.npmjs.com/package/agy-cli-usage)
|
|
10
|
+
[](LICENSE)
|
|
5
11
|
|
|
6
|
-
Headless usage
|
|
12
|
+
**Headless usage & quota monitor for the Antigravity CLI (`agy`).**
|
|
7
13
|
|
|
8
|
-
`agy
|
|
14
|
+
`agy`의 `/usage` 패널(모델 그룹별 주간·5시간 한도, 잔여율, 리프레시 시각)을 **헤드리스로** 조회합니다 — IDE 불필요, macOS · Linux · Windows · 헤드리스 서버 지원.
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
<sub>Inspired by <a href="https://github.com/skainguyen1412/antigravity-usage">skainguyen1412/antigravity-usage</a> — which targets the Antigravity <b>IDE</b>; this targets the <b>CLI</b> (<code>agy</code>).</sub>
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
```text
|
|
23
|
+
Models & Quota
|
|
24
|
+
Account: you@gmail.com
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
GEMINI MODELS
|
|
27
|
+
Weekly Limit
|
|
28
|
+
[██████████████████████████████████████████████░░░░] 91.72%
|
|
29
|
+
92% remaining · Refreshes in 73h 23m
|
|
30
|
+
Five Hour Limit
|
|
31
|
+
[███████████████████████████████████████████████░░░] 94.63%
|
|
32
|
+
95% remaining · Refreshes in 2h 2m
|
|
33
|
+
|
|
34
|
+
CLAUDE AND GPT MODELS
|
|
35
|
+
Weekly Limit
|
|
36
|
+
[█████████████████████████████████████████████████░] 97.78%
|
|
37
|
+
98% remaining · Refreshes in 26h 17m
|
|
38
|
+
Five Hour Limit
|
|
39
|
+
[██████████████████████████████████████████████████] Quota available
|
|
23
40
|
```
|
|
24
41
|
|
|
25
|
-
|
|
42
|
+
## 🚀 빠른 시작
|
|
26
43
|
|
|
27
|
-
|
|
44
|
+
```bash
|
|
45
|
+
# 설치 없이 한 번 실행
|
|
46
|
+
npx agy-cli-usage
|
|
47
|
+
|
|
48
|
+
# 전역 설치 → 어디서나 `agy-cli-usage`
|
|
49
|
+
npm install -g agy-cli-usage
|
|
50
|
+
agy-cli-usage
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
> 사전 조건: 같은 머신에서 `agy`에 로그인되어 있을 것. Node.js >= 18.
|
|
54
|
+
|
|
55
|
+
## ⚡ 사용법
|
|
28
56
|
|
|
29
57
|
```bash
|
|
30
|
-
agy-usage # /usage 와 동일한 패널
|
|
31
|
-
agy-usage --json # 머신 리더블 JSON
|
|
32
|
-
agy-usage --watch 60 # 60초 간격 자동 갱신 (5분 캐시 경유)
|
|
33
|
-
agy-usage --
|
|
34
|
-
agy-usage --
|
|
35
|
-
agy-usage --no-cache # 5분 캐시 무시하고 강제 조회
|
|
58
|
+
agy-cli-usage # /usage 와 동일한 패널 (1회성)
|
|
59
|
+
agy-cli-usage --json # 머신 리더블 JSON
|
|
60
|
+
agy-cli-usage --watch 60 # 60초 간격 자동 갱신 (5분 캐시 경유)
|
|
61
|
+
agy-cli-usage update [--check] # 자기 업데이트 (--check: 알림만)
|
|
62
|
+
agy-cli-usage --version # 버전 출력
|
|
36
63
|
```
|
|
37
64
|
|
|
38
|
-
|
|
65
|
+
| 플래그 | 설명 |
|
|
66
|
+
|--------|------|
|
|
67
|
+
| `--json` | 정규화 JSON 출력 (스크립트/대시보드 연동) |
|
|
68
|
+
| `--watch [초]` | N초 간격 갱신 (기본 60) |
|
|
69
|
+
| `--source <auto\|api\|pty>` | 데이터 소스 (기본 `auto`: API → 실패 시 PTY) |
|
|
70
|
+
| `--channel <auto\|daily\|prod>` | Cloud Code 호스트 |
|
|
71
|
+
| `--no-cache` / `--refresh` | 5분 캐시 무시하고 강제 조회 |
|
|
72
|
+
|
|
73
|
+
## 🤔 왜 필요한가
|
|
39
74
|
|
|
40
|
-
`agy
|
|
75
|
+
`agy -p "<prompt>"`(headless)는 LLM 프롬프트 전용이라 `/usage` 같은 TUI 슬래시 커맨드를 렌더링하지 않습니다. 그래서 사용량을 자동화·폴링할 수 없습니다. 이 도구는 그 공백을 메웁니다.
|
|
41
76
|
|
|
42
|
-
|
|
43
|
-
- `POST https://<host>/v1internal:loadCodeAssist` → `cloudaicompanionProject` 획득
|
|
44
|
-
- `POST https://<host>/v1internal:retrieveUserQuotaSummary {project}` → 쿼타
|
|
45
|
-
- 호스트: `daily-cloudcode-pa.googleapis.com`(현재 CLI) 또는 `cloudcode-pa.googleapis.com`
|
|
46
|
-
- 토큰 소스: **키링 → 토큰 파일 → (실패 시) PTY** 순으로 자동 시도
|
|
47
|
-
2. **PTY 폴백** — 위 토큰 소스를 모두 읽을 수 없거나 내부 API가 바뀌면, `agy`를 가상 터미널로 띄워 `/usage`를 보내고, `@xterm/headless`로 alt-screen을 재구성해 패널을 파싱한다.
|
|
77
|
+
## 🔍 동작 방식
|
|
48
78
|
|
|
49
|
-
|
|
79
|
+
1. **직접 API (기본 · 빠름)** — OS에서 `agy`의 OAuth 토큰을 읽어, `agy`가 시작 시 호출하는 것과 동일한 Cloud Code 내부 API를 직접 호출합니다.
|
|
80
|
+
- `POST /v1internal:loadCodeAssist` → `cloudaicompanionProject` 획득
|
|
81
|
+
- `POST /v1internal:retrieveUserQuotaSummary {project}` → 쿼타
|
|
82
|
+
- 만료 시 OAuth refresh 자동 처리.
|
|
83
|
+
2. **PTY 폴백 (안전망)** — 위 토큰을 못 읽거나 내부 API가 바뀌면, `agy`를 가상 터미널로 띄워 `/usage`를 보내고 `@xterm/headless`로 화면을 재구성해 파싱합니다.
|
|
50
84
|
|
|
51
|
-
|
|
52
|
-
|----|-----------|------|
|
|
53
|
-
| macOS | Keychain | `@napi-rs/keyring`, CLI 폴백 `security` |
|
|
54
|
-
| Windows | Credential Manager | `@napi-rs/keyring` |
|
|
55
|
-
| Linux (데스크톱) | Secret Service | `@napi-rs/keyring`, CLI 폴백 `secret-tool` |
|
|
56
|
-
| Linux (헤드리스) | **토큰 파일** | 키링 부재 시 `~/.gemini/antigravity-cli/antigravity-oauth-token`(순수 JSON) 읽음 → **API 경로 동작**. `AGY_OAUTH_TOKEN_FILE`로 경로 override |
|
|
85
|
+
## 🔐 크로스플랫폼 자격증명
|
|
57
86
|
|
|
58
|
-
|
|
87
|
+
토큰은 `agy`가 저장한 위치에서 **읽기만** 합니다. 플랫폼별로 자동 처리:
|
|
59
88
|
|
|
60
|
-
|
|
89
|
+
| OS / 환경 | 저장 위치 | 읽는 방법 |
|
|
90
|
+
|-----------|----------|----------|
|
|
91
|
+
| macOS | Keychain | `@napi-rs/keyring` (폴백 `security`) |
|
|
92
|
+
| Linux 데스크톱 | Secret Service | `@napi-rs/keyring` (폴백 `secret-tool`) |
|
|
93
|
+
| **Windows** | Credential Manager | 내장 `powershell.exe`로 Win32 `CredRead` 호출 |
|
|
94
|
+
| **헤드리스 Linux** | 토큰 파일 | `~/.gemini/antigravity-cli/antigravity-oauth-token` |
|
|
95
|
+
|
|
96
|
+
읽기 체인: `키링 → OS CLI → Windows credman → 토큰 파일 → PTY`. `AGY_OAUTH_TOKEN_FILE`로 파일 경로 override.
|
|
97
|
+
|
|
98
|
+
## 🔌 HTTP 엔드포인트 (선택)
|
|
61
99
|
|
|
62
100
|
```bash
|
|
63
101
|
PORT=3007 node server.js # GET /quota → 정규화 JSON (5분 캐시), GET /healthz
|
|
64
102
|
```
|
|
65
103
|
|
|
66
|
-
외부 대시보드/스크립트에서 `GET /quota`로
|
|
104
|
+
외부 대시보드/스크립트에서 `GET /quota`로 소비하거나 `agy-cli-usage --json`을 subprocess로 호출하세요.
|
|
67
105
|
|
|
68
|
-
## 개발 / 릴리스
|
|
106
|
+
## 🛠️ 개발 / 릴리스
|
|
69
107
|
|
|
70
108
|
```bash
|
|
71
109
|
npm test # node --test (자격증명·네트워크 불필요, 순수 로직)
|
|
72
110
|
npm run check # 구문 검사
|
|
73
111
|
```
|
|
74
112
|
|
|
75
|
-
- **CI
|
|
76
|
-
- **Release
|
|
77
|
-
|
|
78
|
-
릴리스 절차 — 수동 버전 범프 없음:
|
|
113
|
+
- **CI**: push/PR마다 ubuntu(Node 18/20/22) + macOS/Windows(Node 22)에서 테스트.
|
|
114
|
+
- **Release**: [release-please](https://github.com/googleapis/release-please) — Conventional Commits 기반 완전 자동화. main에 머지된 커밋으로 release-please가 **Release PR**(버전 범프 + CHANGELOG)을 유지하고, 그 PR을 머지하면 태그·GitHub Release·`npm publish --provenance`가 자동 실행됩니다.
|
|
79
115
|
|
|
80
|
-
|
|
81
|
-
2. 그 **Release PR을 머지**하면 → GitHub Release + 태그 생성 → 같은 워크플로가 `npm publish --provenance` 실행.
|
|
116
|
+
## ⚠️ 주의
|
|
82
117
|
|
|
83
|
-
|
|
118
|
+
- `v1internal:retrieveUserQuotaSummary`는 **비공개 내부 엔드포인트**입니다. 스키마/호스트가 예고 없이 바뀔 수 있으며, 그때의 안전망이 PTY 폴백입니다. 본인 계정의 사용량 조회 용도로만 사용하세요.
|
|
119
|
+
- 자격증명은 OS 저장소에서 **읽기만** 하며, refresh 토큰을 되쓰지 않아 `agy` 세션과 충돌하지 않습니다.
|
|
120
|
+
- 코드에 포함된 OAuth client_id/secret은 `agy` 바이너리에 들어 있는 **installed-app(public) 값**으로, [Google 문서](https://developers.google.com/identity/protocols/oauth2)상 기밀이 아닙니다. 사용자 식별은 각자의 키링 토큰으로 이뤄집니다.
|
|
84
121
|
|
|
85
|
-
##
|
|
122
|
+
## 📄 License
|
|
86
123
|
|
|
87
|
-
|
|
88
|
-
- 본인 계정의 키링 토큰만 읽으며(읽기 전용), refresh 토큰을 키링에 되쓰지 않는다.
|
|
89
|
-
- OAuth client_secret은 데스크톱(공개) 클라이언트 값이라 비밀이 아니다.
|
|
124
|
+
[MIT](LICENSE) © abruption
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FetchResult } from './types.js';
|
|
2
|
+
declare class ApiError extends Error {
|
|
3
|
+
status: number;
|
|
4
|
+
constructor(message: string, status: number);
|
|
5
|
+
}
|
|
6
|
+
export interface FetchOptions {
|
|
7
|
+
host?: string;
|
|
8
|
+
channel?: 'daily' | 'prod';
|
|
9
|
+
}
|
|
10
|
+
/** Fetch the raw quota summary from the Cloud Code API. */
|
|
11
|
+
export declare function fetchQuotaSummary(accessToken: string, opts?: FetchOptions): Promise<FetchResult>;
|
|
12
|
+
export { ApiError };
|
package/dist/src/api.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Direct client for the Antigravity / Gemini Code Assist "Cloud Code" internal API.
|
|
2
|
+
//
|
|
3
|
+
// Reproduces exactly what `agy` does on startup to populate its /usage panel:
|
|
4
|
+
// 1. POST /v1internal:loadCodeAssist {metadata:{ideType:"ANTIGRAVITY"}}
|
|
5
|
+
// -> { cloudaicompanionProject, currentTier, ... }
|
|
6
|
+
// 2. POST /v1internal:retrieveUserQuotaSummary {project:<cloudaicompanionProject>}
|
|
7
|
+
// -> { groups:[...], description }
|
|
8
|
+
//
|
|
9
|
+
// Captured from live agy traffic (mitmproxy). The internal endpoint is undocumented;
|
|
10
|
+
// the PTY fallback exists for when it changes.
|
|
11
|
+
const UA = `antigravity-usage-monitor/0.1 ${process.platform}/${process.arch}`;
|
|
12
|
+
// Antigravity ships against the "daily" Cloud Code host; stable builds use the
|
|
13
|
+
// plain host. Try daily first (matches current CLI), fall back to prod.
|
|
14
|
+
const HOSTS = ['daily-cloudcode-pa.googleapis.com', 'cloudcode-pa.googleapis.com'];
|
|
15
|
+
class ApiError extends Error {
|
|
16
|
+
status;
|
|
17
|
+
constructor(message, status) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.status = status;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function extractEmail(uri) {
|
|
23
|
+
const m = uri?.match(/[?&]Email=([^&]+)/);
|
|
24
|
+
if (!m)
|
|
25
|
+
return null;
|
|
26
|
+
try {
|
|
27
|
+
return decodeURIComponent(m[1]);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return m[1];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function postInternal(host, accessToken, method, body) {
|
|
34
|
+
const res = await fetch(`https://${host}/v1internal:${method}`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
Authorization: `Bearer ${accessToken}`,
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
'User-Agent': UA,
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify(body),
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
throw new ApiError(`${method} -> HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`, res.status);
|
|
45
|
+
}
|
|
46
|
+
return (await res.json());
|
|
47
|
+
}
|
|
48
|
+
/** Fetch the raw quota summary from the Cloud Code API. */
|
|
49
|
+
export async function fetchQuotaSummary(accessToken, opts = {}) {
|
|
50
|
+
const candidates = opts.host
|
|
51
|
+
? [opts.host]
|
|
52
|
+
: opts.channel === 'prod'
|
|
53
|
+
? ['cloudcode-pa.googleapis.com']
|
|
54
|
+
: opts.channel === 'daily'
|
|
55
|
+
? ['daily-cloudcode-pa.googleapis.com']
|
|
56
|
+
: HOSTS;
|
|
57
|
+
let lastErr;
|
|
58
|
+
for (const host of candidates) {
|
|
59
|
+
try {
|
|
60
|
+
const lca = await postInternal(host, accessToken, 'loadCodeAssist', {
|
|
61
|
+
metadata: { ideType: 'ANTIGRAVITY' },
|
|
62
|
+
});
|
|
63
|
+
const project = lca.cloudaicompanionProject;
|
|
64
|
+
if (!project)
|
|
65
|
+
throw new ApiError('loadCodeAssist returned no cloudaicompanionProject', 0);
|
|
66
|
+
const raw = await postInternal(host, accessToken, 'retrieveUserQuotaSummary', { project });
|
|
67
|
+
return {
|
|
68
|
+
raw,
|
|
69
|
+
host,
|
|
70
|
+
tier: lca.currentTier?.id ?? null,
|
|
71
|
+
account: extractEmail(lca.currentTier?.upgradeSubscriptionUri),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
lastErr = err;
|
|
76
|
+
// 404 / wrong-host -> try next candidate; auth errors -> stop early.
|
|
77
|
+
if (err instanceof ApiError && (err.status === 401 || err.status === 403))
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
throw lastErr ?? new ApiError('No Cloud Code host responded', 0);
|
|
82
|
+
}
|
|
83
|
+
export { ApiError };
|
|
84
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare class CredentialError extends Error {
|
|
2
|
+
}
|
|
3
|
+
interface Cred {
|
|
4
|
+
accessToken: string;
|
|
5
|
+
refreshToken: string | null;
|
|
6
|
+
expiry: Date | null;
|
|
7
|
+
authMethod: string | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function decodeSecret(raw: string): Cred;
|
|
10
|
+
export interface AccessToken {
|
|
11
|
+
accessToken: string;
|
|
12
|
+
authMethod: string | null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns a valid access token for the Cloud Code API, refreshing if needed.
|
|
16
|
+
* Throws CredentialError if no credential can be read from any keyring backend
|
|
17
|
+
* (the caller should then consider the PTY fallback).
|
|
18
|
+
*/
|
|
19
|
+
export declare function getAccessToken(): Promise<AccessToken>;
|
|
20
|
+
/** Whether a keyring-based credential is readable at all (no refresh attempted). */
|
|
21
|
+
export declare function hasCredential(): Promise<boolean>;
|
|
22
|
+
export { CredentialError };
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// Cross-platform reader for the Antigravity CLI (`agy`) OAuth credential.
|
|
2
|
+
//
|
|
3
|
+
// `agy` stores its token in the OS keyring using the zalando/go-keyring
|
|
4
|
+
// convention: service="gemini", account="antigravity". Long/binary values are
|
|
5
|
+
// stored with a `go-keyring-base64:` prefix followed by base64(JSON). The
|
|
6
|
+
// decoded JSON looks like:
|
|
7
|
+
// { "token": { "access_token", "token_type", "refresh_token", "expiry" },
|
|
8
|
+
// "auth_method": "consumer" }
|
|
9
|
+
//
|
|
10
|
+
// Read backends, tried in order:
|
|
11
|
+
// 1. @napi-rs/keyring native module (macOS / Linux Secret Service)
|
|
12
|
+
// 2. OS CLI fallback (`security` on macOS, `secret-tool` on Linux)
|
|
13
|
+
// 3. Windows Credential Manager (CredRead via powershell.exe — go-keyring's
|
|
14
|
+
// target format differs from keyring-rs's)
|
|
15
|
+
// 4. File fallback (headless Linux: agy can't reach a keyring
|
|
16
|
+
// and writes the token to a plain-JSON file)
|
|
17
|
+
// If every backend fails, the caller falls back to the PTY path which drives
|
|
18
|
+
// `agy` itself.
|
|
19
|
+
import { execFileSync } from 'node:child_process';
|
|
20
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
21
|
+
import { homedir } from 'node:os';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
// OAuth client for the Antigravity CLI. This is an installed/desktop ("public")
|
|
24
|
+
// OAuth client: per Google's own docs the client secret of an installed app is
|
|
25
|
+
// "obviously not treated as a secret" — it ships inside the agy binary and is
|
|
26
|
+
// identical for every user (the per-user identity is the keyring token, not
|
|
27
|
+
// this). Verified: the same client_id appears in agy's browser consent URL
|
|
28
|
+
// regardless of account, and the flow uses PKCE (code_challenge/S256), the
|
|
29
|
+
// mechanism that secures public clients precisely because the secret is public.
|
|
30
|
+
// Same pattern as Google's open-source gemini-cli.
|
|
31
|
+
const OAUTH_CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
|
|
32
|
+
const OAUTH_CLIENT_SECRET = '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';
|
|
33
|
+
const TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
|
34
|
+
const KEYRING_SERVICE = 'gemini';
|
|
35
|
+
const KEYRING_ACCOUNT = 'antigravity';
|
|
36
|
+
const B64_PREFIX = 'go-keyring-base64:';
|
|
37
|
+
class CredentialError extends Error {
|
|
38
|
+
}
|
|
39
|
+
// --- raw keyring read --------------------------------------------------------
|
|
40
|
+
async function readViaNapiEsm() {
|
|
41
|
+
try {
|
|
42
|
+
const { Entry } = await import('@napi-rs/keyring');
|
|
43
|
+
if (!Entry)
|
|
44
|
+
return null;
|
|
45
|
+
return new Entry(KEYRING_SERVICE, KEYRING_ACCOUNT).getPassword() ?? null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function readViaCli() {
|
|
52
|
+
try {
|
|
53
|
+
if (process.platform === 'darwin') {
|
|
54
|
+
return execFileSync('security', ['find-generic-password', '-s', KEYRING_SERVICE, '-a', KEYRING_ACCOUNT, '-w'], { encoding: 'utf8' }).trim();
|
|
55
|
+
}
|
|
56
|
+
if (process.platform === 'linux') {
|
|
57
|
+
return execFileSync('secret-tool', ['lookup', 'service', KEYRING_SERVICE, 'account', KEYRING_ACCOUNT], { encoding: 'utf8' }).trim();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
// On Windows, agy stores the token in Credential Manager via Go's
|
|
66
|
+
// zalando/go-keyring, whose target name is `service:account` ("gemini:antigravity").
|
|
67
|
+
// @napi-rs/keyring (keyring-rs) uses a different target format and can't find it,
|
|
68
|
+
// so we read the credential blob directly via the Win32 CredRead API through the
|
|
69
|
+
// built-in powershell.exe (no extra dependency).
|
|
70
|
+
const WIN_CRED_TARGET = `${KEYRING_SERVICE}:${KEYRING_ACCOUNT}`;
|
|
71
|
+
const PS_READ_CRED = `$ErrorActionPreference='Stop'
|
|
72
|
+
$sig=@'
|
|
73
|
+
using System;
|
|
74
|
+
using System.Runtime.InteropServices;
|
|
75
|
+
public class CredApi {
|
|
76
|
+
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
77
|
+
public static extern bool CredRead(string target, int type, int flags, out IntPtr cred);
|
|
78
|
+
[DllImport("advapi32.dll")] public static extern void CredFree(IntPtr cred);
|
|
79
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
80
|
+
public struct CREDENTIAL {
|
|
81
|
+
public int Flags; public int Type; public IntPtr TargetName; public IntPtr Comment;
|
|
82
|
+
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
|
|
83
|
+
public int CredentialBlobSize; public IntPtr CredentialBlob; public int Persist;
|
|
84
|
+
public int AttributeCount; public IntPtr Attributes; public IntPtr TargetAlias; public IntPtr UserName;
|
|
85
|
+
}
|
|
86
|
+
public static byte[] Read(string target){
|
|
87
|
+
IntPtr p; if(!CredRead(target,1,0,out p)) return null;
|
|
88
|
+
try {
|
|
89
|
+
var c=(CREDENTIAL)Marshal.PtrToStructure(p,typeof(CREDENTIAL));
|
|
90
|
+
var b=new byte[c.CredentialBlobSize];
|
|
91
|
+
if(c.CredentialBlobSize>0) Marshal.Copy(c.CredentialBlob,b,0,c.CredentialBlobSize);
|
|
92
|
+
return b;
|
|
93
|
+
} finally { CredFree(p); }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
'@
|
|
97
|
+
Add-Type -TypeDefinition $sig | Out-Null
|
|
98
|
+
$b=[CredApi]::Read('${WIN_CRED_TARGET}')
|
|
99
|
+
if($b -eq $null){ exit 1 }
|
|
100
|
+
[Console]::Out.Write([Convert]::ToBase64String($b))`;
|
|
101
|
+
function readViaWindowsCredman() {
|
|
102
|
+
if (process.platform !== 'win32')
|
|
103
|
+
return null;
|
|
104
|
+
try {
|
|
105
|
+
const encoded = Buffer.from(PS_READ_CRED, 'utf16le').toString('base64');
|
|
106
|
+
const b64 = execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', encoded], { encoding: 'utf8' }).trim();
|
|
107
|
+
if (!b64)
|
|
108
|
+
return null;
|
|
109
|
+
const raw = Buffer.from(b64, 'base64');
|
|
110
|
+
// go-keyring writes the value as UTF-8; tolerate UTF-16LE just in case.
|
|
111
|
+
const utf8 = raw.toString('utf8');
|
|
112
|
+
const looksValid = (s) => s.startsWith(B64_PREFIX) || s.trimStart().startsWith('{');
|
|
113
|
+
if (looksValid(utf8))
|
|
114
|
+
return utf8;
|
|
115
|
+
const utf16 = raw.toString('utf16le');
|
|
116
|
+
if (looksValid(utf16))
|
|
117
|
+
return utf16;
|
|
118
|
+
return utf8;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// On headless Linux (no Secret Service) agy persists the token to a plain-JSON
|
|
125
|
+
// file instead of the keyring. Same payload shape, no `go-keyring-base64:` prefix.
|
|
126
|
+
function readViaFile() {
|
|
127
|
+
const candidates = [
|
|
128
|
+
process.env.AGY_OAUTH_TOKEN_FILE,
|
|
129
|
+
join(homedir(), '.gemini', 'antigravity-cli', 'antigravity-oauth-token'),
|
|
130
|
+
].filter((p) => Boolean(p));
|
|
131
|
+
for (const path of candidates) {
|
|
132
|
+
try {
|
|
133
|
+
if (existsSync(path)) {
|
|
134
|
+
const content = readFileSync(path, 'utf8').trim();
|
|
135
|
+
if (content)
|
|
136
|
+
return content;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// unreadable (perms) — try next candidate
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
async function readRawSecret() {
|
|
146
|
+
const fromNapi = await readViaNapiEsm();
|
|
147
|
+
if (fromNapi)
|
|
148
|
+
return fromNapi;
|
|
149
|
+
const fromCli = readViaCli();
|
|
150
|
+
if (fromCli)
|
|
151
|
+
return fromCli;
|
|
152
|
+
const fromWin = readViaWindowsCredman();
|
|
153
|
+
if (fromWin)
|
|
154
|
+
return fromWin;
|
|
155
|
+
const fromFile = readViaFile();
|
|
156
|
+
if (fromFile)
|
|
157
|
+
return fromFile;
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
// --- decode ------------------------------------------------------------------
|
|
161
|
+
export function decodeSecret(raw) {
|
|
162
|
+
const payload = raw.startsWith(B64_PREFIX)
|
|
163
|
+
? Buffer.from(raw.slice(B64_PREFIX.length), 'base64').toString('utf8')
|
|
164
|
+
: raw;
|
|
165
|
+
let parsed;
|
|
166
|
+
try {
|
|
167
|
+
parsed = JSON.parse(payload);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
throw new CredentialError('Stored agy credential is not valid JSON');
|
|
171
|
+
}
|
|
172
|
+
const token = (parsed.token ?? parsed);
|
|
173
|
+
const accessToken = token.access_token;
|
|
174
|
+
if (typeof accessToken !== 'string' || !accessToken) {
|
|
175
|
+
throw new CredentialError('Stored agy credential has no access_token');
|
|
176
|
+
}
|
|
177
|
+
const expiry = token.expiry;
|
|
178
|
+
return {
|
|
179
|
+
accessToken,
|
|
180
|
+
refreshToken: typeof token.refresh_token === 'string' ? token.refresh_token : null,
|
|
181
|
+
expiry: typeof expiry === 'string' ? new Date(expiry) : null,
|
|
182
|
+
authMethod: typeof parsed.auth_method === 'string' ? parsed.auth_method : null,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// --- refresh -----------------------------------------------------------------
|
|
186
|
+
function isExpired(cred, skewMs = 60_000) {
|
|
187
|
+
if (!cred.expiry)
|
|
188
|
+
return false;
|
|
189
|
+
return cred.expiry.getTime() - Date.now() < skewMs;
|
|
190
|
+
}
|
|
191
|
+
async function refreshAccessToken(refreshToken) {
|
|
192
|
+
const body = new URLSearchParams({
|
|
193
|
+
grant_type: 'refresh_token',
|
|
194
|
+
refresh_token: refreshToken,
|
|
195
|
+
client_id: OAUTH_CLIENT_ID,
|
|
196
|
+
client_secret: OAUTH_CLIENT_SECRET,
|
|
197
|
+
});
|
|
198
|
+
const res = await fetch(TOKEN_URL, {
|
|
199
|
+
method: 'POST',
|
|
200
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
201
|
+
body: body.toString(),
|
|
202
|
+
});
|
|
203
|
+
if (!res.ok) {
|
|
204
|
+
throw new CredentialError(`Token refresh failed: HTTP ${res.status} ${await res.text()}`);
|
|
205
|
+
}
|
|
206
|
+
const json = (await res.json());
|
|
207
|
+
return json.access_token;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Returns a valid access token for the Cloud Code API, refreshing if needed.
|
|
211
|
+
* Throws CredentialError if no credential can be read from any keyring backend
|
|
212
|
+
* (the caller should then consider the PTY fallback).
|
|
213
|
+
*/
|
|
214
|
+
export async function getAccessToken() {
|
|
215
|
+
const raw = await readRawSecret();
|
|
216
|
+
if (!raw) {
|
|
217
|
+
throw new CredentialError('Could not read agy credential from the OS keyring or token file. ' +
|
|
218
|
+
'Is agy logged in on this machine? (set AGY_OAUTH_TOKEN_FILE to override the path, ' +
|
|
219
|
+
'or use --source pty)');
|
|
220
|
+
}
|
|
221
|
+
const cred = decodeSecret(raw);
|
|
222
|
+
if (isExpired(cred) && cred.refreshToken) {
|
|
223
|
+
const fresh = await refreshAccessToken(cred.refreshToken);
|
|
224
|
+
return { accessToken: fresh, authMethod: cred.authMethod };
|
|
225
|
+
}
|
|
226
|
+
return { accessToken: cred.accessToken, authMethod: cred.authMethod };
|
|
227
|
+
}
|
|
228
|
+
/** Whether a keyring-based credential is readable at all (no refresh attempted). */
|
|
229
|
+
export async function hasCredential() {
|
|
230
|
+
return (await readRawSecret()) != null;
|
|
231
|
+
}
|
|
232
|
+
export { CredentialError };
|
|
233
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import type { Snapshot } from './types.js';
|
|
3
|
+
export interface CliOptions {
|
|
4
|
+
json: boolean;
|
|
5
|
+
watch: number | null;
|
|
6
|
+
source: 'auto' | 'api' | 'pty';
|
|
7
|
+
channel: 'auto' | 'daily' | 'prod';
|
|
8
|
+
cache: boolean;
|
|
9
|
+
command: 'update' | null;
|
|
10
|
+
check: boolean;
|
|
11
|
+
version?: boolean;
|
|
12
|
+
help?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Subset of options needed to produce a snapshot (also usable from server.ts). */
|
|
15
|
+
export interface SnapshotOptions {
|
|
16
|
+
source: 'auto' | 'api' | 'pty';
|
|
17
|
+
channel: 'auto' | 'daily' | 'prod';
|
|
18
|
+
cache: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function getSnapshot(opts: SnapshotOptions): Promise<Snapshot>;
|