import type { Agent } from '../types'; import { aggressiveActionHint, canRunAggressiveAction, type AggressiveRemoteAction, } from './aggressiveActions'; /** Phase C Crucible remote actions (wired in agent + CrucibleExpandedOps). */ export const CRUCIBLE_PHASE_C_ACTIONS = [ 'smb_shares', 'spread_status', 'credential_vault_list', 'secure_wipe', 'tunnel_ssh_forward', ] as const; export type CruciblePhaseCAction = (typeof CRUCIBLE_PHASE_C_ACTIONS)[number]; export function isWindowsPlatform(platform?: string): boolean { if (!platform) return true; return platform.toLowerCase().includes('win'); } export function onlineAgents(agents: Agent[]): Agent[] { return agents.filter((a) => a.status === 'online'); } export function windowsOnlineAgents(agents: Agent[]): Agent[] { return onlineAgents(agents).filter((a) => isWindowsPlatform(a.platform)); } /** True when at least one online selected agent can run the aggressive action. */ export function selectionCanRunAggressive( action: AggressiveRemoteAction, agents: Agent[] ): boolean { const targets = onlineAgents(agents); if (targets.length === 0) return false; return targets.some((a) => canRunAggressiveAction(action, a.capabilities, a.platform)); } /** Disabled-state tooltip for bulk aggressive ops across a mixed selection. */ export function selectionAggressiveHint( action: AggressiveRemoteAction, agents: Agent[] ): string | undefined { const targets = onlineAgents(agents); if (targets.length === 0) return 'Select at least one online node'; if (selectionCanRunAggressive(action, agents)) return undefined; const blocked = targets.find( (a) => !canRunAggressiveAction(action, a.capabilities, a.platform) ); return aggressiveActionHint(action, blocked?.capabilities, blocked?.platform); } /** Parse camera_list newline output into device paths/names. */ export function parseCameraListMessage(message: string): string[] { return message .split('\n') .map((l) => l.trim()) .filter((l) => l.length > 0 && !l.startsWith('[')); } export interface PortForwardRow { id: string; localPort: string; remoteHostPort: string; sshUser: string; } export interface SSHForwardPayload { local_port: number; remote_host: string; remote_port: number; ssh_user?: string; } /** Split "host:port" with optional IPv6 bracket form [::1]:22 */ export function parseRemoteHostPort(raw: string): { host: string; port: number } | null { const trimmed = raw.trim(); if (!trimmed) return null; if (trimmed.startsWith('[')) { const end = trimmed.indexOf(']'); if (end < 0) return null; const host = trimmed.slice(1, end); const rest = trimmed.slice(end + 1); if (!rest.startsWith(':')) return null; const port = parseInt(rest.slice(1), 10); if (!host || !Number.isFinite(port) || port <= 0 || port > 65535) return null; return { host, port }; } const idx = trimmed.lastIndexOf(':'); if (idx <= 0) return null; const host = trimmed.slice(0, idx); const port = parseInt(trimmed.slice(idx + 1), 10); if (!host || !Number.isFinite(port) || port <= 0 || port > 65535) return null; return { host, port }; } export function buildSSHForwardPayload( localPort: string, remoteHostPort: string, sshUser?: string ): SSHForwardPayload | null { const local = parseInt(localPort.trim(), 10); const remote = parseRemoteHostPort(remoteHostPort); if (!Number.isFinite(local) || local <= 0 || local > 65535 || !remote) return null; const payload: SSHForwardPayload = { local_port: local, remote_host: remote.host, remote_port: remote.port, }; const user = sshUser?.trim(); if (user) payload.ssh_user = user; return payload; } export function newPortForwardRow(id?: string): PortForwardRow { const rowId = id ?? `pf-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; return { id: rowId, localPort: '2222', remoteHostPort: '192.168.1.10:22', sshUser: '' }; } export function validatePortForwardRows(rows: PortForwardRow[]): string | null { if (rows.length === 0) return 'Add at least one forward row'; for (const row of rows) { if (!buildSSHForwardPayload(row.localPort, row.remoteHostPort, row.sshUser)) { return `Invalid row: local ${row.localPort} → ${row.remoteHostPort}`; } } return null; } export type CrucibleDispatchArgs = Record; export interface CrucibleDispatchTarget { id: string; name: string; }