openclaw-tool-runner.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. const { spawnSync } = require('child_process');
  5. function parseArgs(argv) {
  6. const args = { toolArgs: [] };
  7. for (let i = 0; i < argv.length; i++) {
  8. const token = argv[i];
  9. if (token === '--') {
  10. args.toolArgs = argv.slice(i + 1);
  11. break;
  12. }
  13. if (token === '--tool') {
  14. args.tool = argv[++i];
  15. continue;
  16. }
  17. if (token === '--result-prefix') {
  18. args.resultPrefix = argv[++i];
  19. continue;
  20. }
  21. }
  22. return args;
  23. }
  24. function usage() {
  25. return [
  26. 'Usage:',
  27. ' node openclaw-tool-runner.js --tool <tool.js> --result-prefix <PREFIX> -- [tool args]',
  28. '',
  29. 'The runner resolves tools from ~/.openclaw/tools first and emits PREFIX=<json> after the child tool output.'
  30. ].join('\n');
  31. }
  32. function unique(values) {
  33. return Array.from(new Set(values.filter(Boolean)));
  34. }
  35. function resolveTool(tool) {
  36. if (!tool) return undefined;
  37. if (path.isAbsolute(tool) && fs.existsSync(tool)) return tool;
  38. const cwd = process.cwd();
  39. const toolRoot = __dirname;
  40. const projectRoot = path.resolve(__dirname, '..', '..');
  41. const candidates = unique([
  42. path.join(toolRoot, tool),
  43. path.join(cwd, tool),
  44. path.join(cwd, 'scripts', 'tools', tool),
  45. path.join(projectRoot, 'scripts', 'tools', tool),
  46. path.join(projectRoot, tool)
  47. ]);
  48. return candidates.find(candidate => fs.existsSync(candidate));
  49. }
  50. function extractLastJson(stdout) {
  51. const text = String(stdout || '').trim();
  52. if (!text) return undefined;
  53. for (let i = text.lastIndexOf('{'); i >= 0; i = text.lastIndexOf('{', i - 1)) {
  54. const candidate = text.slice(i).trim();
  55. try {
  56. return JSON.parse(candidate);
  57. } catch {
  58. continue;
  59. }
  60. }
  61. return undefined;
  62. }
  63. function isUnresolvedValue(value) {
  64. if (value === undefined || value === null) return true;
  65. const text = String(value).trim();
  66. return text === '' || /^\{\{[^}]+\}\}$/.test(text);
  67. }
  68. function sanitizeToolArgs(toolArgs) {
  69. const sanitized = [];
  70. for (let i = 0; i < toolArgs.length; i++) {
  71. const token = toolArgs[i];
  72. const next = toolArgs[i + 1];
  73. if (token.startsWith('--') && next !== undefined && !next.startsWith('--')) {
  74. if (!isUnresolvedValue(next)) {
  75. sanitized.push(token, next);
  76. }
  77. i++;
  78. continue;
  79. }
  80. if (!isUnresolvedValue(token)) {
  81. sanitized.push(token);
  82. }
  83. }
  84. return sanitized;
  85. }
  86. function main() {
  87. const args = parseArgs(process.argv.slice(2));
  88. if (!args.tool || !args.resultPrefix || args.help) {
  89. console.log(usage());
  90. process.exit(args.help ? 0 : 1);
  91. }
  92. const toolPath = resolveTool(args.tool);
  93. if (!toolPath) {
  94. const result = {
  95. status: 'error',
  96. message: `Tool not found: ${args.tool}`,
  97. searchedFrom: {
  98. cwd: process.cwd(),
  99. runnerDir: __dirname
  100. }
  101. };
  102. console.log(`${args.resultPrefix}=${JSON.stringify(result)}`);
  103. process.exit(1);
  104. }
  105. const child = spawnSync(process.execPath, [toolPath, ...sanitizeToolArgs(args.toolArgs)], {
  106. cwd: process.cwd(),
  107. encoding: 'utf8',
  108. maxBuffer: 1024 * 1024 * 100
  109. });
  110. if (child.stdout) process.stdout.write(child.stdout);
  111. if (child.stderr) process.stderr.write(child.stderr);
  112. const parsed = extractLastJson(child.stdout);
  113. const result = parsed || {
  114. status: child.status === 0 ? 'ok' : 'error',
  115. tool: args.tool,
  116. toolPath,
  117. message: parsed ? undefined : 'Tool did not emit parseable JSON on stdout'
  118. };
  119. console.log(`${args.resultPrefix}=${JSON.stringify(result)}`);
  120. if (child.error) {
  121. console.error(child.error.message);
  122. process.exit(1);
  123. }
  124. process.exit(child.status === null || child.status === undefined ? 1 : child.status);
  125. }
  126. main();