@moltbankhq/openclaw 0.1.8 → 0.1.10
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/bin/moltbank.mjs +4 -3
- package/cli.ts +21 -3
- package/index.ts +468 -137
- package/package.json +1 -1
package/bin/moltbank.mjs
CHANGED
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { spawnSync } from 'child_process';
|
|
5
5
|
import { dirname, resolve } from 'path';
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
7
7
|
|
|
8
8
|
const binDir = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
const cliPath = resolve(binDir, '../cli.ts');
|
|
10
10
|
const require = createRequire(import.meta.url);
|
|
11
11
|
const tsxImportPath = require.resolve('tsx/esm');
|
|
12
|
+
const tsxImportUrl = pathToFileURL(tsxImportPath).href;
|
|
12
13
|
|
|
13
|
-
const result = spawnSync(process.execPath, ['--import',
|
|
14
|
+
const result = spawnSync(process.execPath, ['--import', tsxImportUrl, cliPath, ...process.argv.slice(2)], {
|
|
14
15
|
stdio: 'inherit',
|
|
15
16
|
env: process.env
|
|
16
17
|
});
|
|
@@ -20,4 +21,4 @@ if (result.error) {
|
|
|
20
21
|
process.exit(1);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
process.exit(result.status ?? 1);
|
|
24
|
+
process.exit(result.status ?? 1);
|
package/cli.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createSetupCommandLogger,
|
|
6
6
|
ensureMcporterConfig,
|
|
7
7
|
getAppBaseUrl,
|
|
8
|
+
getSetupGatewayRestartEnabled,
|
|
8
9
|
getSkillDir,
|
|
9
10
|
injectSandboxEnv,
|
|
10
11
|
printAuthStatus,
|
|
@@ -23,6 +24,7 @@ type ParsedArgs = {
|
|
|
23
24
|
help: boolean;
|
|
24
25
|
version: boolean;
|
|
25
26
|
blocking: boolean;
|
|
27
|
+
restartGateway: boolean | null;
|
|
26
28
|
verbose: boolean;
|
|
27
29
|
};
|
|
28
30
|
|
|
@@ -44,6 +46,8 @@ Options:
|
|
|
44
46
|
--app-base-url <url> Override MoltBank deployment URL
|
|
45
47
|
--skill-name <name> Override skill folder name
|
|
46
48
|
--blocking Compatibility flag; setup already waits for approval
|
|
49
|
+
--restart-gateway Force gateway restart after successful host setup
|
|
50
|
+
--no-restart-gateway Skip gateway restart after successful host setup
|
|
47
51
|
--verbose Show detailed setup logs
|
|
48
52
|
-h, --help Show help
|
|
49
53
|
-v, --version Show package version
|
|
@@ -67,6 +71,7 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
67
71
|
let help = false;
|
|
68
72
|
let version = false;
|
|
69
73
|
let blocking = false;
|
|
74
|
+
let restartGateway: boolean | null = null;
|
|
70
75
|
let verbose = false;
|
|
71
76
|
|
|
72
77
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -87,6 +92,16 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
87
92
|
continue;
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
if (arg === '--restart-gateway') {
|
|
96
|
+
restartGateway = true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (arg === '--no-restart-gateway') {
|
|
101
|
+
restartGateway = false;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
90
105
|
if (arg === '--verbose') {
|
|
91
106
|
verbose = true;
|
|
92
107
|
continue;
|
|
@@ -135,6 +150,7 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
135
150
|
help,
|
|
136
151
|
version,
|
|
137
152
|
blocking,
|
|
153
|
+
restartGateway,
|
|
138
154
|
verbose
|
|
139
155
|
};
|
|
140
156
|
}
|
|
@@ -144,9 +160,10 @@ async function runCommand(parsed: ParsedArgs): Promise<void> {
|
|
|
144
160
|
|
|
145
161
|
switch (parsed.command) {
|
|
146
162
|
case 'setup': {
|
|
163
|
+
const restartGatewayOnSuccess = parsed.restartGateway ?? getSetupGatewayRestartEnabled(process.platform === 'win32');
|
|
147
164
|
const logger = createSetupCommandLogger({ verbose: parsed.verbose, statusCommand: 'moltbank status' });
|
|
148
165
|
try {
|
|
149
|
-
await runSetup(cfg, logger, { authWaitMode: 'blocking' });
|
|
166
|
+
await runSetup(cfg, logger, { authWaitMode: 'blocking', restartGatewayOnSuccess });
|
|
150
167
|
} finally {
|
|
151
168
|
logger.finish();
|
|
152
169
|
}
|
|
@@ -154,9 +171,10 @@ async function runCommand(parsed: ParsedArgs): Promise<void> {
|
|
|
154
171
|
}
|
|
155
172
|
|
|
156
173
|
case 'setup-blocking': {
|
|
174
|
+
const restartGatewayOnSuccess = parsed.restartGateway ?? getSetupGatewayRestartEnabled(process.platform === 'win32');
|
|
157
175
|
const logger = createSetupCommandLogger({ verbose: parsed.verbose, statusCommand: 'moltbank status' });
|
|
158
176
|
try {
|
|
159
|
-
await runSetup(cfg, logger, { authWaitMode: 'blocking' });
|
|
177
|
+
await runSetup(cfg, logger, { authWaitMode: 'blocking', restartGatewayOnSuccess });
|
|
160
178
|
} finally {
|
|
161
179
|
logger.finish();
|
|
162
180
|
}
|
|
@@ -223,4 +241,4 @@ main().catch((error: unknown) => {
|
|
|
223
241
|
const message = error instanceof Error ? error.message : String(error);
|
|
224
242
|
console.error(`[moltbank] ${message}`);
|
|
225
243
|
process.exit(1);
|
|
226
|
-
});
|
|
244
|
+
});
|
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { execSync, spawn
|
|
2
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
|
1
|
+
import { execSync, spawn } from 'child_process';
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
const IS_WIN = process.platform === 'win32';
|
|
@@ -306,33 +306,68 @@ export function getSetupAuthWaitMode(defaultMode: AuthWaitMode): AuthWaitMode {
|
|
|
306
306
|
return defaultMode;
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
function parseBooleanEnv(raw: string): boolean | null {
|
|
310
|
+
const normalized = raw.trim().toLowerCase();
|
|
311
|
+
if (!normalized) return null;
|
|
312
|
+
|
|
313
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
|
|
314
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function getSetupGatewayRestartEnabled(defaultEnabled: boolean): boolean {
|
|
319
|
+
const parsed = parseBooleanEnv(asString(process.env.MOLTBANK_SETUP_RESTART_GATEWAY));
|
|
320
|
+
return parsed === null ? defaultEnabled : parsed;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function getExecOutputText(error: unknown, field: 'stderr' | 'stdout'): string {
|
|
324
|
+
if (!isRecord(error) || !(field in error)) return '';
|
|
325
|
+
|
|
326
|
+
const raw = (error as { stderr?: unknown; stdout?: unknown })[field];
|
|
327
|
+
if (typeof raw === 'string') return raw.trim();
|
|
328
|
+
if (Buffer.isBuffer(raw)) return raw.toString('utf8').trim();
|
|
329
|
+
|
|
330
|
+
if (isRecord(raw) && 'toString' in raw && typeof (raw as { toString?: unknown }).toString === 'function') {
|
|
331
|
+
const text = (raw as { toString: () => string }).toString().trim();
|
|
332
|
+
if (text && text !== '[object Object]') return text;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return '';
|
|
336
|
+
}
|
|
337
|
+
|
|
309
338
|
function getExecErrorMessage(error: unknown): string {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
339
|
+
const stderr = getExecOutputText(error, 'stderr');
|
|
340
|
+
if (stderr) return stderr;
|
|
341
|
+
|
|
342
|
+
const stdout = getExecOutputText(error, 'stdout');
|
|
343
|
+
if (stdout) return stdout;
|
|
344
|
+
|
|
345
|
+
if (isRecord(error) && 'message' in error) {
|
|
346
|
+
const message = asString((error as { message?: unknown }).message).trim();
|
|
347
|
+
if (message) return message;
|
|
318
348
|
}
|
|
349
|
+
|
|
319
350
|
return String(error);
|
|
320
351
|
}
|
|
321
352
|
|
|
322
|
-
function run(
|
|
353
|
+
function run(
|
|
354
|
+
cmd: string,
|
|
355
|
+
opts: { cwd?: string; silent?: boolean; env?: Record<string, string | undefined>; timeoutMs?: number } = {}
|
|
356
|
+
) {
|
|
323
357
|
try {
|
|
324
358
|
const mergedEnv: NodeJS.ProcessEnv = { ...process.env, ...(opts.env ?? {}) };
|
|
325
359
|
const out = execSync(cmd, {
|
|
326
360
|
cwd: opts.cwd,
|
|
327
361
|
stdio: opts.silent ? 'pipe' : 'inherit',
|
|
328
362
|
env: mergedEnv,
|
|
363
|
+
timeout: opts.timeoutMs,
|
|
329
364
|
shell: IS_WIN ? (process.env.ComSpec ?? 'cmd.exe') : '/bin/bash'
|
|
330
365
|
});
|
|
331
366
|
return { ok: true, stdout: out?.toString().trim() ?? '' };
|
|
332
367
|
} catch (e: unknown) {
|
|
333
368
|
return {
|
|
334
369
|
ok: false,
|
|
335
|
-
stdout: '',
|
|
370
|
+
stdout: getExecOutputText(e, 'stdout'),
|
|
336
371
|
stderr: getExecErrorMessage(e)
|
|
337
372
|
};
|
|
338
373
|
}
|
|
@@ -342,6 +377,171 @@ function hasBin(bin: string) {
|
|
|
342
377
|
return run(IS_WIN ? `where ${bin}` : `which ${bin}`, { silent: true }).ok;
|
|
343
378
|
}
|
|
344
379
|
|
|
380
|
+
function getJqInstallHint(): string {
|
|
381
|
+
if (hasBin('apt-get')) return '`sudo apt-get install -y jq`';
|
|
382
|
+
if (hasBin('dnf')) return '`sudo dnf install -y jq`';
|
|
383
|
+
if (hasBin('yum')) return '`sudo yum install -y jq`';
|
|
384
|
+
if (hasBin('pacman')) return '`sudo pacman -S --noconfirm jq`';
|
|
385
|
+
if (hasBin('apk')) return '`sudo apk add jq`';
|
|
386
|
+
if (hasBin('zypper')) return '`sudo zypper install -y jq`';
|
|
387
|
+
if (hasBin('brew')) return '`brew install jq`';
|
|
388
|
+
return 'using your distro package manager';
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function splitNonEmptyLines(text: string): string[] {
|
|
392
|
+
return (text || '')
|
|
393
|
+
.split(/\r?\n/)
|
|
394
|
+
.map((line) => line.trim())
|
|
395
|
+
.filter(Boolean);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function getNpmGlobalBinDir(): string {
|
|
399
|
+
const prefix = run('npm config get prefix', { silent: true });
|
|
400
|
+
if (!prefix.ok) return '';
|
|
401
|
+
|
|
402
|
+
const value = prefix.stdout.trim();
|
|
403
|
+
if (!value || value === 'undefined' || value === 'null') return '';
|
|
404
|
+
return IS_WIN ? value : join(value, 'bin');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function findFirstMatchingFile(rootDir: string, predicate: (name: string) => boolean, maxDepth = 3): string {
|
|
408
|
+
if (!rootDir || !existsSync(rootDir)) return '';
|
|
409
|
+
|
|
410
|
+
const queue: Array<{ dir: string; depth: number }> = [{ dir: rootDir, depth: 0 }];
|
|
411
|
+
while (queue.length) {
|
|
412
|
+
const current = queue.shift();
|
|
413
|
+
if (!current) break;
|
|
414
|
+
|
|
415
|
+
let entries: ReturnType<typeof readdirSync>;
|
|
416
|
+
try {
|
|
417
|
+
entries = readdirSync(current.dir, { withFileTypes: true });
|
|
418
|
+
} catch {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
for (const entry of entries) {
|
|
423
|
+
const fullPath = join(current.dir, entry.name);
|
|
424
|
+
if (entry.isFile() && predicate(entry.name)) {
|
|
425
|
+
return fullPath;
|
|
426
|
+
}
|
|
427
|
+
if (entry.isDirectory() && current.depth < maxDepth) {
|
|
428
|
+
queue.push({ dir: fullPath, depth: current.depth + 1 });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return '';
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function findWindowsJqBinaryPath(): string {
|
|
437
|
+
if (!IS_WIN) return '';
|
|
438
|
+
|
|
439
|
+
const fromEnv = asString(process.env.JQ_PATH).trim();
|
|
440
|
+
if (fromEnv && existsSync(fromEnv)) return fromEnv;
|
|
441
|
+
|
|
442
|
+
const whereJq = run('where jq', { silent: true });
|
|
443
|
+
if (whereJq.ok) {
|
|
444
|
+
for (const line of splitNonEmptyLines(whereJq.stdout)) {
|
|
445
|
+
if (existsSync(line)) {
|
|
446
|
+
return line;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const localAppData = asString(process.env.LOCALAPPDATA).trim();
|
|
452
|
+
const appData = asString(process.env.APPDATA).trim();
|
|
453
|
+
const userProfile = asString(process.env.USERPROFILE).trim();
|
|
454
|
+
const programData = asString(process.env.ProgramData).trim();
|
|
455
|
+
const programFiles = asString(process.env.ProgramFiles).trim();
|
|
456
|
+
const chocolateyInstall = asString(process.env.ChocolateyInstall).trim();
|
|
457
|
+
|
|
458
|
+
const candidates = [
|
|
459
|
+
localAppData ? join(localAppData, 'Microsoft', 'WinGet', 'Links', 'jq.exe') : '',
|
|
460
|
+
localAppData ? join(localAppData, 'Microsoft', 'WindowsApps', 'jq.exe') : '',
|
|
461
|
+
localAppData ? join(localAppData, 'Programs', 'jq', 'jq.exe') : '',
|
|
462
|
+
localAppData ? join(localAppData, 'Programs', 'jqlang', 'jq.exe') : '',
|
|
463
|
+
appData ? join(appData, 'npm', 'jq.exe') : '',
|
|
464
|
+
appData ? join(appData, 'npm', 'jq.cmd') : '',
|
|
465
|
+
userProfile ? join(userProfile, 'scoop', 'shims', 'jq.exe') : '',
|
|
466
|
+
chocolateyInstall ? join(chocolateyInstall, 'bin', 'jq.exe') : '',
|
|
467
|
+
programData ? join(programData, 'chocolatey', 'bin', 'jq.exe') : '',
|
|
468
|
+
programFiles ? join(programFiles, 'jqlang', 'jq.exe') : '',
|
|
469
|
+
programFiles ? join(programFiles, 'jq', 'jq.exe') : ''
|
|
470
|
+
].filter(Boolean);
|
|
471
|
+
|
|
472
|
+
for (const candidate of candidates) {
|
|
473
|
+
if (existsSync(candidate)) {
|
|
474
|
+
return candidate;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const isJqExecutable = (name: string): boolean => {
|
|
479
|
+
const lower = name.toLowerCase();
|
|
480
|
+
return lower === 'jq.exe' || lower === 'jq-windows-amd64.exe' || (lower.startsWith('jq') && lower.endsWith('.exe'));
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const wingetPackagesRoot = localAppData ? join(localAppData, 'Microsoft', 'WinGet', 'Packages') : '';
|
|
484
|
+
if (wingetPackagesRoot && existsSync(wingetPackagesRoot)) {
|
|
485
|
+
let packageEntries: ReturnType<typeof readdirSync> = [];
|
|
486
|
+
try {
|
|
487
|
+
packageEntries = readdirSync(wingetPackagesRoot, { withFileTypes: true });
|
|
488
|
+
} catch {
|
|
489
|
+
packageEntries = [];
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
for (const entry of packageEntries) {
|
|
493
|
+
if (!entry.isDirectory()) continue;
|
|
494
|
+
const lower = entry.name.toLowerCase();
|
|
495
|
+
if (!lower.includes('jqlang.jq') && !lower.startsWith('jq')) continue;
|
|
496
|
+
|
|
497
|
+
const found = findFirstMatchingFile(join(wingetPackagesRoot, entry.name), isJqExecutable, 3);
|
|
498
|
+
if (found) return found;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const fallback = findFirstMatchingFile(wingetPackagesRoot, isJqExecutable, 2);
|
|
502
|
+
if (fallback) return fallback;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return '';
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function ensureWindowsJqShim(api: LoggerApi): boolean {
|
|
509
|
+
if (!IS_WIN) return false;
|
|
510
|
+
|
|
511
|
+
const jqSourcePath = findWindowsJqBinaryPath();
|
|
512
|
+
if (!jqSourcePath) return false;
|
|
513
|
+
|
|
514
|
+
const npmBinDir = getNpmGlobalBinDir();
|
|
515
|
+
if (!npmBinDir) return false;
|
|
516
|
+
|
|
517
|
+
const shimPath = join(npmBinDir, 'jq.cmd');
|
|
518
|
+
try {
|
|
519
|
+
mkdirSync(npmBinDir, { recursive: true });
|
|
520
|
+
if (jqSourcePath.toLowerCase() !== shimPath.toLowerCase()) {
|
|
521
|
+
const escapedSource = jqSourcePath.replace(/"/g, '""');
|
|
522
|
+
const shimBody = `@echo off\r\n"${escapedSource}" %*\r\n`;
|
|
523
|
+
writeFileSync(shimPath, shimBody, 'utf8');
|
|
524
|
+
}
|
|
525
|
+
} catch (e) {
|
|
526
|
+
api.logger.warn('[moltbank] could not create jq shim in npm global bin: ' + String(e));
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (!hasBin('jq')) return false;
|
|
531
|
+
|
|
532
|
+
const v = run('jq --version', { silent: true });
|
|
533
|
+
api.logger.info(`[moltbank] ✓ jq available via npm global bin shim (${v.stdout || 'unknown version'})`);
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function lastNonEmptyLine(text: string): string {
|
|
538
|
+
const lines = text
|
|
539
|
+
.split(/\r?\n/)
|
|
540
|
+
.map((line) => line.trim())
|
|
541
|
+
.filter(Boolean);
|
|
542
|
+
return lines.length ? lines[lines.length - 1] : '';
|
|
543
|
+
}
|
|
544
|
+
|
|
345
545
|
function getWorkspace(): string {
|
|
346
546
|
return process.env.OPENCLAW_WORKSPACE || join(homedir(), '.openclaw', 'workspace');
|
|
347
547
|
}
|
|
@@ -478,6 +678,105 @@ function ensureMcporter(api: LoggerApi) {
|
|
|
478
678
|
}
|
|
479
679
|
}
|
|
480
680
|
|
|
681
|
+
function ensureJq(api: LoggerApi) {
|
|
682
|
+
if (hasBin('jq')) {
|
|
683
|
+
const v = run('jq --version', { silent: true });
|
|
684
|
+
api.logger.info(`[moltbank] ✓ jq already installed (${v.stdout || 'unknown version'})`);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (!IS_WIN) {
|
|
689
|
+
api.logger.warn('[moltbank] ✗ jq not found in PATH (required by MoltBank wrapper on host mode)');
|
|
690
|
+
api.logger.warn(`[moltbank] install jq (${getJqInstallHint()}) and rerun setup`);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (ensureWindowsJqShim(api)) {
|
|
695
|
+
api.logger.info('[moltbank] ✓ jq found on disk and shimmed into current PATH scope');
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
api.logger.info('[moltbank] jq not found — attempting Windows install...');
|
|
700
|
+
|
|
701
|
+
const installers: Array<{ name: string; checkCmd: string; installCmd: string }> = [
|
|
702
|
+
{
|
|
703
|
+
name: 'winget',
|
|
704
|
+
checkCmd: 'where winget',
|
|
705
|
+
installCmd: 'winget install -e --id jqlang.jq --accept-package-agreements --accept-source-agreements --silent'
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
name: 'scoop',
|
|
709
|
+
checkCmd: 'where scoop',
|
|
710
|
+
installCmd: 'scoop install jq'
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
name: 'choco',
|
|
714
|
+
checkCmd: 'where choco',
|
|
715
|
+
installCmd: 'choco install jq -y'
|
|
716
|
+
}
|
|
717
|
+
];
|
|
718
|
+
|
|
719
|
+
for (const installer of installers) {
|
|
720
|
+
if (!run(installer.checkCmd, { silent: true }).ok) continue;
|
|
721
|
+
|
|
722
|
+
api.logger.info(`[moltbank] trying jq install via ${installer.name}...`);
|
|
723
|
+
const install = run(installer.installCmd, { timeoutMs: 180000 });
|
|
724
|
+
const installOutput = `${install.stdout}\n${install.stderr}`;
|
|
725
|
+
const installOutputLower = installOutput.toLowerCase();
|
|
726
|
+
const alreadyInstalledOrNoUpdate =
|
|
727
|
+
installOutputLower.includes('already installed') ||
|
|
728
|
+
installOutputLower.includes('paquete existente ya instalado') ||
|
|
729
|
+
installOutputLower.includes('no update available') ||
|
|
730
|
+
installOutputLower.includes('no updates available') ||
|
|
731
|
+
installOutputLower.includes('no newer package versions are available') ||
|
|
732
|
+
installOutputLower.includes('no se ha encontrado ninguna actualización disponible') ||
|
|
733
|
+
installOutputLower.includes('no hay versiones más recientes');
|
|
734
|
+
|
|
735
|
+
if (!install.ok) {
|
|
736
|
+
if (!alreadyInstalledOrNoUpdate) {
|
|
737
|
+
api.logger.warn(`[moltbank] jq install via ${installer.name} failed: ${install.stderr}`);
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
api.logger.info(`[moltbank] jq already installed via ${installer.name}; validating availability...`);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (hasBin('jq')) {
|
|
744
|
+
const v = run('jq --version', { silent: true });
|
|
745
|
+
api.logger.info(`[moltbank] ✓ jq installed via ${installer.name} (${v.stdout || 'unknown version'})`);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (ensureWindowsJqShim(api)) {
|
|
750
|
+
api.logger.info(`[moltbank] ✓ jq installed via ${installer.name} and made available without shell restart`);
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (alreadyInstalledOrNoUpdate) {
|
|
755
|
+
api.logger.warn(`[moltbank] jq appears installed via ${installer.name}, but is not visible in the current PATH`);
|
|
756
|
+
api.logger.warn('[moltbank] close and reopen PowerShell, then run: `where jq`');
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const installOutputLowerCheck = installOutputLower;
|
|
761
|
+
const shellRestartNeeded =
|
|
762
|
+
installOutputLowerCheck.includes('variable de entorno path modificada') ||
|
|
763
|
+
installOutputLowerCheck.includes('path modificada') ||
|
|
764
|
+
installOutputLowerCheck.includes('path modified') ||
|
|
765
|
+
installOutputLowerCheck.includes('reinicie el shell') ||
|
|
766
|
+
installOutputLowerCheck.includes('restart the shell') ||
|
|
767
|
+
installOutputLowerCheck.includes('restart your shell');
|
|
768
|
+
|
|
769
|
+
if (shellRestartNeeded) {
|
|
770
|
+
api.logger.warn(`[moltbank] jq was installed via ${installer.name}, but this terminal has a stale PATH`);
|
|
771
|
+
api.logger.warn('[moltbank] close and reopen PowerShell, then run: `jq --version`');
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
api.logger.warn('[moltbank] ✗ jq is still missing after auto-install attempts');
|
|
777
|
+
api.logger.warn('[moltbank] install manually on Windows: winget install jqlang.jq');
|
|
778
|
+
}
|
|
779
|
+
|
|
481
780
|
function ensureWrapperExecutable(skillDir: string, api: LoggerApi): void {
|
|
482
781
|
if (IS_WIN) return;
|
|
483
782
|
const wrapperPath = join(skillDir, 'scripts', 'moltbank.sh');
|
|
@@ -699,6 +998,31 @@ function isMoltBankRegistered(): boolean {
|
|
|
699
998
|
return result.stdout.toLowerCase().includes('moltbank');
|
|
700
999
|
}
|
|
701
1000
|
|
|
1001
|
+
function removeHomeScopedMoltBankConfig(api: LoggerApi): boolean {
|
|
1002
|
+
const configPath = join(homedir(), '.mcporter', 'mcporter.json');
|
|
1003
|
+
if (!existsSync(configPath)) {
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
try {
|
|
1008
|
+
const parsed = JSON.parse(readFileSync(configPath, 'utf8')) as unknown;
|
|
1009
|
+
if (!isRecord(parsed)) return false;
|
|
1010
|
+
|
|
1011
|
+
const mcpServers = parsed.mcpServers;
|
|
1012
|
+
if (!isRecord(mcpServers) || !('MoltBank' in mcpServers)) {
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
delete mcpServers.MoltBank;
|
|
1017
|
+
writeFileSync(configPath, JSON.stringify(parsed, null, 2) + '\n', 'utf8');
|
|
1018
|
+
api.logger.info('[moltbank] ✓ removed stale home-scoped mcporter MoltBank config');
|
|
1019
|
+
return true;
|
|
1020
|
+
} catch (e) {
|
|
1021
|
+
api.logger.warn('[moltbank] could not clean home-scoped mcporter MoltBank config: ' + String(e));
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
702
1026
|
function parseActiveTokenFromCredentials(): {
|
|
703
1027
|
ok: boolean;
|
|
704
1028
|
activeOrg?: string;
|
|
@@ -763,84 +1087,6 @@ function readPendingOauthCode(skillDir: string): {
|
|
|
763
1087
|
}
|
|
764
1088
|
}
|
|
765
1089
|
|
|
766
|
-
function clearPendingOauthCode(skillDir: string): void {
|
|
767
|
-
const pendingPath = join(skillDir, '.oauth_device_code.json');
|
|
768
|
-
try {
|
|
769
|
-
if (existsSync(pendingPath)) unlinkSync(pendingPath);
|
|
770
|
-
} catch {
|
|
771
|
-
// ignore
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
function runProcess(
|
|
776
|
-
command: string,
|
|
777
|
-
args: string[],
|
|
778
|
-
opts: { cwd?: string; silent?: boolean; env?: Record<string, string | undefined> } = {}
|
|
779
|
-
) {
|
|
780
|
-
try {
|
|
781
|
-
const mergedEnv: NodeJS.ProcessEnv = { ...process.env, ...(opts.env ?? {}) };
|
|
782
|
-
const result = spawnSync(command, args, {
|
|
783
|
-
cwd: opts.cwd,
|
|
784
|
-
env: mergedEnv,
|
|
785
|
-
encoding: 'utf8',
|
|
786
|
-
stdio: opts.silent ? 'pipe' : 'inherit'
|
|
787
|
-
});
|
|
788
|
-
return {
|
|
789
|
-
ok: result.status === 0,
|
|
790
|
-
stdout: (result.stdout ?? '').toString().trim(),
|
|
791
|
-
stderr: (result.stderr ?? '').toString().trim()
|
|
792
|
-
};
|
|
793
|
-
} catch (e: unknown) {
|
|
794
|
-
return {
|
|
795
|
-
ok: false,
|
|
796
|
-
stdout: '',
|
|
797
|
-
stderr: getExecErrorMessage(e)
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
function runMoltbankWrapper(skillDir: string, args: string[]) {
|
|
803
|
-
if (IS_WIN) {
|
|
804
|
-
const ps1Path = join(skillDir, 'scripts', 'moltbank.ps1');
|
|
805
|
-
return runProcess('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ps1Path, ...args], {
|
|
806
|
-
cwd: skillDir,
|
|
807
|
-
silent: true
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
return runProcess(join(skillDir, 'scripts', 'moltbank.sh'), args, {
|
|
812
|
-
cwd: skillDir,
|
|
813
|
-
silent: true
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
function verifyMoltbankOperationalReadiness(skillDir: string, api: LoggerApi): boolean {
|
|
818
|
-
const existing = parseActiveTokenFromCredentials();
|
|
819
|
-
if (!existing.ok || !existing.activeOrg) {
|
|
820
|
-
api.logger.warn('[moltbank] ✗ setup incomplete: authenticated org could not be resolved for balance verification');
|
|
821
|
-
return false;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
const verification = runMoltbankWrapper(skillDir, [
|
|
825
|
-
'call',
|
|
826
|
-
'MoltBank.get_balance',
|
|
827
|
-
`organizationName=${existing.activeOrg}`,
|
|
828
|
-
'date=today'
|
|
829
|
-
]);
|
|
830
|
-
|
|
831
|
-
if (verification.ok) {
|
|
832
|
-
api.logger.info(`[moltbank] ✓ balance verification passed (org: ${existing.activeOrg})`);
|
|
833
|
-
return true;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
api.logger.warn('[moltbank] ✗ setup incomplete: auth is linked, but balance verification failed (`MoltBank.get_balance`)');
|
|
837
|
-
const detail = verification.stderr || verification.stdout;
|
|
838
|
-
if (detail) {
|
|
839
|
-
api.logger.warn('[moltbank] verification detail: ' + detail);
|
|
840
|
-
}
|
|
841
|
-
return false;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
1090
|
function startBackgroundOauthPoll(
|
|
845
1091
|
skillDir: string,
|
|
846
1092
|
appBaseUrl: string,
|
|
@@ -892,7 +1138,12 @@ function startBackgroundOauthPoll(
|
|
|
892
1138
|
oauthPollers.delete(skillDir);
|
|
893
1139
|
const after = parseActiveTokenFromCredentials();
|
|
894
1140
|
if (after.ok) {
|
|
895
|
-
|
|
1141
|
+
const pendingPath = join(skillDir, '.oauth_device_code.json');
|
|
1142
|
+
try {
|
|
1143
|
+
if (existsSync(pendingPath)) unlinkSync(pendingPath);
|
|
1144
|
+
} catch {
|
|
1145
|
+
// ignore
|
|
1146
|
+
}
|
|
896
1147
|
api.logger.info(`[moltbank] ✓ background onboarding completed (active org: ${after.activeOrg})`);
|
|
897
1148
|
try {
|
|
898
1149
|
startBackgroundFinalizeAfterAuth(skillDir, appBaseUrl, api);
|
|
@@ -972,8 +1223,6 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
972
1223
|
const existing = parseActiveTokenFromCredentials();
|
|
973
1224
|
if (existing.ok) {
|
|
974
1225
|
stopBackgroundOauthPoll(skillDir);
|
|
975
|
-
stopBackgroundFinalize(skillDir);
|
|
976
|
-
clearPendingOauthCode(skillDir);
|
|
977
1226
|
api.logger.info(`[moltbank] ✓ credentials.json already available (active org: ${existing.activeOrg})`);
|
|
978
1227
|
return true;
|
|
979
1228
|
}
|
|
@@ -1112,7 +1361,11 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
1112
1361
|
|
|
1113
1362
|
if (oauthError === 'invalid_grant') {
|
|
1114
1363
|
api.logger.warn('[moltbank] ✗ onboarding code expired or already consumed (invalid_grant)');
|
|
1115
|
-
|
|
1364
|
+
try {
|
|
1365
|
+
if (existsSync(pendingPath)) unlinkSync(pendingPath);
|
|
1366
|
+
} catch {
|
|
1367
|
+
// ignore
|
|
1368
|
+
}
|
|
1116
1369
|
} else {
|
|
1117
1370
|
api.logger.warn('[moltbank] ✗ onboarding poll failed or timed out');
|
|
1118
1371
|
}
|
|
@@ -1129,7 +1382,11 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
1129
1382
|
return false;
|
|
1130
1383
|
}
|
|
1131
1384
|
|
|
1132
|
-
|
|
1385
|
+
try {
|
|
1386
|
+
if (existsSync(pendingPath)) unlinkSync(pendingPath);
|
|
1387
|
+
} catch {
|
|
1388
|
+
// ignore
|
|
1389
|
+
}
|
|
1133
1390
|
|
|
1134
1391
|
api.logger.info(`[moltbank] ✓ onboarding completed (active org: ${after.activeOrg})`);
|
|
1135
1392
|
return true;
|
|
@@ -1138,10 +1395,7 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
1138
1395
|
export function printAuthStatus(skillDir: string, appBaseUrl: string, api: LoggerApi): void {
|
|
1139
1396
|
const now = Math.floor(Date.now() / 1000);
|
|
1140
1397
|
const existing = parseActiveTokenFromCredentials();
|
|
1141
|
-
|
|
1142
|
-
clearPendingOauthCode(skillDir);
|
|
1143
|
-
}
|
|
1144
|
-
const pending = existing.ok ? null : readPendingOauthCode(skillDir);
|
|
1398
|
+
const pending = readPendingOauthCode(skillDir);
|
|
1145
1399
|
const poller = oauthPollers.get(skillDir);
|
|
1146
1400
|
const finalizer = backgroundFinalizers.get(skillDir);
|
|
1147
1401
|
const pollerRunning = Boolean(poller && poller.exitCode === null && !poller.killed);
|
|
@@ -1192,35 +1446,12 @@ export function ensureMcporterConfig(skillDir: string, appBaseUrl: string, api:
|
|
|
1192
1446
|
api.logger.info('[moltbank] ✓ mcporter.json written: ' + cfgPath);
|
|
1193
1447
|
|
|
1194
1448
|
if (!hasBin('mcporter')) {
|
|
1195
|
-
api.logger.warn('[moltbank] ✗ mcporter not in PATH — cannot
|
|
1449
|
+
api.logger.warn('[moltbank] ✗ mcporter not in PATH — cannot validate local config usage');
|
|
1196
1450
|
return;
|
|
1197
1451
|
}
|
|
1198
1452
|
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
return;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
api.logger.info('[moltbank] registering MoltBank in mcporter (scope: home)...');
|
|
1205
|
-
|
|
1206
|
-
const addCmd =
|
|
1207
|
-
`mcporter config add MoltBank ` +
|
|
1208
|
-
`--url "${appBaseUrl}/api/mcp" ` +
|
|
1209
|
-
`--transport sse ` +
|
|
1210
|
-
`--header "Authorization=Bearer \${MOLTBANK}" ` +
|
|
1211
|
-
`--header "Content-Type=application/json" ` +
|
|
1212
|
-
`--description "MoltBank stablecoin treasury MCP server powered by Fondu" ` +
|
|
1213
|
-
`--scope home`;
|
|
1214
|
-
|
|
1215
|
-
const result = run(addCmd, { silent: false });
|
|
1216
|
-
|
|
1217
|
-
if (result.ok) {
|
|
1218
|
-
api.logger.info('[moltbank] ✓ MoltBank registered in mcporter');
|
|
1219
|
-
const list = run('mcporter config list', { silent: true });
|
|
1220
|
-
api.logger.info('[moltbank] mcporter config list: ' + list.stdout);
|
|
1221
|
-
} else {
|
|
1222
|
-
api.logger.warn('[moltbank] ✗ mcporter config add failed: ' + result.stderr);
|
|
1223
|
-
}
|
|
1453
|
+
removeHomeScopedMoltBankConfig(api);
|
|
1454
|
+
api.logger.info('[moltbank] ✓ MoltBank will use skill-local mcporter config only');
|
|
1224
1455
|
}
|
|
1225
1456
|
|
|
1226
1457
|
// ─── sandbox env vars ────────────────────────────────────────────────────────
|
|
@@ -1428,12 +1659,29 @@ export function recreateSandboxAndRestart(api: LoggerApi) {
|
|
|
1428
1659
|
}, 8000);
|
|
1429
1660
|
}
|
|
1430
1661
|
|
|
1662
|
+
function restartGatewayAfterHostSetup(api: LoggerApi): boolean {
|
|
1663
|
+
api.logger.info('[moltbank] restarting gateway to refresh host skill state...');
|
|
1664
|
+
const restart = run('openclaw gateway restart', { silent: true, timeoutMs: 30000 });
|
|
1665
|
+
if (restart.ok) {
|
|
1666
|
+
api.logger.info('[moltbank] ✓ gateway restarted');
|
|
1667
|
+
return true;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
const combined = `${restart.stderr}\n${restart.stdout}`.trim();
|
|
1671
|
+
const detail = lastNonEmptyLine(combined);
|
|
1672
|
+
if (detail) {
|
|
1673
|
+
api.logger.warn(`[moltbank] gateway restart detail: ${detail}`);
|
|
1674
|
+
}
|
|
1675
|
+
api.logger.warn('[moltbank] could not restart gateway automatically; run `openclaw gateway restart`');
|
|
1676
|
+
return false;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1431
1679
|
// ─── main setup ───────────────────────────────────────────────────────────────
|
|
1432
1680
|
|
|
1433
1681
|
export async function runSetup(
|
|
1434
1682
|
cfg: MoltbankPluginConfig,
|
|
1435
1683
|
api: LoggerApi,
|
|
1436
|
-
options: { authWaitMode?: AuthWaitMode } = {}
|
|
1684
|
+
options: { authWaitMode?: AuthWaitMode; restartGatewayOnSuccess?: boolean } = {}
|
|
1437
1685
|
) {
|
|
1438
1686
|
let hostReady = false;
|
|
1439
1687
|
const appBaseUrl = getAppBaseUrl(cfg);
|
|
@@ -1442,6 +1690,7 @@ export async function runSetup(
|
|
|
1442
1690
|
const sandbox = isSandboxEnabled();
|
|
1443
1691
|
const skillDir = getSkillDir(cfg);
|
|
1444
1692
|
const waitForAuth = (options.authWaitMode ?? 'blocking') === 'blocking';
|
|
1693
|
+
const restartGatewayOnSuccess = Boolean(options.restartGatewayOnSuccess);
|
|
1445
1694
|
|
|
1446
1695
|
api.logger.info(`[moltbank] ══════════════════════════════════════`);
|
|
1447
1696
|
api.logger.info(`[moltbank] MoltBank setup starting`);
|
|
@@ -1457,6 +1706,7 @@ export async function runSetup(
|
|
|
1457
1706
|
|
|
1458
1707
|
api.logger.info('[moltbank] [sandbox 0/10] ensuring mcporter on host...');
|
|
1459
1708
|
ensureMcporter(api);
|
|
1709
|
+
ensureJq(api);
|
|
1460
1710
|
|
|
1461
1711
|
api.logger.info('[moltbank] [sandbox 1/10] installing skill files...');
|
|
1462
1712
|
const skillInstalled = ensureSkillInstalled(skillDir, skillBundleBaseUrl, skillName, api, 'sandbox');
|
|
@@ -1511,14 +1761,10 @@ export async function runSetup(
|
|
|
1511
1761
|
} else {
|
|
1512
1762
|
api.logger.info('[moltbank] sandbox unchanged — no restart needed');
|
|
1513
1763
|
}
|
|
1514
|
-
|
|
1515
|
-
api.logger.info('[moltbank] [sandbox 11/11] verifying authenticated balance read...');
|
|
1516
|
-
if (!verifyMoltbankOperationalReadiness(skillDir, api)) {
|
|
1517
|
-
return;
|
|
1518
|
-
}
|
|
1519
1764
|
} else {
|
|
1520
1765
|
api.logger.info('[moltbank] [host 1/8] ensuring mcporter on host...');
|
|
1521
1766
|
ensureMcporter(api);
|
|
1767
|
+
ensureJq(api);
|
|
1522
1768
|
|
|
1523
1769
|
api.logger.info('[moltbank] [host 2/8] installing skill files...');
|
|
1524
1770
|
const installed = ensureSkillInstalled(skillDir, skillBundleBaseUrl, skillName, api, 'host');
|
|
@@ -1558,13 +1804,96 @@ export async function runSetup(
|
|
|
1558
1804
|
api.logger.info('[moltbank] [host 7/8] writing/registering mcporter config...');
|
|
1559
1805
|
ensureMcporterConfig(skillDir, appBaseUrl, api);
|
|
1560
1806
|
|
|
1561
|
-
api.logger.info('[moltbank] [host 8/8]
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1807
|
+
api.logger.info('[moltbank] [host 8/8] running wrapper smoke test...');
|
|
1808
|
+
const smokeTimeoutMs = IS_WIN ? 15000 : 20000;
|
|
1809
|
+
let smokeOk = false;
|
|
1810
|
+
|
|
1811
|
+
if (IS_WIN) {
|
|
1812
|
+
const mcporterConfigPath = join(skillDir, 'assets', 'mcporter.json');
|
|
1813
|
+
const active = parseActiveTokenFromCredentials();
|
|
1814
|
+
const mcpSmoke = run(`mcporter --config "${mcporterConfigPath}" list MoltBank`, {
|
|
1815
|
+
cwd: skillDir,
|
|
1816
|
+
silent: true,
|
|
1817
|
+
timeoutMs: smokeTimeoutMs,
|
|
1818
|
+
env: {
|
|
1819
|
+
MOLTBANK: active.token ?? '__MOLTBANK_PLACEHOLDER__'
|
|
1820
|
+
}
|
|
1821
|
+
});
|
|
1822
|
+
|
|
1823
|
+
if (mcpSmoke.ok) {
|
|
1824
|
+
api.logger.info('[moltbank] host smoke test passed (`mcporter list MoltBank`)');
|
|
1825
|
+
smokeOk = true;
|
|
1826
|
+
} else {
|
|
1827
|
+
const mcpCombined = `${mcpSmoke.stderr}\n${mcpSmoke.stdout}`.trim();
|
|
1828
|
+
const mcpCombinedLower = mcpCombined.toLowerCase();
|
|
1829
|
+
const mcpDetail = lastNonEmptyLine(mcpCombined);
|
|
1830
|
+
if (mcpDetail) {
|
|
1831
|
+
api.logger.warn(`[moltbank] smoke detail: ${mcpDetail}`);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
const timedOut = mcpSmoke.stderr.includes('ETIMEDOUT') || mcpCombinedLower.includes('timed out');
|
|
1835
|
+
if (timedOut) {
|
|
1836
|
+
api.logger.warn(
|
|
1837
|
+
`[moltbank] host MCP smoke timed out after ${Math.floor(smokeTimeoutMs / 1000)}s; running local fallback checks`
|
|
1838
|
+
);
|
|
1839
|
+
|
|
1840
|
+
const ps1Path = join(skillDir, 'scripts', 'moltbank.ps1');
|
|
1841
|
+
const fallback = run(
|
|
1842
|
+
'powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "$ErrorActionPreference = \'Stop\'; if (-not (Test-Path -LiteralPath $env:MOLTBANK_WRAPPER_PATH)) { throw \'moltbank.ps1 not found\' }; if (-not (Get-Command bash -ErrorAction SilentlyContinue)) { throw \'bash is not installed or not on PATH\' }; if (-not (Get-Command mcporter -ErrorAction SilentlyContinue)) { throw \'mcporter is not installed or not on PATH\' }; Write-Output \'ok\'"',
|
|
1843
|
+
{
|
|
1844
|
+
cwd: skillDir,
|
|
1845
|
+
silent: true,
|
|
1846
|
+
timeoutMs: 10000,
|
|
1847
|
+
env: { MOLTBANK_WRAPPER_PATH: ps1Path }
|
|
1848
|
+
}
|
|
1849
|
+
);
|
|
1850
|
+
|
|
1851
|
+
if (fallback.ok) {
|
|
1852
|
+
api.logger.warn('[moltbank] host smoke fallback passed (wrapper prerequisites available), but MCP endpoint was not verified');
|
|
1853
|
+
smokeOk = true;
|
|
1854
|
+
} else {
|
|
1855
|
+
const fallbackCombined = `${fallback.stderr}\n${fallback.stdout}`.trim();
|
|
1856
|
+
const fallbackDetail = lastNonEmptyLine(fallbackCombined);
|
|
1857
|
+
if (fallbackDetail) {
|
|
1858
|
+
api.logger.warn(`[moltbank] fallback detail: ${fallbackDetail}`);
|
|
1859
|
+
}
|
|
1860
|
+
api.logger.warn('[moltbank] host setup incomplete: MCP smoke timed out and fallback checks failed');
|
|
1861
|
+
}
|
|
1862
|
+
} else if (
|
|
1863
|
+
mcpCombinedLower.includes('mcporter') &&
|
|
1864
|
+
(mcpCombinedLower.includes('not recognized') || mcpCombinedLower.includes('not found'))
|
|
1865
|
+
) {
|
|
1866
|
+
api.logger.warn('[moltbank] mcporter binary not found in PATH during smoke test');
|
|
1867
|
+
} else {
|
|
1868
|
+
api.logger.warn('[moltbank] host setup incomplete: smoke test failed (`mcporter list MoltBank`)');
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
} else {
|
|
1872
|
+
const smoke = run(`"${skillDir}/scripts/moltbank.sh" list MoltBank`, {
|
|
1873
|
+
cwd: skillDir,
|
|
1874
|
+
silent: true,
|
|
1875
|
+
timeoutMs: smokeTimeoutMs
|
|
1876
|
+
});
|
|
1877
|
+
if (!smoke.ok) {
|
|
1878
|
+
const smokeCombined = `${smoke.stderr}\n${smoke.stdout}`.trim();
|
|
1879
|
+
const smokeDetail = lastNonEmptyLine(smokeCombined);
|
|
1880
|
+
if (smokeDetail) {
|
|
1881
|
+
api.logger.warn(`[moltbank] smoke detail: ${smokeDetail}`);
|
|
1882
|
+
}
|
|
1883
|
+
api.logger.warn('[moltbank] host setup incomplete: smoke test failed (`moltbank list MoltBank` via platform script)');
|
|
1884
|
+
} else {
|
|
1885
|
+
api.logger.info('[moltbank] host smoke test passed (`moltbank list MoltBank`)');
|
|
1886
|
+
smokeOk = true;
|
|
1887
|
+
}
|
|
1565
1888
|
}
|
|
1566
1889
|
|
|
1890
|
+
hostReady = smokeOk;
|
|
1891
|
+
|
|
1567
1892
|
api.logger.info('[moltbank] host mode — setup completed with onboarding flow');
|
|
1893
|
+
|
|
1894
|
+
if (hostReady && restartGatewayOnSuccess) {
|
|
1895
|
+
restartGatewayAfterHostSetup(api);
|
|
1896
|
+
}
|
|
1568
1897
|
}
|
|
1569
1898
|
|
|
1570
1899
|
api.logger.info(`[moltbank] ══════════════════════════════════════`);
|
|
@@ -1640,9 +1969,10 @@ export default function register(api: PluginApi) {
|
|
|
1640
1969
|
.description('Re-run MoltBank setup')
|
|
1641
1970
|
.action(async () => {
|
|
1642
1971
|
const verbose = process.argv.includes('--verbose');
|
|
1972
|
+
const restartGatewayOnSuccess = getSetupGatewayRestartEnabled(IS_WIN);
|
|
1643
1973
|
const logger = createSetupCommandLogger({ verbose, statusCommand: 'openclaw moltbank status' });
|
|
1644
1974
|
try {
|
|
1645
|
-
await runSetup(cfg, logger, { authWaitMode: 'blocking' });
|
|
1975
|
+
await runSetup(cfg, logger, { authWaitMode: 'blocking', restartGatewayOnSuccess });
|
|
1646
1976
|
} finally {
|
|
1647
1977
|
logger.finish();
|
|
1648
1978
|
}
|
|
@@ -1654,9 +1984,10 @@ export default function register(api: PluginApi) {
|
|
|
1654
1984
|
.description('Re-run full MoltBank setup and wait for OAuth approval')
|
|
1655
1985
|
.action(async () => {
|
|
1656
1986
|
const verbose = process.argv.includes('--verbose');
|
|
1987
|
+
const restartGatewayOnSuccess = getSetupGatewayRestartEnabled(IS_WIN);
|
|
1657
1988
|
const logger = createSetupCommandLogger({ verbose, statusCommand: 'openclaw moltbank status' });
|
|
1658
1989
|
try {
|
|
1659
|
-
await runSetup(cfg, logger, { authWaitMode: 'blocking' });
|
|
1990
|
+
await runSetup(cfg, logger, { authWaitMode: 'blocking', restartGatewayOnSuccess });
|
|
1660
1991
|
} finally {
|
|
1661
1992
|
logger.finish();
|
|
1662
1993
|
}
|