"""Stage the Pi browser workspace for the existing static Hugging Face Space. Run `cd app && npm ci && npm run build` first. Model weights remain in the pinned companion model repository; only static assets and selected source and validation files are published here. """ from pathlib import Path import hashlib import json import shutil root = Path(__file__).resolve().parent.parent out = root / '.space-agent' built = root / 'app/dist' if not (built / 'index.html').is_file(): raise SystemExit('Build app/ first: npm ci && npm run build') if out.exists(): shutil.rmtree(out) shutil.copytree(built, out) # Include reproducible browser source; never include environments or weights. app_out = out / 'app' app_out.mkdir() for name in ['package.json', 'package-lock.json', 'index.html', 'vite.config.mjs', 'README.md']: shutil.copyfile(root / 'app' / name, app_out / name) for name in ['src', 'scripts', 'public', 'tests', 'vendor']: shutil.copytree(root / 'app' / name, app_out / name) modal_design = out / 'design' / 'modal-cat' modal_design.mkdir(parents=True) shutil.copyfile(root / 'design/modal-cat/prompt.md', modal_design / 'prompt.md') validation = out / 'validation' validation.mkdir() for name in [ 'package-manifest.json', 'onnx-validation.json', 'webgpu-parity.json', 'benchmark-summary.json', 'checksum-verification.json', 'template-tests.json', 'control-equivalence.json', 'chinese-arithmetic-investigation.json', 'browser-integration.json', 'space-deployment.json', 'hub-checksums.json', 'agent-browser-hub.json', 'agent-browser-limit128.json', 'agent-browser-dev.json', 'agent-browser-no-gpu.json', 'agent-completion.json', 'agent-space-deployment.json', 'agent-space-embedded.json', 'agent-space-restricted.json', 'agent-space-checksums.json', 'agent-terminal.json', 'agent-terminal-model.json', 'agent-terminal-dev.json', 'agent-terminal-space.json', 'agent-terminal-space-embedded.json', 'agent-terminal-space-checksums.json', 'agent-terminal-dark.json', 'agent-terminal-dark-dev.json', 'agent-terminal-dark-render.json', 'agent-terminal-dark-render-dev.json', 'agent-terminal-dark-model.json', 'agent-terminal-dark-conversation.json', 'agent-terminal-dark-space.json', 'agent-terminal-dark-space-render.json', 'agent-terminal-dark-space-embedded.json', 'agent-terminal-dark-space-checksums.json', 'agent-app-dark-render.json', 'agent-app-dark-terminal.json', 'agent-first-message.json', 'agent-first-message-space-embedded.json', 'agent-app-dark-space-render.json', 'agent-first-message-space-checksums.json', 'agent-shell.json', 'agent-shell-layout.json', 'agent-shell-terminal.json', 'agent-shell-first-message.json', 'agent-shell-space.json', 'agent-shell-space-embedded.json', 'agent-shell-space-checksums.json', 'agent-rounded-header.json', 'agent-rounded-header-render.json', 'agent-local-demo-modal.json', 'agent-compact-header.json', 'agent-context-render.json', 'agent-context-usage.json', 'agent-compact-shell.json', 'agent-white-action-modal.json', 'agent-thinking-context.json', 'agent-thinking-conversation.json', 'agent-thinking-render.json', 'agent-thinking-space.json', 'agent-thinking-space-checksums.json', 'agent-cached-start.json', 'agent-cached-first-message.json', 'agent-runtime-meter.json', 'agent-runtime-meter-render.json', 'agent-nested-radius.json', 'agent-runtime-meter-adaptive.json', 'agent-adaptive-width.json', 'agent-mascot.json', 'agent-mascot-render.json', 'agent-modal-cat.json', 'agent-modal-cat-space.json', 'agent-loading-paws.json', 'agent-loading-paws-webkit.json', 'agent-credits-send.json', 'agent-mascot-motion.json', 'agent-mascot-motion-layout.json', 'agent-editor-focus.json', 'agent-editor.json', 'agent-starter-products.json', 'agent-products-mascot.json', 'agent-products-json.json', 'agent-json-mascot.json', 'agent-safari-cat.json', 'agent-profit-demo-review.md', 'agent-profit-demo.json', 'agent-profit-demo-guidance.json', 'agent-profit-demo-verified.json', 'agent-profit-demo-clear.json', 'agent-profit-demo-cost-change.json', 'agent-profit-demo-focused.json', 'agent-profit-demo-focused-cost.json', 'agent-profit-starter.json', 'agent-profit-onboarding.json', 'agent-profit-mascot-final.json', 'agent-sales-demo.json', 'agent-follow-read.json', 'agent-workspace-runtime.json', 'load-flow.json', 'column-resizer.json', 'cat-interactions.json', 'modal-paws-open.json', 'terminal-paws.json', 'thinking-spacing.json', ]: source = root / 'validation' / name if source.is_file(): shutil.copyfile(source, validation / name) scripts = out / 'scripts' scripts.mkdir() for name in ['download_source.py', 'build.sh', 'package_model.py', 'reference.py', 'validate_onnx.py', 'validate_webgpu.py', 'serve.py', 'prepare_space.py']: shutil.copyfile(root / 'scripts' / name, scripts / name) for folder in ['browser-agent', 'download-audit']: report = out / 'research' / folder report.mkdir(parents=True) shutil.copyfile(root / 'research' / folder / 'REPORT.md', report / 'REPORT.md') for name in ['pi-agent-README.md', 'pi-ai-README.md', 'just-bash-README.md']: shutil.copyfile(root / 'research/browser-agent' / name, out / 'research/browser-agent' / name) audit = out / 'research/multiturn-audit' audit.mkdir(parents=True) for source in sorted((root / 'research/multiturn-audit').iterdir()): if source.is_file() and (source.name == 'REPORT.md' or source.suffix in {'.json', '.py', '.mjs'}): shutil.copyfile(source, audit / source.name) shutil.copyfile(root / 'requirements-convert.txt', out / 'requirements-convert.txt') shutil.copyfile('/usr/share/common-licenses/Apache-2.0', out / 'LICENSE') shutil.copyfile(root / 'SPACE_README.md', out / 'README.md') shutil.copyfile(root / 'SPACE_README.md', out / 'SPACE_README.md') (out / 'NOTICE').write_text( 'Base model: OpenBMB MiniCPM5-2B, Apache-2.0. Independently converted and quantized.\n' 'Agent: Pi, earendil-works. Virtual shell: just-bash, Vercel.\n' 'Inference: Hugging Face Transformers.js and Microsoft ONNX Runtime.\n' 'Bundled dependency licenses and notices are in licenses/.\n' ) (out / '.gitattributes').write_text( '*.onnx filter=lfs diff=lfs merge=lfs -text\n' '*.onnx_data* filter=lfs diff=lfs merge=lfs -text\n' '*.wasm filter=lfs diff=lfs merge=lfs -text\n' ) files = {str(p.relative_to(out)): {'bytes': p.stat().st_size, 'sha256': hashlib.sha256(p.read_bytes()).hexdigest()} for p in sorted(out.rglob('*')) if p.is_file()} (out / 'deployment-manifest.json').write_text(json.dumps({'files': files}, indent=2) + '\n') assert not any('.onnx_data' in name or 'node_modules/' in name for name in files) print('Staged', len(files) + 1, 'files;', sum(f['bytes'] for f in files.values()), 'bytes') print('Upload:', 'hf upload townbox/MiniCPM5-2B-WebGPU-Pi .space-agent . --type space')