add 6 skills to repo + update skill-review for xiaoming
- Add code-interpreter, kokoro-tts, remotion-best-practices, research-to-paper-slides, summarize, tavily-tool to source repo - skill-review: add main/xiaoming agent mapping in handler.ts + SKILL.md - tts-voice: handler.ts updates from agent workspace Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
241
skills/code-interpreter/scripts/run_code.py
Normal file
241
skills/code-interpreter/scripts/run_code.py
Normal file
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
WORKSPACE = pathlib.Path('/home/selig/.openclaw/workspace').resolve()
|
||||
RUNS_DIR = WORKSPACE / '.tmp' / 'code-interpreter-runs'
|
||||
MAX_PREVIEW = 12000
|
||||
ARTIFACT_SCAN_LIMIT = 100
|
||||
PACKAGE_PROBES = ['pandas', 'numpy', 'matplotlib']
|
||||
PYTHON_BIN = str(WORKSPACE / '.venv-code-interpreter' / 'bin' / 'python')
|
||||
|
||||
|
||||
def current_python_paths(run_dir_path: pathlib.Path) -> str:
|
||||
"""Build PYTHONPATH: run_dir (for ci_helpers) only.
|
||||
Venv site-packages are already on sys.path when using PYTHON_BIN."""
|
||||
return str(run_dir_path)
|
||||
|
||||
|
||||
def read_code(args: argparse.Namespace) -> str:
|
||||
sources = [bool(args.code), bool(args.file), bool(args.stdin)]
|
||||
if sum(sources) != 1:
|
||||
raise SystemExit('Provide exactly one of --code, --file, or --stdin')
|
||||
if args.code:
|
||||
return args.code
|
||||
if args.file:
|
||||
return pathlib.Path(args.file).read_text(encoding='utf-8')
|
||||
return sys.stdin.read()
|
||||
|
||||
|
||||
def ensure_within_workspace(path_str: Optional[str], must_exist: bool = True) -> pathlib.Path:
|
||||
if not path_str:
|
||||
return WORKSPACE
|
||||
p = pathlib.Path(path_str).expanduser().resolve()
|
||||
if p != WORKSPACE and WORKSPACE not in p.parents:
|
||||
raise SystemExit(f'Path must stay inside workspace: {WORKSPACE}')
|
||||
if must_exist and (not p.exists() or not p.is_dir()):
|
||||
raise SystemExit(f'Path not found or not a directory: {p}')
|
||||
return p
|
||||
|
||||
|
||||
def ensure_output_path(path_str: Optional[str]) -> Optional[pathlib.Path]:
|
||||
if not path_str:
|
||||
return None
|
||||
p = pathlib.Path(path_str).expanduser().resolve()
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
return p
|
||||
|
||||
|
||||
def write_text(path_str: Optional[str], text: str) -> None:
|
||||
p = ensure_output_path(path_str)
|
||||
if not p:
|
||||
return
|
||||
p.write_text(text, encoding='utf-8')
|
||||
|
||||
|
||||
def truncate(text: str) -> str:
|
||||
if len(text) <= MAX_PREVIEW:
|
||||
return text
|
||||
extra = len(text) - MAX_PREVIEW
|
||||
return text[:MAX_PREVIEW] + f'\n...[truncated {extra} chars]'
|
||||
|
||||
|
||||
def package_status() -> dict:
|
||||
out: dict[str, bool] = {}
|
||||
for name in PACKAGE_PROBES:
|
||||
proc = subprocess.run(
|
||||
[PYTHON_BIN, '-c', f"import importlib.util; print('1' if importlib.util.find_spec('{name}') else '0')"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
errors='replace',
|
||||
)
|
||||
out[name] = proc.stdout.strip() == '1'
|
||||
return out
|
||||
|
||||
|
||||
def rel_to(path: pathlib.Path, base: pathlib.Path) -> str:
|
||||
try:
|
||||
return str(path.relative_to(base))
|
||||
except Exception:
|
||||
return str(path)
|
||||
|
||||
|
||||
def scan_artifacts(base_dir: pathlib.Path, root_label: str) -> list[dict]:
|
||||
if not base_dir.exists():
|
||||
return []
|
||||
items: list[dict] = []
|
||||
for p in sorted(base_dir.rglob('*')):
|
||||
if len(items) >= ARTIFACT_SCAN_LIMIT:
|
||||
break
|
||||
if p.is_file():
|
||||
try:
|
||||
size = p.stat().st_size
|
||||
except Exception:
|
||||
size = None
|
||||
items.append({
|
||||
'root': root_label,
|
||||
'path': str(p),
|
||||
'relative': rel_to(p, base_dir),
|
||||
'bytes': size,
|
||||
})
|
||||
return items
|
||||
|
||||
|
||||
def write_helper(run_dir_path: pathlib.Path, artifact_dir: pathlib.Path) -> None:
|
||||
helper = run_dir_path / 'ci_helpers.py'
|
||||
helper.write_text(
|
||||
"""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import os
|
||||
|
||||
WORKSPACE = Path(os.environ['OPENCLAW_WORKSPACE'])
|
||||
RUN_DIR = Path(os.environ['CODE_INTERPRETER_RUN_DIR'])
|
||||
ARTIFACT_DIR = Path(os.environ['CODE_INTERPRETER_ARTIFACT_DIR'])
|
||||
|
||||
|
||||
def save_text(name: str, text: str) -> str:
|
||||
path = ARTIFACT_DIR / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text, encoding='utf-8')
|
||||
return str(path)
|
||||
|
||||
|
||||
def save_json(name: str, data) -> str:
|
||||
path = ARTIFACT_DIR / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding='utf-8')
|
||||
return str(path)
|
||||
""".lstrip(),
|
||||
encoding='utf-8',
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description='Local Python runner for OpenClaw code-interpreter skill')
|
||||
parser.add_argument('--code', help='Python code to execute')
|
||||
parser.add_argument('--file', help='Path to a Python file to execute')
|
||||
parser.add_argument('--stdin', action='store_true', help='Read Python code from stdin')
|
||||
parser.add_argument('--cwd', help='Working directory inside workspace')
|
||||
parser.add_argument('--artifact-dir', help='Artifact directory inside workspace to keep outputs')
|
||||
parser.add_argument('--timeout', type=int, default=20, help='Timeout seconds (default: 20)')
|
||||
parser.add_argument('--stdout-file', help='Optional file path to save full stdout')
|
||||
parser.add_argument('--stderr-file', help='Optional file path to save full stderr')
|
||||
parser.add_argument('--keep-run-dir', action='store_true', help='Keep generated temp run directory even on success')
|
||||
args = parser.parse_args()
|
||||
|
||||
code = read_code(args)
|
||||
cwd = ensure_within_workspace(args.cwd)
|
||||
RUNS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
run_dir_path = pathlib.Path(tempfile.mkdtemp(prefix='run-', dir=str(RUNS_DIR))).resolve()
|
||||
artifact_dir = ensure_within_workspace(args.artifact_dir, must_exist=False) if args.artifact_dir else (run_dir_path / 'artifacts')
|
||||
artifact_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
script_path = run_dir_path / 'main.py'
|
||||
script_path.write_text(code, encoding='utf-8')
|
||||
write_helper(run_dir_path, artifact_dir)
|
||||
|
||||
env = {
|
||||
'PATH': os.environ.get('PATH', '/usr/bin:/bin'),
|
||||
'HOME': str(run_dir_path),
|
||||
'PYTHONPATH': current_python_paths(run_dir_path),
|
||||
'PYTHONIOENCODING': 'utf-8',
|
||||
'PYTHONUNBUFFERED': '1',
|
||||
'OPENCLAW_WORKSPACE': str(WORKSPACE),
|
||||
'CODE_INTERPRETER_RUN_DIR': str(run_dir_path),
|
||||
'CODE_INTERPRETER_ARTIFACT_DIR': str(artifact_dir),
|
||||
'MPLBACKEND': 'Agg',
|
||||
}
|
||||
|
||||
started = time.time()
|
||||
timed_out = False
|
||||
exit_code = None
|
||||
stdout = ''
|
||||
stderr = ''
|
||||
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[PYTHON_BIN, '-B', str(script_path)],
|
||||
cwd=str(cwd),
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
errors='replace',
|
||||
timeout=max(1, args.timeout),
|
||||
)
|
||||
exit_code = proc.returncode
|
||||
stdout = proc.stdout
|
||||
stderr = proc.stderr
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
timed_out = True
|
||||
exit_code = 124
|
||||
raw_out = exc.stdout or ''
|
||||
raw_err = exc.stderr or ''
|
||||
stdout = raw_out if isinstance(raw_out, str) else raw_out.decode('utf-8', errors='replace')
|
||||
stderr = (raw_err if isinstance(raw_err, str) else raw_err.decode('utf-8', errors='replace')) + f'\nExecution timed out after {args.timeout}s.'
|
||||
|
||||
duration = round(time.time() - started, 3)
|
||||
|
||||
write_text(args.stdout_file, stdout)
|
||||
write_text(args.stderr_file, stderr)
|
||||
|
||||
artifacts = scan_artifacts(artifact_dir, 'artifactDir')
|
||||
if artifact_dir != run_dir_path:
|
||||
artifacts.extend(scan_artifacts(run_dir_path / 'artifacts', 'runArtifacts'))
|
||||
|
||||
result = {
|
||||
'ok': (exit_code == 0 and not timed_out),
|
||||
'exitCode': exit_code,
|
||||
'timeout': timed_out,
|
||||
'durationSec': duration,
|
||||
'cwd': str(cwd),
|
||||
'runDir': str(run_dir_path),
|
||||
'artifactDir': str(artifact_dir),
|
||||
'packageStatus': package_status(),
|
||||
'artifacts': artifacts,
|
||||
'stdout': truncate(stdout),
|
||||
'stderr': truncate(stderr),
|
||||
}
|
||||
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
|
||||
if not args.keep_run_dir and result['ok'] and artifact_dir != run_dir_path:
|
||||
shutil.rmtree(run_dir_path, ignore_errors=True)
|
||||
|
||||
return 0 if result['ok'] else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user