@moltbankhq/openclaw 0.1.11 → 0.1.12
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/index.ts +254 -7
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -365,10 +365,136 @@ function getExecErrorMessage(error: unknown): string {
|
|
|
365
365
|
return String(error);
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
+
function getPathEnvKey(env: NodeJS.ProcessEnv): string {
|
|
369
|
+
const existing = Object.keys(env).find((key) => key.toLowerCase() === 'path');
|
|
370
|
+
return existing ?? 'Path';
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function splitPathEntries(value: string): string[] {
|
|
374
|
+
return value
|
|
375
|
+
.split(IS_WIN ? ';' : ':')
|
|
376
|
+
.map((entry) => entry.trim())
|
|
377
|
+
.filter(Boolean);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function prependPathEntries(env: NodeJS.ProcessEnv, entries: string[]): void {
|
|
381
|
+
if (entries.length === 0) return;
|
|
382
|
+
|
|
383
|
+
const pathKey = getPathEnvKey(env);
|
|
384
|
+
const existing = splitPathEntries(asString(env[pathKey]));
|
|
385
|
+
const seen = new Set<string>();
|
|
386
|
+
const ordered: string[] = [];
|
|
387
|
+
|
|
388
|
+
for (const entry of [...entries, ...existing]) {
|
|
389
|
+
const normalized = IS_WIN ? entry.toLowerCase() : entry;
|
|
390
|
+
if (seen.has(normalized)) continue;
|
|
391
|
+
seen.add(normalized);
|
|
392
|
+
ordered.push(entry);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
env[pathKey] = ordered.join(IS_WIN ? ';' : ':');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function isExplicitCommandPath(command: string): boolean {
|
|
399
|
+
return command.includes('/') || command.includes('\\') || /^[A-Za-z]:/.test(command);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function getWindowsCommandExtensions(env: NodeJS.ProcessEnv): string[] {
|
|
403
|
+
const fromEnv = asString(env.PATHEXT || process.env.PATHEXT)
|
|
404
|
+
.split(';')
|
|
405
|
+
.map((value) => value.trim().toLowerCase())
|
|
406
|
+
.filter(Boolean);
|
|
407
|
+
|
|
408
|
+
const preferred = ['.exe', '.com', '.ps1', '.cmd', '.bat'];
|
|
409
|
+
const out: string[] = [];
|
|
410
|
+
for (const ext of [...preferred, ...fromEnv]) {
|
|
411
|
+
const normalized = ext.startsWith('.') ? ext : `.${ext}`;
|
|
412
|
+
if (!out.includes(normalized)) {
|
|
413
|
+
out.push(normalized);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return out;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function resolveWindowsCommandPath(command: string, env: NodeJS.ProcessEnv): string {
|
|
420
|
+
const extensions = getWindowsCommandExtensions(env);
|
|
421
|
+
|
|
422
|
+
const tryCandidates = (basePath: string): string => {
|
|
423
|
+
if (existsSync(basePath)) return basePath;
|
|
424
|
+
|
|
425
|
+
const lower = basePath.toLowerCase();
|
|
426
|
+
for (const ext of extensions) {
|
|
427
|
+
if (lower.endsWith(ext)) continue;
|
|
428
|
+
const candidate = `${basePath}${ext}`;
|
|
429
|
+
if (existsSync(candidate)) {
|
|
430
|
+
return candidate;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return '';
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
if (isExplicitCommandPath(command)) {
|
|
438
|
+
return tryCandidates(command);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const pathValue = asString(env[getPathEnvKey(env)]);
|
|
442
|
+
for (const entry of splitPathEntries(pathValue)) {
|
|
443
|
+
const resolved = tryCandidates(join(entry, command));
|
|
444
|
+
if (resolved) {
|
|
445
|
+
return resolved;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return '';
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function quoteCmdArgument(value: string): string {
|
|
453
|
+
if (!value) return '""';
|
|
454
|
+
|
|
455
|
+
const escaped = value
|
|
456
|
+
.replace(/([()%!^"<>&|])/g, '^$1')
|
|
457
|
+
.replace(/\r/g, ' ')
|
|
458
|
+
.replace(/\n/g, ' ');
|
|
459
|
+
|
|
460
|
+
return /[\s()%!^"<>&|]/.test(value) ? `"${escaped}"` : escaped;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function prepareCommandForSpawn(command: string, args: string[], env: NodeJS.ProcessEnv): { command: string; args: string[] } {
|
|
464
|
+
if (!IS_WIN) {
|
|
465
|
+
return { command, args };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const resolved = resolveWindowsCommandPath(command, env);
|
|
469
|
+
if (!resolved) {
|
|
470
|
+
return { command, args };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const lower = resolved.toLowerCase();
|
|
474
|
+
if (lower.endsWith('.ps1')) {
|
|
475
|
+
return {
|
|
476
|
+
command: 'powershell.exe',
|
|
477
|
+
args: ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', resolved, ...args]
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (lower.endsWith('.cmd') || lower.endsWith('.bat')) {
|
|
482
|
+
const cmdExe = asString(env.ComSpec || env.COMSPEC, 'cmd.exe');
|
|
483
|
+
const commandLine = [resolved, ...args].map((value) => quoteCmdArgument(value)).join(' ');
|
|
484
|
+
return {
|
|
485
|
+
command: cmdExe,
|
|
486
|
+
args: ['/d', '/s', '/c', commandLine]
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return { command: resolved, args };
|
|
491
|
+
}
|
|
492
|
+
|
|
368
493
|
function runCommand(command: string, args: string[], opts: RunOptions = {}): RunResult {
|
|
369
494
|
try {
|
|
370
495
|
const mergedEnv: NodeJS.ProcessEnv = { ...process.env, ...(opts.env ?? {}) };
|
|
371
|
-
const
|
|
496
|
+
const prepared = prepareCommandForSpawn(command, args, mergedEnv);
|
|
497
|
+
const result = spawnSync(prepared.command, prepared.args, {
|
|
372
498
|
cwd: opts.cwd,
|
|
373
499
|
env: mergedEnv,
|
|
374
500
|
encoding: 'utf8',
|
|
@@ -563,6 +689,122 @@ function applyRecursiveMode(rootDir: string, mode: number): void {
|
|
|
563
689
|
});
|
|
564
690
|
}
|
|
565
691
|
|
|
692
|
+
function applyRecursiveModes(rootDir: string, dirMode: number, fileMode: number): void {
|
|
693
|
+
try {
|
|
694
|
+
chmodSync(rootDir, dirMode);
|
|
695
|
+
} catch {
|
|
696
|
+
// ignore
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
visitFiles(rootDir, (fullPath, entry) => {
|
|
700
|
+
try {
|
|
701
|
+
chmodSync(fullPath, entry.isDirectory() ? dirMode : fileMode);
|
|
702
|
+
} catch {
|
|
703
|
+
// ignore
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
type CommandSpec = {
|
|
709
|
+
command: string;
|
|
710
|
+
args: string[];
|
|
711
|
+
env?: Record<string, string | undefined>;
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
function withOptionalSudo(command: string, args: string[], env?: Record<string, string | undefined>): CommandSpec {
|
|
715
|
+
const isRoot = typeof process.getuid === 'function' ? process.getuid() === 0 : false;
|
|
716
|
+
if (isRoot || !hasBin('sudo')) {
|
|
717
|
+
return { command, args, env };
|
|
718
|
+
}
|
|
719
|
+
return { command: 'sudo', args: [command, ...args], env };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function getUnixJqInstallPlan(): { name: string; steps: CommandSpec[] } | null {
|
|
723
|
+
if (hasBin('apt-get')) {
|
|
724
|
+
return {
|
|
725
|
+
name: 'apt-get',
|
|
726
|
+
steps: [
|
|
727
|
+
withOptionalSudo('apt-get', ['update', '-qq'], { DEBIAN_FRONTEND: 'noninteractive' }),
|
|
728
|
+
withOptionalSudo('apt-get', ['install', '-y', 'jq'], { DEBIAN_FRONTEND: 'noninteractive' })
|
|
729
|
+
]
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (hasBin('dnf')) {
|
|
734
|
+
return {
|
|
735
|
+
name: 'dnf',
|
|
736
|
+
steps: [withOptionalSudo('dnf', ['install', '-y', 'jq'])]
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (hasBin('yum')) {
|
|
741
|
+
return {
|
|
742
|
+
name: 'yum',
|
|
743
|
+
steps: [withOptionalSudo('yum', ['install', '-y', 'jq'])]
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (hasBin('pacman')) {
|
|
748
|
+
return {
|
|
749
|
+
name: 'pacman',
|
|
750
|
+
steps: [withOptionalSudo('pacman', ['-Sy', '--noconfirm', 'jq'])]
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (hasBin('apk')) {
|
|
755
|
+
return {
|
|
756
|
+
name: 'apk',
|
|
757
|
+
steps: [withOptionalSudo('apk', ['add', 'jq'])]
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (hasBin('zypper')) {
|
|
762
|
+
return {
|
|
763
|
+
name: 'zypper',
|
|
764
|
+
steps: [withOptionalSudo('zypper', ['install', '-y', 'jq'])]
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (hasBin('brew')) {
|
|
769
|
+
return {
|
|
770
|
+
name: 'brew',
|
|
771
|
+
steps: [{ command: 'brew', args: ['install', 'jq'] }]
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
return null;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function ensureUnixJq(api: LoggerApi): boolean {
|
|
779
|
+
const plan = getUnixJqInstallPlan();
|
|
780
|
+
if (!plan) {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
api.logger.info(`[moltbank] jq not found — attempting install via ${plan.name}...`);
|
|
785
|
+
|
|
786
|
+
for (const step of plan.steps) {
|
|
787
|
+
const result = runCommand(step.command, step.args, {
|
|
788
|
+
env: step.env,
|
|
789
|
+
timeoutMs: 180000
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
if (!result.ok) {
|
|
793
|
+
api.logger.warn(`[moltbank] jq install via ${plan.name} failed: ${result.stderr}`);
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (!hasBin('jq')) {
|
|
799
|
+
api.logger.warn(`[moltbank] jq install via ${plan.name} finished, but jq is still not visible in PATH`);
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const version = runCommand('jq', ['--version'], { silent: true });
|
|
804
|
+
api.logger.info(`[moltbank] ✓ jq installed via ${plan.name} (${version.stdout || 'unknown version'})`);
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
|
|
566
808
|
function findWindowsJqBinaryPath(): string {
|
|
567
809
|
if (!IS_WIN) return '';
|
|
568
810
|
|
|
@@ -652,6 +894,7 @@ function ensureWindowsJqShim(api: LoggerApi): boolean {
|
|
|
652
894
|
const shimBody = `@echo off\r\n"${escapedSource}" %*\r\n`;
|
|
653
895
|
writeFileSync(shimPath, shimBody, 'utf8');
|
|
654
896
|
}
|
|
897
|
+
prependPathEntries(process.env, [npmBinDir]);
|
|
655
898
|
} catch (e) {
|
|
656
899
|
api.logger.warn('[moltbank] could not create jq shim in npm global bin: ' + String(e));
|
|
657
900
|
return false;
|
|
@@ -816,6 +1059,10 @@ function ensureJq(api: LoggerApi) {
|
|
|
816
1059
|
}
|
|
817
1060
|
|
|
818
1061
|
if (!IS_WIN) {
|
|
1062
|
+
if (ensureUnixJq(api)) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
819
1066
|
api.logger.warn('[moltbank] ✗ jq not found in PATH (required by MoltBank wrapper on host mode)');
|
|
820
1067
|
api.logger.warn(`[moltbank] install jq (${getJqInstallHint()}) and rerun setup`);
|
|
821
1068
|
return;
|
|
@@ -1111,20 +1358,20 @@ function ensureSkillPermissions(skillDir: string, api: LoggerApi) {
|
|
|
1111
1358
|
}
|
|
1112
1359
|
|
|
1113
1360
|
if (existsSync(assetsDir)) {
|
|
1114
|
-
|
|
1115
|
-
api.logger.info('[moltbank] ✓ assets/ permissions →
|
|
1361
|
+
applyRecursiveModes(assetsDir, 0o750, 0o640);
|
|
1362
|
+
api.logger.info('[moltbank] ✓ assets/ permissions → 750 dirs, 640 files');
|
|
1116
1363
|
}
|
|
1117
1364
|
if (existsSync(configFile)) {
|
|
1118
1365
|
try {
|
|
1119
|
-
chmodSync(configFile,
|
|
1366
|
+
chmodSync(configFile, 0o600);
|
|
1120
1367
|
} catch {
|
|
1121
1368
|
// ignore
|
|
1122
1369
|
}
|
|
1123
|
-
api.logger.info('[moltbank] ✓ mcporter.json permissions →
|
|
1370
|
+
api.logger.info('[moltbank] ✓ mcporter.json permissions → 600');
|
|
1124
1371
|
}
|
|
1125
1372
|
if (existsSync(scriptsDir)) {
|
|
1126
|
-
|
|
1127
|
-
api.logger.info('[moltbank] ✓ scripts/ permissions →
|
|
1373
|
+
applyRecursiveModes(scriptsDir, 0o750, 0o750);
|
|
1374
|
+
api.logger.info('[moltbank] ✓ scripts/ permissions → 750');
|
|
1128
1375
|
normalizeFilesMatching(scriptsDir, () => true);
|
|
1129
1376
|
api.logger.info('[moltbank] ✓ scripts/ line endings normalized (CRLF → LF)');
|
|
1130
1377
|
}
|