agy-cli-usage 0.3.0 → 0.3.1

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 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.3.1](https://github.com/abruption/agy-cli-usage/compare/v0.3.0...v0.3.1) (2026-06-24)
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * 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)
19
+
13
20
  ## [0.2.0](https://github.com/abruption/agy-cli-usage/compare/v0.1.0...v0.2.0) (2026-06-24)
14
21
 
15
22
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agy-cli-usage",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
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
  "bin": {
@@ -8,9 +8,11 @@
8
8
  // "auth_method": "consumer" }
9
9
  //
10
10
  // Read backends, tried in order:
11
- // 1. @napi-rs/keyring native module (macOS / Windows / Linux Secret Service)
11
+ // 1. @napi-rs/keyring native module (macOS / Linux Secret Service)
12
12
  // 2. OS CLI fallback (`security` on macOS, `secret-tool` on Linux)
13
- // 3. File fallback (headless Linux: agy can't reach a keyring
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
14
16
  // and writes the token to a plain-JSON file)
15
17
  // If every backend fails, the caller falls back to the PTY path which drives
16
18
  // `agy` itself.
@@ -73,6 +75,67 @@ function readViaCli() {
73
75
  return null;
74
76
  }
75
77
 
78
+ // On Windows, agy stores the token in Credential Manager via Go's
79
+ // zalando/go-keyring, whose target name is `service:account` ("gemini:antigravity").
80
+ // @napi-rs/keyring (keyring-rs) uses a different target format and can't find it,
81
+ // so we read the credential blob directly via the Win32 CredRead API through the
82
+ // built-in powershell.exe (no extra dependency).
83
+ const WIN_CRED_TARGET = `${KEYRING_SERVICE}:${KEYRING_ACCOUNT}`;
84
+
85
+ const PS_READ_CRED = `$ErrorActionPreference='Stop'
86
+ $sig=@'
87
+ using System;
88
+ using System.Runtime.InteropServices;
89
+ public class CredApi {
90
+ [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
91
+ public static extern bool CredRead(string target, int type, int flags, out IntPtr cred);
92
+ [DllImport("advapi32.dll")] public static extern void CredFree(IntPtr cred);
93
+ [StructLayout(LayoutKind.Sequential)]
94
+ public struct CREDENTIAL {
95
+ public int Flags; public int Type; public IntPtr TargetName; public IntPtr Comment;
96
+ public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
97
+ public int CredentialBlobSize; public IntPtr CredentialBlob; public int Persist;
98
+ public int AttributeCount; public IntPtr Attributes; public IntPtr TargetAlias; public IntPtr UserName;
99
+ }
100
+ public static byte[] Read(string target){
101
+ IntPtr p; if(!CredRead(target,1,0,out p)) return null;
102
+ try {
103
+ var c=(CREDENTIAL)Marshal.PtrToStructure(p,typeof(CREDENTIAL));
104
+ var b=new byte[c.CredentialBlobSize];
105
+ if(c.CredentialBlobSize>0) Marshal.Copy(c.CredentialBlob,b,0,c.CredentialBlobSize);
106
+ return b;
107
+ } finally { CredFree(p); }
108
+ }
109
+ }
110
+ '@
111
+ Add-Type -TypeDefinition $sig | Out-Null
112
+ $b=[CredApi]::Read('${WIN_CRED_TARGET}')
113
+ if($b -eq $null){ exit 1 }
114
+ [Console]::Out.Write([Convert]::ToBase64String($b))`;
115
+
116
+ function readViaWindowsCredman() {
117
+ if (process.platform !== 'win32') return null;
118
+ try {
119
+ const encoded = Buffer.from(PS_READ_CRED, 'utf16le').toString('base64');
120
+ const b64 = execFileSync(
121
+ 'powershell.exe',
122
+ ['-NoProfile', '-NonInteractive', '-EncodedCommand', encoded],
123
+ { encoding: 'utf8' },
124
+ ).trim();
125
+ if (!b64) return null;
126
+ const raw = Buffer.from(b64, 'base64');
127
+ // go-keyring writes the value as UTF-8; tolerate UTF-16LE just in case.
128
+ const utf8 = raw.toString('utf8');
129
+ const looksValid = (s) => s.startsWith(B64_PREFIX) || s.trimStart().startsWith('{');
130
+ if (looksValid(utf8)) return utf8;
131
+ const utf16 = raw.toString('utf16le');
132
+ if (looksValid(utf16)) return utf16;
133
+ return utf8;
134
+ } catch {
135
+ return null;
136
+ }
137
+ }
138
+
76
139
  // On headless Linux (no Secret Service) agy persists the token to a plain-JSON
77
140
  // file instead of the keyring. Same payload shape, no `go-keyring-base64:` prefix.
78
141
  function readViaFile() {
@@ -98,6 +161,8 @@ async function readRawSecret() {
98
161
  if (fromNapi) return fromNapi;
99
162
  const fromCli = readViaCli();
100
163
  if (fromCli) return fromCli;
164
+ const fromWin = readViaWindowsCredman();
165
+ if (fromWin) return fromWin;
101
166
  const fromFile = readViaFile();
102
167
  if (fromFile) return fromFile;
103
168
  return null;