aquaman-plugin 0.2.0 → 0.2.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.
package/index.ts CHANGED
@@ -22,6 +22,7 @@ import { HttpInterceptor, createHttpInterceptor } from "./src/http-interceptor.j
22
22
 
23
23
  let proxyProcess: ChildProcess | null = null;
24
24
  let httpInterceptor: HttpInterceptor | null = null;
25
+ let clientToken: string | null = null;
25
26
  const proxyPort = 8081;
26
27
  const services = ["anthropic", "openai"];
27
28
 
@@ -33,6 +34,13 @@ function getExternalProxyUrl(): string | null {
33
34
  return process.env.AQUAMAN_PROXY_URL || null;
34
35
  }
35
36
 
37
+ /**
38
+ * Get external client token from environment (for Docker two-container mode).
39
+ */
40
+ function getExternalClientToken(): string | null {
41
+ return process.env.AQUAMAN_CLIENT_TOKEN || null;
42
+ }
43
+
36
44
  /**
37
45
  * Check if aquaman CLI is installed
38
46
  */
@@ -62,11 +70,33 @@ async function startProxy(port: number, log: OpenClawPluginApi["logger"]): Promi
62
70
  }
63
71
 
64
72
  let started = false;
73
+ let stdoutBuffer = "";
65
74
 
66
75
  proxyProcess.stdout?.on("data", (data: Buffer) => {
67
- const output = data.toString();
68
- log.debug(`[aquaman] ${output.trim()}`);
69
- if (output.includes("listening") || output.includes("started")) {
76
+ stdoutBuffer += data.toString();
77
+ log.debug(`[aquaman] ${data.toString().trim()}`);
78
+ if (started) return;
79
+
80
+ // Try to parse JSON connection info from stdout
81
+ const lines = stdoutBuffer.split("\n");
82
+ for (const line of lines) {
83
+ const trimmed = line.trim();
84
+ if (!trimmed.startsWith("{")) continue;
85
+ try {
86
+ const info = JSON.parse(trimmed);
87
+ if (info.ready === true) {
88
+ started = true;
89
+ clientToken = info.token || null;
90
+ resolve(true);
91
+ return;
92
+ }
93
+ } catch {
94
+ // Not valid JSON yet, keep buffering
95
+ }
96
+ }
97
+
98
+ // Fall back to string matching for backward compat
99
+ if (stdoutBuffer.includes("listening") || stdoutBuffer.includes("started")) {
70
100
  started = true;
71
101
  resolve(true);
72
102
  }
@@ -111,6 +141,7 @@ function stopProxy(): void {
111
141
  proxyProcess.kill("SIGTERM");
112
142
  proxyProcess = null;
113
143
  }
144
+ clientToken = null;
114
145
  }
115
146
 
116
147
  /**
@@ -124,6 +155,8 @@ function activateHttpInterceptor(log: OpenClawPluginApi["logger"]): void {
124
155
  ['api.anthropic.com', 'anthropic'],
125
156
  ['api.openai.com', 'openai'],
126
157
  ['api.github.com', 'github'],
158
+ ['api.x.ai', 'xai'],
159
+ ['gateway.ai.cloudflare.com', 'cloudflare-ai'],
127
160
  // Channel APIs
128
161
  ['slack.com', 'slack'],
129
162
  ['*.slack.com', 'slack'],
@@ -152,6 +185,7 @@ function activateHttpInterceptor(log: OpenClawPluginApi["logger"]): void {
152
185
  httpInterceptor = createHttpInterceptor({
153
186
  proxyBaseUrl: baseUrl,
154
187
  hostMap,
188
+ clientToken: clientToken || undefined,
155
189
  log: (msg) => log.info(msg),
156
190
  });
157
191
 
@@ -243,6 +277,7 @@ export default function register(api: OpenClawPluginApi): void {
243
277
  // External proxy mode (Docker two-container architecture)
244
278
  if (externalUrl) {
245
279
  api.logger.info(`External proxy mode: ${externalUrl}`);
280
+ clientToken = getExternalClientToken();
246
281
  configureEnvironment(api.logger);
247
282
 
248
283
  if (api.registerLifecycle) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aquaman-plugin",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Credential isolation plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -15,6 +15,8 @@ export interface HttpInterceptorOptions {
15
15
  proxyBaseUrl: string;
16
16
  /** Map of hostname (or *.domain wildcard) → service name */
17
17
  hostMap: Map<string, string>;
18
+ /** Client authentication token for the proxy */
19
+ clientToken?: string;
18
20
  /** Optional logger */
19
21
  log?: (msg: string) => void;
20
22
  }
@@ -23,6 +25,7 @@ export class HttpInterceptor {
23
25
  private proxyBaseUrl: string;
24
26
  private proxyHost: string;
25
27
  private hostMap: Map<string, string>;
28
+ private clientToken: string | null;
26
29
  private originalFetch: typeof globalThis.fetch | null = null;
27
30
  private active = false;
28
31
  private log: (msg: string) => void;
@@ -30,6 +33,7 @@ export class HttpInterceptor {
30
33
  constructor(options: HttpInterceptorOptions) {
31
34
  this.proxyBaseUrl = options.proxyBaseUrl.replace(/\/$/, '');
32
35
  this.hostMap = options.hostMap;
36
+ this.clientToken = options.clientToken || null;
33
37
  this.log = options.log || (() => {});
34
38
 
35
39
  // Extract proxy hostname to avoid intercepting requests to the proxy itself
@@ -51,9 +55,11 @@ export class HttpInterceptor {
51
55
  const origFetch = this.originalFetch;
52
56
  const proxyBase = this.proxyBaseUrl;
53
57
  const proxyHostname = this.proxyHost;
58
+ const token = this.clientToken;
54
59
  const matchHost = this.matchHost.bind(this);
55
60
  const extractUrl = this.extractUrl.bind(this);
56
61
  const stripAuthHeaders = this.stripAuthHeaders.bind(this);
62
+ const injectToken = this.injectTokenHeader.bind(this);
57
63
  const logFn = this.log;
58
64
 
59
65
  globalThis.fetch = (
@@ -65,8 +71,12 @@ export class HttpInterceptor {
65
71
  return origFetch.call(globalThis, input, init);
66
72
  }
67
73
 
68
- // Don't intercept requests to the proxy itself
74
+ // Requests to the proxy itself (SDK traffic via env vars) — inject token, pass through
69
75
  if (url.hostname === proxyHostname || url.hostname === 'localhost') {
76
+ if (token) {
77
+ const tokenInit = injectToken(init, token);
78
+ return origFetch.call(globalThis, input, tokenInit);
79
+ }
70
80
  return origFetch.call(globalThis, input, init);
71
81
  }
72
82
 
@@ -86,6 +96,11 @@ export class HttpInterceptor {
86
96
  newInit = { ...init, headers: stripped };
87
97
  }
88
98
 
99
+ // Inject client token for proxy authentication
100
+ if (token) {
101
+ newInit = injectToken(newInit, token);
102
+ }
103
+
89
104
  return origFetch.call(globalThis, proxyUrl, newInit);
90
105
  };
91
106
 
@@ -138,6 +153,28 @@ export class HttpInterceptor {
138
153
  return null;
139
154
  }
140
155
 
156
+ private injectTokenHeader(init: RequestInit | undefined, token: string): RequestInit {
157
+ const base = init || {};
158
+ const headers = base.headers;
159
+
160
+ if (!headers) {
161
+ return { ...base, headers: { 'x-aquaman-token': token } };
162
+ }
163
+
164
+ if (headers instanceof Headers) {
165
+ const h = new Headers(headers);
166
+ h.set('x-aquaman-token', token);
167
+ return { ...base, headers: h };
168
+ }
169
+
170
+ if (Array.isArray(headers)) {
171
+ return { ...base, headers: [...headers, ['x-aquaman-token', token]] };
172
+ }
173
+
174
+ // Plain object
175
+ return { ...base, headers: { ...headers, 'x-aquaman-token': token } };
176
+ }
177
+
141
178
  private stripAuthHeaders(headers: HeadersInit): HeadersInit {
142
179
  if (headers instanceof Headers) {
143
180
  const h = new Headers(headers);
@@ -17,6 +17,7 @@ export interface ProxyConnectionInfo {
17
17
  baseUrl: string;
18
18
  services: string[];
19
19
  backend: string;
20
+ token?: string;
20
21
  }
21
22
 
22
23
  export interface ProxyManagerOptions {
@@ -193,6 +194,13 @@ export class ProxyManager {
193
194
  return this.connectionInfo;
194
195
  }
195
196
 
197
+ /**
198
+ * Get client authentication token
199
+ */
200
+ getClientToken(): string | null {
201
+ return this.connectionInfo?.token || null;
202
+ }
203
+
196
204
  /**
197
205
  * Get base URL for a service
198
206
  */