aquaman-proxy 0.11.5 → 0.12.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 +49 -56
- package/dist/cli/index.js +608 -59
- package/dist/cli/index.js.map +1 -1
- package/dist/core/credentials/backends/onepassword.d.ts +2 -0
- package/dist/core/credentials/backends/onepassword.d.ts.map +1 -1
- package/dist/core/credentials/backends/onepassword.js +75 -33
- package/dist/core/credentials/backends/onepassword.js.map +1 -1
- package/dist/core/credentials/index.d.ts +1 -1
- package/dist/core/credentials/index.d.ts.map +1 -1
- package/dist/core/credentials/index.js +1 -1
- package/dist/core/credentials/index.js.map +1 -1
- package/dist/core/credentials/store.d.ts.map +1 -1
- package/dist/core/credentials/store.js +2 -4
- package/dist/core/credentials/store.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/redactor/index.d.ts +96 -0
- package/dist/core/redactor/index.d.ts.map +1 -0
- package/dist/core/redactor/index.js +230 -0
- package/dist/core/redactor/index.js.map +1 -0
- package/dist/daemon.d.ts +20 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +147 -0
- package/dist/daemon.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/openclaw/integration.d.ts +13 -0
- package/dist/openclaw/integration.d.ts.map +1 -1
- package/dist/openclaw/integration.js +30 -0
- package/dist/openclaw/integration.js.map +1 -1
- package/package.json +4 -4
package/dist/cli/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Hash-chained audit logs: Tamper-evident logging
|
|
9
9
|
* - Custom service registry: YAML-based service config
|
|
10
10
|
*/
|
|
11
|
-
import { Command } from 'commander';
|
|
11
|
+
import { Command, Help } from 'commander';
|
|
12
12
|
import * as path from 'node:path';
|
|
13
13
|
import * as fs from 'node:fs';
|
|
14
14
|
import * as http from 'node:http';
|
|
@@ -16,7 +16,7 @@ import { loadConfig, getConfigDir, ensureConfigDir, getDefaultConfig, expandPath
|
|
|
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 } from '../openclaw/integration.js';
|
|
19
|
+
import { createOpenClawIntegration, authProfilesAreSqliteOnly } from '../openclaw/integration.js';
|
|
20
20
|
import { loadPolicyFromConfig, validatePolicyConfig, getDefaultPolicyPresets, matchPolicy } from '../request-policy.js';
|
|
21
21
|
import { stringify as yamlStringify, parse as yamlParse } from 'yaml';
|
|
22
22
|
// Read version from package.json (single source of truth)
|
|
@@ -108,9 +108,8 @@ async function promptSecretInput(prompt) {
|
|
|
108
108
|
const program = new Command();
|
|
109
109
|
program
|
|
110
110
|
.name('aquaman')
|
|
111
|
-
.description('Credential isolation
|
|
111
|
+
.description('Credential isolation for AI agents \u2014 vault for the proxy core, OpenClaw plugin path, coding-agent adapter path')
|
|
112
112
|
.version(VERSION)
|
|
113
|
-
.addHelpText('before', `\n\u{1F531}\u{1F99E} Aquaman ${aqua(VERSION)} \u2014 Credential isolation for OpenClaw\n`)
|
|
114
113
|
.configureHelp({
|
|
115
114
|
subcommandTerm(cmd) {
|
|
116
115
|
const args = cmd.registeredArguments
|
|
@@ -119,11 +118,443 @@ program
|
|
|
119
118
|
return arg.required ? `<${n}>` : `[${n}]`;
|
|
120
119
|
})
|
|
121
120
|
.join(' ');
|
|
122
|
-
|
|
121
|
+
// Command names render in default color; only section headings
|
|
122
|
+
// (rendered by our custom formatHelp below) get the aqua treatment.
|
|
123
|
+
return cmd.name() + (cmd.options.length ? ' [options]' : '') + (args ? ' ' + args : '');
|
|
124
|
+
},
|
|
125
|
+
// Custom grouped help layout for the root program. Subcommand helps
|
|
126
|
+
// (e.g. `aquaman openclaw --help`) use Commander's default formatter.
|
|
127
|
+
formatHelp(cmd, helper) {
|
|
128
|
+
// Only customize the ROOT command's help. Nested subcommands keep the
|
|
129
|
+
// default layout \u2014 call the un-overridden formatter directly to avoid
|
|
130
|
+
// recursing into this override.
|
|
131
|
+
if (cmd !== program)
|
|
132
|
+
return Help.prototype.formatHelp.call(helper, cmd, helper);
|
|
133
|
+
const lines = [];
|
|
134
|
+
lines.push('');
|
|
135
|
+
lines.push(` \u{1F531} ${aqua('Aquaman')} ${VERSION} \u2014 Credential isolation for AI agents`);
|
|
136
|
+
lines.push('');
|
|
137
|
+
lines.push(`Usage: ${helper.commandUsage(cmd)}`);
|
|
138
|
+
lines.push('');
|
|
139
|
+
lines.push(cmd.description());
|
|
140
|
+
lines.push('');
|
|
141
|
+
// Options
|
|
142
|
+
const opts = helper.visibleOptions(cmd);
|
|
143
|
+
if (opts.length) {
|
|
144
|
+
lines.push('Options:');
|
|
145
|
+
for (const opt of opts) {
|
|
146
|
+
lines.push(` ${helper.optionTerm(opt).padEnd(20)} ${helper.optionDescription(opt)}`);
|
|
147
|
+
}
|
|
148
|
+
lines.push('');
|
|
149
|
+
}
|
|
150
|
+
const all = helper.visibleCommands(cmd);
|
|
151
|
+
const byName = new Map(all.map((c) => [c.name(), c]));
|
|
152
|
+
const renderRow = (term, desc) => ` ${term.padEnd(30)} ${desc}`;
|
|
153
|
+
const renderCmd = (c) => renderRow(c.name(), c.description() || '');
|
|
154
|
+
// --- Vault & core (agent-agnostic) ---
|
|
155
|
+
lines.push(aqua('Vault & core (agent-agnostic)'));
|
|
156
|
+
const vaultCore = ['setup', 'doctor', 'status', 'daemon', 'stop', 'init'];
|
|
157
|
+
for (const name of vaultCore) {
|
|
158
|
+
const c = byName.get(name);
|
|
159
|
+
if (c)
|
|
160
|
+
lines.push(renderCmd(c));
|
|
161
|
+
}
|
|
162
|
+
lines.push('');
|
|
163
|
+
lines.push(aqua('Vault management'));
|
|
164
|
+
for (const name of ['credentials', 'audit', 'services', 'policy']) {
|
|
165
|
+
const c = byName.get(name);
|
|
166
|
+
if (c)
|
|
167
|
+
lines.push(renderCmd(c));
|
|
168
|
+
}
|
|
169
|
+
lines.push('');
|
|
170
|
+
// --- OpenClaw namespace (nested) ---
|
|
171
|
+
const oc = byName.get('openclaw');
|
|
172
|
+
if (oc) {
|
|
173
|
+
lines.push(aqua('OpenClaw Gateway integration'));
|
|
174
|
+
// Ordered for readability (setup/doctor/status first, then lifecycle).
|
|
175
|
+
const ocOrder = ['setup', 'doctor', 'status', 'start', 'configure', 'migrate'];
|
|
176
|
+
const ocSubs = helper.visibleCommands(oc).filter((s) => s.name() !== 'help');
|
|
177
|
+
const ocSorted = [
|
|
178
|
+
...ocOrder.map((n) => ocSubs.find((s) => s.name() === n)).filter(Boolean),
|
|
179
|
+
...ocSubs.filter((s) => !ocOrder.includes(s.name())),
|
|
180
|
+
];
|
|
181
|
+
for (const sub of ocSorted) {
|
|
182
|
+
lines.push(renderRow(`openclaw ${sub.name()}`, sub.description() || ''));
|
|
183
|
+
}
|
|
184
|
+
lines.push('');
|
|
185
|
+
}
|
|
186
|
+
// --- Coder namespace (shim \u2014 list documented subcommands) ---
|
|
187
|
+
if (byName.has('coder')) {
|
|
188
|
+
lines.push(aqua('AI coding-agent integration (Claude Code today)'));
|
|
189
|
+
const coderSubs = [
|
|
190
|
+
['coder setup <agent>', 'Install hooks for an agent (claude-code today)'],
|
|
191
|
+
['coder doctor', 'Deep diagnostic \u2014 projects, broker, per-project vault'],
|
|
192
|
+
['coder status', 'Configured projects + hook wiring + broker activity'],
|
|
193
|
+
['coder project list/add/remove', 'Manage ~/.aquaman/projects.yaml'],
|
|
194
|
+
['coder get <ref>', 'Resolve an aquaman://service/key reference'],
|
|
195
|
+
['coder exec <cmd>', 'Run command with project env injected + output redacted'],
|
|
196
|
+
];
|
|
197
|
+
for (const [term, desc] of coderSubs) {
|
|
198
|
+
lines.push(renderRow(term, desc));
|
|
199
|
+
}
|
|
200
|
+
lines.push(` ${'(delegates to the aquaman-coder binary; install: npm install -g aquaman-coder)'}`);
|
|
201
|
+
lines.push('');
|
|
202
|
+
}
|
|
203
|
+
// --- Other ---
|
|
204
|
+
const helpCmd = byName.get('help');
|
|
205
|
+
if (helpCmd) {
|
|
206
|
+
lines.push(aqua('Other'));
|
|
207
|
+
lines.push(renderCmd(helpCmd));
|
|
208
|
+
lines.push('');
|
|
209
|
+
}
|
|
210
|
+
return lines.join('\n');
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
// ============================================================================
|
|
214
|
+
// Top-level commands (vault-only, agent-agnostic)
|
|
215
|
+
// ============================================================================
|
|
216
|
+
//
|
|
217
|
+
// `aquaman setup`, `aquaman doctor`, `aquaman status` cover the proxy + vault
|
|
218
|
+
// surface only. For full bundles, use the namespaced versions:
|
|
219
|
+
// aquaman openclaw setup full OpenClaw bundle
|
|
220
|
+
// aquaman coder setup ... coding-agent adapter
|
|
221
|
+
// ============================================================================
|
|
222
|
+
// aquaman setup \u2014 vault-only minimal setup wizard.
|
|
223
|
+
program
|
|
224
|
+
.command('setup')
|
|
225
|
+
.description('Vault-only setup wizard \u2014 backend + credentials (use `aquaman openclaw setup` or `aquaman coder setup` for full bundles)')
|
|
226
|
+
.option('--backend <backend>', 'Credential backend (keychain, encrypted-file, keepassxc, 1password, vault, systemd-creds, bitwarden)')
|
|
227
|
+
.option('--no-policy', 'Skip request policy preset configuration')
|
|
228
|
+
.option('--non-interactive', 'Use environment variables instead of prompts (for CI)')
|
|
229
|
+
.action(async (options) => {
|
|
230
|
+
await runVaultSetup({
|
|
231
|
+
backend: options.backend,
|
|
232
|
+
policy: options.policy !== false,
|
|
233
|
+
nonInteractive: !!options.nonInteractive,
|
|
234
|
+
});
|
|
235
|
+
console.log('');
|
|
236
|
+
console.log(' Next steps:');
|
|
237
|
+
console.log(' \u2022 OpenClaw Gateway user? ' + aqua('aquaman openclaw setup'));
|
|
238
|
+
console.log(' \u2022 Claude Code / Codex / etc? ' + aqua('aquaman coder setup claude-code') + ' (requires aquaman-coder)');
|
|
239
|
+
console.log('');
|
|
240
|
+
});
|
|
241
|
+
// aquaman doctor \u2014 overview with persona-aware soft upsells.
|
|
242
|
+
program
|
|
243
|
+
.command('doctor')
|
|
244
|
+
.description('Overview health check (vault + integration summaries with soft upsells)')
|
|
245
|
+
.action(async () => {
|
|
246
|
+
const os = await import('node:os');
|
|
247
|
+
const configDir = getConfigDir();
|
|
248
|
+
const configPath = path.join(configDir, 'config.yaml');
|
|
249
|
+
const openclawStateDir = process.env['OPENCLAW_STATE_DIR'] || path.join(os.homedir(), '.openclaw');
|
|
250
|
+
let issues = 0;
|
|
251
|
+
console.log('');
|
|
252
|
+
console.log(` \u{1F531} Aquaman ${VERSION} \u2014 health check`);
|
|
253
|
+
console.log('');
|
|
254
|
+
console.log(` ${aqua('Vault')}`);
|
|
255
|
+
// Vault \u2014 config file
|
|
256
|
+
if (!fs.existsSync(configPath)) {
|
|
257
|
+
console.log(` \u2717 Config missing (${configPath})`);
|
|
258
|
+
console.log(' \u2192 Run: aquaman setup');
|
|
259
|
+
issues++;
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
// Vault \u2014 backend + creds
|
|
263
|
+
try {
|
|
264
|
+
const config = loadConfig();
|
|
265
|
+
const store = await createCredentialStore({
|
|
266
|
+
backend: config.credentials.backend,
|
|
267
|
+
encryptionPassword: config.credentials.encryptionPassword,
|
|
268
|
+
vaultAddress: config.credentials.vaultAddress,
|
|
269
|
+
vaultToken: config.credentials.vaultToken,
|
|
270
|
+
onePasswordVault: config.credentials.onePasswordVault,
|
|
271
|
+
onePasswordAccount: config.credentials.onePasswordAccount,
|
|
272
|
+
keepassxcDatabasePath: config.credentials.keepassxcDatabasePath,
|
|
273
|
+
keepassxcKeyFilePath: config.credentials.keepassxcKeyFilePath,
|
|
274
|
+
bitwardenFolder: config.credentials.bitwardenFolder,
|
|
275
|
+
bitwardenOrganizationId: config.credentials.bitwardenOrganizationId,
|
|
276
|
+
bitwardenCollectionId: config.credentials.bitwardenCollectionId
|
|
277
|
+
});
|
|
278
|
+
const creds = await store.list();
|
|
279
|
+
console.log(` \u2713 ${config.credentials.backend} backend (${creds.length} credential${creds.length !== 1 ? 's' : ''})`);
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
console.log(` \u2717 Backend not accessible: ${err.message}`);
|
|
283
|
+
console.log(' \u2192 Run: aquaman setup');
|
|
284
|
+
issues++;
|
|
285
|
+
}
|
|
286
|
+
// Vault \u2014 proxy running
|
|
287
|
+
const sockPath = path.join(configDir, 'proxy.sock');
|
|
288
|
+
try {
|
|
289
|
+
await new Promise((resolve, reject) => {
|
|
290
|
+
const req = http.request({ socketPath: sockPath, path: '/_health', method: 'GET' }, (res) => {
|
|
291
|
+
res.resume();
|
|
292
|
+
res.on('end', () => res.statusCode === 200 ? resolve() : reject(new Error(`HTTP ${res.statusCode}`)));
|
|
293
|
+
});
|
|
294
|
+
req.on('error', reject);
|
|
295
|
+
req.end();
|
|
296
|
+
});
|
|
297
|
+
console.log(` \u2713 Proxy running on socket`);
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
console.log(` \u2717 Proxy not running`);
|
|
301
|
+
console.log(' \u2192 Run: aquaman daemon &');
|
|
302
|
+
issues++;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// OpenClaw integration
|
|
306
|
+
let openclawDetected = false;
|
|
307
|
+
try {
|
|
308
|
+
const { execSync } = await import('node:child_process');
|
|
309
|
+
execSync('which openclaw', { stdio: 'pipe' });
|
|
310
|
+
openclawDetected = true;
|
|
311
|
+
}
|
|
312
|
+
catch { /* */ }
|
|
313
|
+
if (!openclawDetected)
|
|
314
|
+
openclawDetected = fs.existsSync(openclawStateDir);
|
|
315
|
+
console.log('');
|
|
316
|
+
console.log(` ${aqua('OpenClaw integration')}`);
|
|
317
|
+
if (!openclawDetected) {
|
|
318
|
+
console.log(` \u2022 not detected (skipping \u2014 only relevant if you run an OpenClaw Gateway)`);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
const pluginInstalled = fs.existsSync(path.join(openclawStateDir, 'extensions', 'aquaman-plugin'));
|
|
322
|
+
if (pluginInstalled) {
|
|
323
|
+
console.log(` \u2713 plugin installed (deep: ${aqua('aquaman openclaw doctor')})`);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
console.log(` \u2717 plugin not installed`);
|
|
327
|
+
console.log(' \u2192 Run: aquaman openclaw setup');
|
|
328
|
+
issues++;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Coder integration
|
|
332
|
+
const projectsYaml = path.join(configDir, 'projects.yaml');
|
|
333
|
+
const claudeSettings = path.join(os.homedir(), '.claude', 'settings.json');
|
|
334
|
+
let coderConfigured = fs.existsSync(projectsYaml);
|
|
335
|
+
if (!coderConfigured && fs.existsSync(claudeSettings)) {
|
|
336
|
+
try {
|
|
337
|
+
const raw = fs.readFileSync(claudeSettings, 'utf-8');
|
|
338
|
+
coderConfigured = raw.includes('aquaman-coder hook') || raw.includes('aquaman coder hook');
|
|
339
|
+
}
|
|
340
|
+
catch { /* */ }
|
|
341
|
+
}
|
|
342
|
+
console.log('');
|
|
343
|
+
console.log(` ${aqua('Coder integration')}`);
|
|
344
|
+
if (coderConfigured) {
|
|
345
|
+
console.log(` \u2713 configured (deep: ${aqua('aquaman coder doctor')})`);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
console.log(` \u2022 not configured`);
|
|
349
|
+
console.log(` Your coding agents (Claude Code, Codex, \u2026) could use the same`);
|
|
350
|
+
console.log(` vault protection. Install: ${aqua('npm install -g aquaman-coder')}`);
|
|
351
|
+
}
|
|
352
|
+
console.log('');
|
|
353
|
+
if (issues === 0) {
|
|
354
|
+
console.log(' All baseline checks passed.');
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
console.log(` ${issues} issue${issues > 1 ? 's' : ''} found in baseline. Fix above and re-run.`);
|
|
123
358
|
}
|
|
359
|
+
console.log('');
|
|
360
|
+
process.exitCode = issues > 0 ? 1 : 0;
|
|
124
361
|
});
|
|
125
|
-
//
|
|
362
|
+
// aquaman status \u2014 proxy overview.
|
|
126
363
|
program
|
|
364
|
+
.command('status')
|
|
365
|
+
.description('Proxy daemon status overview')
|
|
366
|
+
.action(async () => {
|
|
367
|
+
const config = loadConfig();
|
|
368
|
+
console.log('');
|
|
369
|
+
console.log(` \u{1F531} ${aqua('Aquaman')} ${VERSION} — status`);
|
|
370
|
+
console.log('');
|
|
371
|
+
// Proxy state (probe socket first — the headline number)
|
|
372
|
+
const sockPath = path.join(getConfigDir(), 'proxy.sock');
|
|
373
|
+
let proxyLine = ` ${aqua('Proxy:')} not running`;
|
|
374
|
+
try {
|
|
375
|
+
const body = await new Promise((resolve, reject) => {
|
|
376
|
+
const req = http.request({ socketPath: sockPath, path: '/_health', method: 'GET' }, (res) => {
|
|
377
|
+
let buf = '';
|
|
378
|
+
res.on('data', (c) => { buf += c; });
|
|
379
|
+
res.on('end', () => resolve(buf));
|
|
380
|
+
});
|
|
381
|
+
req.on('error', reject);
|
|
382
|
+
req.end();
|
|
383
|
+
});
|
|
384
|
+
const health = JSON.parse(body);
|
|
385
|
+
proxyLine = ` ${aqua('Proxy:')} running (v${health.version}, uptime ${Math.floor(health.uptime ?? 0)}s)`;
|
|
386
|
+
}
|
|
387
|
+
catch { /* not running */ }
|
|
388
|
+
console.log(proxyLine);
|
|
389
|
+
// Configuration
|
|
390
|
+
console.log('');
|
|
391
|
+
console.log(` ${aqua('Configuration')}`);
|
|
392
|
+
console.log(` Config dir: ${getConfigDir()}`);
|
|
393
|
+
console.log(` Backend: ${config.credentials.backend}`);
|
|
394
|
+
console.log(` Socket: ${sockPath}`);
|
|
395
|
+
console.log(` Audit: ${config.audit.enabled ? 'enabled' : 'disabled'}`);
|
|
396
|
+
// Proxied services
|
|
397
|
+
console.log('');
|
|
398
|
+
console.log(` ${aqua('Proxied services')} (${config.credentials.proxiedServices.length})`);
|
|
399
|
+
for (const svc of config.credentials.proxiedServices)
|
|
400
|
+
console.log(` - ${svc}`);
|
|
401
|
+
console.log('');
|
|
402
|
+
console.log(` For deeper views: ${aqua('aquaman openclaw status')} / ${aqua('aquaman coder status')}`);
|
|
403
|
+
console.log('');
|
|
404
|
+
});
|
|
405
|
+
// ---------------- Shared helper: vault-only setup ----------------
|
|
406
|
+
async function runVaultSetup(opts) {
|
|
407
|
+
const os = await import('node:os');
|
|
408
|
+
const configDir = getConfigDir();
|
|
409
|
+
const configPath = path.join(configDir, 'config.yaml');
|
|
410
|
+
if (!opts.quiet)
|
|
411
|
+
console.log('\n \u{1F531} Vault setup\n');
|
|
412
|
+
// Backend detection
|
|
413
|
+
const platform = os.platform();
|
|
414
|
+
let backend = opts.backend;
|
|
415
|
+
if (!backend) {
|
|
416
|
+
if (platform === 'darwin') {
|
|
417
|
+
backend = 'keychain';
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
try {
|
|
421
|
+
const { execSync } = await import('node:child_process');
|
|
422
|
+
execSync('pkg-config --exists libsecret-1', { stdio: 'pipe' });
|
|
423
|
+
backend = 'keychain';
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
const { isSystemdCredsAvailable } = await import('../core/credentials/backends/systemd-creds.js');
|
|
427
|
+
backend = isSystemdCredsAvailable() ? 'systemd-creds' : 'encrypted-file';
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const validBackends = ['keychain', 'encrypted-file', 'keepassxc', '1password', 'vault', 'systemd-creds', 'bitwarden'];
|
|
432
|
+
if (!validBackends.includes(backend)) {
|
|
433
|
+
console.error(` Invalid backend: ${backend}. Valid: ${validBackends.join(', ')}`);
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
if (!opts.quiet) {
|
|
437
|
+
const platformLabel = platform === 'darwin' ? 'macOS' : platform === 'linux' ? 'Linux' : platform;
|
|
438
|
+
console.log(` Platform: ${platformLabel}`);
|
|
439
|
+
console.log(` Backend: ${backend}\n`);
|
|
440
|
+
}
|
|
441
|
+
ensureConfigDir();
|
|
442
|
+
let config = getDefaultConfig();
|
|
443
|
+
if (fs.existsSync(configPath)) {
|
|
444
|
+
config = loadConfig();
|
|
445
|
+
}
|
|
446
|
+
config.credentials.backend = backend;
|
|
447
|
+
fs.writeFileSync(configPath, yamlStringify(config), { encoding: 'utf-8', mode: 0o600 });
|
|
448
|
+
const auditDir = path.join(configDir, 'audit');
|
|
449
|
+
fs.mkdirSync(auditDir, { recursive: true, mode: 0o700 });
|
|
450
|
+
let store;
|
|
451
|
+
try {
|
|
452
|
+
store = await createCredentialStore({
|
|
453
|
+
backend: config.credentials.backend,
|
|
454
|
+
encryptionPassword: config.credentials.encryptionPassword || process.env['AQUAMAN_ENCRYPTION_PASSWORD'] || process.env['AQUAMAN_KEEPASS_PASSWORD'],
|
|
455
|
+
vaultAddress: config.credentials.vaultAddress || process.env['VAULT_ADDR'],
|
|
456
|
+
vaultToken: config.credentials.vaultToken || process.env['VAULT_TOKEN'],
|
|
457
|
+
onePasswordVault: config.credentials.onePasswordVault,
|
|
458
|
+
onePasswordAccount: config.credentials.onePasswordAccount,
|
|
459
|
+
keepassxcDatabasePath: config.credentials.keepassxcDatabasePath,
|
|
460
|
+
keepassxcKeyFilePath: config.credentials.keepassxcKeyFilePath,
|
|
461
|
+
bitwardenFolder: config.credentials.bitwardenFolder,
|
|
462
|
+
bitwardenOrganizationId: config.credentials.bitwardenOrganizationId,
|
|
463
|
+
bitwardenCollectionId: config.credentials.bitwardenCollectionId
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
console.error(` Failed to initialize ${backend}: ${err instanceof Error ? err.message : err}`);
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
const storedServices = [];
|
|
471
|
+
if (opts.nonInteractive) {
|
|
472
|
+
const anth = process.env['ANTHROPIC_API_KEY'];
|
|
473
|
+
if (anth) {
|
|
474
|
+
await store.set('anthropic', 'api_key', anth);
|
|
475
|
+
storedServices.push('anthropic');
|
|
476
|
+
console.log(' \u2713 Stored anthropic/api_key');
|
|
477
|
+
}
|
|
478
|
+
const op = process.env['OPENAI_API_KEY'];
|
|
479
|
+
if (op) {
|
|
480
|
+
await store.set('openai', 'api_key', op);
|
|
481
|
+
storedServices.push('openai');
|
|
482
|
+
console.log(' \u2713 Stored openai/api_key');
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
const readline = await import('node:readline');
|
|
487
|
+
const promptSecret = (prompt) => new Promise((resolve) => {
|
|
488
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
489
|
+
if (process.stdin.isTTY) {
|
|
490
|
+
process.stdout.write(prompt);
|
|
491
|
+
const stdin = process.stdin;
|
|
492
|
+
stdin.setRawMode(true);
|
|
493
|
+
stdin.resume();
|
|
494
|
+
let input = '';
|
|
495
|
+
const onData = (data) => {
|
|
496
|
+
const char = data.toString();
|
|
497
|
+
if (char === '\n' || char === '\r') {
|
|
498
|
+
stdin.setRawMode(false);
|
|
499
|
+
stdin.removeListener('data', onData);
|
|
500
|
+
stdin.pause();
|
|
501
|
+
rl.close();
|
|
502
|
+
process.stdout.write('\n');
|
|
503
|
+
resolve(input.trim());
|
|
504
|
+
}
|
|
505
|
+
else if (char === '\x7f' || char === '\b') {
|
|
506
|
+
input = input.slice(0, -1);
|
|
507
|
+
}
|
|
508
|
+
else if (char === '\x03') {
|
|
509
|
+
stdin.setRawMode(false);
|
|
510
|
+
process.exit(0);
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
input += char;
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
stdin.on('data', onData);
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
rl.question(prompt, (a) => { rl.close(); resolve(a.trim()); });
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
const anth = await promptSecret(' ? Anthropic API key (or Enter to skip): ');
|
|
523
|
+
if (anth) {
|
|
524
|
+
await store.set('anthropic', 'api_key', anth);
|
|
525
|
+
storedServices.push('anthropic');
|
|
526
|
+
console.log(' \u2713 anthropic/api_key\n');
|
|
527
|
+
}
|
|
528
|
+
const op = await promptSecret(' ? OpenAI API key (or Enter to skip): ');
|
|
529
|
+
if (op) {
|
|
530
|
+
await store.set('openai', 'api_key', op);
|
|
531
|
+
storedServices.push('openai');
|
|
532
|
+
console.log(' \u2713 openai/api_key\n');
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (opts.policy && storedServices.length > 0) {
|
|
536
|
+
const presets = getDefaultPolicyPresets();
|
|
537
|
+
const policyToApply = {};
|
|
538
|
+
for (const svc of storedServices) {
|
|
539
|
+
if (presets[svc])
|
|
540
|
+
policyToApply[svc] = presets[svc];
|
|
541
|
+
}
|
|
542
|
+
if (Object.keys(policyToApply).length > 0) {
|
|
543
|
+
config.policy = policyToApply;
|
|
544
|
+
saveConfig(config);
|
|
545
|
+
if (!opts.quiet)
|
|
546
|
+
console.log(' \u2713 Default policy presets applied.\n');
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return { store, storedServices, backend: backend };
|
|
550
|
+
}
|
|
551
|
+
// OpenClaw integration namespace \u2014 full bundle setup, deep doctor/status,
|
|
552
|
+
// migration tools, and the (hidden) plugin-mode entry the plugin spawns.
|
|
553
|
+
const openclaw = program
|
|
554
|
+
.command('openclaw')
|
|
555
|
+
.description('OpenClaw Gateway integration (full setup, deep diagnostics, migration)');
|
|
556
|
+
// openclaw start \u2014 launches credential proxy + OpenClaw
|
|
557
|
+
openclaw
|
|
127
558
|
.command('start')
|
|
128
559
|
.description('Start credential proxy and launch OpenClaw')
|
|
129
560
|
.option('-w, --workspace <path>', 'Workspace directory for OpenClaw')
|
|
@@ -375,9 +806,9 @@ program
|
|
|
375
806
|
process.on('SIGTERM', shutdown);
|
|
376
807
|
});
|
|
377
808
|
// Plugin mode command - for use when managed by OpenClaw plugin
|
|
378
|
-
|
|
379
|
-
.command('plugin-mode')
|
|
380
|
-
.description('Run in plugin mode (
|
|
809
|
+
openclaw
|
|
810
|
+
.command('plugin-mode', { hidden: true })
|
|
811
|
+
.description('Run in plugin mode (invoked by OpenClaw plugin, not by humans)')
|
|
381
812
|
.action(async () => {
|
|
382
813
|
const config = loadConfig();
|
|
383
814
|
const socketPath = path.join(getConfigDir(), 'proxy.sock');
|
|
@@ -458,8 +889,8 @@ program
|
|
|
458
889
|
process.on('SIGINT', shutdown);
|
|
459
890
|
process.on('SIGTERM', shutdown);
|
|
460
891
|
});
|
|
461
|
-
//
|
|
462
|
-
|
|
892
|
+
// openclaw configure — write OpenClaw environment configuration
|
|
893
|
+
openclaw
|
|
463
894
|
.command('configure')
|
|
464
895
|
.description('Generate environment configuration for OpenClaw')
|
|
465
896
|
.option('--method <method>', 'Output method: env, dotenv, shell-rc', 'env')
|
|
@@ -524,11 +955,13 @@ program
|
|
|
524
955
|
}
|
|
525
956
|
});
|
|
526
957
|
// Setup command - all-in-one guided onboarding
|
|
527
|
-
|
|
958
|
+
// openclaw setup — full OpenClaw bundle: vault wizard + plugin install +
|
|
959
|
+
// openclaw.json wiring + auth-profiles.json + optional auto-migration.
|
|
960
|
+
openclaw
|
|
528
961
|
.command('setup')
|
|
529
|
-
.description('
|
|
962
|
+
.description('Full setup for OpenClaw — vault + plugin + auth profiles + (optional) auto-migrate')
|
|
530
963
|
.option('--backend <backend>', 'Credential backend (keychain, encrypted-file, keepassxc, 1password, vault, systemd-creds, bitwarden)')
|
|
531
|
-
.option('--no-openclaw', 'Skip OpenClaw plugin installation')
|
|
964
|
+
.option('--no-openclaw', 'Skip OpenClaw plugin installation step (run vault setup only)')
|
|
532
965
|
.option('--no-policy', 'Skip request policy preset configuration')
|
|
533
966
|
.option('--non-interactive', 'Use environment variables instead of prompts (for CI)')
|
|
534
967
|
.action(async (options) => {
|
|
@@ -930,6 +1363,26 @@ program
|
|
|
930
1363
|
fs.mkdirSync(profilesDir, { recursive: true, mode: 0o700 });
|
|
931
1364
|
fs.writeFileSync(profilesPath, JSON.stringify({ version: 1, profiles, order }, null, 2), { mode: 0o600 });
|
|
932
1365
|
console.log(' \u2713 Auth profiles generated at ' + profilesPath);
|
|
1366
|
+
// OpenClaw >= 2026.6.5 reads provider auth profiles from each agent's
|
|
1367
|
+
// openclaw-agent.sqlite, not auth-profiles.json (openclaw/openclaw#89102).
|
|
1368
|
+
// The placeholder above must be imported into SQLite once with
|
|
1369
|
+
// `openclaw doctor --fix`. We deliberately do NOT auto-run that here:
|
|
1370
|
+
// it is OpenClaw's broad config-healing command \u2014 verified to take
|
|
1371
|
+
// ~19s (gateway probes) and to rewrite openclaw.json (it reset our
|
|
1372
|
+
// plugin entry to bundled defaults in testing). `aquaman openclaw
|
|
1373
|
+
// doctor` detects the SQLite-only case and prints the import step so
|
|
1374
|
+
// the operator runs it intentionally. Holistic fix (SecretRef
|
|
1375
|
+
// provider manifest) is tracked for the next plugin touch.
|
|
1376
|
+
let ocVersion = null;
|
|
1377
|
+
try {
|
|
1378
|
+
const { execSync } = await import('node:child_process');
|
|
1379
|
+
ocVersion = execSync('openclaw --version', { stdio: 'pipe', encoding: 'utf-8', timeout: 5000 }).trim();
|
|
1380
|
+
}
|
|
1381
|
+
catch { /* CLI not on PATH */ }
|
|
1382
|
+
if (authProfilesAreSqliteOnly(ocVersion)) {
|
|
1383
|
+
console.log(' \u2192 OpenClaw \u2265 2026.6.5 reads auth profiles from SQLite; import the placeholder once:');
|
|
1384
|
+
console.log(' openclaw doctor --fix (backs up openclaw.json first; see aquaman openclaw doctor)');
|
|
1385
|
+
}
|
|
933
1386
|
}
|
|
934
1387
|
}
|
|
935
1388
|
}
|
|
@@ -983,10 +1436,12 @@ program
|
|
|
983
1436
|
console.log(' Troubleshooting: aquaman doctor');
|
|
984
1437
|
console.log('');
|
|
985
1438
|
});
|
|
986
|
-
//
|
|
987
|
-
|
|
1439
|
+
// openclaw doctor — deep diagnostic for the OpenClaw integration. Includes
|
|
1440
|
+
// the agent-agnostic vault/audit/policy checks too so a single command gives
|
|
1441
|
+
// the OpenClaw operator the full picture.
|
|
1442
|
+
openclaw
|
|
988
1443
|
.command('doctor')
|
|
989
|
-
.description('
|
|
1444
|
+
.description('Deep diagnostic for the OpenClaw integration (vault + plugin + auth profiles)')
|
|
990
1445
|
.action(async () => {
|
|
991
1446
|
const os = await import('node:os');
|
|
992
1447
|
const configDir = getConfigDir();
|
|
@@ -994,7 +1449,7 @@ program
|
|
|
994
1449
|
const openclawStateDir = process.env['OPENCLAW_STATE_DIR'] || path.join(os.homedir(), '.openclaw');
|
|
995
1450
|
let issues = 0;
|
|
996
1451
|
console.log('');
|
|
997
|
-
console.log(` \u{1F531}
|
|
1452
|
+
console.log(` \u{1F531} Aquaman ${VERSION} \u2014 Welcome to the doctor\u2019s office.`);
|
|
998
1453
|
console.log('');
|
|
999
1454
|
// 1. Config file
|
|
1000
1455
|
if (fs.existsSync(configPath)) {
|
|
@@ -1167,6 +1622,7 @@ program
|
|
|
1167
1622
|
}
|
|
1168
1623
|
// 6. OpenClaw detection
|
|
1169
1624
|
let openclawDetected = false;
|
|
1625
|
+
let openclawVersion = null;
|
|
1170
1626
|
let cliFound = false;
|
|
1171
1627
|
try {
|
|
1172
1628
|
const { execSync } = await import('node:child_process');
|
|
@@ -1178,6 +1634,7 @@ program
|
|
|
1178
1634
|
try {
|
|
1179
1635
|
const { execSync } = await import('node:child_process');
|
|
1180
1636
|
const ver = execSync('openclaw --version', { stdio: 'pipe', encoding: 'utf-8', timeout: 5000 }).trim();
|
|
1637
|
+
openclawVersion = ver;
|
|
1181
1638
|
console.log(` \u2713 ${aqua('OpenClaw')} installed (${ver})`);
|
|
1182
1639
|
}
|
|
1183
1640
|
catch {
|
|
@@ -1260,9 +1717,31 @@ program
|
|
|
1260
1717
|
// openclaw.json invalid — already reported above
|
|
1261
1718
|
}
|
|
1262
1719
|
}
|
|
1263
|
-
// 9. Auth profiles
|
|
1264
|
-
|
|
1265
|
-
|
|
1720
|
+
// 9. Auth profiles. OpenClaw >= 2026.6.5 reads provider auth profiles from
|
|
1721
|
+
// each agent's openclaw-agent.sqlite and no longer reads
|
|
1722
|
+
// auth-profiles.json at runtime (openclaw/openclaw#89102). On those
|
|
1723
|
+
// versions the placeholder the plugin writes to the JSON file must be
|
|
1724
|
+
// imported into SQLite once via `openclaw doctor --fix`.
|
|
1725
|
+
const agentDir = path.join(openclawStateDir, 'agents', 'main', 'agent');
|
|
1726
|
+
const profilesPath = path.join(agentDir, 'auth-profiles.json');
|
|
1727
|
+
const sqlitePath = path.join(agentDir, 'openclaw-agent.sqlite');
|
|
1728
|
+
if (authProfilesAreSqliteOnly(openclawVersion)) {
|
|
1729
|
+
if (fs.existsSync(profilesPath)) {
|
|
1730
|
+
console.log(` \u2717 ${aqua('Auth profiles')} JSON present but ${openclawVersion} reads from SQLite`);
|
|
1731
|
+
console.log(' \u2192 Import the placeholder once: openclaw doctor --fix (backs up openclaw.json first)');
|
|
1732
|
+
console.log(' (OpenClaw \u2265 2026.6.5 dropped the runtime auth-profiles.json read path \u2014 openclaw/openclaw#89102)');
|
|
1733
|
+
issues++;
|
|
1734
|
+
}
|
|
1735
|
+
else if (fs.existsSync(sqlitePath)) {
|
|
1736
|
+
console.log(` \u2713 ${aqua('Auth profiles')} (SQLite store present; OpenClaw \u2265 2026.6.5)`);
|
|
1737
|
+
}
|
|
1738
|
+
else {
|
|
1739
|
+
console.log(` \u2717 ${aqua('Auth profiles')} missing (no SQLite store found)`);
|
|
1740
|
+
console.log(' \u2192 Run: aquaman setup, then: openclaw doctor --fix');
|
|
1741
|
+
issues++;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
else if (fs.existsSync(profilesPath)) {
|
|
1266
1745
|
console.log(` \u2713 ${aqua('Auth profiles')} exist`);
|
|
1267
1746
|
}
|
|
1268
1747
|
else {
|
|
@@ -1323,7 +1802,7 @@ program
|
|
|
1323
1802
|
for (const c of unmigrated) {
|
|
1324
1803
|
console.log(` ${c.service}/${c.key} \u2190 ${c.source}`);
|
|
1325
1804
|
}
|
|
1326
|
-
console.log(' \u2192 Run: aquaman migrate
|
|
1805
|
+
console.log(' \u2192 Run: aquaman openclaw migrate --auto');
|
|
1327
1806
|
issues++;
|
|
1328
1807
|
}
|
|
1329
1808
|
if (needsCleanup.length > 0) {
|
|
@@ -1454,38 +1933,64 @@ credentials
|
|
|
1454
1933
|
console.error('Credential store not available:', error instanceof Error ? error.message : error);
|
|
1455
1934
|
process.exit(1);
|
|
1456
1935
|
}
|
|
1457
|
-
//
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
const char = data.toString();
|
|
1471
|
-
if (char === '\n' || char === '\r') {
|
|
1472
|
-
process.stdin.setRawMode(false);
|
|
1473
|
-
rl.close();
|
|
1474
|
-
storeCredential();
|
|
1475
|
-
}
|
|
1476
|
-
else if (char === '\x7f' || char === '\b') {
|
|
1477
|
-
value = value.slice(0, -1);
|
|
1478
|
-
}
|
|
1479
|
-
else {
|
|
1480
|
-
value += char;
|
|
1481
|
-
}
|
|
1482
|
-
});
|
|
1483
|
-
async function storeCredential() {
|
|
1484
|
-
console.log('');
|
|
1485
|
-
await store.set(service, key, value.trim());
|
|
1486
|
-
console.log(`Credential stored: ${service}/${key}`);
|
|
1936
|
+
// Two stdin paths:
|
|
1937
|
+
// - TTY (interactive shell): per-character raw-mode read so the value
|
|
1938
|
+
// never echoes. Submit on Enter, backspace on DEL/^H.
|
|
1939
|
+
// - Pipe (scripts / CI / migrations): read all stdin into one buffer.
|
|
1940
|
+
// Strip exactly ONE trailing newline so `printf x | ...` and
|
|
1941
|
+
// `echo x | ...` both round-trip to `x` without mangling embedded
|
|
1942
|
+
// newlines in PEM keys or JSON blobs.
|
|
1943
|
+
const value = process.stdin.isTTY
|
|
1944
|
+
? await readTtyHiddenInput(`Enter value for ${service}/${key} (input hidden):`)
|
|
1945
|
+
: await readAllStdin();
|
|
1946
|
+
if (value.length === 0) {
|
|
1947
|
+
console.error('Empty credential value rejected. Pipe a value or type one.');
|
|
1948
|
+
process.exit(1);
|
|
1487
1949
|
}
|
|
1950
|
+
await store.set(service, key, value);
|
|
1951
|
+
console.log(`Credential stored: ${service}/${key}`);
|
|
1488
1952
|
});
|
|
1953
|
+
async function readAllStdin() {
|
|
1954
|
+
const chunks = [];
|
|
1955
|
+
for await (const chunk of process.stdin) {
|
|
1956
|
+
chunks.push(chunk);
|
|
1957
|
+
}
|
|
1958
|
+
const raw = Buffer.concat(chunks).toString('utf-8');
|
|
1959
|
+
// Strip a single trailing \n (and the optional \r before it for CRLF).
|
|
1960
|
+
return raw.endsWith('\n') ? raw.replace(/\r?\n$/, '') : raw;
|
|
1961
|
+
}
|
|
1962
|
+
async function readTtyHiddenInput(prompt) {
|
|
1963
|
+
console.log(prompt);
|
|
1964
|
+
process.stdin.setRawMode(true);
|
|
1965
|
+
process.stdin.resume();
|
|
1966
|
+
process.stdin.setEncoding('utf-8');
|
|
1967
|
+
return new Promise((resolve) => {
|
|
1968
|
+
let value = '';
|
|
1969
|
+
const onData = (chunk) => {
|
|
1970
|
+
for (const char of chunk) {
|
|
1971
|
+
if (char === '\n' || char === '\r') {
|
|
1972
|
+
process.stdin.setRawMode(false);
|
|
1973
|
+
process.stdin.pause();
|
|
1974
|
+
process.stdin.off('data', onData);
|
|
1975
|
+
console.log('');
|
|
1976
|
+
resolve(value);
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
if (char === '\x7f' || char === '\b') {
|
|
1980
|
+
value = value.slice(0, -1);
|
|
1981
|
+
}
|
|
1982
|
+
else if (char === '\x03') { // Ctrl-C
|
|
1983
|
+
process.stdin.setRawMode(false);
|
|
1984
|
+
process.exit(130);
|
|
1985
|
+
}
|
|
1986
|
+
else {
|
|
1987
|
+
value += char;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
process.stdin.on('data', onData);
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1489
1994
|
credentials
|
|
1490
1995
|
.command('list')
|
|
1491
1996
|
.description('List stored credentials')
|
|
@@ -1600,7 +2105,10 @@ credentials
|
|
|
1600
2105
|
console.log(` vault kv put secret/aquaman/${name}/${key} credential="YOUR_KEY"`);
|
|
1601
2106
|
break;
|
|
1602
2107
|
case '1password':
|
|
1603
|
-
|
|
2108
|
+
// Prefer aquaman's own CLI — it pipes the value via a 0o600 temp
|
|
2109
|
+
// template file, never exposing the secret on argv or in op's
|
|
2110
|
+
// /proc/<pid>/cmdline.
|
|
2111
|
+
console.log(` aquaman credentials add ${name} ${key}`);
|
|
1604
2112
|
break;
|
|
1605
2113
|
default:
|
|
1606
2114
|
console.log(` aquaman credentials add ${name} ${key}`);
|
|
@@ -1718,9 +2226,8 @@ policy
|
|
|
1718
2226
|
}
|
|
1719
2227
|
});
|
|
1720
2228
|
// Migration commands
|
|
1721
|
-
|
|
1722
|
-
migrate
|
|
1723
|
-
.command('openclaw')
|
|
2229
|
+
openclaw
|
|
2230
|
+
.command('migrate')
|
|
1724
2231
|
.description('Migrate channel credentials from openclaw.json into aquaman')
|
|
1725
2232
|
.option('-c, --config <path>', 'Path to openclaw.json')
|
|
1726
2233
|
.option('--dry-run', 'Show what would be migrated without writing')
|
|
@@ -1741,7 +2248,7 @@ migrate
|
|
|
1741
2248
|
console.error('No aquaman config found. Run `aquaman setup` first.');
|
|
1742
2249
|
process.exit(1);
|
|
1743
2250
|
}
|
|
1744
|
-
console.log(`\n \u{1F531}
|
|
2251
|
+
console.log(`\n \u{1F531} Aquaman ${aqua(VERSION)} \u2014 Time to put your secrets somewhere safe.\n`);
|
|
1745
2252
|
console.log(' Scanning for plaintext credentials...\n');
|
|
1746
2253
|
const openclawStateDir = process.env['OPENCLAW_STATE_DIR'] || path.join(os.homedir(), '.openclaw');
|
|
1747
2254
|
const openclawConfigPath = findOpenClawConfig(opts.config || path.join(openclawStateDir, 'openclaw.json'));
|
|
@@ -2109,9 +2616,10 @@ migrate
|
|
|
2109
2616
|
}
|
|
2110
2617
|
});
|
|
2111
2618
|
// Status command
|
|
2112
|
-
|
|
2619
|
+
// openclaw status — deep status for the OpenClaw integration.
|
|
2620
|
+
openclaw
|
|
2113
2621
|
.command('status')
|
|
2114
|
-
.description('
|
|
2622
|
+
.description('OpenClaw-specific status (plugin lifecycle, sentinel env vars)')
|
|
2115
2623
|
.action(async () => {
|
|
2116
2624
|
const config = loadConfig();
|
|
2117
2625
|
console.log('aquaman status\n');
|
|
@@ -2187,6 +2695,47 @@ function formatEntry(entry) {
|
|
|
2187
2695
|
return JSON.stringify(entry.data).slice(0, 80);
|
|
2188
2696
|
}
|
|
2189
2697
|
}
|
|
2698
|
+
// ---------------- coder namespace (shim → aquaman-coder bin) ----------------
|
|
2699
|
+
//
|
|
2700
|
+
// The `aquaman coder *` namespace presents a unified user-facing surface for
|
|
2701
|
+
// the coding-agent adapter. The actual implementation lives in the separate
|
|
2702
|
+
// `aquaman-coder` package (see packages/coder/). This shim execs that binary
|
|
2703
|
+
// with the remaining argv, so the proxy CLI never imports coder code — the
|
|
2704
|
+
// `proxy → coder ✗` boundary in docs/PACKAGES.md stays intact.
|
|
2705
|
+
//
|
|
2706
|
+
// If aquaman-coder isn't installed, we print a clear install hint.
|
|
2707
|
+
// Intercept `aquaman coder ...` BEFORE Commander parses it, so the catch-all
|
|
2708
|
+
// behavior is exact: every token after `coder` flows through to the
|
|
2709
|
+
// aquaman-coder binary verbatim, including --flags Commander would otherwise
|
|
2710
|
+
// interpret as unknown options.
|
|
2711
|
+
//
|
|
2712
|
+
// Only intercept when `coder` is the first command token (argv[2]). This
|
|
2713
|
+
// avoids false positives like `aquaman policy test coder/api /foo` where
|
|
2714
|
+
// `coder` appears as a positional argument elsewhere.
|
|
2715
|
+
if (process.argv[2] === 'coder') {
|
|
2716
|
+
const subArgs = process.argv.slice(3);
|
|
2717
|
+
const { spawnSync } = await import('node:child_process');
|
|
2718
|
+
const result = spawnSync('aquaman-coder', subArgs, { stdio: 'inherit' });
|
|
2719
|
+
if (result.error && result.error.code === 'ENOENT') {
|
|
2720
|
+
console.error('aquaman-coder is not installed.');
|
|
2721
|
+
console.error('Install it with: npm install -g aquaman-coder');
|
|
2722
|
+
process.exit(127);
|
|
2723
|
+
}
|
|
2724
|
+
if (result.error) {
|
|
2725
|
+
console.error(`Failed to run aquaman-coder: ${result.error.message}`);
|
|
2726
|
+
process.exit(1);
|
|
2727
|
+
}
|
|
2728
|
+
process.exit(result.status ?? 0);
|
|
2729
|
+
}
|
|
2730
|
+
// Register the `coder` namespace so it shows up in `aquaman --help`.
|
|
2731
|
+
// Its action is unreachable (the intercept above bypasses Commander entirely),
|
|
2732
|
+
// but documentation matters.
|
|
2733
|
+
program
|
|
2734
|
+
.command('coder')
|
|
2735
|
+
.description('AI coding-agent integration (delegates to `aquaman-coder`)')
|
|
2736
|
+
.allowUnknownOption()
|
|
2737
|
+
.helpOption(false)
|
|
2738
|
+
.action(() => { });
|
|
2190
2739
|
// Show help when run without arguments (like openclaw does)
|
|
2191
2740
|
if (process.argv.length <= 2) {
|
|
2192
2741
|
program.help();
|