OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / plugins / openclaw / index.ts

162 lines · typescript ref:
/**
 * HiveRelay OpenClaw Plugin
 *
 * Provides in-process control of a HiveRelay node for OpenClaw agents.
 * Falls back to HTTP API calls if the node is running as a separate process.
 *
 * Tools exposed:
 *   hiverelay_start   — Start relay node
 *   hiverelay_stop    — Stop relay node
 *   hiverelay_seed    — Seed a Pear app
 *   hiverelay_status  — Get node stats
 *   hiverelay_metrics — Get Prometheus metrics
 */

const API_BASE = 'http://127.0.0.1:9100';

interface RelayConfig {
  storage?: string;
  region?: string;
  maxStorage?: string;
  port?: number;
}

interface StatusResponse {
  running: boolean;
  publicKey: string | null;
  seededApps: number;
  connections: number;
  relay: {
    activeCircuits: number;
    totalCircuitsServed: number;
    totalBytesRelayed: number;
    capacityUsedPct: number;
  } | null;
  seeder: {
    coresSeeded: number;
    totalBytesStored: number;
    totalBytesServed: number;
    capacityUsedPct: number;
  } | null;
}

interface SeedResult {
  ok: boolean;
  discoveryKey?: string;
  error?: string;
}

interface HealthResponse {
  ok: boolean;
  running: boolean;
  uptime: {
    ms: number;
    hours: number;
    human: string;
  } | null;
}

// ─── API Helpers ────────────────────────────────────────────────────

async function apiGet<T>(path: string, port: number = 9100): Promise<T> {
  const base = port === 9100 ? API_BASE : `http://127.0.0.1:${port}`;
  const res = await fetch(`${base}${path}`);
  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    throw new Error((body as any).error || `API error: ${res.status}`);
  }
  return res.json() as Promise<T>;
}

async function apiPost<T>(path: string, body: object, port: number = 9100): Promise<T> {
  const base = port === 9100 ? API_BASE : `http://127.0.0.1:${port}`;
  const res = await fetch(`${base}${path}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const data = await res.json().catch(() => ({}));
    throw new Error((data as any).error || `API error: ${res.status}`);
  }
  return res.json() as Promise<T>;
}

// ─── Tools ──────────────────────────────────────────────────────────

/**
 * Start a HiveRelay node.
 * Launches the relay daemon as a background process via CLI.
 */
export async function hiverelay_start(config: RelayConfig = {}): Promise<string> {
  const args = ['hiverelay', 'start', '--quiet'];
  if (config.storage) args.push('--storage', config.storage);
  if (config.region) args.push('--region', config.region);
  if (config.maxStorage) args.push('--max-storage', config.maxStorage);
  if (config.port) args.push('--port', String(config.port));

  // Use child_process to spawn in background
  const { execSync } = await import('child_process');
  execSync(
    `nohup ${args.join(' ')} > ~/.hiverelay/relay.log 2>&1 &` +
    ` && echo $! > ~/.hiverelay/relay.pid`,
    { shell: '/bin/bash' }
  );

  // Wait briefly for startup
  await new Promise(r => setTimeout(r, 2000));

  try {
    const health = await apiGet<HealthResponse>('/health', config.port);
    return `Relay node started. Running: ${health.running}, Uptime: ${health.uptime?.human || 'starting'}`;
  } catch {
    return 'Relay node process started. Waiting for it to become ready...';
  }
}

/**
 * Stop a running HiveRelay node.
 */
export async function hiverelay_stop(): Promise<string> {
  try {
    const { execSync } = await import('child_process');
    execSync(
      'kill $(cat ~/.hiverelay/relay.pid 2>/dev/null) 2>/dev/null; rm -f ~/.hiverelay/relay.pid',
      { shell: '/bin/bash' }
    );
    return 'Relay node stopped.';
  } catch {
    return 'No running relay node found (or already stopped).';
  }
}

/**
 * Seed a Pear app by its public key.
 */
export async function hiverelay_seed(appKey: string, opts?: { port?: number }): Promise<SeedResult> {
  return apiPost<SeedResult>('/seed', { appKey }, opts?.port);
}

/**
 * Get current relay node status.
 */
export async function hiverelay_status(opts?: { port?: number }): Promise<StatusResponse> {
  return apiGet<StatusResponse>('/status', opts?.port);
}

/**
 * Get Prometheus-formatted metrics.
 */
export async function hiverelay_metrics(opts?: { port?: number }): Promise<string> {
  const port = opts?.port || 9100;
  const res = await fetch(`http://127.0.0.1:${port}/metrics`);
  return res.text();
}

/**
 * Health check.
 */
export async function hiverelay_health(opts?: { port?: number }): Promise<HealthResponse> {
  return apiGet<HealthResponse>('/health', opts?.port);
}