@moltbankhq/openclaw 0.1.9 → 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 -147
- 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');
|
|
@@ -498,7 +797,6 @@ const SKILL_FILES = [
|
|
|
498
797
|
'SKILL.md',
|
|
499
798
|
'skill.json',
|
|
500
799
|
'install.sh',
|
|
501
|
-
'package.json',
|
|
502
800
|
'assets/mcporter.json',
|
|
503
801
|
'references/heartbeat.md',
|
|
504
802
|
'references/onboarding.md',
|
|
@@ -700,6 +998,31 @@ function isMoltBankRegistered(): boolean {
|
|
|
700
998
|
return result.stdout.toLowerCase().includes('moltbank');
|
|
701
999
|
}
|
|
702
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
|
+
|
|
703
1026
|
function parseActiveTokenFromCredentials(): {
|
|
704
1027
|
ok: boolean;
|
|
705
1028
|
activeOrg?: string;
|
|
@@ -764,93 +1087,6 @@ function readPendingOauthCode(skillDir: string): {
|
|
|
764
1087
|
}
|
|
765
1088
|
}
|
|
766
1089
|
|
|
767
|
-
function clearPendingOauthCode(skillDir: string): void {
|
|
768
|
-
const pendingPath = join(skillDir, '.oauth_device_code.json');
|
|
769
|
-
try {
|
|
770
|
-
if (existsSync(pendingPath)) unlinkSync(pendingPath);
|
|
771
|
-
} catch {
|
|
772
|
-
// ignore
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
function runProcess(
|
|
777
|
-
command: string,
|
|
778
|
-
args: string[],
|
|
779
|
-
opts: { cwd?: string; silent?: boolean; env?: Record<string, string | undefined> } = {}
|
|
780
|
-
) {
|
|
781
|
-
try {
|
|
782
|
-
const mergedEnv: NodeJS.ProcessEnv = { ...process.env, ...(opts.env ?? {}) };
|
|
783
|
-
const result = spawnSync(command, args, {
|
|
784
|
-
cwd: opts.cwd,
|
|
785
|
-
env: mergedEnv,
|
|
786
|
-
encoding: 'utf8',
|
|
787
|
-
stdio: opts.silent ? 'pipe' : 'inherit'
|
|
788
|
-
});
|
|
789
|
-
return {
|
|
790
|
-
ok: result.status === 0,
|
|
791
|
-
stdout: (result.stdout ?? '').toString().trim(),
|
|
792
|
-
stderr: (result.stderr ?? '').toString().trim()
|
|
793
|
-
};
|
|
794
|
-
} catch (e: unknown) {
|
|
795
|
-
return {
|
|
796
|
-
ok: false,
|
|
797
|
-
stdout: '',
|
|
798
|
-
stderr: getExecErrorMessage(e)
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
function runMoltbankWrapper(
|
|
804
|
-
skillDir: string,
|
|
805
|
-
args: string[],
|
|
806
|
-
env: Record<string, string | undefined> = {}
|
|
807
|
-
) {
|
|
808
|
-
if (IS_WIN) {
|
|
809
|
-
const ps1Path = join(skillDir, 'scripts', 'moltbank.ps1');
|
|
810
|
-
return runProcess('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ps1Path, ...args], {
|
|
811
|
-
cwd: skillDir,
|
|
812
|
-
silent: true,
|
|
813
|
-
env
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
return runProcess(join(skillDir, 'scripts', 'moltbank.sh'), args, {
|
|
818
|
-
cwd: skillDir,
|
|
819
|
-
silent: true,
|
|
820
|
-
env
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
function verifyMoltbankOperationalReadiness(skillDir: string, api: LoggerApi): boolean {
|
|
825
|
-
const existing = parseActiveTokenFromCredentials();
|
|
826
|
-
if (!existing.ok || !existing.activeOrg || !existing.token) {
|
|
827
|
-
api.logger.warn('[moltbank] ✗ setup incomplete: authenticated org could not be resolved for balance verification');
|
|
828
|
-
return false;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
const verification = runMoltbankWrapper(skillDir, [
|
|
832
|
-
'call',
|
|
833
|
-
'MoltBank.get_balance',
|
|
834
|
-
`organizationName=${existing.activeOrg}`,
|
|
835
|
-
'date=today'
|
|
836
|
-
], {
|
|
837
|
-
MOLTBANK: existing.token,
|
|
838
|
-
ACTIVE_ORG_OVERRIDE: existing.activeOrg
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
if (verification.ok) {
|
|
842
|
-
api.logger.info(`[moltbank] ✓ balance verification passed (org: ${existing.activeOrg})`);
|
|
843
|
-
return true;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
api.logger.warn('[moltbank] ✗ setup incomplete: auth is linked, but balance verification failed (`MoltBank.get_balance`)');
|
|
847
|
-
const detail = verification.stderr || verification.stdout;
|
|
848
|
-
if (detail) {
|
|
849
|
-
api.logger.warn('[moltbank] verification detail: ' + detail);
|
|
850
|
-
}
|
|
851
|
-
return false;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
1090
|
function startBackgroundOauthPoll(
|
|
855
1091
|
skillDir: string,
|
|
856
1092
|
appBaseUrl: string,
|
|
@@ -902,7 +1138,12 @@ function startBackgroundOauthPoll(
|
|
|
902
1138
|
oauthPollers.delete(skillDir);
|
|
903
1139
|
const after = parseActiveTokenFromCredentials();
|
|
904
1140
|
if (after.ok) {
|
|
905
|
-
|
|
1141
|
+
const pendingPath = join(skillDir, '.oauth_device_code.json');
|
|
1142
|
+
try {
|
|
1143
|
+
if (existsSync(pendingPath)) unlinkSync(pendingPath);
|
|
1144
|
+
} catch {
|
|
1145
|
+
// ignore
|
|
1146
|
+
}
|
|
906
1147
|
api.logger.info(`[moltbank] ✓ background onboarding completed (active org: ${after.activeOrg})`);
|
|
907
1148
|
try {
|
|
908
1149
|
startBackgroundFinalizeAfterAuth(skillDir, appBaseUrl, api);
|
|
@@ -982,8 +1223,6 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
982
1223
|
const existing = parseActiveTokenFromCredentials();
|
|
983
1224
|
if (existing.ok) {
|
|
984
1225
|
stopBackgroundOauthPoll(skillDir);
|
|
985
|
-
stopBackgroundFinalize(skillDir);
|
|
986
|
-
clearPendingOauthCode(skillDir);
|
|
987
1226
|
api.logger.info(`[moltbank] ✓ credentials.json already available (active org: ${existing.activeOrg})`);
|
|
988
1227
|
return true;
|
|
989
1228
|
}
|
|
@@ -1122,7 +1361,11 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
1122
1361
|
|
|
1123
1362
|
if (oauthError === 'invalid_grant') {
|
|
1124
1363
|
api.logger.warn('[moltbank] ✗ onboarding code expired or already consumed (invalid_grant)');
|
|
1125
|
-
|
|
1364
|
+
try {
|
|
1365
|
+
if (existsSync(pendingPath)) unlinkSync(pendingPath);
|
|
1366
|
+
} catch {
|
|
1367
|
+
// ignore
|
|
1368
|
+
}
|
|
1126
1369
|
} else {
|
|
1127
1370
|
api.logger.warn('[moltbank] ✗ onboarding poll failed or timed out');
|
|
1128
1371
|
}
|
|
@@ -1139,7 +1382,11 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
1139
1382
|
return false;
|
|
1140
1383
|
}
|
|
1141
1384
|
|
|
1142
|
-
|
|
1385
|
+
try {
|
|
1386
|
+
if (existsSync(pendingPath)) unlinkSync(pendingPath);
|
|
1387
|
+
} catch {
|
|
1388
|
+
// ignore
|
|
1389
|
+
}
|
|
1143
1390
|
|
|
1144
1391
|
api.logger.info(`[moltbank] ✓ onboarding completed (active org: ${after.activeOrg})`);
|
|
1145
1392
|
return true;
|
|
@@ -1148,10 +1395,7 @@ function ensureMoltbankAuth(skillDir: string, appBaseUrl: string, api: LoggerApi
|
|
|
1148
1395
|
export function printAuthStatus(skillDir: string, appBaseUrl: string, api: LoggerApi): void {
|
|
1149
1396
|
const now = Math.floor(Date.now() / 1000);
|
|
1150
1397
|
const existing = parseActiveTokenFromCredentials();
|
|
1151
|
-
|
|
1152
|
-
clearPendingOauthCode(skillDir);
|
|
1153
|
-
}
|
|
1154
|
-
const pending = existing.ok ? null : readPendingOauthCode(skillDir);
|
|
1398
|
+
const pending = readPendingOauthCode(skillDir);
|
|
1155
1399
|
const poller = oauthPollers.get(skillDir);
|
|
1156
1400
|
const finalizer = backgroundFinalizers.get(skillDir);
|
|
1157
1401
|
const pollerRunning = Boolean(poller && poller.exitCode === null && !poller.killed);
|
|
@@ -1202,35 +1446,12 @@ export function ensureMcporterConfig(skillDir: string, appBaseUrl: string, api:
|
|
|
1202
1446
|
api.logger.info('[moltbank] ✓ mcporter.json written: ' + cfgPath);
|
|
1203
1447
|
|
|
1204
1448
|
if (!hasBin('mcporter')) {
|
|
1205
|
-
api.logger.warn('[moltbank] ✗ mcporter not in PATH — cannot
|
|
1449
|
+
api.logger.warn('[moltbank] ✗ mcporter not in PATH — cannot validate local config usage');
|
|
1206
1450
|
return;
|
|
1207
1451
|
}
|
|
1208
1452
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
return;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
api.logger.info('[moltbank] registering MoltBank in mcporter (scope: home)...');
|
|
1215
|
-
|
|
1216
|
-
const addCmd =
|
|
1217
|
-
`mcporter config add MoltBank ` +
|
|
1218
|
-
`--url "${appBaseUrl}/api/mcp" ` +
|
|
1219
|
-
`--transport sse ` +
|
|
1220
|
-
`--header "Authorization=Bearer \${MOLTBANK}" ` +
|
|
1221
|
-
`--header "Content-Type=application/json" ` +
|
|
1222
|
-
`--description "MoltBank stablecoin treasury MCP server powered by Fondu" ` +
|
|
1223
|
-
`--scope home`;
|
|
1224
|
-
|
|
1225
|
-
const result = run(addCmd, { silent: false });
|
|
1226
|
-
|
|
1227
|
-
if (result.ok) {
|
|
1228
|
-
api.logger.info('[moltbank] ✓ MoltBank registered in mcporter');
|
|
1229
|
-
const list = run('mcporter config list', { silent: true });
|
|
1230
|
-
api.logger.info('[moltbank] mcporter config list: ' + list.stdout);
|
|
1231
|
-
} else {
|
|
1232
|
-
api.logger.warn('[moltbank] ✗ mcporter config add failed: ' + result.stderr);
|
|
1233
|
-
}
|
|
1453
|
+
removeHomeScopedMoltBankConfig(api);
|
|
1454
|
+
api.logger.info('[moltbank] ✓ MoltBank will use skill-local mcporter config only');
|
|
1234
1455
|
}
|
|
1235
1456
|
|
|
1236
1457
|
// ─── sandbox env vars ────────────────────────────────────────────────────────
|
|
@@ -1438,12 +1659,29 @@ export function recreateSandboxAndRestart(api: LoggerApi) {
|
|
|
1438
1659
|
}, 8000);
|
|
1439
1660
|
}
|
|
1440
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
|
+
|
|
1441
1679
|
// ─── main setup ───────────────────────────────────────────────────────────────
|
|
1442
1680
|
|
|
1443
1681
|
export async function runSetup(
|
|
1444
1682
|
cfg: MoltbankPluginConfig,
|
|
1445
1683
|
api: LoggerApi,
|
|
1446
|
-
options: { authWaitMode?: AuthWaitMode } = {}
|
|
1684
|
+
options: { authWaitMode?: AuthWaitMode; restartGatewayOnSuccess?: boolean } = {}
|
|
1447
1685
|
) {
|
|
1448
1686
|
let hostReady = false;
|
|
1449
1687
|
const appBaseUrl = getAppBaseUrl(cfg);
|
|
@@ -1452,6 +1690,7 @@ export async function runSetup(
|
|
|
1452
1690
|
const sandbox = isSandboxEnabled();
|
|
1453
1691
|
const skillDir = getSkillDir(cfg);
|
|
1454
1692
|
const waitForAuth = (options.authWaitMode ?? 'blocking') === 'blocking';
|
|
1693
|
+
const restartGatewayOnSuccess = Boolean(options.restartGatewayOnSuccess);
|
|
1455
1694
|
|
|
1456
1695
|
api.logger.info(`[moltbank] ══════════════════════════════════════`);
|
|
1457
1696
|
api.logger.info(`[moltbank] MoltBank setup starting`);
|
|
@@ -1467,6 +1706,7 @@ export async function runSetup(
|
|
|
1467
1706
|
|
|
1468
1707
|
api.logger.info('[moltbank] [sandbox 0/10] ensuring mcporter on host...');
|
|
1469
1708
|
ensureMcporter(api);
|
|
1709
|
+
ensureJq(api);
|
|
1470
1710
|
|
|
1471
1711
|
api.logger.info('[moltbank] [sandbox 1/10] installing skill files...');
|
|
1472
1712
|
const skillInstalled = ensureSkillInstalled(skillDir, skillBundleBaseUrl, skillName, api, 'sandbox');
|
|
@@ -1521,14 +1761,10 @@ export async function runSetup(
|
|
|
1521
1761
|
} else {
|
|
1522
1762
|
api.logger.info('[moltbank] sandbox unchanged — no restart needed');
|
|
1523
1763
|
}
|
|
1524
|
-
|
|
1525
|
-
api.logger.info('[moltbank] [sandbox 11/11] verifying authenticated balance read...');
|
|
1526
|
-
if (!verifyMoltbankOperationalReadiness(skillDir, api)) {
|
|
1527
|
-
return;
|
|
1528
|
-
}
|
|
1529
1764
|
} else {
|
|
1530
1765
|
api.logger.info('[moltbank] [host 1/8] ensuring mcporter on host...');
|
|
1531
1766
|
ensureMcporter(api);
|
|
1767
|
+
ensureJq(api);
|
|
1532
1768
|
|
|
1533
1769
|
api.logger.info('[moltbank] [host 2/8] installing skill files...');
|
|
1534
1770
|
const installed = ensureSkillInstalled(skillDir, skillBundleBaseUrl, skillName, api, 'host');
|
|
@@ -1568,13 +1804,96 @@ export async function runSetup(
|
|
|
1568
1804
|
api.logger.info('[moltbank] [host 7/8] writing/registering mcporter config...');
|
|
1569
1805
|
ensureMcporterConfig(skillDir, appBaseUrl, api);
|
|
1570
1806
|
|
|
1571
|
-
api.logger.info('[moltbank] [host 8/8]
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
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
|
+
}
|
|
1575
1888
|
}
|
|
1576
1889
|
|
|
1890
|
+
hostReady = smokeOk;
|
|
1891
|
+
|
|
1577
1892
|
api.logger.info('[moltbank] host mode — setup completed with onboarding flow');
|
|
1893
|
+
|
|
1894
|
+
if (hostReady && restartGatewayOnSuccess) {
|
|
1895
|
+
restartGatewayAfterHostSetup(api);
|
|
1896
|
+
}
|
|
1578
1897
|
}
|
|
1579
1898
|
|
|
1580
1899
|
api.logger.info(`[moltbank] ══════════════════════════════════════`);
|
|
@@ -1650,9 +1969,10 @@ export default function register(api: PluginApi) {
|
|
|
1650
1969
|
.description('Re-run MoltBank setup')
|
|
1651
1970
|
.action(async () => {
|
|
1652
1971
|
const verbose = process.argv.includes('--verbose');
|
|
1972
|
+
const restartGatewayOnSuccess = getSetupGatewayRestartEnabled(IS_WIN);
|
|
1653
1973
|
const logger = createSetupCommandLogger({ verbose, statusCommand: 'openclaw moltbank status' });
|
|
1654
1974
|
try {
|
|
1655
|
-
await runSetup(cfg, logger, { authWaitMode: 'blocking' });
|
|
1975
|
+
await runSetup(cfg, logger, { authWaitMode: 'blocking', restartGatewayOnSuccess });
|
|
1656
1976
|
} finally {
|
|
1657
1977
|
logger.finish();
|
|
1658
1978
|
}
|
|
@@ -1664,9 +1984,10 @@ export default function register(api: PluginApi) {
|
|
|
1664
1984
|
.description('Re-run full MoltBank setup and wait for OAuth approval')
|
|
1665
1985
|
.action(async () => {
|
|
1666
1986
|
const verbose = process.argv.includes('--verbose');
|
|
1987
|
+
const restartGatewayOnSuccess = getSetupGatewayRestartEnabled(IS_WIN);
|
|
1667
1988
|
const logger = createSetupCommandLogger({ verbose, statusCommand: 'openclaw moltbank status' });
|
|
1668
1989
|
try {
|
|
1669
|
-
await runSetup(cfg, logger, { authWaitMode: 'blocking' });
|
|
1990
|
+
await runSetup(cfg, logger, { authWaitMode: 'blocking', restartGatewayOnSuccess });
|
|
1670
1991
|
} finally {
|
|
1671
1992
|
logger.finish();
|
|
1672
1993
|
}
|