aquaman-plugin 0.11.2 → 0.11.4
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/README.md +38 -10
- package/dist/index.d.ts +81 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +502 -0
- package/dist/index.js.map +1 -0
- package/dist/src/config-schema.d.ts +37 -0
- package/dist/src/config-schema.d.ts.map +1 -0
- package/dist/src/config-schema.js +58 -0
- package/dist/src/config-schema.js.map +1 -0
- package/dist/src/http-interceptor.d.ts +48 -0
- package/dist/src/http-interceptor.d.ts.map +1 -0
- package/dist/src/http-interceptor.js +143 -0
- package/dist/src/http-interceptor.js.map +1 -0
- package/dist/src/proxy-health.d.ts +23 -0
- package/dist/src/proxy-health.d.ts.map +1 -0
- package/dist/src/proxy-health.js +61 -0
- package/dist/src/proxy-health.js.map +1 -0
- package/dist/src/proxy-manager.d.ts +79 -0
- package/dist/src/proxy-manager.d.ts.map +1 -0
- package/dist/src/proxy-manager.js +246 -0
- package/dist/src/proxy-manager.js.map +1 -0
- package/openclaw.plugin.json +6 -1
- package/package.json +12 -11
- package/index.ts +0 -592
- package/src/commands.ts +0 -306
- package/src/config-schema.ts +0 -68
- package/src/http-interceptor.ts +0 -176
- package/src/index.ts +0 -62
- package/src/plugin.ts +0 -281
- package/src/proxy-health.ts +0 -67
- package/src/proxy-manager.ts +0 -309
package/src/commands.ts
DELETED
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slash commands for the OpenClaw plugin
|
|
3
|
-
*
|
|
4
|
-
* Provides /aquaman commands for users to interact with the plugin.
|
|
5
|
-
* Non-interactive commands execute the aquaman proxy binary directly.
|
|
6
|
-
* Interactive commands (add) show instructions since slash commands
|
|
7
|
-
* run in the chat UI where TTY is not available.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { ProxyManager } from './proxy-manager.js';
|
|
11
|
-
import { execAquamanProxyCli, findAquamanProxyBinary } from './proxy-manager.js';
|
|
12
|
-
import type { PluginConfig } from './config-schema.js';
|
|
13
|
-
|
|
14
|
-
export interface CommandContext {
|
|
15
|
-
config: PluginConfig;
|
|
16
|
-
proxyManager?: ProxyManager;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface CommandResult {
|
|
20
|
-
success: boolean;
|
|
21
|
-
message: string;
|
|
22
|
-
data?: any;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Plugin command definition for OpenClaw
|
|
27
|
-
*/
|
|
28
|
-
export interface PluginCommand {
|
|
29
|
-
name: string;
|
|
30
|
-
description: string;
|
|
31
|
-
execute: (args: Record<string, string>) => Promise<string | object>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* /aquaman status - Show plugin status
|
|
36
|
-
*/
|
|
37
|
-
export async function statusCommand(ctx: CommandContext): Promise<CommandResult> {
|
|
38
|
-
const lines: string[] = [];
|
|
39
|
-
const cliInstalled = findAquamanProxyBinary() !== null;
|
|
40
|
-
|
|
41
|
-
lines.push('aquaman plugin status');
|
|
42
|
-
lines.push('');
|
|
43
|
-
lines.push(`Backend: ${ctx.config.backend || 'keychain'}`);
|
|
44
|
-
lines.push(`Services: ${(ctx.config.services || []).join(', ')}`);
|
|
45
|
-
lines.push(`Proxy binary: ${cliInstalled ? 'found' : 'NOT FOUND'}`);
|
|
46
|
-
|
|
47
|
-
if (ctx.proxyManager?.isRunning()) {
|
|
48
|
-
const info = ctx.proxyManager.getConnectionInfo();
|
|
49
|
-
lines.push('');
|
|
50
|
-
lines.push('Proxy Status: Running');
|
|
51
|
-
lines.push(` Socket: ${info?.socketPath}`);
|
|
52
|
-
} else {
|
|
53
|
-
lines.push('');
|
|
54
|
-
lines.push('Proxy Status: Not running');
|
|
55
|
-
if (!cliInstalled) {
|
|
56
|
-
lines.push('');
|
|
57
|
-
lines.push('Setup: npm install -g aquaman-proxy && aquaman setup');
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
success: true,
|
|
63
|
-
message: lines.join('\n')
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* /aquaman add <service> - Add a credential (shows instructions — TTY not available in chat UI)
|
|
69
|
-
*/
|
|
70
|
-
export async function addCommand(
|
|
71
|
-
_ctx: CommandContext,
|
|
72
|
-
service: string,
|
|
73
|
-
key: string = 'api_key'
|
|
74
|
-
): Promise<CommandResult> {
|
|
75
|
-
return {
|
|
76
|
-
success: true,
|
|
77
|
-
message: `To add a credential for ${service}/${key}:\n\n` +
|
|
78
|
-
`Run: openclaw aquaman credentials add ${service} ${key}\n` +
|
|
79
|
-
`Or in terminal: aquaman credentials add ${service} ${key}`
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* /aquaman list - List stored credentials
|
|
85
|
-
*/
|
|
86
|
-
export async function listCommand(_ctx: CommandContext): Promise<CommandResult> {
|
|
87
|
-
try {
|
|
88
|
-
const result = await execAquamanProxyCli(['credentials', 'list']);
|
|
89
|
-
return { success: result.exitCode === 0, message: result.stdout || result.stderr };
|
|
90
|
-
} catch (err: any) {
|
|
91
|
-
return { success: false, message: `Failed: ${err.message}\n\nRun in terminal: aquaman credentials list` };
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* /aquaman doctor - Run diagnostic checks
|
|
97
|
-
*/
|
|
98
|
-
export async function doctorCommand(_ctx: CommandContext): Promise<CommandResult> {
|
|
99
|
-
try {
|
|
100
|
-
const result = await execAquamanProxyCli(['doctor']);
|
|
101
|
-
return { success: result.exitCode === 0, message: result.stdout || result.stderr };
|
|
102
|
-
} catch (err: any) {
|
|
103
|
-
return { success: false, message: `Failed: ${err.message}\n\nRun in terminal: aquaman doctor` };
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* /aquaman logs - Show recent audit entries
|
|
109
|
-
*/
|
|
110
|
-
export async function logsCommand(_ctx: CommandContext, count: number = 10): Promise<CommandResult> {
|
|
111
|
-
try {
|
|
112
|
-
const result = await execAquamanProxyCli(['audit', 'tail', '-n', String(count)]);
|
|
113
|
-
return { success: result.exitCode === 0, message: result.stdout || result.stderr };
|
|
114
|
-
} catch (err: any) {
|
|
115
|
-
return { success: false, message: `Failed: ${err.message}\n\nRun in terminal: aquaman audit tail` };
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* /aquaman verify - Verify audit log integrity
|
|
121
|
-
*/
|
|
122
|
-
export async function verifyCommand(_ctx: CommandContext): Promise<CommandResult> {
|
|
123
|
-
try {
|
|
124
|
-
const result = await execAquamanProxyCli(['audit', 'verify']);
|
|
125
|
-
return { success: result.exitCode === 0, message: result.stdout || result.stderr };
|
|
126
|
-
} catch (err: any) {
|
|
127
|
-
return { success: false, message: `Failed: ${err.message}\n\nRun in terminal: aquaman audit verify` };
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* /aquaman policy - List policy rules
|
|
133
|
-
*/
|
|
134
|
-
export async function policyListCommand(_ctx: CommandContext): Promise<CommandResult> {
|
|
135
|
-
try {
|
|
136
|
-
const result = await execAquamanProxyCli(['policy', 'list']);
|
|
137
|
-
return { success: result.exitCode === 0, message: result.stdout || result.stderr };
|
|
138
|
-
} catch (err: any) {
|
|
139
|
-
return { success: false, message: `Failed: ${err.message}\n\nRun in terminal: aquaman policy list` };
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* /aquaman services - List configured services
|
|
145
|
-
*/
|
|
146
|
-
export async function servicesListCommand(_ctx: CommandContext): Promise<CommandResult> {
|
|
147
|
-
try {
|
|
148
|
-
const result = await execAquamanProxyCli(['services', 'list']);
|
|
149
|
-
return { success: result.exitCode === 0, message: result.stdout || result.stderr };
|
|
150
|
-
} catch (err: any) {
|
|
151
|
-
return { success: false, message: `Failed: ${err.message}\n\nRun in terminal: aquaman services list` };
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Parse and execute a command
|
|
157
|
-
*/
|
|
158
|
-
export async function executeCommand(
|
|
159
|
-
ctx: CommandContext,
|
|
160
|
-
command: string,
|
|
161
|
-
args: string[]
|
|
162
|
-
): Promise<CommandResult> {
|
|
163
|
-
switch (command.toLowerCase()) {
|
|
164
|
-
case 'status':
|
|
165
|
-
return statusCommand(ctx);
|
|
166
|
-
|
|
167
|
-
case 'add':
|
|
168
|
-
if (args.length < 1) {
|
|
169
|
-
return { success: false, message: 'Usage: /aquaman add <service> [key]' };
|
|
170
|
-
}
|
|
171
|
-
return addCommand(ctx, args[0], args[1]);
|
|
172
|
-
|
|
173
|
-
case 'list':
|
|
174
|
-
return listCommand(ctx);
|
|
175
|
-
|
|
176
|
-
case 'doctor':
|
|
177
|
-
return doctorCommand(ctx);
|
|
178
|
-
|
|
179
|
-
case 'logs': {
|
|
180
|
-
const count = args[0] ? parseInt(args[0], 10) : 10;
|
|
181
|
-
return logsCommand(ctx, count);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
case 'verify':
|
|
185
|
-
return verifyCommand(ctx);
|
|
186
|
-
|
|
187
|
-
case 'policy':
|
|
188
|
-
return policyListCommand(ctx);
|
|
189
|
-
|
|
190
|
-
case 'services':
|
|
191
|
-
return servicesListCommand(ctx);
|
|
192
|
-
|
|
193
|
-
case 'help':
|
|
194
|
-
default:
|
|
195
|
-
return {
|
|
196
|
-
success: true,
|
|
197
|
-
message: `aquaman plugin commands:
|
|
198
|
-
|
|
199
|
-
/aquaman status - Show plugin and proxy status
|
|
200
|
-
/aquaman doctor - Run diagnostic checks
|
|
201
|
-
/aquaman add - Add a credential
|
|
202
|
-
/aquaman list - List stored credentials
|
|
203
|
-
/aquaman policy - List request policy rules
|
|
204
|
-
/aquaman services - List configured services
|
|
205
|
-
/aquaman logs [n] - Show recent audit entries
|
|
206
|
-
/aquaman verify - Verify audit log integrity
|
|
207
|
-
/aquaman help - Show this help message
|
|
208
|
-
|
|
209
|
-
CLI commands (via terminal or openclaw aquaman <cmd>):
|
|
210
|
-
|
|
211
|
-
openclaw aquaman setup - Run the setup wizard
|
|
212
|
-
openclaw aquaman doctor - Diagnose issues
|
|
213
|
-
openclaw aquaman credentials list - List credentials
|
|
214
|
-
openclaw aquaman credentials add - Add a credential (interactive)
|
|
215
|
-
openclaw aquaman policy-list - Show policy rules
|
|
216
|
-
openclaw aquaman audit-tail - Recent audit entries
|
|
217
|
-
openclaw aquaman services-list - List services`
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Get available commands for OpenClaw to register
|
|
224
|
-
*/
|
|
225
|
-
export function getAvailableCommands(ctx: CommandContext): PluginCommand[] {
|
|
226
|
-
return [
|
|
227
|
-
{
|
|
228
|
-
name: 'status',
|
|
229
|
-
description: 'Show aquaman plugin status',
|
|
230
|
-
execute: async () => {
|
|
231
|
-
const result = await statusCommand(ctx);
|
|
232
|
-
return result.message;
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
name: 'add',
|
|
237
|
-
description: 'Add a credential for a service',
|
|
238
|
-
execute: async (args) => {
|
|
239
|
-
const service = args.service || args._?.[0];
|
|
240
|
-
const key = args.key || args._?.[1] || 'api_key';
|
|
241
|
-
if (!service) {
|
|
242
|
-
return 'Usage: /aquaman add <service> [key]';
|
|
243
|
-
}
|
|
244
|
-
const result = await addCommand(ctx, service, key);
|
|
245
|
-
return result.message;
|
|
246
|
-
}
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
name: 'list',
|
|
250
|
-
description: 'List stored credentials',
|
|
251
|
-
execute: async () => {
|
|
252
|
-
const result = await listCommand(ctx);
|
|
253
|
-
return result.message;
|
|
254
|
-
}
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
name: 'doctor',
|
|
258
|
-
description: 'Run diagnostic checks',
|
|
259
|
-
execute: async () => {
|
|
260
|
-
const result = await doctorCommand(ctx);
|
|
261
|
-
return result.message;
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
name: 'policy',
|
|
266
|
-
description: 'List request policy rules',
|
|
267
|
-
execute: async () => {
|
|
268
|
-
const result = await policyListCommand(ctx);
|
|
269
|
-
return result.message;
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
name: 'services',
|
|
274
|
-
description: 'List configured services',
|
|
275
|
-
execute: async () => {
|
|
276
|
-
const result = await servicesListCommand(ctx);
|
|
277
|
-
return result.message;
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
name: 'logs',
|
|
282
|
-
description: 'Show recent audit log entries',
|
|
283
|
-
execute: async (args) => {
|
|
284
|
-
const count = args.count ? parseInt(args.count, 10) : 10;
|
|
285
|
-
const result = await logsCommand(ctx, count);
|
|
286
|
-
return result.message;
|
|
287
|
-
}
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
name: 'verify',
|
|
291
|
-
description: 'Verify audit log integrity',
|
|
292
|
-
execute: async () => {
|
|
293
|
-
const result = await verifyCommand(ctx);
|
|
294
|
-
return result.message;
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
name: 'help',
|
|
299
|
-
description: 'Show help for aquaman commands',
|
|
300
|
-
execute: async () => {
|
|
301
|
-
const result = await executeCommand(ctx, 'help', []);
|
|
302
|
-
return result.message;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
];
|
|
306
|
-
}
|
package/src/config-schema.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypeBox configuration schema for the OpenClaw plugin
|
|
3
|
-
*
|
|
4
|
-
* Defines the structure of plugin configuration in openclaw.json
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Type, type Static } from '@sinclair/typebox';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Credential backend type
|
|
11
|
-
*/
|
|
12
|
-
export const CredentialBackend = Type.Union([
|
|
13
|
-
Type.Literal('keychain'),
|
|
14
|
-
Type.Literal('1password'),
|
|
15
|
-
Type.Literal('vault'),
|
|
16
|
-
Type.Literal('encrypted-file'),
|
|
17
|
-
Type.Literal('keepassxc')
|
|
18
|
-
], { default: 'keychain' });
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Services to proxy
|
|
22
|
-
*/
|
|
23
|
-
export const ProxiedServices = Type.Array(Type.String(), {
|
|
24
|
-
default: ['anthropic', 'openai']
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Complete plugin configuration schema
|
|
29
|
-
*/
|
|
30
|
-
export const ConfigSchema = Type.Object({
|
|
31
|
-
// Credential backend
|
|
32
|
-
backend: Type.Optional(CredentialBackend),
|
|
33
|
-
|
|
34
|
-
// Services to proxy
|
|
35
|
-
services: Type.Optional(ProxiedServices),
|
|
36
|
-
|
|
37
|
-
// 1Password options
|
|
38
|
-
onePasswordVault: Type.Optional(Type.String()),
|
|
39
|
-
onePasswordAccount: Type.Optional(Type.String()),
|
|
40
|
-
|
|
41
|
-
// HashiCorp Vault options
|
|
42
|
-
vaultAddress: Type.Optional(Type.String({ format: 'uri' })),
|
|
43
|
-
vaultToken: Type.Optional(Type.String()),
|
|
44
|
-
vaultNamespace: Type.Optional(Type.String()),
|
|
45
|
-
vaultMountPath: Type.Optional(Type.String({ default: 'secret' })),
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
export type PluginConfig = Static<typeof ConfigSchema>;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Default configuration values
|
|
52
|
-
*/
|
|
53
|
-
export const defaultConfig: PluginConfig = {
|
|
54
|
-
backend: 'keychain',
|
|
55
|
-
services: ['anthropic', 'openai'],
|
|
56
|
-
vaultMountPath: 'secret',
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Merge user config with defaults
|
|
61
|
-
*/
|
|
62
|
-
export function mergeConfig(userConfig: Partial<PluginConfig>): PluginConfig {
|
|
63
|
-
return {
|
|
64
|
-
...defaultConfig,
|
|
65
|
-
...userConfig,
|
|
66
|
-
services: userConfig.services || defaultConfig.services
|
|
67
|
-
};
|
|
68
|
-
}
|
package/src/http-interceptor.ts
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP interceptor for channel credential isolation.
|
|
3
|
-
*
|
|
4
|
-
* Overrides globalThis.fetch to redirect requests targeting known channel API
|
|
5
|
-
* hosts through the aquaman credential proxy via Unix domain socket.
|
|
6
|
-
* The proxy injects the real credentials, so the Gateway process never sees them.
|
|
7
|
-
*
|
|
8
|
-
* OpenClaw channels use globalThis.fetch (backed by undici) for all HTTP calls.
|
|
9
|
-
* Many channel monitor functions also accept a `proxyFetch` parameter that
|
|
10
|
-
* falls back to globalThis.fetch, so overriding it covers both paths.
|
|
11
|
-
*
|
|
12
|
-
* Uses sentinel hostname `aquaman.local` — SDK base URLs are set to
|
|
13
|
-
* `http://aquaman.local/<service>` and the interceptor routes them through UDS.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { Agent } from 'undici';
|
|
17
|
-
|
|
18
|
-
/** Sentinel hostname used in SDK base URLs to route through UDS */
|
|
19
|
-
const PROXY_HOST = 'aquaman.local';
|
|
20
|
-
|
|
21
|
-
export interface HttpInterceptorOptions {
|
|
22
|
-
/** Unix domain socket path for the proxy */
|
|
23
|
-
socketPath: string;
|
|
24
|
-
/** Map of hostname (or *.domain wildcard) → service name */
|
|
25
|
-
hostMap: Map<string, string>;
|
|
26
|
-
/** Optional logger */
|
|
27
|
-
log?: (msg: string) => void;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export class HttpInterceptor {
|
|
31
|
-
private socketPath: string;
|
|
32
|
-
private hostMap: Map<string, string>;
|
|
33
|
-
private originalFetch: typeof globalThis.fetch | null = null;
|
|
34
|
-
private active = false;
|
|
35
|
-
private log: (msg: string) => void;
|
|
36
|
-
private socketAgent: Agent | null = null;
|
|
37
|
-
|
|
38
|
-
constructor(options: HttpInterceptorOptions) {
|
|
39
|
-
this.socketPath = options.socketPath;
|
|
40
|
-
this.hostMap = options.hostMap;
|
|
41
|
-
this.log = options.log || (() => {});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Activate the interceptor by replacing globalThis.fetch.
|
|
46
|
-
*/
|
|
47
|
-
activate(): void {
|
|
48
|
-
if (this.active) return;
|
|
49
|
-
|
|
50
|
-
this.originalFetch = globalThis.fetch;
|
|
51
|
-
this.socketAgent = new Agent({ connect: { socketPath: this.socketPath } });
|
|
52
|
-
|
|
53
|
-
const origFetch = this.originalFetch;
|
|
54
|
-
const socketAgent = this.socketAgent;
|
|
55
|
-
const matchHost = this.matchHost.bind(this);
|
|
56
|
-
const extractUrl = this.extractUrl.bind(this);
|
|
57
|
-
const stripAuthHeaders = this.stripAuthHeaders.bind(this);
|
|
58
|
-
const logFn = this.log;
|
|
59
|
-
|
|
60
|
-
(globalThis as any).fetch = (
|
|
61
|
-
input: RequestInfo | URL,
|
|
62
|
-
init?: RequestInit
|
|
63
|
-
): Promise<Response> => {
|
|
64
|
-
const url = extractUrl(input);
|
|
65
|
-
if (!url) {
|
|
66
|
-
return origFetch.call(globalThis, input, init);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// SDK traffic via env vars (hostname = aquaman.local) — route through UDS
|
|
70
|
-
if (url.hostname === PROXY_HOST) {
|
|
71
|
-
return origFetch.call(globalThis, input, { ...init, dispatcher: socketAgent } as any);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Channel traffic — match against host map
|
|
75
|
-
const service = matchHost(url.hostname);
|
|
76
|
-
if (!service) {
|
|
77
|
-
return origFetch.call(globalThis, input, init);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Rewrite the URL to go through the proxy
|
|
81
|
-
const proxyUrl = `http://${PROXY_HOST}/${service}${url.pathname}${url.search}`;
|
|
82
|
-
logFn(`[aquaman] Intercepted ${url.hostname}${url.pathname} → ${service}`);
|
|
83
|
-
|
|
84
|
-
// Strip any existing authorization headers — the proxy will inject the real ones
|
|
85
|
-
let newInit = init;
|
|
86
|
-
if (init?.headers) {
|
|
87
|
-
const stripped = stripAuthHeaders(init.headers);
|
|
88
|
-
newInit = { ...init, headers: stripped };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return origFetch.call(globalThis, proxyUrl, { ...newInit, redirect: 'manual', dispatcher: socketAgent } as any);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
this.active = true;
|
|
95
|
-
this.log(`[aquaman] HTTP interceptor active for ${this.hostMap.size} host patterns`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Deactivate the interceptor and restore the original fetch.
|
|
100
|
-
*/
|
|
101
|
-
deactivate(): void {
|
|
102
|
-
if (!this.active || !this.originalFetch) return;
|
|
103
|
-
|
|
104
|
-
globalThis.fetch = this.originalFetch;
|
|
105
|
-
this.originalFetch = null;
|
|
106
|
-
this.active = false;
|
|
107
|
-
|
|
108
|
-
if (this.socketAgent) {
|
|
109
|
-
this.socketAgent.close();
|
|
110
|
-
this.socketAgent = null;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
this.log('[aquaman] HTTP interceptor deactivated');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
isActive(): boolean {
|
|
117
|
-
return this.active;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Match a hostname against the host map, supporting wildcard patterns.
|
|
122
|
-
*/
|
|
123
|
-
matchHost(hostname: string): string | null {
|
|
124
|
-
// Direct match
|
|
125
|
-
const direct = this.hostMap.get(hostname);
|
|
126
|
-
if (direct) return direct;
|
|
127
|
-
|
|
128
|
-
// Wildcard match: *.example.com matches sub.example.com
|
|
129
|
-
for (const [pattern, service] of this.hostMap) {
|
|
130
|
-
if (pattern.startsWith('*.') && hostname.endsWith(pattern.slice(1))) {
|
|
131
|
-
return service;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private extractUrl(input: RequestInfo | URL): URL | null {
|
|
139
|
-
try {
|
|
140
|
-
if (input instanceof URL) return input;
|
|
141
|
-
if (typeof input === 'string') return new URL(input);
|
|
142
|
-
if (typeof input === 'object' && 'url' in input) return new URL(input.url);
|
|
143
|
-
} catch {
|
|
144
|
-
// Not a valid URL — pass through
|
|
145
|
-
}
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private stripAuthHeaders(headers: HeadersInit): HeadersInit {
|
|
150
|
-
if (headers instanceof Headers) {
|
|
151
|
-
const h = new Headers(headers);
|
|
152
|
-
h.delete('authorization');
|
|
153
|
-
h.delete('x-api-key');
|
|
154
|
-
return h;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (Array.isArray(headers)) {
|
|
158
|
-
return headers.filter(
|
|
159
|
-
([key]) => key.toLowerCase() !== 'authorization' && key.toLowerCase() !== 'x-api-key'
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Plain object
|
|
164
|
-
const result: Record<string, string> = {};
|
|
165
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
166
|
-
if (key.toLowerCase() !== 'authorization' && key.toLowerCase() !== 'x-api-key') {
|
|
167
|
-
result[key] = value;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return result;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function createHttpInterceptor(options: HttpInterceptorOptions): HttpInterceptor {
|
|
175
|
-
return new HttpInterceptor(options);
|
|
176
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* aquaman-plugin - Credential isolation plugin for OpenClaw
|
|
3
|
-
*
|
|
4
|
-
* This package provides an OpenClaw plugin that enables:
|
|
5
|
-
* - Secure credential storage using enterprise backends (Keychain, 1Password, Vault)
|
|
6
|
-
* - Proxy mode: Separate process, credentials never in Gateway memory
|
|
7
|
-
* - Hash-chained tamper-evident audit logs
|
|
8
|
-
* - Slash commands for credential management
|
|
9
|
-
*
|
|
10
|
-
* Installation:
|
|
11
|
-
* npm install aquaman-plugin
|
|
12
|
-
*
|
|
13
|
-
* Configuration in openclaw.json:
|
|
14
|
-
* {
|
|
15
|
-
* "plugins": {
|
|
16
|
-
* "aquaman-plugin": {
|
|
17
|
-
* "backend": "keychain",
|
|
18
|
-
* "services": ["anthropic", "openai"]
|
|
19
|
-
* }
|
|
20
|
-
* }
|
|
21
|
-
* }
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
// Plugin
|
|
25
|
-
export {
|
|
26
|
-
type AquamanPluginOptions,
|
|
27
|
-
AquamanPlugin,
|
|
28
|
-
createAquamanPlugin
|
|
29
|
-
} from './plugin.js';
|
|
30
|
-
|
|
31
|
-
// Config Schema
|
|
32
|
-
export {
|
|
33
|
-
ConfigSchema,
|
|
34
|
-
CredentialBackend,
|
|
35
|
-
ProxiedServices,
|
|
36
|
-
type PluginConfig,
|
|
37
|
-
defaultConfig,
|
|
38
|
-
mergeConfig
|
|
39
|
-
} from './config-schema.js';
|
|
40
|
-
|
|
41
|
-
// Proxy Manager
|
|
42
|
-
export {
|
|
43
|
-
type ProxyConnectionInfo,
|
|
44
|
-
type ProxyManagerOptions,
|
|
45
|
-
ProxyManager,
|
|
46
|
-
createProxyManager
|
|
47
|
-
} from './proxy-manager.js';
|
|
48
|
-
|
|
49
|
-
// Commands
|
|
50
|
-
export {
|
|
51
|
-
type CommandContext,
|
|
52
|
-
type CommandResult,
|
|
53
|
-
statusCommand,
|
|
54
|
-
addCommand,
|
|
55
|
-
listCommand,
|
|
56
|
-
logsCommand,
|
|
57
|
-
verifyCommand,
|
|
58
|
-
executeCommand
|
|
59
|
-
} from './commands.js';
|
|
60
|
-
|
|
61
|
-
// Default export for OpenClaw plugin loading
|
|
62
|
-
export { default } from './plugin.js';
|