import fs from 'node:fs'; import path from 'node:path'; const ROOT_DIR = path.resolve(import.meta.dirname, '..'); const DEFAULT_OUTPUT_DIR = path.join(ROOT_DIR, 'dist', 'hf-space'); const COPY_PATHS = [ 'Dockerfile', 'package.json', 'package-lock.json', 'client', 'server', 'scripts' ]; const EXCLUDED_NAMES = new Set([ '.DS_Store', '.env', '.codexmobile', '.git', 'node_modules', 'dist', 'coverage', '.vite', '.cache' ]); const EXCLUDED_SCRIPT_NAMES = new Set([ 'start-all.ps1', 'start-server.mjs', 'start-asr.mjs' ]); const SPACE_README = `--- title: CodexMobile Relay sdk: docker app_port: 7860 --- # CodexMobile Relay This HuggingFace Docker Space runs the CodexMobile relay entrypoint. Configure Space variables: \`\`\`text CODEXMOBILE_MODE=relay HOST=0.0.0.0 PORT=7860 CODEXMOBILE_RELAY_REQUEST_TIMEOUT_MS=120000 CODEXMOBILE_RELAY_HEARTBEAT_MS=15000 CODEXMOBILE_RELAY_IDLE_HEARTBEAT_MS=300000 CODEXMOBILE_RELAY_PENDING_REQUESTS_MAX=64 CODEXMOBILE_RELAY_BROWSER_PENDING_REQUESTS_MAX=6 CODEXMOBILE_RELAY_TOKEN_REQUESTS_PER_MINUTE=120 CODEXMOBILE_RELAY_TOKEN_REQUEST_WINDOW_MS=60000 \`\`\` Mac connector defaults to an unauthenticated HTTP keepalive against \`/api/status?keepalive=1\` every 240000 ms. Active intervals below 60000 ms are ignored. Set \`CODEXMOBILE_RELAY_KEEPALIVE_MS=0\` on the connector only when you explicitly want to disable that Space wakeup traffic. Configure Space secrets: \`\`\`text CODEXMOBILE_RELAY_SECRET= CODEXMOBILE_RELAY_PREVIOUS_SECRET= \`\`\` Do not commit relay secrets, browser tokens, pairing codes, logs, or local state. `; function parseArgs(argv) { const options = { outDir: DEFAULT_OUTPUT_DIR }; for (let index = 0; index < argv.length; index += 1) { const item = argv[index]; if (item === '--out') { const value = argv[index + 1]; if (!value) { throw new Error('--out requires a directory.'); } options.outDir = path.resolve(ROOT_DIR, value); index += 1; continue; } if (item.startsWith('--out=')) { options.outDir = path.resolve(ROOT_DIR, item.slice('--out='.length)); continue; } throw new Error(`Unknown argument: ${item}`); } return options; } function assertSafeOutputDir(outDir) { const resolved = path.resolve(outDir); const distDir = path.join(ROOT_DIR, 'dist'); if (resolved === ROOT_DIR || resolved === distDir) { throw new Error('Refusing to overwrite the repository root or dist root.'); } const relativeToDist = path.relative(distDir, resolved); if (isOutsideRelativePath(relativeToDist)) { throw new Error('Output directory must be inside dist/.'); } assertExistingAncestorInsideDist(nearestExistingPath(resolved), distDir); } function isOutsideRelativePath(relativePath) { return relativePath === '..' || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath); } function nearestExistingPath(targetPath) { let current = targetPath; while (true) { try { fs.lstatSync(current); return current; } catch (error) { if (error.code !== 'ENOENT') { throw error; } const parent = path.dirname(current); if (parent === current) { throw error; } current = parent; } } } function assertExistingAncestorInsideDist(existingPath, distDir) { const resolvedRoot = fs.realpathSync(ROOT_DIR); const resolvedExisting = fs.realpathSync(existingPath); const relativeToRoot = path.relative(resolvedRoot, resolvedExisting); if (isOutsideRelativePath(relativeToRoot)) { throw new Error('Output directory must be inside dist/.'); } if (!fs.existsSync(distDir)) { return; } const resolvedDist = fs.realpathSync(distDir); const relativeToDist = path.relative(resolvedDist, resolvedExisting); if (isOutsideRelativePath(relativeToDist)) { throw new Error('Output directory must be inside dist/.'); } } function shouldCopy(sourcePath) { const name = path.basename(sourcePath); if (EXCLUDED_NAMES.has(name)) { return false; } if (path.basename(path.dirname(sourcePath)) === 'scripts' && EXCLUDED_SCRIPT_NAMES.has(name)) { return false; } return true; } function copyPath(relativePath, outDir) { const source = path.join(ROOT_DIR, relativePath); const target = path.join(outDir, relativePath); if (!fs.existsSync(source)) { throw new Error(`Required path is missing: ${relativePath}`); } fs.cpSync(source, target, { recursive: true, filter: shouldCopy }); } function writeSpaceFiles(outDir) { fs.writeFileSync(path.join(outDir, 'README.md'), SPACE_README); fs.writeFileSync(path.join(outDir, '.dockerignore'), [ '.git', 'node_modules', 'client/dist', 'dist', 'coverage', '.vite', '.cache', '.codexmobile', '.env', '.env.*', '*.log', '.DS_Store', '' ].join('\n')); } function listOutput(outDir) { const stack = ['.']; const files = []; while (stack.length > 0) { const relative = stack.pop(); const absolute = path.join(outDir, relative); for (const entry of fs.readdirSync(absolute, { withFileTypes: true })) { const nextRelative = path.join(relative, entry.name); if (entry.isDirectory()) { stack.push(nextRelative); } else { files.push(nextRelative.replaceAll(path.sep, '/')); } } } return files.sort(); } function main() { const options = parseArgs(process.argv.slice(2)); assertSafeOutputDir(options.outDir); fs.rmSync(options.outDir, { recursive: true, force: true }); fs.mkdirSync(options.outDir, { recursive: true }); for (const relativePath of COPY_PATHS) { copyPath(relativePath, options.outDir); } writeSpaceFiles(options.outDir); const files = listOutput(options.outDir); console.log(`Prepared HuggingFace Docker Space at ${options.outDir}`); console.log(`Files: ${files.length}`); console.log('Next: push this directory to the target HuggingFace Space repository, then configure secrets in Space Settings.'); } main();