clawmoney 0.13.0 → 0.13.2

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.
@@ -68,6 +68,7 @@ interface AntigravityAccountsFile {
68
68
  version: 1;
69
69
  accounts: AntigravityAccount[];
70
70
  }
71
+ export declare function configureAntigravityDispatcher(): void;
71
72
  export declare function ensureClawmoneyDir(): void;
72
73
  export declare function loadAccounts(): AntigravityAccountsFile;
73
74
  export declare function saveAccounts(file: AntigravityAccountsFile): void;
@@ -29,7 +29,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
29
29
  import { join } from "node:path";
30
30
  import { homedir } from "node:os";
31
31
  import { randomUUID } from "node:crypto";
32
- import { ProxyAgent, setGlobalDispatcher } from "undici";
32
+ import { ProxyAgent } from "undici";
33
33
  import { relayLogger as logger } from "../logger.js";
34
34
  import { RateGuard, RateGuardBudgetExceededError, RateGuardCooldownError, } from "./rate-guard.js";
35
35
  import { calculateCost } from "../pricing.js";
@@ -83,24 +83,73 @@ const ANTIGRAVITY_VERSION = "1.21.9";
83
83
  const CLAWMONEY_DIR = join(homedir(), ".clawmoney");
84
84
  const ACCOUNTS_FILE = join(CLAWMONEY_DIR, "antigravity-accounts.json");
85
85
  // ── Proxy ──
86
- let dispatcherConfigured = false;
87
- function configureDispatcher() {
88
- if (dispatcherConfigured)
89
- return;
90
- dispatcherConfigured = true;
86
+ //
87
+ // We build our own ProxyAgent instead of relying on setGlobalDispatcher,
88
+ // then pass it explicitly to each fetch() call. This is more reliable than
89
+ // the global-dispatcher approach (no cross-module timing issues with Node's
90
+ // built-in fetch) and makes errors surface with a real cause chain.
91
+ //
92
+ // Also exported so the `antigravity login` command can trigger the same
93
+ // setup before it hits oauth2.googleapis.com / userinfo / loadCodeAssist —
94
+ // providers behind the GFW were seeing "fetch failed" at the token-exchange
95
+ // step before we honored HTTPS_PROXY here.
96
+ let cachedProxyAgent = null;
97
+ let proxyResolved = false;
98
+ function getProxyAgent() {
99
+ if (proxyResolved)
100
+ return cachedProxyAgent;
101
+ proxyResolved = true;
91
102
  const url = process.env.HTTPS_PROXY ||
92
103
  process.env.https_proxy ||
93
104
  process.env.HTTP_PROXY ||
94
105
  process.env.http_proxy;
95
106
  if (!url)
96
- return;
107
+ return null;
97
108
  if (!/^https?:\/\//.test(url)) {
98
109
  logger.warn(`[antigravity-api] ignoring non-HTTP proxy ${url} (SOCKS not supported)`);
99
- return;
110
+ return null;
100
111
  }
101
- setGlobalDispatcher(new ProxyAgent(url));
112
+ cachedProxyAgent = new ProxyAgent(url);
102
113
  logger.info(`[antigravity-api] upstream proxy ${url}`);
114
+ return cachedProxyAgent;
103
115
  }
116
+ /**
117
+ * fetch wrapper that: (1) auto-applies the configured ProxyAgent when
118
+ * HTTPS_PROXY is set, and (2) unwraps undici's TypeError("fetch failed")
119
+ * to surface the real underlying error code — without this, users on the
120
+ * GFW side see opaque "fetch failed" messages and can't tell whether it's
121
+ * a DNS failure, cert error, proxy refusal, or timeout.
122
+ */
123
+ async function fetchWithProxy(url, init = {}) {
124
+ const agent = getProxyAgent();
125
+ const opts = { ...init };
126
+ if (agent)
127
+ opts.dispatcher = agent;
128
+ try {
129
+ // Node's built-in fetch accepts `dispatcher` through the undici
130
+ // extension, but the DOM `RequestInit` type doesn't expose it. We cast
131
+ // via a local type to keep strict TS happy.
132
+ return await fetch(url, opts);
133
+ }
134
+ catch (err) {
135
+ // undici wraps the real network error in TypeError("fetch failed")
136
+ // with the real cause on err.cause. Surface it so "ECONNREFUSED"
137
+ // / "ETIMEDOUT" / "CERT_HAS_EXPIRED" is visible in logs.
138
+ const cause = err.cause;
139
+ if (cause) {
140
+ const code = cause.code;
141
+ const message = cause.message;
142
+ throw new Error(`fetch ${url} failed: ${code ?? ""} ${message ?? String(cause)}`.trim());
143
+ }
144
+ throw err;
145
+ }
146
+ }
147
+ // Legacy name kept for call sites that haven't been migrated. Also acts as
148
+ // the public preflight hook for the daemon's `preflightAntigravityApi`.
149
+ export function configureAntigravityDispatcher() {
150
+ getProxyAgent();
151
+ }
152
+ const configureDispatcher = configureAntigravityDispatcher;
104
153
  // ── Account storage ──
105
154
  export function ensureClawmoneyDir() {
106
155
  mkdirSync(CLAWMONEY_DIR, { recursive: true });
@@ -157,7 +206,7 @@ async function refreshUpstreamToken(refreshToken) {
157
206
  client_id: ANTIGRAVITY_CLIENT_ID,
158
207
  client_secret: ANTIGRAVITY_CLIENT_SECRET,
159
208
  });
160
- const resp = await fetch(OAUTH_TOKEN_URL, {
209
+ const resp = await fetchWithProxy(OAUTH_TOKEN_URL, {
161
210
  method: "POST",
162
211
  headers: {
163
212
  "content-type": "application/x-www-form-urlencoded",
@@ -223,6 +272,7 @@ async function getFreshAccount() {
223
272
  * sub2api and opencode-antigravity-auth behavior.
224
273
  */
225
274
  export async function resolveAntigravityProjectId(accessToken) {
275
+ configureDispatcher();
226
276
  const body = JSON.stringify({
227
277
  metadata: {
228
278
  ideType: "ANTIGRAVITY",
@@ -233,11 +283,7 @@ export async function resolveAntigravityProjectId(accessToken) {
233
283
  const headers = antigravityHeaders(accessToken);
234
284
  for (const baseEndpoint of ANTIGRAVITY_ENDPOINTS) {
235
285
  try {
236
- const resp = await fetch(`${baseEndpoint}/v1internal:loadCodeAssist`, {
237
- method: "POST",
238
- headers,
239
- body,
240
- });
286
+ const resp = await fetchWithProxy(`${baseEndpoint}/v1internal:loadCodeAssist`, { method: "POST", headers, body });
241
287
  if (!resp.ok)
242
288
  continue;
243
289
  const data = (await resp.json());
@@ -375,7 +421,7 @@ async function doCallAntigravityApi(opts) {
375
421
  const url = `${baseEndpoint}${GENERATE_PATH}`;
376
422
  let resp;
377
423
  try {
378
- resp = await fetch(url, {
424
+ resp = await fetchWithProxy(url, {
379
425
  method: "POST",
380
426
  headers: antigravityHeaders(creds.access_token),
381
427
  body: bodyJson,
@@ -511,7 +557,7 @@ export async function storeNewAntigravityAccount(input) {
511
557
  */
512
558
  export async function exchangeAntigravityAuthCode(input) {
513
559
  const start = Date.now();
514
- const resp = await fetch(OAUTH_TOKEN_URL, {
560
+ const resp = await fetchWithProxy(OAUTH_TOKEN_URL, {
515
561
  method: "POST",
516
562
  headers: {
517
563
  "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
@@ -548,7 +594,7 @@ export async function exchangeAntigravityAuthCode(input) {
548
594
  */
549
595
  export async function fetchAntigravityUserEmail(accessToken) {
550
596
  try {
551
- const resp = await fetch(OAUTH_USERINFO_URL, {
597
+ const resp = await fetchWithProxy(OAUTH_USERINFO_URL, {
552
598
  headers: { authorization: `Bearer ${accessToken}` },
553
599
  });
554
600
  if (!resp.ok)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {