@0xmaxma/claude-gateway 1.4.1 → 1.4.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.
@@ -1,4 +1,5 @@
1
1
  import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
2
3
  import * as path from 'node:path';
3
4
  import * as dns from 'node:dns';
4
5
  import * as net from 'node:net';
@@ -60,8 +61,12 @@ export class ImageModule implements ToolModule {
60
61
  toolVisibility: ToolVisibility = 'all-configured';
61
62
 
62
63
  isEnabled(): boolean {
63
- // Enabled when the image service endpoint is configured (env-driven, like browser).
64
- if (!this.baseUrl() || process.env.IMAGE_DISABLED === 'true') return false;
64
+ // Enabled when BOTH the endpoint and an auth token resolve (from env or the CLI config
65
+ // at ~/.claude/settings.json; see baseUrl()/authToken()) and the tool isn't explicitly
66
+ // turned off. Requiring the token here means a config that resolves a URL but no token
67
+ // disables the tool cleanly, instead of advertising it and then failing every call with a
68
+ // 401 on an empty Bearer.
69
+ if (!this.baseUrl() || !this.authToken() || process.env.IMAGE_DISABLED === 'true') return false;
65
70
  // The Bearer proxy_secret rides every call — refuse a cleartext http URL to a
66
71
  // PUBLIC host (that would leak the secret). http to a local/internal host is a
67
72
  // trusted hop (e.g. host.docker.internal in dev) and stays allowed.
@@ -106,18 +111,53 @@ export class ImageModule implements ToolModule {
106
111
  // ── config ────────────────────────────────────────────────────────────────
107
112
 
108
113
  private baseUrl(): string {
109
- // Image generation can target any provider not necessarily the same host as
110
- // the LLM. IMAGE_BASE_URL overrides so an operator can point image at a separate
111
- // endpoint; it falls back to ANTHROPIC_BASE_URL when they share one provider that
112
- // fronts /v1/images/{generations,jobs} alongside /v1/messages.
113
- const raw = process.env.IMAGE_BASE_URL || process.env.ANTHROPIC_BASE_URL || '';
114
+ // Image generation may target a provider separate from the LLM: IMAGE_BASE_URL
115
+ // (or ANTHROPIC_BASE_URL when they share a provider that fronts /v1/images
116
+ // alongside /v1/messages) overrides. When neither env var is set, fall back to
117
+ // the Claude Code CLI config at ~/.claude/settings.json — its `env` block carries
118
+ // ANTHROPIC_BASE_URL, and the CLI applies that block internally (not to the OS
119
+ // environment), so an MCP subprocess can't inherit it and reads the file instead.
120
+ const raw =
121
+ process.env.IMAGE_BASE_URL ||
122
+ process.env.ANTHROPIC_BASE_URL ||
123
+ this.settingsEnv('ANTHROPIC_BASE_URL');
114
124
  return raw.replace(/\/+$/, '');
115
125
  }
116
126
 
117
127
  private authToken(): string {
118
- // Image API key overrides so a separate image endpoint can carry its own secret;
119
- // falls back to ANTHROPIC_AUTH_TOKEN (the M2M proxy secret) when they share one.
120
- return process.env.IMAGE_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || '';
128
+ // IMAGE_API_KEY overrides so a separate image endpoint can carry its own secret;
129
+ // falls back to ANTHROPIC_AUTH_TOKEN, then to the CLI config's `env` block in
130
+ // ~/.claude/settings.json. Check BOTH token keys there: a proxy deployment may store the
131
+ // M2M secret under ANTHROPIC_AUTH_TOKEN (the same key the env path uses) or under
132
+ // CLAUDE_CODE_OAUTH_TOKEN — mirror the env precedence and accept either.
133
+ return (
134
+ process.env.IMAGE_API_KEY ||
135
+ process.env.ANTHROPIC_AUTH_TOKEN ||
136
+ this.settingsEnv('ANTHROPIC_AUTH_TOKEN') ||
137
+ this.settingsEnv('CLAUDE_CODE_OAUTH_TOKEN')
138
+ );
139
+ }
140
+
141
+ // Parsed `env` block of the CLI config, read once per instance (settings don't
142
+ // change within a session). undefined = not read yet; null = unreadable.
143
+ private settingsEnvCache?: Record<string, unknown> | null;
144
+
145
+ // settingsEnv reads a single key out of ~/.claude/settings.json's `env` block.
146
+ // CLAUDE_CONFIG_DIR overrides the ~/.claude location, matching Claude Code. Returns
147
+ // '' on any error (missing file, bad JSON, absent/non-string key) so callers degrade
148
+ // to "not configured" rather than throwing.
149
+ private settingsEnv(key: string): string {
150
+ if (this.settingsEnvCache === undefined) {
151
+ try {
152
+ const dir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
153
+ this.settingsEnvCache =
154
+ JSON.parse(fs.readFileSync(path.join(dir, 'settings.json'), 'utf8'))?.env ?? null;
155
+ } catch {
156
+ this.settingsEnvCache = null;
157
+ }
158
+ }
159
+ const v = this.settingsEnvCache?.[key];
160
+ return typeof v === 'string' ? v : '';
121
161
  }
122
162
 
123
163
  private headers(): Record<string, string> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xmaxma/claude-gateway",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "Multi-agent gateway for Claude",
5
5
  "repository": {
6
6
  "type": "git",