aquaman-proxy 0.16.0 → 0.17.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.
Files changed (34) hide show
  1. package/README.md +9 -0
  2. package/dist/cli/index.js +189 -3
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/core/types.d.ts +10 -0
  5. package/dist/core/types.d.ts.map +1 -1
  6. package/dist/core/utils/config.d.ts.map +1 -1
  7. package/dist/core/utils/config.js +9 -1
  8. package/dist/core/utils/config.js.map +1 -1
  9. package/dist/openclaw/channel-routing.d.ts +5 -3
  10. package/dist/openclaw/channel-routing.d.ts.map +1 -1
  11. package/dist/openclaw/channel-routing.js +5 -3
  12. package/dist/openclaw/channel-routing.js.map +1 -1
  13. package/dist/openclaw/integration.d.ts +11 -0
  14. package/dist/openclaw/integration.d.ts.map +1 -1
  15. package/dist/openclaw/integration.js +13 -0
  16. package/dist/openclaw/integration.js.map +1 -1
  17. package/dist/openshell/credential-driver.d.ts +117 -0
  18. package/dist/openshell/credential-driver.d.ts.map +1 -0
  19. package/dist/openshell/credential-driver.js +181 -0
  20. package/dist/openshell/credential-driver.js.map +1 -0
  21. package/dist/openshell/grpc-server.d.ts +23 -0
  22. package/dist/openshell/grpc-server.d.ts.map +1 -0
  23. package/dist/openshell/grpc-server.js +122 -0
  24. package/dist/openshell/grpc-server.js.map +1 -0
  25. package/dist/openshell/integration.d.ts +37 -0
  26. package/dist/openshell/integration.d.ts.map +1 -0
  27. package/dist/openshell/integration.js +69 -0
  28. package/dist/openshell/integration.js.map +1 -0
  29. package/package.json +12 -3
  30. package/proto/openshell/README.md +9 -0
  31. package/proto/openshell/credential_driver.proto +146 -0
  32. package/proto/openshell/datamodel.proto +128 -0
  33. package/proto/openshell/extension.proto +34 -0
  34. package/proto/openshell/options.proto +36 -0
package/README.md CHANGED
@@ -99,6 +99,15 @@ Scoped since v0.15.0, because it hands out values rather than injecting them:
99
99
 
100
100
  `aquaman get <aquaman://service/key>` (v0.16.0+) is the command-line form, for tools that take a command printing a secret: Docker Sandboxes (`sbx secret set <svc> --command`), Codex (`model_providers.<id>.auth.command`), Claude Code (`apiKeyHelper`), OpenClaw exec secret providers and Hermes command secret sources. It only talks to the running daemon, so the same scope applies and every read is audited. It refuses to print to an interactive terminal without `--show`, and prints no trailing newline when another program reads it. The value goes to the program that runs the command.
101
101
 
102
+ ## OpenShell credential driver (v0.17.0+)
103
+
104
+ `aquaman daemon` can serve NVIDIA OpenShell's external credential driver protocol (`credential_driver.proto`, extension protocol 1.0) on `<configDir>/openshell.sock` (0600). Enable it with `aquaman openshell setup`, which prints the gateway TOML.
105
+
106
+ - **Reference mode:** `openshell provider create --credential KEY=aquaman://svc/key`. The ref must be declared (`aquaman broker allow`) and exist in the vault; nothing is stored, the ref is the handle, and deleting the provider never deletes the vault item.
107
+ - **Copy mode:** a plain value is written to the vault under the `openshell` service.
108
+ - Every resolve is audited as a `read` (agent `openshell`), without the value. Handles are interpreted by namespace, never by gateway-supplied metadata, so a reference can't bypass the allow-list.
109
+ - `@grpc/grpc-js` and `@grpc/proto-loader` are optional peer dependencies, loaded only when the driver is on. The vendored protocol files live in `proto/openshell/`.
110
+
102
111
  ## Documentation
103
112
 
104
113
  - **[Root README](https://github.com/tech4242/aquaman#readme)**: value prop, three-path Quick Start, security model
package/dist/cli/index.js CHANGED
@@ -16,7 +16,7 @@ import { loadConfig, getConfigDir, getConfigPath, ensureConfigDir, getDefaultCon
16
16
  import { fileURLToPath } from 'node:url';
17
17
  import { createCredentialProxy } from '../daemon.js';
18
18
  import { createServiceRegistry, ServiceRegistry } from '../service-registry.js';
19
- import { createOpenClawIntegration, authProfilesAreSqliteOnly, legacyAuthProfilesBlockProviders, pluginInstallNeedsCapabilityConsent } from '../openclaw/integration.js';
19
+ import { createOpenClawIntegration, authProfilesAreSqliteOnly, legacyAuthProfilesBlockProviders, pluginInstallNeedsCapabilityConsent, pluginProvenanceIsReported } from '../openclaw/integration.js';
20
20
  import { supportsSecretRefIntegrations, wireSecretRefProviders, secretRefWiringStatus } from '../openclaw/secretref.js';
21
21
  import { CHANNEL_ROUTING_SUPPORTED, wireChannelRouting, channelRoutingStatus } from '../openclaw/channel-routing.js';
22
22
  import { createHermesIntegration, detectHermes } from '../hermes/integration.js';
@@ -307,7 +307,7 @@ function describeChannelSkip(reason, channel) {
307
307
  case 'not-wired':
308
308
  return 'routable but not wired yet (re-run: aquaman openclaw setup)';
309
309
  case 'unroutable':
310
- return 'this OpenClaw exposes no endpoint override for it, so its traffic cannot be routed';
310
+ return 'this OpenClaw has no per-channel endpoint override in its config for it, so aquaman does not route its traffic';
311
311
  }
312
312
  }
313
313
  const program = new Command();
@@ -419,6 +419,14 @@ program
419
419
  }
420
420
  lines.push('');
421
421
  }
422
+ const os_ = byName.get('openshell');
423
+ if (os_) {
424
+ lines.push(aqua('NVIDIA OpenShell integration'));
425
+ for (const sub of helper.visibleCommands(os_).filter((s) => s.name() !== 'help')) {
426
+ lines.push(renderRow(`openshell ${sub.name()}`, sub.description() || ''));
427
+ }
428
+ lines.push('');
429
+ }
422
430
  // --- Other ---
423
431
  const helpCmd = byName.get('help');
424
432
  if (helpCmd) {
@@ -996,10 +1004,27 @@ program
996
1004
  }
997
1005
  });
998
1006
  await credentialProxy.start();
1007
+ // OpenShell external credential driver (v0.17.0+). Opt-in; a failure here
1008
+ // is reported but never takes the proxy down.
1009
+ let openshellDriver;
1010
+ let openshellDriverError;
1011
+ if (config.openshell?.driver?.enabled) {
1012
+ try {
1013
+ const { startOpenShellDriver } = await import('../openshell/integration.js');
1014
+ openshellDriver = await startOpenShellDriver({ config, store: credentialStore, scope: brokerScope, audit: auditLogger, version: VERSION });
1015
+ }
1016
+ catch (err) {
1017
+ openshellDriverError = err instanceof Error ? err.message : String(err);
1018
+ }
1019
+ }
999
1020
  // Write PID file
1000
1021
  writePidFile();
1001
1022
  console.log(`Credential proxy: ${socketPath}`);
1002
1023
  console.log(`Credential broker: ${describeBroker(brokerScope)}`);
1024
+ if (openshellDriver)
1025
+ console.log(`OpenShell credential driver: ${openshellDriver.socketPath}`);
1026
+ if (openshellDriverError)
1027
+ console.error(` ⚠ OpenShell credential driver not started: ${openshellDriverError}`);
1003
1028
  if (brokerScope?.declarationError()) {
1004
1029
  console.error(` ⚠ ${brokerScope.declarationError()} — refs declared there are refused until it parses`);
1005
1030
  }
@@ -1011,6 +1036,7 @@ program
1011
1036
  // Handle shutdown
1012
1037
  const shutdown = async () => {
1013
1038
  console.log('\nShutting down daemon...');
1039
+ await openshellDriver?.stop();
1014
1040
  await credentialProxy.stop();
1015
1041
  removePidFile();
1016
1042
  try {
@@ -2294,6 +2320,9 @@ openclaw
2294
2320
  catch { /* ok */ }
2295
2321
  const verLabel = pluginVer ? ` v${pluginVer}` : '';
2296
2322
  console.log(` \u2713 ${aqua('Plugin')} installed${verLabel} (${pluginPath})`);
2323
+ if (pluginProvenanceIsReported(openclawVersion)) {
2324
+ console.log(` \u2192 ${aqua('Plugin source')} OpenClaw 2026.11+ reports "can't verify where this plugin came from" for it and \`openclaw plugins doctor\` exits 1. The plugin still loads; this is how that OpenClaw classes a plugin installed from ClawHub or copied by setup.`);
2325
+ }
2297
2326
  if (pluginVer && pluginVer !== VERSION) {
2298
2327
  console.log(` \u2717 ${aqua('Version mismatch:')} CLI v${VERSION} \u2260 plugin v${pluginVer}`);
2299
2328
  console.log(' \u2192 Update: openclaw plugins install aquaman-plugin');
@@ -2384,6 +2413,18 @@ openclaw
2384
2413
  }
2385
2414
  else if (status.baseUrlProviders.length > 0) {
2386
2415
  console.log(` ✓ ${aqua('Model routing')} through the proxy for ${status.baseUrlProviders.join(', ')}`);
2416
+ // A provider key exported in the gateway's environment wins over
2417
+ // the configured SecretRef: verified live on OpenClaw 2026.11.3,
2418
+ // the gateway sent $OPENAI_API_KEY itself to the loopback
2419
+ // listener, which rejected it (401, no loopback token). The
2420
+ // real key is then in the agent process, and model calls fail.
2421
+ // Doctor can only see its own environment, so this is a note.
2422
+ const exported = status.baseUrlProviders
2423
+ .map((svc) => `${svc.toUpperCase()}_API_KEY`)
2424
+ .filter((name) => !!process.env[name]);
2425
+ if (exported.length > 0) {
2426
+ console.log(` \u2192 ${aqua('Environment')} ${exported.join(', ')} ${exported.length > 1 ? 'are' : 'is'} set in this shell. If OpenClaw starts with ${exported.length > 1 ? 'them' : 'it'}, it sends that key instead of the proxy token: the key is in the agent process and the proxy answers 401. Unset ${exported.length > 1 ? 'them' : 'it'} where OpenClaw runs.`);
2427
+ }
2387
2428
  }
2388
2429
  }
2389
2430
  else if (status.providerConfigured) {
@@ -2433,7 +2474,7 @@ openclaw
2433
2474
  if (routing.unroutable.length > 0) {
2434
2475
  // Not a failure: nothing the user can do, and the credentials can
2435
2476
  // still live in the vault. Stated so nobody assumes coverage.
2436
- console.log(` \u2192 ${aqua('Channel routing')} not available for ${routing.unroutable.join(', ')} on this OpenClaw (no endpoint override)`);
2477
+ console.log(` \u2192 ${aqua('Channel routing')} not available for ${routing.unroutable.join(', ')} on this OpenClaw (no config-level endpoint override)`);
2437
2478
  console.log(' (their tokens can live in the vault, but the proxy is not in their egress path)');
2438
2479
  }
2439
2480
  }
@@ -2974,6 +3015,151 @@ policy
2974
3015
  console.log(` \u2717 DENIED by rule: ${rule.method} ${rule.path} \u2192 ${rule.action}`);
2975
3016
  }
2976
3017
  });
3018
+ // ── aquaman openshell …: NVIDIA OpenShell external credential driver (v0.17.0+) ──
3019
+ const openshell = program
3020
+ .command('openshell')
3021
+ .description('NVIDIA OpenShell integration: aquaman as the gateway credential driver');
3022
+ /** Edit config.yaml `openshell.driver` in the raw file so env overrides are never persisted. */
3023
+ function updateOpenShellDriverConfig(enabled, socketPath) {
3024
+ ensureConfigDir();
3025
+ const configPath = getConfigPath();
3026
+ let raw = {};
3027
+ if (fs.existsSync(configPath)) {
3028
+ const parsed = yamlParse(fs.readFileSync(configPath, 'utf-8'));
3029
+ if (parsed && typeof parsed === 'object')
3030
+ raw = parsed;
3031
+ }
3032
+ const current = raw.openshell?.driver && typeof raw.openshell.driver === 'object' ? raw.openshell.driver : {};
3033
+ raw.openshell = { ...(raw.openshell && typeof raw.openshell === 'object' ? raw.openshell : {}), driver: { ...current, enabled } };
3034
+ if (socketPath)
3035
+ raw.openshell.driver.socketPath = socketPath;
3036
+ fs.writeFileSync(configPath, yamlStringify(raw), { encoding: 'utf-8', mode: 0o600 });
3037
+ }
3038
+ openshell
3039
+ .command('setup')
3040
+ .description('Serve the OpenShell credential driver from `aquaman daemon` and print the gateway config')
3041
+ .option('--socket <path>', 'Absolute Unix socket path for the driver (default: <configDir>/openshell.sock)')
3042
+ .option('--disable', 'Turn the driver off again')
3043
+ .action(async (opts) => {
3044
+ const { grpcAvailable, gatewayConfigSnippet, openshellDriverSocketPath, MAX_SOCKET_PATH } = await import('../openshell/integration.js');
3045
+ const { GRPC_INSTALL_HINT } = await import('../openshell/grpc-server.js');
3046
+ if (opts.disable) {
3047
+ updateOpenShellDriverConfig(false);
3048
+ console.log('OpenShell credential driver disabled. Restart the daemon: aquaman stop && aquaman daemon');
3049
+ console.log('Remove the [openshell.credential_drivers.aquaman] table from your gateway config too.');
3050
+ return;
3051
+ }
3052
+ if (opts.socket && !path.isAbsolute(opts.socket)) {
3053
+ console.error('--socket must be an absolute path (the gateway config needs one).');
3054
+ process.exit(1);
3055
+ }
3056
+ updateOpenShellDriverConfig(true, opts.socket);
3057
+ const socketPath = openshellDriverSocketPath(loadConfig());
3058
+ console.log(`OpenShell credential driver enabled in ${getConfigPath()}`);
3059
+ if (Buffer.byteLength(socketPath) > MAX_SOCKET_PATH) {
3060
+ console.error(` ⚠ ${socketPath} is longer than the ${MAX_SOCKET_PATH}-byte Unix socket limit. Rerun with --socket <shorter path>.`);
3061
+ }
3062
+ if (!(await grpcAvailable())) {
3063
+ console.log(`\n The driver needs two optional packages: ${aqua(GRPC_INSTALL_HINT)}`);
3064
+ }
3065
+ console.log('\n1. Add this to your OpenShell gateway config (TOML):\n');
3066
+ console.log(gatewayConfigSnippet(socketPath).split('\n').map((l) => ` ${l}`).join('\n'));
3067
+ console.log('\n2. Restart aquaman, then the gateway:');
3068
+ console.log(' aquaman stop; aquaman daemon');
3069
+ console.log('\n3. Create providers by reference. The secret stays in your vault; nothing is copied:');
3070
+ console.log(` ${aqua('aquaman broker allow aquaman://anthropic/api_key')}`);
3071
+ console.log(` openshell provider create --name claude --type anthropic --credential ANTHROPIC_API_KEY=aquaman://anthropic/api_key`);
3072
+ console.log('\n A plain value (--credential KEY=sk-...) is stored in your vault under the "openshell" service instead.');
3073
+ console.log(`\nCheck everything with: ${aqua('aquaman openshell doctor')}`);
3074
+ });
3075
+ openshell
3076
+ .command('doctor')
3077
+ .description('Check the OpenShell credential driver: packages, config, socket, declared refs')
3078
+ .option('--gateway-config <path>', 'Also check an OpenShell gateway TOML file points at this driver')
3079
+ .action(async (opts) => {
3080
+ const net = await import('node:net');
3081
+ const { grpcAvailable, openshellDriverSocketPath, MAX_SOCKET_PATH } = await import('../openshell/integration.js');
3082
+ const { GRPC_INSTALL_HINT } = await import('../openshell/grpc-server.js');
3083
+ const config = loadConfig();
3084
+ const socketPath = openshellDriverSocketPath(config);
3085
+ let failed = 0;
3086
+ const pass = (m) => console.log(` ✓ ${m}`);
3087
+ const fail = (m, fix) => { failed++; console.log(` ✗ ${m}`); if (fix)
3088
+ console.log(` → ${fix}`); };
3089
+ console.log(`\n ${aqua('OpenShell credential driver')}\n`);
3090
+ if (await grpcAvailable())
3091
+ pass('gRPC packages installed (@grpc/grpc-js, @grpc/proto-loader)');
3092
+ else
3093
+ fail('gRPC packages missing', GRPC_INSTALL_HINT);
3094
+ if (config.openshell?.driver?.enabled)
3095
+ pass('Driver enabled in config.yaml');
3096
+ else
3097
+ fail('Driver not enabled', 'aquaman openshell setup');
3098
+ if (Buffer.byteLength(socketPath) <= MAX_SOCKET_PATH)
3099
+ pass(`Socket path ${socketPath}`);
3100
+ else
3101
+ fail(`Socket path over ${MAX_SOCKET_PATH} bytes: ${socketPath}`, 'aquaman openshell setup --socket <shorter absolute path>');
3102
+ let isSocket = false;
3103
+ try {
3104
+ isSocket = fs.statSync(socketPath).isSocket();
3105
+ }
3106
+ catch { /* absent */ }
3107
+ if (!isSocket) {
3108
+ fail('Driver socket not present (daemon not running, or started before setup)', 'aquaman stop; aquaman daemon');
3109
+ }
3110
+ else {
3111
+ const mode = fs.statSync(socketPath).mode & 0o777;
3112
+ if (mode === 0o600)
3113
+ pass('Socket is owner-only (0600)');
3114
+ else
3115
+ fail(`Socket mode is ${mode.toString(8)}, expected 600`, 'aquaman stop; aquaman daemon');
3116
+ const reachable = await new Promise((resolve) => {
3117
+ const c = net.connect(socketPath, () => { c.end(); resolve(true); });
3118
+ c.on('error', () => resolve(false));
3119
+ setTimeout(() => { c.destroy(); resolve(false); }, 2000);
3120
+ });
3121
+ if (reachable)
3122
+ pass('Driver socket accepts connections');
3123
+ else
3124
+ fail('Driver socket does not accept connections', 'aquaman stop; aquaman daemon');
3125
+ }
3126
+ const scope = daemonBrokerScope(config);
3127
+ if (!scope) {
3128
+ fail('Broker disabled (broker.enabled: false): references cannot resolve', 'set broker.enabled: true in config.yaml');
3129
+ }
3130
+ else {
3131
+ const n = scope.declared().length;
3132
+ if (n > 0)
3133
+ pass(`${n} declared ref${n === 1 ? '' : 's'} usable by reference (aquaman broker list)`);
3134
+ else
3135
+ console.log(' • No declared refs yet: reference-mode providers will be refused until you run aquaman broker allow <ref>');
3136
+ }
3137
+ if (opts.gatewayConfig) {
3138
+ let toml = '';
3139
+ try {
3140
+ toml = fs.readFileSync(opts.gatewayConfig, 'utf-8');
3141
+ }
3142
+ catch {
3143
+ fail(`Cannot read ${opts.gatewayConfig}`);
3144
+ }
3145
+ if (toml) {
3146
+ const listsDriver = /credential_drivers\s*=\s*\[[^\]]*"aquaman"[^\]]*\]/.test(toml);
3147
+ const table = /\[openshell\.credential_drivers\.aquaman\]([\s\S]*?)(?=\n\[|$)/.exec(toml)?.[1] ?? '';
3148
+ const uds = /transport\s*=\s*"uds"/.test(table);
3149
+ const sock = /socket_path\s*=\s*"([^"]*)"/.exec(table)?.[1];
3150
+ if (listsDriver && uds && sock === socketPath)
3151
+ pass(`Gateway config ${opts.gatewayConfig} points at this driver`);
3152
+ else
3153
+ fail(`Gateway config ${opts.gatewayConfig} does not point at this driver`, 'paste the snippet from: aquaman openshell setup');
3154
+ }
3155
+ }
3156
+ console.log('');
3157
+ if (failed > 0) {
3158
+ console.log(` ${failed} check${failed === 1 ? '' : 's'} failed.\n`);
3159
+ process.exit(1);
3160
+ }
3161
+ console.log(' All checks passed.\n');
3162
+ });
2977
3163
  // Credential broker scope (v0.15.0+)
2978
3164
  const broker = program
2979
3165
  .command('broker')