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/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
- }
@@ -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
- }
@@ -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';