aquaman-plugin 0.6.0 → 0.7.1

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 CHANGED
@@ -8,15 +8,16 @@ OpenClaw Gateway plugin for [aquaman](https://github.com/tech4242/aquaman) crede
8
8
  Agent / OpenClaw Gateway Aquaman Proxy
9
9
  ┌──────────────────────┐ ┌──────────────────────┐
10
10
  │ │ │ │
11
- │ ANTHROPIC_BASE_URL │──request────>│ Keychain / 1Pass / │
12
- │ = localhost:8081 Vault / Encrypted │
13
- │<─response────│
14
- │ fetch() interceptor │──channel────>│ + Auth injected: │
15
- │ redirects channel │ traffic │ header / url-path │
11
+ │ ANTHROPIC_BASE_URL │══ Unix ════>│ Keychain / 1Pass / │
12
+ │ = aquaman.local │ Domain │ Vault / Encrypted │
13
+ │<═ Socket ═══│
14
+ │ fetch() interceptor │══ (UDS) ══=>│ + Auth injected: │
15
+ │ redirects channel │ │ header / url-path │
16
16
  │ API traffic │ │ basic / oauth │
17
17
  │ │ │ │
18
- │ No credentials. │ │ │
19
- Nothing to steal. │ │
18
+ │ No credentials. │ ~/.aquaman/ │ │
19
+ No open ports. proxy.sock │ │
20
+ │ Nothing to steal. │ (chmod 600) │ │
20
21
  └──────────────────────┘ └───┬──────────┬───────┘
21
22
  │ │
22
23
  │ ▼
@@ -28,7 +29,7 @@ Agent / OpenClaw Gateway Aquaman Proxy
28
29
  slack.com/api ...
29
30
  ```
30
31
 
31
- This plugin makes the left side work. It routes all LLM and channel API traffic through the aquaman proxy so credentials never enter the Gateway process.
32
+ This plugin makes the left side work. It routes all LLM and channel API traffic through the aquaman proxy via Unix domain socket so credentials never enter the Gateway process. No TCP port is opened — traffic flows through `~/.aquaman/proxy.sock`.
32
33
 
33
34
  ## Quick Start
34
35
 
@@ -54,16 +55,19 @@ Troubleshooting: `aquaman doctor`
54
55
 
55
56
  | Key | Type | Default | Description |
56
57
  |-----|------|---------|-------------|
57
- | `mode` | `"embedded"` \| `"proxy"` | `"embedded"` | Isolation mode |
58
58
  | `backend` | `"keychain"` \| `"1password"` \| `"vault"` \| `"encrypted-file"` \| `"keepassxc"` | `"keychain"` | Credential store |
59
59
  | `services` | `string[]` | `["anthropic", "openai"]` | Services to proxy |
60
- | `proxyPort` | `number` | `8081` | Proxy listen port |
61
60
 
62
- > Advanced settings (TLS, audit, vault) go in `~/.aquaman/config.yaml`.
61
+ > Advanced settings (audit, vault) go in `~/.aquaman/config.yaml`.
63
62
 
64
63
  ## Security Audit Note
65
64
 
66
- Running `openclaw security audit --deep` will show a `dangerous-exec` finding for this plugin. That's expected — the plugin spawns the aquaman proxy as a separate process, which is the whole point of credential isolation. `aquaman setup` adds the plugin to your `plugins.allow` trust list automatically.
65
+ Running `openclaw security audit --deep` will show two expected findings:
66
+
67
+ - **`dangerous-exec`** on `proxy-manager.ts` — the plugin spawns the aquaman proxy as a separate process, which is the whole point of credential isolation.
68
+ - **`tools_reachable_permissive_policy`** — advisory that plugin tools are reachable under the default tool policy. This is about your OpenClaw tool profile setting, not about aquaman. Set `"tools": { "profile": "coding" }` in `openclaw.json` if your agents handle untrusted input.
69
+
70
+ `aquaman setup` adds the plugin to your `plugins.allow` trust list automatically.
67
71
 
68
72
  ## Documentation
69
73
 
package/index.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * The plugin will:
13
13
  * - Start the aquaman proxy on plugin load
14
- * - Set ANTHROPIC_BASE_URL, OPENAI_BASE_URL etc. to route through proxy
14
+ * - Set ANTHROPIC_BASE_URL, OPENAI_BASE_URL etc. to route through proxy via UDS
15
15
  * - The proxy injects credentials into requests
16
16
  * - Agent never sees the actual API keys
17
17
  */
@@ -50,12 +50,17 @@ try { PLUGIN_VERSION = JSON.parse(fs.readFileSync(pluginPkgPath, 'utf-8')).versi
50
50
 
51
51
  let proxyManager: ProxyManager | null = null;
52
52
  let httpInterceptor: HttpInterceptor | null = null;
53
- let clientToken: string | null = null;
53
+ let socketPath: string | null = null;
54
54
  let dynamicHostMap: Map<string, string> | null = null;
55
- const proxyPort = 8081;
56
55
  const services = ["anthropic", "openai"];
57
56
 
58
- /** Fallback host map used when proxy doesn't provide one (backward compat) */
57
+ /** Default socket path */
58
+ function getDefaultSocketPath(): string {
59
+ const configDir = path.join(os.homedir(), '.aquaman');
60
+ return path.join(configDir, 'proxy.sock');
61
+ }
62
+
63
+ /** Fallback host map used when proxy doesn't provide one */
59
64
  const FALLBACK_HOST_MAP = new Map<string, string>([
60
65
  ['api.anthropic.com', 'anthropic'],
61
66
  ['api.openai.com', 'openai'],
@@ -84,21 +89,6 @@ const FALLBACK_HOST_MAP = new Map<string, string>([
84
89
  ['chat.googleapis.com', 'google-chat'],
85
90
  ]);
86
91
 
87
- /**
88
- * Get external proxy URL from environment (for Docker two-container mode).
89
- * When set, the plugin skips spawning a local proxy and routes traffic to the external URL.
90
- */
91
- function getExternalProxyUrl(): string | null {
92
- return process.env.AQUAMAN_PROXY_URL || null;
93
- }
94
-
95
- /**
96
- * Get external client token from environment (for Docker two-container mode).
97
- */
98
- function getExternalClientToken(): string | null {
99
- return process.env.AQUAMAN_CLIENT_TOKEN || null;
100
- }
101
-
102
92
  /**
103
93
  * Check if aquaman CLI is installed (fs-based, no shell execution)
104
94
  */
@@ -109,12 +99,12 @@ function isAquamanInstalled(): boolean {
109
99
  /**
110
100
  * Start the aquaman proxy daemon using ProxyManager
111
101
  */
112
- async function startProxy(port: number, log: OpenClawPluginApi["logger"]): Promise<boolean> {
102
+ async function startProxy(log: OpenClawPluginApi["logger"]): Promise<boolean> {
113
103
  try {
114
104
  const mgr = createProxyManager({
115
- config: { proxyPort: port },
105
+ config: {},
116
106
  onReady: (info) => {
117
- clientToken = info.token || null;
107
+ socketPath = info.socketPath;
118
108
  if (info.hostMap && typeof info.hostMap === "object") {
119
109
  dynamicHostMap = new Map(Object.entries(info.hostMap));
120
110
  }
@@ -127,6 +117,7 @@ async function startProxy(port: number, log: OpenClawPluginApi["logger"]): Promi
127
117
  });
128
118
  await mgr.start();
129
119
  proxyManager = mgr;
120
+ socketPath = mgr.getSocketPath();
130
121
  return true;
131
122
  } catch (err) {
132
123
  log.error(`Failed to start proxy: ${err}`);
@@ -146,7 +137,7 @@ function stopProxy(): void {
146
137
  proxyManager.stop();
147
138
  proxyManager = null;
148
139
  }
149
- clientToken = null;
140
+ socketPath = null;
150
141
  }
151
142
 
152
143
  /**
@@ -154,16 +145,18 @@ function stopProxy(): void {
154
145
  * This is what provides credential isolation for channels that don't support base URL overrides.
155
146
  */
156
147
  function activateHttpInterceptor(log: OpenClawPluginApi["logger"]): void {
148
+ if (!socketPath) {
149
+ log.error("Cannot activate HTTP interceptor: no socket path");
150
+ return;
151
+ }
152
+
157
153
  // Use dynamic host map from proxy (includes custom services from services.yaml)
158
154
  // Falls back to builtin map for backward compatibility
159
155
  const hostMap = dynamicHostMap || FALLBACK_HOST_MAP;
160
156
 
161
- const baseUrl = getExternalProxyUrl() || `http://127.0.0.1:${proxyPort}`;
162
-
163
157
  httpInterceptor = createHttpInterceptor({
164
- proxyBaseUrl: baseUrl,
158
+ socketPath,
165
159
  hostMap,
166
- clientToken: clientToken || undefined,
167
160
  log: (msg) => log.info(msg),
168
161
  });
169
162
 
@@ -172,13 +165,11 @@ function activateHttpInterceptor(log: OpenClawPluginApi["logger"]): void {
172
165
  }
173
166
 
174
167
  /**
175
- * Set environment variables for SDK clients
168
+ * Set environment variables for SDK clients using sentinel hostname
176
169
  */
177
170
  function configureEnvironment(log: OpenClawPluginApi["logger"]): void {
178
- const baseUrl = getExternalProxyUrl() || `http://127.0.0.1:${proxyPort}`;
179
-
180
171
  for (const service of services) {
181
- const serviceUrl = `${baseUrl}/${service}`;
172
+ const serviceUrl = `http://aquaman.local/${service}`;
182
173
 
183
174
  switch (service) {
184
175
  case "anthropic":
@@ -202,10 +193,9 @@ function configureEnvironment(log: OpenClawPluginApi["logger"]): void {
202
193
  }
203
194
 
204
195
  /**
205
- * Register the aquaman_status tool (shared between local and external proxy modes)
196
+ * Register the aquaman_status tool
206
197
  */
207
198
  function registerStatusTool(api: OpenClawPluginApi): void {
208
- const externalUrl = getExternalProxyUrl();
209
199
  api.registerTool(
210
200
  () => {
211
201
  return {
@@ -219,10 +209,8 @@ function registerStatusTool(api: OpenClawPluginApi): void {
219
209
  },
220
210
  async execute() {
221
211
  return {
222
- externalProxy: externalUrl !== null,
223
- proxyUrl: externalUrl || `http://127.0.0.1:${proxyPort}`,
224
- proxyRunning: externalUrl !== null || proxyManager !== null,
225
- proxyPort,
212
+ proxyRunning: proxyManager !== null,
213
+ socketPath: socketPath || getDefaultSocketPath(),
226
214
  services,
227
215
  httpInterceptorActive: httpInterceptor?.isActive() ?? false,
228
216
  environmentVariables: Object.fromEntries(
@@ -278,10 +266,11 @@ function ensureAuthProfiles(log: OpenClawPluginApi["logger"]): void {
278
266
  }
279
267
 
280
268
  const dir = path.dirname(profilesPath);
281
- fs.mkdirSync(dir, { recursive: true });
269
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
282
270
  fs.writeFileSync(
283
271
  profilesPath,
284
- JSON.stringify({ version: 1, profiles, order }, null, 2)
272
+ JSON.stringify({ version: 1, profiles, order }, null, 2),
273
+ { mode: 0o600 }
285
274
  );
286
275
  log.info(
287
276
  `Generated auth-profiles.json with placeholder keys at ${profilesPath}`
@@ -297,37 +286,6 @@ export default function register(api: OpenClawPluginApi): void {
297
286
  // Auto-generate auth-profiles.json if missing
298
287
  ensureAuthProfiles(api.logger);
299
288
 
300
- const externalUrl = getExternalProxyUrl();
301
-
302
- // External proxy mode (Docker two-container architecture)
303
- if (externalUrl) {
304
- api.logger.info(`External proxy mode: ${externalUrl}`);
305
- clientToken = getExternalClientToken();
306
- configureEnvironment(api.logger);
307
-
308
- if (api.registerLifecycle) {
309
- api.registerLifecycle({
310
- async onGatewayStart() {
311
- // Load dynamic host map from external proxy (includes custom services)
312
- const map = await loadHostMap(externalUrl, clientToken);
313
- dynamicHostMap = map.size > 0 ? map : FALLBACK_HOST_MAP;
314
- activateHttpInterceptor(api.logger);
315
- api.logger.info("HTTP interceptor active (external proxy mode)");
316
- },
317
- async onGatewayStop() {
318
- if (httpInterceptor) {
319
- httpInterceptor.deactivate();
320
- httpInterceptor = null;
321
- }
322
- },
323
- });
324
- }
325
-
326
- registerStatusTool(api);
327
- api.logger.info("Aquaman plugin registered successfully");
328
- return;
329
- }
330
-
331
289
  // Local proxy mode — requires aquaman CLI
332
290
  if (!isAquamanInstalled()) {
333
291
  api.logger.warn(
@@ -342,22 +300,21 @@ export default function register(api: OpenClawPluginApi): void {
342
300
 
343
301
  api.logger.info("aquaman CLI found, will start proxy on gateway start");
344
302
 
345
- // Configure environment variables immediately
303
+ // Configure environment variables immediately (sentinel hostname)
346
304
  configureEnvironment(api.logger);
347
305
 
348
306
  // Register lifecycle hooks if available
349
307
  if (api.registerLifecycle) {
350
308
  api.registerLifecycle({
351
309
  async onGatewayStart() {
352
- api.logger.info(`Starting aquaman proxy on port ${proxyPort}...`);
310
+ api.logger.info("Starting aquaman proxy...");
353
311
 
354
- const started = await startProxy(proxyPort, api.logger);
355
- if (started) {
312
+ const started = await startProxy(api.logger);
313
+ if (started && socketPath) {
356
314
  api.logger.info("Aquaman proxy started successfully");
357
315
 
358
316
  // Check for version mismatch between plugin and proxy
359
- const proxyBaseUrl = `http://127.0.0.1:${proxyPort}`;
360
- const proxyVersion = await getProxyVersion(proxyBaseUrl);
317
+ const proxyVersion = await getProxyVersion(socketPath);
361
318
  if (proxyVersion && proxyVersion !== PLUGIN_VERSION) {
362
319
  api.logger.warn(
363
320
  `Warning: plugin version ${PLUGIN_VERSION} \u2260 proxy version ${proxyVersion}. ` +
@@ -368,19 +325,22 @@ export default function register(api: OpenClawPluginApi): void {
368
325
  // Activate HTTP interceptor to redirect channel traffic through proxy
369
326
  activateHttpInterceptor(api.logger);
370
327
  } else {
371
- api.logger.error(
372
- `Failed to start aquaman proxy on port ${proxyPort}`
373
- );
374
- // Check if another instance is already running on the port
375
- const alreadyRunning = await isProxyRunning(proxyPort);
328
+ api.logger.error("Failed to start aquaman proxy");
329
+ // Check if another instance is already running
330
+ const defaultSock = getDefaultSocketPath();
331
+ const alreadyRunning = await isProxyRunning(defaultSock);
376
332
  if (alreadyRunning) {
333
+ socketPath = defaultSock;
377
334
  api.logger.info(
378
- `Another aquaman instance is already running on port ${proxyPort} — using it`
335
+ "Another aquaman instance is already running — using it"
379
336
  );
337
+ // Load host map from existing proxy
338
+ const map = await loadHostMap(defaultSock);
339
+ dynamicHostMap = map.size > 0 ? map : FALLBACK_HOST_MAP;
380
340
  activateHttpInterceptor(api.logger);
381
341
  } else {
382
342
  api.logger.error(
383
- `Port ${proxyPort} may be in use. Check with: lsof -i :${proxyPort}`
343
+ "No running proxy found. Check: aquaman doctor"
384
344
  );
385
345
  }
386
346
  }
@@ -407,7 +367,7 @@ export default function register(api: OpenClawPluginApi): void {
407
367
  .action(() => {
408
368
  console.log("\nAquaman Status:");
409
369
  console.log(` Proxy running: ${proxyManager !== null}`);
410
- console.log(` Proxy port: ${proxyPort}`);
370
+ console.log(` Socket path: ${socketPath || getDefaultSocketPath()}`);
411
371
  console.log(` Services: ${services.join(", ")}`);
412
372
  console.log("\nEnvironment Variables:");
413
373
  for (const service of services) {
@@ -12,12 +12,6 @@
12
12
  "type": "object",
13
13
  "additionalProperties": false,
14
14
  "properties": {
15
- "mode": {
16
- "type": "string",
17
- "enum": ["embedded", "proxy"],
18
- "default": "embedded",
19
- "description": "embedded: credentials in gateway memory, proxy: separate process"
20
- },
21
15
  "backend": {
22
16
  "type": "string",
23
17
  "enum": ["keychain", "1password", "vault", "encrypted-file", "keepassxc"],
@@ -29,11 +23,6 @@
29
23
  "items": { "type": "string" },
30
24
  "default": ["anthropic", "openai"],
31
25
  "description": "Services to proxy credentials for"
32
- },
33
- "proxyPort": {
34
- "type": "number",
35
- "default": 8081,
36
- "description": "Port for credential proxy (proxy mode only)"
37
26
  }
38
27
  }
39
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aquaman-plugin",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Credential isolation plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -13,6 +13,7 @@
13
13
  "keywords": [
14
14
  "aquaman",
15
15
  "openclaw",
16
+ "openclaw-plugin",
16
17
  "plugin",
17
18
  "security",
18
19
  "credentials",
@@ -21,10 +22,12 @@
21
22
  ],
22
23
  "author": "tech4242",
23
24
  "license": "MIT",
24
- "dependencies": {},
25
+ "dependencies": {
26
+ "undici": "^7.0.0"
27
+ },
25
28
  "peerDependencies": {
26
29
  "openclaw": ">=2026.1.0",
27
- "aquaman-proxy": "0.6.0"
30
+ "aquaman-proxy": "0.7.1"
28
31
  },
29
32
  "peerDependenciesMeta": {
30
33
  "aquaman-proxy": {
package/src/commands.ts CHANGED
@@ -4,13 +4,11 @@
4
4
  * Provides /aquaman commands for users to interact with the plugin.
5
5
  */
6
6
 
7
- import type { EmbeddedMode } from './embedded.js';
8
7
  import type { ProxyManager } from './proxy-manager.js';
9
8
  import type { PluginConfig } from './config-schema.js';
10
9
 
11
10
  export interface CommandContext {
12
11
  config: PluginConfig;
13
- embeddedMode?: EmbeddedMode;
14
12
  proxyManager?: ProxyManager;
15
13
  }
16
14
 
@@ -37,43 +35,17 @@ export async function statusCommand(ctx: CommandContext): Promise<CommandResult>
37
35
 
38
36
  lines.push('aquaman plugin status');
39
37
  lines.push('');
40
- lines.push(`Mode: ${ctx.config.mode || 'embedded'}`);
41
38
  lines.push(`Backend: ${ctx.config.backend || 'keychain'}`);
42
39
  lines.push(`Services: ${(ctx.config.services || []).join(', ')}`);
43
40
 
44
- if (ctx.config.mode === 'proxy') {
45
- if (ctx.proxyManager?.isRunning()) {
46
- const info = ctx.proxyManager.getConnectionInfo();
47
- lines.push('');
48
- lines.push('Proxy Status: Running');
49
- lines.push(` URL: ${info?.baseUrl}`);
50
- lines.push(` Port: ${info?.port}`);
51
- lines.push(` Protocol: ${info?.protocol}`);
52
- } else {
53
- lines.push('');
54
- lines.push('Proxy Status: Not running');
55
- }
56
- } else if (ctx.embeddedMode) {
57
- const status = ctx.embeddedMode.getStatus();
41
+ if (ctx.proxyManager?.isRunning()) {
42
+ const info = ctx.proxyManager.getConnectionInfo();
58
43
  lines.push('');
59
- lines.push('Embedded Mode Status:');
60
- lines.push(` Initialized: ${status.initialized}`);
61
- lines.push(` Audit Enabled: ${status.auditEnabled}`);
62
- }
63
-
64
- // List credentials (without values)
65
- if (ctx.embeddedMode) {
66
- try {
67
- const creds = await ctx.embeddedMode.listCredentials();
68
- lines.push('');
69
- lines.push(`Stored Credentials: ${creds.length}`);
70
- for (const cred of creds) {
71
- lines.push(` - ${cred.service}/${cred.key}`);
72
- }
73
- } catch (error) {
74
- lines.push('');
75
- lines.push('Credentials: (unavailable)');
76
- }
44
+ lines.push('Proxy Status: Running');
45
+ lines.push(` Socket: ${info?.socketPath}`);
46
+ } else {
47
+ lines.push('');
48
+ lines.push('Proxy Status: Not running');
77
49
  }
78
50
 
79
51
  return {
@@ -86,19 +58,10 @@ export async function statusCommand(ctx: CommandContext): Promise<CommandResult>
86
58
  * /aquaman add <service> - Add a credential (prompts for value)
87
59
  */
88
60
  export async function addCommand(
89
- ctx: CommandContext,
61
+ _ctx: CommandContext,
90
62
  service: string,
91
63
  key: string = 'api_key'
92
64
  ): Promise<CommandResult> {
93
- if (!ctx.embeddedMode) {
94
- return {
95
- success: false,
96
- message: 'Embedded mode not available. Use proxy mode for credential management.'
97
- };
98
- }
99
-
100
- // Note: In a real OpenClaw plugin, this would trigger a secure input prompt
101
- // For now, we return instructions
102
65
  return {
103
66
  success: true,
104
67
  message: `To add a credential for ${service}/${key}:\n\n` +
@@ -111,143 +74,30 @@ export async function addCommand(
111
74
  /**
112
75
  * /aquaman list - List stored credentials
113
76
  */
114
- export async function listCommand(ctx: CommandContext): Promise<CommandResult> {
115
- if (!ctx.embeddedMode) {
116
- return {
117
- success: false,
118
- message: 'Embedded mode not available.'
119
- };
120
- }
121
-
122
- try {
123
- const creds = await ctx.embeddedMode.listCredentials();
124
-
125
- if (creds.length === 0) {
126
- return {
127
- success: true,
128
- message: 'No credentials stored.\n\nUse `aquaman credentials add <service> <key>` to add one.'
129
- };
130
- }
131
-
132
- const lines = ['Stored credentials:', ''];
133
- for (const cred of creds) {
134
- lines.push(` ${cred.service}/${cred.key}`);
135
- }
136
-
137
- return {
138
- success: true,
139
- message: lines.join('\n'),
140
- data: creds
141
- };
142
- } catch (error) {
143
- return {
144
- success: false,
145
- message: `Failed to list credentials: ${error}`
146
- };
147
- }
77
+ export async function listCommand(_ctx: CommandContext): Promise<CommandResult> {
78
+ return {
79
+ success: true,
80
+ message: 'Run in your terminal:\n aquaman credentials list'
81
+ };
148
82
  }
149
83
 
150
84
  /**
151
85
  * /aquaman logs - Show recent audit entries
152
86
  */
153
- export async function logsCommand(ctx: CommandContext, count: number = 10): Promise<CommandResult> {
154
- if (!ctx.embeddedMode) {
155
- return {
156
- success: false,
157
- message: 'Embedded mode not available.'
158
- };
159
- }
160
-
161
- try {
162
- const entries = await ctx.embeddedMode.getRecentAuditEntries(count);
163
-
164
- if (entries.length === 0) {
165
- return {
166
- success: true,
167
- message: 'No audit entries found.'
168
- };
169
- }
170
-
171
- const lines = [`Last ${entries.length} audit entries:`, ''];
172
- for (const entry of entries) {
173
- const time = new Date(entry.timestamp).toISOString();
174
- const type = entry.type.toUpperCase().padEnd(16);
175
- let details = '';
176
-
177
- if (entry.type === 'credential_access') {
178
- details = `${entry.data.service} ${entry.data.operation} ${entry.data.success ? 'OK' : 'FAIL'}`;
179
- } else {
180
- details = JSON.stringify(entry.data).slice(0, 60);
181
- }
182
-
183
- lines.push(`${time} [${type}] ${details}`);
184
- }
185
-
186
- return {
187
- success: true,
188
- message: lines.join('\n'),
189
- data: entries
190
- };
191
- } catch (error) {
192
- return {
193
- success: false,
194
- message: `Failed to get audit logs: ${error}`
195
- };
196
- }
87
+ export async function logsCommand(_ctx: CommandContext, _count: number = 10): Promise<CommandResult> {
88
+ return {
89
+ success: true,
90
+ message: 'Run in your terminal:\n aquaman audit tail'
91
+ };
197
92
  }
198
93
 
199
94
  /**
200
95
  * /aquaman verify - Verify audit log integrity
201
96
  */
202
- export async function verifyCommand(ctx: CommandContext): Promise<CommandResult> {
203
- if (!ctx.embeddedMode) {
204
- return {
205
- success: false,
206
- message: 'Embedded mode not available.'
207
- };
208
- }
209
-
210
- try {
211
- const result = await ctx.embeddedMode.verifyAuditIntegrity();
212
-
213
- if (result.valid) {
214
- return {
215
- success: true,
216
- message: 'Audit log integrity verified. No tampering detected.'
217
- };
218
- } else {
219
- return {
220
- success: false,
221
- message: 'Audit log integrity FAILED!\n\n' +
222
- 'Errors:\n' +
223
- result.errors.map(e => ` - ${e}`).join('\n')
224
- };
225
- }
226
- } catch (error) {
227
- return {
228
- success: false,
229
- message: `Failed to verify audit log: ${error}`
230
- };
231
- }
232
- }
233
-
234
- /**
235
- * /aquaman mode <embedded|proxy> - Switch mode
236
- */
237
- export async function modeCommand(ctx: CommandContext, mode: 'embedded' | 'proxy'): Promise<CommandResult> {
238
- if (mode !== 'embedded' && mode !== 'proxy') {
239
- return {
240
- success: false,
241
- message: 'Invalid mode. Use "embedded" or "proxy".'
242
- };
243
- }
244
-
245
- // Mode switching requires configuration change
97
+ export async function verifyCommand(_ctx: CommandContext): Promise<CommandResult> {
246
98
  return {
247
99
  success: true,
248
- message: `To switch to ${mode} mode, update your openclaw.json:\n\n` +
249
- `{\n "plugins": {\n "aquaman": {\n "mode": "${mode}"\n }\n }\n}\n\n` +
250
- `Then restart OpenClaw.`
100
+ message: 'Run in your terminal:\n aquaman audit verify'
251
101
  };
252
102
  }
253
103
 
@@ -272,36 +122,26 @@ export async function executeCommand(
272
122
  case 'list':
273
123
  return listCommand(ctx);
274
124
 
275
- case 'logs':
125
+ case 'logs': {
276
126
  const count = args[0] ? parseInt(args[0], 10) : 10;
277
127
  return logsCommand(ctx, count);
128
+ }
278
129
 
279
130
  case 'verify':
280
131
  return verifyCommand(ctx);
281
132
 
282
- case 'mode':
283
- if (args.length < 1) {
284
- return { success: false, message: 'Usage: /aquaman mode <embedded|proxy>' };
285
- }
286
- return modeCommand(ctx, args[0] as 'embedded' | 'proxy');
287
-
288
133
  case 'help':
289
134
  default:
290
135
  return {
291
136
  success: true,
292
137
  message: `aquaman plugin commands:
293
138
 
294
- /aquaman status - Show plugin status and stored credentials
139
+ /aquaman status - Show plugin status
295
140
  /aquaman add - Add a credential (shows instructions)
296
141
  /aquaman list - List stored credentials
297
- /aquaman logs [n] - Show recent audit entries (default: 10)
142
+ /aquaman logs [n] - Show recent audit entries
298
143
  /aquaman verify - Verify audit log integrity
299
- /aquaman mode - Switch between embedded and proxy mode
300
- /aquaman help - Show this help message
301
-
302
- Mode comparison:
303
- embedded: Simpler setup, credentials in Gateway memory
304
- proxy: Stronger isolation, credentials in separate process`
144
+ /aquaman help - Show this help message`
305
145
  };
306
146
  }
307
147
  }
@@ -313,7 +153,7 @@ export function getAvailableCommands(ctx: CommandContext): PluginCommand[] {
313
153
  return [
314
154
  {
315
155
  name: 'status',
316
- description: 'Show aquaman plugin status and stored credentials',
156
+ description: 'Show aquaman plugin status',
317
157
  execute: async () => {
318
158
  const result = await statusCommand(ctx);
319
159
  return result.message;
@@ -357,18 +197,6 @@ export function getAvailableCommands(ctx: CommandContext): PluginCommand[] {
357
197
  return result.message;
358
198
  }
359
199
  },
360
- {
361
- name: 'mode',
362
- description: 'Switch between embedded and proxy mode',
363
- execute: async (args) => {
364
- const mode = args.mode || args._?.[0];
365
- if (!mode) {
366
- return 'Usage: /aquaman mode <embedded|proxy>';
367
- }
368
- const result = await modeCommand(ctx, mode as 'embedded' | 'proxy');
369
- return result.message;
370
- }
371
- },
372
200
  {
373
201
  name: 'help',
374
202
  description: 'Show help for aquaman commands',