@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.
Files changed (2) hide show
  1. package/index.ts +254 -7
  2. 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 result = spawnSync(command, args, {
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
- applyRecursiveMode(assetsDir, 0o777);
1115
- api.logger.info('[moltbank] ✓ assets/ permissions → 777');
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, 0o666);
1366
+ chmodSync(configFile, 0o600);
1120
1367
  } catch {
1121
1368
  // ignore
1122
1369
  }
1123
- api.logger.info('[moltbank] ✓ mcporter.json permissions → 666');
1370
+ api.logger.info('[moltbank] ✓ mcporter.json permissions → 600');
1124
1371
  }
1125
1372
  if (existsSync(scriptsDir)) {
1126
- applyRecursiveMode(scriptsDir, 0o755);
1127
- api.logger.info('[moltbank] ✓ scripts/ permissions → 755');
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moltbankhq/openclaw",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "MoltBank stablecoin treasury CLI and OpenClaw plugin",
5
5
  "main": "index.ts",
6
6
  "bin": {