#!/usr/bin/env bash
set -euo pipefail

INSTALL_DIR="${NETITGO_INSTALL_DIR:-$HOME/.netitgo/bin}"
BIN_PATH="$INSTALL_DIR/netitgo"
PROFILE_MARKER="# NetItGo CLI"

if ! command -v node >/dev/null 2>&1; then
  echo "NetItGo requires Node.js 18 or newer."
  echo "Install Node.js, then run this installer again."
  exit 1
fi

NODE_MAJOR="$(node -p "process.versions.node.split('.')[0]")"
if [ "$NODE_MAJOR" -lt 18 ]; then
  echo "NetItGo requires Node.js 18 or newer. Found: $(node -v)"
  exit 1
fi

case "$(uname -s)" in
  Darwin|Linux) ;;
  *)
    echo "NetItGo preview supports macOS and Linux first."
    exit 1
    ;;
esac

mkdir -p "$INSTALL_DIR"
cat > "$BIN_PATH" <<'NETITGO_CLI_SOURCE'
#!/usr/bin/env node
import fs from "node:fs";
import http from "node:http";
import os from "node:os";
import path from "node:path";
import crypto from "node:crypto";
import { execFileSync, spawn } from "node:child_process";

const VERSION = "0.3.0";
const HOME = process.env.NETITGO_HOME || path.join(os.homedir(), ".netitgo");
const STATE_FILE = path.join(HOME, "state.json");
const BROWSER_SEATS_FILE = path.join(HOME, "browser-seats.json");
const MODELS_DIR = path.join(HOME, "models");
const LOG_DIR = path.join(HOME, "logs");
const DEFAULT_PORT = 8877;
const GB = 1024 ** 3;
const QUANT_BYTES = {
  fp16: 2,
  bf16: 2,
  fp8: 1,
  q8: 1,
  int8: 1,
  q6: 0.75,
  q5: 0.625,
  q4: 0.5,
  q3: 0.375,
  q2: 0.25,
};
const NETWORK_PROFILES = {
  wifi: { bandwidthGbps: 0.25, latencyMs: 8, label: "Wi-Fi" },
  "1gbe": { bandwidthGbps: 1, latencyMs: 1.2, label: "1GbE" },
  "10gbe": { bandwidthGbps: 10, latencyMs: 0.25, label: "10GbE" },
  thunderbolt: { bandwidthGbps: 40, latencyMs: 0.08, label: "Thunderbolt" },
  local: { bandwidthGbps: 120, latencyMs: 0.02, label: "single machine" },
};
const DEFAULT_COUNCIL_ROLES = ["architect", "skeptic", "implementer", "eli10"];
const COUNCIL_ROLES = {
  architect:
    "You are a senior systems architect. Focus on durable architecture, interfaces, failure modes, and what should be built first.",
  skeptic:
    "You are a skeptical technical reviewer. Challenge assumptions, look for hidden costs, and name what could break.",
  implementer:
    "You are a pragmatic implementation lead. Convert the idea into concrete steps, components, and tradeoffs.",
  eli10:
    "Explain the idea clearly as if the reader is 10 years old, without being condescending.",
  optimist:
    "You are optimistic but technical. Describe the strongest upside and where the idea could work surprisingly well.",
  pessimist:
    "You are pessimistic but fair. Identify the strongest reasons the idea may fail or disappoint users.",
  security:
    "You are a security engineer. Focus on privacy, trust boundaries, authentication, abuse, and data leakage.",
  cost:
    "You are a cost and operations analyst. Focus on cost, energy, maintenance, hardware utilization, and support burden.",
  researcher:
    "You are a research analyst. Compare this idea to known systems and identify what should be validated empirically.",
  chinese:
    "请用中文回答。你是一位资深技术顾问，重点解释这个想法的价值、风险和可执行路径。",
};

const command = process.argv[2] || "help";
const args = process.argv.slice(3);

main().catch((error) => {
  console.error(`netitgo: ${error.message}`);
  process.exit(1);
});

async function main() {
  ensureHome();

  if (command === "help" || command === "--help" || command === "-h") return printHelp();
  if (command === "version" || command === "--version" || command === "-v") return console.log(VERSION);
  if (command === "node") return node(args);
  if (command === "system-info") return printSystemInfo();
  if (command === "doctor") return doctor();
  if (command === "status") return status();
  if (command === "api-key") return printApiKey();
  if (command === "policy") return policy(args);
  if (command === "plan") return plan(args);
  if (command === "council") return council(args);
  if (command === "browser") return browser(args);
  if (command === "start" || command === "serve") return serve(args);
  if (command === "models") return models(args);
  if (command === "pod") return pod(args);

  throw new Error(`unknown command "${command}". Run "netitgo help".`);
}

function ensureHome() {
  fs.mkdirSync(HOME, { recursive: true });
  fs.mkdirSync(MODELS_DIR, { recursive: true });
  fs.mkdirSync(LOG_DIR, { recursive: true });
}

function readState() {
  if (!fs.existsSync(STATE_FILE)) {
    const now = new Date().toISOString();
    const nodeId = `node_${randomToken(10)}`;
    const state = {
      version: VERSION,
      nodeId,
      identity: createNodeIdentity(nodeId, now),
      createdAt: now,
      updatedAt: now,
      activePodId: null,
      pods: {},
    };
    writeState(state);
    return state;
  }

  const state = JSON.parse(fs.readFileSync(STATE_FILE, "utf8"));
  return ensureStateShape(state);
}

function writeState(state) {
  state.updatedAt = new Date().toISOString();
  fs.writeFileSync(STATE_FILE, `${JSON.stringify(state, null, 2)}\n`);
}

function ensureStateShape(state) {
  let changed = false;
  const now = new Date().toISOString();
  if (!state.nodeId) {
    state.nodeId = `node_${randomToken(10)}`;
    changed = true;
  }
  if (!state.identity) {
    state.identity = createNodeIdentity(state.nodeId, now);
    changed = true;
  }

  state.pods ||= {};
  for (const pod of Object.values(state.pods)) {
    if (!pod.policy) {
      pod.policy = defaultPolicy();
      changed = true;
    }
    if (!Array.isArray(pod.members)) {
      pod.members = [];
      changed = true;
    }
    if (!Array.isArray(pod.workers)) {
      pod.workers = [];
      changed = true;
    }
    pod.members = pod.members.map((member) => {
      if (member.nodeId !== state.nodeId || member.fingerprint) return member;
      changed = true;
      return {
        ...member,
        fingerprint: state.identity.fingerprint,
        publicKey: state.identity.publicKey,
      };
    });
  }

  if (changed) writeState(state);
  return state;
}

function createNodeIdentity(nodeId, createdAt) {
  const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519", {
    publicKeyEncoding: { type: "spki", format: "pem" },
    privateKeyEncoding: { type: "pkcs8", format: "pem" },
  });
  const fingerprint = crypto.createHash("sha256").update(publicKey).digest("hex").slice(0, 24);
  return {
    nodeId,
    fingerprint,
    publicKey,
    privateKey,
    createdAt,
  };
}

function defaultPolicy() {
  return {
    admission: "invite-only",
    remoteAccess: "local-only",
    cloudFallback: "disabled",
    coldMode: "ask",
    telemetry: "local",
    allowedBackends: ["ollama", "gguf", "mlx", "llama.cpp", "vllm", "sglang"],
  };
}

function randomToken(bytes = 16) {
  return crypto.randomBytes(bytes).toString("base64url");
}

function parseFlag(flag, fallback) {
  const index = args.indexOf(flag);
  if (index === -1) return fallback;
  return args[index + 1] || fallback;
}

function formatGb(bytes) {
  return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;
}

function activePod(state) {
  if (!state.activePodId) return null;
  return state.pods[state.activePodId] || null;
}

function publicPod(pod) {
  if (!pod) return null;
  return {
    id: pod.id,
    name: pod.name,
    createdAt: pod.createdAt,
    port: pod.port || DEFAULT_PORT,
    ownerFingerprint: pod.ownerFingerprint,
    policy: pod.policy,
    members: pod.members || [],
    workers: (pod.workers || []).map((worker) => ({
      id: worker.id,
      name: worker.name,
      endpoint: worker.endpoint,
      addedAt: worker.addedAt,
      hasToken: Boolean(worker.token),
      lastSeenAt: worker.lastSeenAt || null,
      lastStatus: worker.lastStatus || null,
    })),
  };
}

function publicNodeState(state) {
  return {
    version: state.version || VERSION,
    nodeId: state.nodeId,
    createdAt: state.createdAt,
    updatedAt: state.updatedAt,
    identity: {
      nodeId: state.identity.nodeId,
      fingerprint: state.identity.fingerprint,
      publicKey: state.identity.publicKey,
      createdAt: state.identity.createdAt,
    },
    activePodId: state.activePodId,
  };
}

async function detectHardware() {
  const platform = os.platform();
  const cpus = os.cpus();
  const hw = {
    hostname: os.hostname(),
    platform,
    arch: os.arch(),
    cpu: cpus[0]?.model || "Unknown CPU",
    cpuCores: cpus.length,
    totalMemoryBytes: os.totalmem(),
    freeMemoryBytes: os.freemem(),
    gpus: [],
    accelerators: [],
  };

  if (platform === "darwin") {
    hw.accelerators.push("Metal");
    hw.gpus = detectMacGpus();
  } else if (platform === "linux") {
    hw.gpus = detectLinuxGpus();
    if (hw.gpus.some((gpu) => gpu.backend === "CUDA")) hw.accelerators.push("CUDA");
  }

  const usableMemoryBytes = Math.max(0, os.freemem() - 2 * 1024 ** 3);
  hw.usableMemoryBytes = usableMemoryBytes;
  hw.tier = classifyTier(hw);
  return hw;
}

function detectMacGpus() {
  try {
    const raw = execFileSync("system_profiler", ["SPDisplaysDataType", "-json"], {
      encoding: "utf8",
      timeout: 8000,
    });
    const data = JSON.parse(raw);
    return (data.SPDisplaysDataType || []).map((item) => ({
      name: item.sppci_model || item._name || "Apple GPU",
      vendor: item.spdisplays_vendor || "Apple",
      memory: item.spdisplays_vram || "unified",
      backend: "Metal",
    }));
  } catch {
    return [
      {
        name: "Apple GPU",
        vendor: "Apple",
        memory: "unified",
        backend: "Metal",
      },
    ];
  }
}

function detectLinuxGpus() {
  const gpus = [];

  try {
    const raw = execFileSync(
      "nvidia-smi",
      ["--query-gpu=name,memory.total", "--format=csv,noheader,nounits"],
      { encoding: "utf8", timeout: 5000 },
    );
    raw
      .trim()
      .split("\n")
      .filter(Boolean)
      .forEach((line) => {
        const [name, memoryMb] = line.split(",").map((value) => value.trim());
        gpus.push({
          name,
          vendor: "NVIDIA",
          memory: `${memoryMb} MB`,
          memoryBytes: Number(memoryMb) * 1024 * 1024,
          backend: "CUDA",
        });
      });
  } catch {
    // Continue with best-effort PCI detection.
  }

  if (gpus.length > 0) return gpus;

  try {
    const raw = execFileSync("lspci", { encoding: "utf8", timeout: 5000 });
    raw
      .split("\n")
      .filter((line) => /VGA|3D|Display/i.test(line))
      .forEach((line) => {
        gpus.push({
          name: line.replace(/^[^:]+:\s*/, ""),
          vendor: "Unknown",
          memory: "unknown",
          backend: "CPU/offload",
        });
      });
  } catch {
    // lspci is optional.
  }

  return gpus;
}

function classifyTier(hw) {
  const gpuBytes = hw.gpus.reduce((sum, gpu) => sum + (gpu.memoryBytes || 0), 0);
  const ramGb = hw.totalMemoryBytes / 1024 ** 3;
  const gpuGb = gpuBytes / 1024 ** 3;

  if (gpuGb >= 40 || ramGb >= 96) return "premium";
  if (gpuGb >= 8 || ramGb >= 32) return "standard";
  return "economy";
}

async function scanModels() {
  const models = [];
  const ollama = await fetchOllamaModels();
  models.push(...ollama);
  const mlx = scanMlxModels();
  models.push(...mlx);

  if (fs.existsSync(MODELS_DIR)) {
    for (const file of fs.readdirSync(MODELS_DIR)) {
      if (file.toLowerCase().endsWith(".gguf")) {
        const fullPath = path.join(MODELS_DIR, file);
        const stat = fs.statSync(fullPath);
        models.push({
          id: file.replace(/\.gguf$/i, ""),
          source: "netitgo",
          sizeBytes: stat.size,
          path: fullPath,
        });
      }
    }
  }

  return models;
}

function scanMlxModels() {
  if (os.platform() !== "darwin") return [];
  const roots = [
    path.join(HOME, "mlx-models"),
    path.join(os.homedir(), ".cache", "huggingface", "hub"),
  ];
  const models = [];

  for (const root of roots) {
    if (!fs.existsSync(root)) continue;
    for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
      if (!entry.isDirectory()) continue;
      const fullPath = path.join(root, entry.name);
      const configPath = findModelConfig(fullPath);
      if (!configPath) continue;
      const id = entry.name
        .replace(/^models--/, "")
        .replace(/--/g, "/")
        .replace(/^mlx-community\//, "mlx/");
      models.push({
        id,
        source: "mlx",
        backend: "MLX",
        path: fullPath,
        sizeBytes: directorySizeBytes(fullPath, 2),
      });
    }
  }

  return dedupeModels(models);
}

function findModelConfig(root) {
  const direct = path.join(root, "config.json");
  if (fs.existsSync(direct)) return direct;
  const snapshots = path.join(root, "snapshots");
  if (!fs.existsSync(snapshots)) return null;
  for (const snapshot of fs.readdirSync(snapshots)) {
    const config = path.join(snapshots, snapshot, "config.json");
    if (fs.existsSync(config)) return config;
  }
  return null;
}

function directorySizeBytes(root, maxDepth, depth = 0) {
  if (depth > maxDepth) return 0;
  let total = 0;
  try {
    for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
      const fullPath = path.join(root, entry.name);
      if (entry.isDirectory()) total += directorySizeBytes(fullPath, maxDepth, depth + 1);
      else if (entry.isFile()) total += fs.statSync(fullPath).size;
    }
  } catch {
    return total;
  }
  return total;
}

function dedupeModels(models) {
  const seen = new Set();
  return models.filter((model) => {
    const key = `${model.source}:${model.id}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

function fetchOllamaModels() {
  return new Promise((resolve) => {
    const req = http.get("http://127.0.0.1:11434/api/tags", { timeout: 900 }, (res) => {
      let body = "";
      res.on("data", (chunk) => {
        body += chunk;
      });
      res.on("end", () => {
        try {
          const parsed = JSON.parse(body);
          resolve(
            (parsed.models || []).map((model) => ({
              id: model.name,
              source: "ollama",
              sizeBytes: model.size,
              modifiedAt: model.modified_at,
            })),
          );
        } catch {
          resolve([]);
        }
      });
    });

    req.on("timeout", () => {
      req.destroy();
      resolve([]);
    });
    req.on("error", () => resolve([]));
  });
}

async function printSystemInfo() {
  const hw = await detectHardware();
  console.log(`Node:        ${hw.hostname}`);
  console.log(`Platform:    ${hw.platform}/${hw.arch}`);
  console.log(`CPU:         ${hw.cpu}`);
  console.log(`CPU cores:   ${hw.cpuCores}`);
  console.log(`Memory:      ${formatGb(hw.totalMemoryBytes)} total, ${formatGb(hw.freeMemoryBytes)} free`);
  console.log(`Tier:        ${hw.tier}`);
  console.log(`Accelerator: ${hw.accelerators.join(", ") || "none detected"}`);
  if (hw.gpus.length === 0) {
    console.log("GPU:         none detected");
  } else {
    hw.gpus.forEach((gpu, index) => {
      console.log(`GPU ${index + 1}:       ${gpu.name} (${gpu.backend}, ${gpu.memory})`);
    });
  }
}

async function status() {
  const state = readState();
  const pod = activePod(state);
  const hw = await detectHardware();
  const models = await scanModels();

  console.log(`NetItGo CLI ${VERSION}`);
  console.log(`Home:        ${HOME}`);
  console.log(`Node ID:     ${state.nodeId}`);
  console.log(`Fingerprint: ${state.identity.fingerprint}`);
  console.log(`Tier:        ${hw.tier}`);
  console.log(`Memory:      ${formatGb(hw.totalMemoryBytes)} total, ${formatGb(hw.freeMemoryBytes)} free`);
  console.log(`Models:      ${models.length}`);

  if (!pod) {
    console.log("Pod:         none");
    console.log("Next:        netitgo pod create my-team");
    return;
  }

  console.log(`Pod:         ${pod.name} (${pod.id})`);
  console.log(`API key:     ${pod.apiKey}`);
  console.log(`Endpoint:    http://127.0.0.1:${pod.port || DEFAULT_PORT}/v1`);
  console.log(`Members:     ${pod.members.length}`);
  console.log(`Workers:     ${pod.workers?.length || 0}`);
  console.log(`Policy:      fallback=${pod.policy.cloudFallback}, cold=${pod.policy.coldMode}, access=${pod.policy.remoteAccess}`);
}

function printApiKey() {
  const pod = activePod(readState());
  if (!pod) throw new Error("no active pod. Run \"netitgo pod create <name>\" first.");
  console.log(pod.apiKey);
}

function node(input) {
  const sub = input[0] || "identity";
  if (sub !== "identity") {
    console.log("Usage: netitgo node identity [--json]");
    return;
  }

  const state = readState();
  const publicIdentity = {
    nodeId: state.nodeId,
    fingerprint: state.identity.fingerprint,
    publicKey: state.identity.publicKey,
    home: HOME,
  };

  if (input.includes("--json")) {
    console.log(JSON.stringify(publicIdentity, null, 2));
    return;
  }

  console.log(`Node ID:     ${publicIdentity.nodeId}`);
  console.log(`Fingerprint: ${publicIdentity.fingerprint}`);
  console.log(`Home:        ${publicIdentity.home}`);
  console.log("Public key:");
  console.log(publicIdentity.publicKey.trim());
}

function policy(input) {
  const sub = input[0] || "show";
  if (sub === "show") return showPolicy();
  if (sub === "set") return setPolicy(input.slice(1));

  console.log("Usage:");
  console.log("  netitgo policy show");
  console.log("  netitgo policy set cloud-fallback disabled|overflow|ask");
  console.log("  netitgo policy set remote-access local-only|private-mesh");
  console.log("  netitgo policy set cold-mode disabled|ask|allow");
}

function showPolicy() {
  const pod = activePod(readState());
  if (!pod) throw new Error("no active pod. Run \"netitgo pod create <name>\" first.");
  console.log(`${pod.name} policy:`);
  console.log(`  admission:      ${pod.policy.admission}`);
  console.log(`  remote-access:  ${pod.policy.remoteAccess}`);
  console.log(`  cloud-fallback: ${pod.policy.cloudFallback}`);
  console.log(`  cold-mode:      ${pod.policy.coldMode}`);
  console.log(`  telemetry:      ${pod.policy.telemetry}`);
  console.log(`  backends:       ${pod.policy.allowedBackends.join(", ")}`);
}

function setPolicy(input) {
  const key = input[0];
  const value = input[1];
  if (!key || !value) throw new Error("usage: netitgo policy set <key> <value>");

  const state = readState();
  const pod = activePod(state);
  if (!pod) throw new Error("no active pod. Run \"netitgo pod create <name>\" first.");

  const normalizedKey = key.replace(/_/g, "-");
  const schema = {
    "cloud-fallback": ["disabled", "overflow", "ask"],
    "remote-access": ["local-only", "private-mesh"],
    "cold-mode": ["disabled", "ask", "allow"],
    admission: ["invite-only"],
    telemetry: ["local", "off"],
  };

  if (!schema[normalizedKey]) throw new Error(`unknown policy key "${key}".`);
  if (!schema[normalizedKey].includes(value)) {
    throw new Error(`invalid ${key} value "${value}". Expected: ${schema[normalizedKey].join(", ")}`);
  }

  const field = {
    "cloud-fallback": "cloudFallback",
    "remote-access": "remoteAccess",
    "cold-mode": "coldMode",
    admission: "admission",
    telemetry: "telemetry",
  }[normalizedKey];

  pod.policy[field] = value;
  writeState(state);
  console.log(`Set ${normalizedKey}=${value}`);
}

function pod(input) {
  const sub = input[0] || "help";
  if (sub === "create") return createPod(input.slice(1));
  if (sub === "join") return joinPod(input.slice(1));
  if (sub === "invite") return invitePod();
  if (sub === "members" || sub === "nodes") return listMembers();
  if (sub === "worker" || sub === "workers") return podWorker(input.slice(1));
  if (sub === "list") return listPods();

  console.log("Usage:");
  console.log("  netitgo pod create <name>");
  console.log("  netitgo pod join <invite-url>");
  console.log("  netitgo pod invite");
  console.log("  netitgo pod members");
  console.log("  netitgo pod worker add <name> <url> [--token <api-key>]");
  console.log("  netitgo pod workers [--check]");
  console.log("  netitgo pod list");
}

function createPod(input) {
  const name = input[0] || "local-pod";
  const state = readState();
  const podId = `pod_${randomToken(8)}`;
  const now = new Date().toISOString();
  const pod = {
    id: podId,
    name,
    createdAt: now,
    apiKey: `pk_netitgo_${randomToken(24)}`,
    joinSecret: randomToken(24),
    port: DEFAULT_PORT,
    ownerFingerprint: state.identity.fingerprint,
    policy: defaultPolicy(),
    workers: [],
    members: [
      {
        nodeId: state.nodeId,
        hostname: os.hostname(),
        fingerprint: state.identity.fingerprint,
        publicKey: state.identity.publicKey,
        role: "owner",
        joinedAt: now,
      },
    ],
  };

  state.pods[podId] = pod;
  state.activePodId = podId;
  writeState(state);

  console.log(`Created pod "${name}"`);
  console.log(`Endpoint: http://127.0.0.1:${DEFAULT_PORT}/v1`);
  console.log(`API key:  ${pod.apiKey}`);
  console.log(`Policy:   fallback=${pod.policy.cloudFallback}, cold=${pod.policy.coldMode}, access=${pod.policy.remoteAccess}`);
  console.log("");
  console.log("Invite:");
  console.log(makeInvite(pod));
}

function joinPod(input) {
  const invite = input[0];
  if (!invite) throw new Error("missing invite URL.");
  const parsed = parseInvite(invite);
  const state = readState();
  const now = new Date().toISOString();
  const pod = {
    id: parsed.id,
    name: parsed.name,
    createdAt: parsed.createdAt || now,
    apiKey: `pk_netitgo_${randomToken(24)}`,
    joinSecret: parsed.joinSecret,
    port: DEFAULT_PORT,
    ownerFingerprint: parsed.ownerFingerprint,
    policy: parsed.policy || defaultPolicy(),
    workers: parsed.workers || [],
    members: [
      {
        nodeId: state.nodeId,
        hostname: os.hostname(),
        fingerprint: state.identity.fingerprint,
        publicKey: state.identity.publicKey,
        role: "member",
        joinedAt: now,
      },
    ],
  };

  state.pods[pod.id] = pod;
  state.activePodId = pod.id;
  writeState(state);

  console.log(`Joined pod "${pod.name}"`);
  console.log(`Endpoint: http://127.0.0.1:${DEFAULT_PORT}/v1`);
  console.log(`API key:  ${pod.apiKey}`);
  console.log(`Policy:   fallback=${pod.policy.cloudFallback}, cold=${pod.policy.coldMode}, access=${pod.policy.remoteAccess}`);
}

function invitePod() {
  const pod = activePod(readState());
  if (!pod) throw new Error("no active pod. Run \"netitgo pod create <name>\" first.");
  console.log(makeInvite(pod));
}

function listPods() {
  const state = readState();
  const pods = Object.values(state.pods);
  if (pods.length === 0) {
    console.log("No pods yet.");
    return;
  }

  pods.forEach((pod) => {
    const active = pod.id === state.activePodId ? "*" : " ";
    console.log(`${active} ${pod.name} ${pod.id} members=${pod.members.length}`);
  });
}

function listMembers() {
  const pod = activePod(readState());
  if (!pod) throw new Error("no active pod. Run \"netitgo pod create <name>\" first.");
  console.log(`${pod.name} members:`);
  pod.members.forEach((member) => {
    console.log(`- ${member.hostname || "unknown"} ${member.nodeId} ${member.role || "member"} ${member.fingerprint || "no-fingerprint"}`);
  });
}

async function podWorker(input) {
  const hasSubcommand = input[0] && !input[0].startsWith("--");
  const sub = hasSubcommand ? input[0] : "list";
  const rest = hasSubcommand ? input.slice(1) : input;
  if (sub === "add") return addWorker(rest);
  if (sub === "remove" || sub === "rm") return removeWorker(rest);
  if (sub === "list" || sub === "ls") return listWorkers(rest);

  console.log("Usage:");
  console.log("  netitgo pod worker add <name> <url> [--token <api-key>]");
  console.log("  netitgo pod worker remove <name>");
  console.log("  netitgo pod workers [--check] [--json]");
}

function addWorker(input) {
  const token = parseFlagFrom(input, "--token", parseFlagFrom(input, "--api-key", null));
  const positional = stripFlagValues(input, new Set(["--token", "--api-key"]));
  const name = positional[0];
  const endpoint = normalizeWorkerUrl(positional[1]);
  if (!name || !endpoint) throw new Error("usage: netitgo pod worker add <name> <url>");

  const state = readState();
  const pod = activePod(state);
  if (!pod) throw new Error("no active pod. Run \"netitgo pod create <name>\" first.");

  pod.workers = (pod.workers || []).filter((worker) => worker.name !== name);
  pod.workers.push({
    id: `worker_${randomToken(6)}`,
    name,
    endpoint,
    token: token || null,
    addedAt: new Date().toISOString(),
  });
  writeState(state);
  console.log(`Added worker ${name} ${endpoint}`);
  console.log(token ? "Auth:   bearer token stored for this worker" : "Auth:   no token stored; LAN calls will only work against local or unauthenticated workers");
}

function removeWorker(input) {
  const name = input[0];
  if (!name) throw new Error("usage: netitgo pod worker remove <name>");
  const state = readState();
  const pod = activePod(state);
  if (!pod) throw new Error("no active pod. Run \"netitgo pod create <name>\" first.");
  const before = pod.workers?.length || 0;
  pod.workers = (pod.workers || []).filter((worker) => worker.name !== name);
  writeState(state);
  console.log(before === pod.workers.length ? `No worker named ${name}` : `Removed worker ${name}`);
}

async function listWorkers(input = []) {
  const state = readState();
  const pod = activePod(state);
  if (!pod) throw new Error("no active pod. Run \"netitgo pod create <name>\" first.");
  if (!pod.workers?.length) {
    console.log("No workers registered.");
    console.log("Add one with: netitgo pod worker add macbook http://10.0.0.12:8877 --token <worker-api-key>");
    return;
  }

  if (input.includes("--check")) {
    const checked = await Promise.all((pod.workers || []).map((worker) => checkWorker(worker)));
    pod.workers = pod.workers.map((worker, index) => ({
      ...worker,
      lastSeenAt: checked[index].ok ? new Date().toISOString() : worker.lastSeenAt || null,
      lastStatus: checked[index].ok ? "ok" : checked[index].status,
    }));
    writeState(state);

    if (input.includes("--json")) {
      console.log(JSON.stringify(checked, null, 2));
      return;
    }

    console.log(`${pod.name} workers:`);
    checked.forEach((worker) => {
      const modelCount = worker.capabilities?.inventory?.models?.length ?? 0;
      const tier = worker.capabilities?.node?.tier || "unknown";
      console.log(`- ${worker.name} ${worker.endpoint} ${worker.ok ? "ok" : worker.status} tier=${tier} models=${modelCount}`);
    });
    return;
  }

  if (input.includes("--json")) {
    console.log(JSON.stringify(publicPod(pod).workers, null, 2));
    return;
  }

  console.log(`${pod.name} workers:`);
  pod.workers.forEach((worker) => {
    console.log(`- ${worker.name} ${worker.endpoint} auth=${worker.token ? "token" : "none"} status=${worker.lastStatus || "unknown"}`);
  });
}

function normalizeWorkerUrl(value) {
  if (!value) return null;
  const withProtocol = /^https?:\/\//i.test(value) ? value : `http://${value}`;
  const url = new URL(withProtocol);
  return `${url.protocol}//${url.host}`.replace(/\/$/, "");
}

function stripFlagValues(input, flags) {
  const values = [];
  for (let index = 0; index < input.length; index += 1) {
    const value = input[index];
    if (flags.has(value)) {
      index += 1;
      continue;
    }
    values.push(value);
  }
  return values;
}

async function checkWorker(worker) {
  try {
    const capabilities = await getJson(`${worker.endpoint}/api/capabilities`, 3000, authHeaders(worker.token));
    if (capabilities?.authRequired) {
      return {
        name: worker.name,
        endpoint: worker.endpoint,
        ok: false,
        status: "auth_required",
        capabilities,
      };
    }
    return {
      name: worker.name,
      endpoint: worker.endpoint,
      ok: Boolean(capabilities?.ok),
      status: capabilities?.ok ? "ok" : "unavailable",
      capabilities,
    };
  } catch (error) {
    return {
      name: worker.name,
      endpoint: worker.endpoint,
      ok: false,
      status: error.message || "unavailable",
    };
  }
}

function makeInvite(pod) {
  const payload = Buffer.from(
    JSON.stringify({
      id: pod.id,
      name: pod.name,
      joinSecret: pod.joinSecret,
      createdAt: pod.createdAt,
      ownerFingerprint: pod.ownerFingerprint,
      policy: pod.policy,
      workers: pod.workers || [],
    }),
  ).toString("base64url");
  return `netitgo://join/${payload}`;
}

function parseInvite(invite) {
  const encoded = invite.replace(/^netitgo:\/\/join\//, "");
  try {
    return JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
  } catch {
    throw new Error("invalid invite URL.");
  }
}

async function models(input) {
  const sub = input[0] || "list";
  if (sub !== "list" && sub !== "scan") {
    console.log("Usage: netitgo models list");
    return;
  }

  const models = await scanModels();
  if (models.length === 0) {
    console.log("No local models found.");
    console.log(`Drop GGUF files into ${MODELS_DIR} or start Ollama.`);
    return;
  }

  models.forEach((model) => {
    const size = model.sizeBytes ? formatGb(model.sizeBytes) : "unknown size";
    console.log(`${model.id}  ${model.backend || model.source}  ${size}`);
  });
}

async function plan(input) {
  const modelArg = firstPositional(input);
  if (!modelArg || modelArg === "help") {
    printPlanHelp();
    return;
  }

  const hw = await detectHardware();
  const models = await scanModels();
  const state = readState();
  const pod = activePod(state);
  const matchedModel = models.find((model) => model.id === modelArg);
  const paramsB = parseParamsFlag(input) || inferParamsB(modelArg);
  const quant = normalizeQuant(parseFlagFrom(input, "--quant", "q4"));
  const contextTokens = Number(parseFlagFrom(input, "--context", "8192"));
  const nodeCount = Number(parseFlagFrom(input, "--nodes", String(pod?.members?.length || 1)));
  const networkKey = String(parseFlagFrom(input, "--network", nodeCount > 1 ? "1gbe" : "local")).toLowerCase();
  const network = NETWORK_PROFILES[networkKey] || NETWORK_PROFILES["1gbe"];
  const sizeOverride = parseOptionalSize(parseFlagFrom(input, "--size", null));
  const nodeMemoryOverride = parseOptionalSize(parseFlagFrom(input, "--node-memory", null));
  const gpuMemoryOverride = parseOptionalSize(parseFlagFrom(input, "--gpu-memory", null));
  const json = input.includes("--json");

  if (!matchedModel && !paramsB && !sizeOverride) {
    throw new Error("cannot infer model size. Add --params 70b or --size 42gb.");
  }

  const estimate = buildInferencePlan({
    modelId: modelArg,
    matchedModel,
    paramsB,
    quant,
    contextTokens,
    nodeCount,
    network,
    hw,
    sizeOverride,
    nodeMemoryOverride,
    gpuMemoryOverride,
  });

  if (json) {
    console.log(JSON.stringify(estimate, null, 2));
    return;
  }

  printPlan(estimate);
}

async function council(input) {
  if (input.length === 0 || input[0] === "help" || input.includes("--help")) {
    printCouncilHelp();
    return;
  }

  const prompt = collectFreeText(input);
  if (!prompt) throw new Error("missing prompt. Usage: netitgo council \"your question\"");

  const result = await runCouncil({
    prompt,
    roles: parseCsvFlag(input, "--roles") || DEFAULT_COUNCIL_ROLES,
    requestedModels: parseCsvFlag(input, "--models"),
    aggregatorModel: parseFlagFrom(input, "--aggregator", null),
    timeoutMs: Number(parseFlagFrom(input, "--timeout", "45000")),
  });

  if (input.includes("--json")) {
    console.log(JSON.stringify(result, null, 2));
    return;
  }

  printCouncil(result, input.includes("--trace"));
}

async function browser(input) {
  const sub = input[0] || "url";
  if (sub === "url" || sub === "open") return printBrowserWorkerUrl();
  if (sub === "seats" || sub === "submissions") return listBrowserSeats(input.slice(1));
  if (sub === "clear") return clearBrowserSeats();

  console.log("Usage:");
  console.log("  netitgo browser                 Print the browser WebGPU worker URL");
  console.log("  netitgo browser seats [--json]  List submitted browser role outputs");
  console.log("  netitgo browser clear           Clear submitted browser role outputs");
}

function printBrowserWorkerUrl() {
  const pod = activePod(readState());
  if (!pod) throw new Error("no active pod. Run \"netitgo pod create <name>\" first.");
  const port = pod.port || DEFAULT_PORT;
  console.log("Browser WebGPU seat:");
  console.log(`  http://127.0.0.1:${port}/browser-worker.html`);
  console.log("");
  console.log("Use it for a trusted human-opened browser tab that runs Gemma 4 E2B locally through WebGPU and submits one council role result back to the chair.");
  console.log("For another machine on the LAN, open the LAN URL and paste this pod API key into the page:");
  console.log(`  ${pod.apiKey}`);
}

function listBrowserSeats(input = []) {
  const seats = readBrowserSeats();
  if (input.includes("--json")) {
    console.log(JSON.stringify(seats, null, 2));
    return;
  }
  if (seats.length === 0) {
    console.log("No browser seat submissions yet.");
    console.log("Open the worker page with: netitgo browser");
    return;
  }
  seats.forEach((seat) => {
    console.log(`- ${seat.submittedAt} ${seat.role} ${seat.model} ${seat.ok ? "ok" : "failed"} ${seat.elapsedMs || 0}ms`);
    console.log(`  ${seat.prompt.slice(0, 120).replace(/\s+/g, " ")}${seat.prompt.length > 120 ? "..." : ""}`);
  });
}

function clearBrowserSeats() {
  writeBrowserSeats([]);
  console.log("Cleared browser seat submissions.");
}

function printCouncilHelp() {
  console.log("Usage:");
  console.log("  netitgo council \"Should we use small model councils?\"");
  console.log("  netitgo council \"Review this plan\" --roles architect,skeptic,security,eli10");
  console.log("  netitgo council \"Translate and critique\" --roles researcher,chinese --models qwen3:8b,gemma3:4b");
  console.log("");
  console.log("Options:");
  console.log("  --roles <list>        Comma-separated roles");
  console.log("  --models <list>       Comma-separated local Ollama models to cycle across roles");
  console.log("  --aggregator <model>  Local Ollama model used for synthesis");
  console.log("  --timeout <ms>        Per-model timeout");
  console.log("  --trace               Print each role response");
  console.log("  --json                Print machine-readable output");
}

async function runCouncil({ prompt, roles, requestedModels, aggregatorModel, timeoutMs = 45000 }) {
  const models = await scanModels();
  const ollamaModels = models.filter((model) => model.source === "ollama");
  const normalizedRoles = roles.map((role) => role.trim()).filter(Boolean);
  const selectedModels = selectCouncilModels(ollamaModels, requestedModels, normalizedRoles.length);
  const pod = activePod(readState());
  const workers = pod?.workers || [];
  const startedAt = Date.now();

  if (selectedModels.length === 0 && workers.length === 0) {
    return {
      mode: "council",
      status: "no_models",
      prompt,
      roles: normalizedRoles,
      participants: [],
      synthesis:
        "No local Ollama chat models were found. Start Ollama or install a small model such as qwen3:8b, gemma3:4b, or another 7B-9B class model.",
      elapsedMs: Date.now() - startedAt,
      warnings: ["Council Mode needs complete local models on each participating node; it does not shard one model."],
    };
  }

  const participants = await Promise.all(
    normalizedRoles.map(async (role, index) => {
      const model = selectedModels[index % selectedModels.length];
      const worker = workers.length > 0 ? workers[index % workers.length] : null;
      if (worker) {
        const remote = await askCouncilWorker(worker, {
          role,
          prompt,
          model: model?.id || null,
          timeoutMs,
        });
        if (remote?.ok) return remote;
      }

      if (!model) {
        return {
          role,
          model: "none",
          backend: "local",
          node: "local",
          ok: false,
          content: "No local model available and no worker returned a response.",
        };
      }

      return runCouncilRole({ role, prompt, modelId: model.id, timeoutMs });
    }),
  );

  const successful = participants.filter((item) => item.ok);
  const synthesis = await synthesizeCouncil({
    prompt,
    participants: successful.length > 0 ? participants : [],
    model: aggregatorModel || selectedModels[0].id,
    timeoutMs,
  });

  return {
    mode: "council",
    status: successful.length > 0 ? "ok" : "partial",
    prompt,
    roles: normalizedRoles,
    participants,
    synthesis,
    elapsedMs: Date.now() - startedAt,
    workers: workers.map((worker) => ({ name: worker.name, endpoint: worker.endpoint })),
    warnings: [
      "Council Mode improves coverage and critique, not raw model capacity.",
      "Best results come from diverse models, roles, temperatures, and a strong aggregator.",
    ],
  };
}

async function runCouncilRole({ role, prompt, modelId, timeoutMs = 45000 }) {
  const models = await scanModels();
  const ollamaModels = models.filter((model) => model.source === "ollama");
  const model = modelId
    ? ollamaModels.find((item) => item.id === modelId) || { id: modelId, source: "ollama" }
    : selectCouncilModels(ollamaModels, null, 1)[0];

  if (!model) {
    return {
      role,
      model: "none",
      backend: "ollama",
      node: os.hostname(),
      ok: false,
      content: "No local Ollama model available for this council role.",
    };
  }

  const rolePrompt =
    COUNCIL_ROLES[role] ||
    `You are the ${role} participant in a technical model council. Give a distinct, useful perspective.`;
  const messages = [
    {
      role: "system",
      content: `Context: NetItGo Council Mode means several complete small local AI models run different role-specific prompt variants in parallel, then a chair model synthesizes one answer. It is not a human meeting tool or screen-sharing feature.\n\n${rolePrompt}\nBe concise, concrete, and avoid repeating the other roles. Give your own answer first, then list 2-4 key points.`,
    },
    {
      role: "user",
      content: prompt,
    },
  ];
  const answer = await askOllama(model.id, messages, { timeoutMs, temperature: roleTemperature(role) });
  return {
    role,
    model: model.id,
    backend: model.source,
    node: os.hostname(),
    ok: Boolean(answer),
    content: answer || `No response from ${model.id} before timeout.`,
  };
}

async function askCouncilWorker(worker, body) {
  try {
    const response = await postJson(
      `${worker.endpoint}/api/council/role`,
      body,
      body.timeoutMs + 5000,
      authHeaders(worker.token),
    );
    if (!response) return null;
    return {
      ...response,
      worker: worker.name,
      endpoint: worker.endpoint,
    };
  } catch {
    return null;
  }
}

function selectCouncilModels(ollamaModels, requestedModels, roleCount) {
  if (requestedModels?.length) {
    const byId = new Map(ollamaModels.map((model) => [model.id, model]));
    return requestedModels.map((id) => byId.get(id) || { id, source: "ollama", sizeBytes: null });
  }

  const preferred = [...ollamaModels]
    .filter((model) => !model.sizeBytes || model.sizeBytes <= 20 * GB)
    .sort((a, b) => (a.sizeBytes || 0) - (b.sizeBytes || 0));
  return (preferred.length > 0 ? preferred : ollamaModels).slice(0, Math.max(1, Math.min(roleCount, 6)));
}

function roleTemperature(role) {
  if (role === "skeptic" || role === "security" || role === "cost") return 0.2;
  if (role === "optimist" || role === "researcher") return 0.55;
  return 0.35;
}

async function synthesizeCouncil({ prompt, participants, model, timeoutMs }) {
  if (participants.length === 0) {
    return "The council produced no model responses. Check local models and retry.";
  }

  const transcript = participants
    .map((item) => `## ${item.role} (${item.model})\n${item.content}`)
    .join("\n\n");
  const messages = [
    {
      role: "system",
      content:
        "You are the NetItGo council chair. NetItGo Council Mode means several complete small local AI models run role-specific prompts in parallel, then you synthesize one answer. Preserve disagreement. Do not average weak ideas. Lead with the answer, then give key reasons, risks, and next steps.",
    },
    {
      role: "user",
      content: `Original prompt:\n${prompt}\n\nCouncil responses:\n${transcript}`,
    },
  ];
  const answer = await askOllama(model, messages, { timeoutMs, temperature: 0.25 });
  return answer || heuristicSynthesis(participants);
}

function heuristicSynthesis(participants) {
  const best = participants.find((item) => item.ok)?.content || "";
  const risks = participants
    .filter((item) => /skeptic|security|cost|pessimist/.test(item.role))
    .map((item) => item.content)
    .join("\n\n");
  return ["Council synthesis fallback:", best, risks ? `\nCritical notes:\n${risks}` : ""].join("\n").trim();
}

function printCouncil(result, trace) {
  console.log(`Council:     ${result.status}`);
  console.log(`Roles:       ${result.roles.join(", ")}`);
  console.log(`Elapsed:     ${result.elapsedMs}ms`);
  console.log("");
  console.log("Synthesis:");
  console.log(result.synthesis);
  console.log("");

  if (trace) {
    console.log("Council trace:");
    result.participants.forEach((item) => {
      console.log(`\n## ${item.role} (${item.model})`);
      console.log(item.content);
    });
    console.log("");
  }

  printList("Warnings", result.warnings || []);
}

function parseCsvFlag(input, flag) {
  const value = parseFlagFrom(input, flag, null);
  if (!value) return null;
  return String(value)
    .split(",")
    .map((item) => item.trim())
    .filter(Boolean);
}

function collectFreeText(input) {
  const flagsWithValues = new Set(["--roles", "--models", "--aggregator", "--timeout"]);
  const parts = [];
  for (let index = 0; index < input.length; index += 1) {
    const value = input[index];
    if (flagsWithValues.has(value)) {
      index += 1;
      continue;
    }
    if (value === "--json" || value === "--trace" || value === "--help") continue;
    if (value.startsWith("--")) continue;
    parts.push(value);
  }
  return parts.join(" ").trim();
}

function firstPositional(input) {
  const flagsWithValues = new Set([
    "--params",
    "--quant",
    "--context",
    "--nodes",
    "--network",
    "--size",
    "--node-memory",
    "--gpu-memory",
  ]);
  for (let index = 0; index < input.length; index += 1) {
    const value = input[index];
    if (flagsWithValues.has(value)) {
      index += 1;
      continue;
    }
    if (!value.startsWith("-")) return value;
  }
  return null;
}

function printPlanHelp() {
  console.log("Usage:");
  console.log("  netitgo plan <model> [--params 70b] [--quant q4] [--context 8192]");
  console.log("  netitgo plan qwen3.5:122b --params 122b --quant q4 --nodes 4 --network 10gbe");
  console.log("  netitgo plan llama3.1:405b --params 405b --quant q4 --gpu-memory 8gb");
  console.log("");
  console.log("Options:");
  console.log("  --params <size>       Parameter count, e.g. 70b or 122b");
  console.log("  --size <size>         Known model file size, e.g. 81gb");
  console.log("  --quant <type>        fp16, q8, q6, q5, q4, q3, q2");
  console.log("  --context <tokens>    Context window to estimate KV cache");
  console.log("  --nodes <count>       Simulated pod node count");
  console.log("  --node-memory <size>  Simulated RAM per node, e.g. 16gb");
  console.log("  --gpu-memory <size>   Simulated accelerator memory per node");
  console.log("  --network <type>      wifi, 1gbe, 10gbe, thunderbolt, local");
  console.log("  --json                Print machine-readable output");
}

function buildInferencePlan({
  modelId,
  matchedModel,
  paramsB,
  quant,
  contextTokens,
  nodeCount,
  network,
  hw,
  sizeOverride,
  nodeMemoryOverride,
  gpuMemoryOverride,
}) {
  const modelSizeBytes = sizeOverride || matchedModel?.sizeBytes || estimateWeightBytes(paramsB, quant);
  const estimateSource = sizeOverride
    ? "size override"
    : matchedModel?.sizeBytes
      ? `${matchedModel.source} inventory`
      : `${paramsB}B ${quant} estimate`;
  const layerCount = estimateLayerCount(paramsB, modelId);
  const layerBytes = Math.ceil(modelSizeBytes / layerCount);
  const kvCacheBytes = estimateKvCacheBytes(paramsB, contextTokens);
  const runtimeOverheadBytes = Math.max(1.5 * GB, modelSizeBytes * 0.1);
  const coldRuntimeOverheadBytes = Math.max(1.5 * GB, layerBytes * 2);
  const rawNodeMemoryBytes = nodeMemoryOverride || hw.totalMemoryBytes;
  const usableSystemBytes = estimateUsableSystemBytes(rawNodeMemoryBytes);
  const currentFreeBytes = nodeMemoryOverride ? usableSystemBytes : hw.usableMemoryBytes;
  const acceleratorBytes = gpuMemoryOverride || bestAcceleratorBytes(hw, usableSystemBytes);
  const totalRequiredBytes = modelSizeBytes + kvCacheBytes + runtimeOverheadBytes;
  const perNodeUsableBytes = Math.max(acceleratorBytes, usableSystemBytes);
  const aggregateUsableBytes = perNodeUsableBytes * Math.max(1, nodeCount);
  const coldPeakBytes = layerBytes * 2 + kvCacheBytes + coldRuntimeOverheadBytes;
  const diskFree = diskFreeBytes(HOME);
  const diskRequiredBytes = modelSizeBytes * 1.35;
  const reasons = [];
  const warnings = [];
  const next = [];

  let verdict = "fallback";
  let mode = "Cloud fallback recommended";
  let tokenSpeed = "unknown";

  if (totalRequiredBytes <= acceleratorBytes * 0.9) {
    verdict = "fast";
    mode = "Fast single-node accelerator";
    tokenSpeed = "interactive";
    reasons.push("Estimated model, KV cache, and runtime overhead fit in one node accelerator memory.");
    next.push("Route requests to this node directly.");
  } else if (totalRequiredBytes <= usableSystemBytes * 0.86 && hw.platform === "darwin") {
    verdict = "unified";
    mode = "Single-node Apple unified memory";
    tokenSpeed = "usable if memory pressure is low";
    reasons.push("Estimated footprint fits in local Apple unified/system memory.");
    next.push("Prefer MLX or Ollama/Metal on Apple Silicon for this node.");
  } else if (totalRequiredBytes <= aggregateUsableBytes * 0.82 && nodeCount > 1) {
    verdict = network.bandwidthGbps >= 10 ? "distributed" : "distributed_slow";
    mode = network.bandwidthGbps >= 10 ? "Distributed split-model" : "Distributed, but slow";
    tokenSpeed = network.bandwidthGbps >= 10 ? "possibly interactive" : "not interactive";
    reasons.push("Aggregate pod memory can hold the estimated model footprint.");
    warnings.push(`${network.label} may bottleneck every cross-node layer boundary.`);
    next.push("Use one backend family for all shards; do not mix MLX and CUDA inside one model run.");
  } else if (coldPeakBytes <= perNodeUsableBytes * 0.86) {
    verdict = "cold";
    mode = "Cold large model mode";
    tokenSpeed = "slow; batch/offline use only";
    reasons.push("A single layer plus KV cache can fit, so layer streaming is possible.");
    warnings.push("This solves memory capacity, not speed. Disk and memory transfers dominate latency.");
    next.push("Use for offline evaluation, research, and overnight jobs.");
  } else {
    reasons.push("Estimated footprint does not fit a single node, aggregate pod memory, or cold layer peak.");
    next.push("Use a smaller quantization, lower context, more memory, or cloud fallback.");
  }

  if (network.bandwidthGbps < 1 && nodeCount > 1) {
    warnings.push("Wi-Fi-class networking is not recommended for split-model inference.");
  }
  if (!nodeMemoryOverride && currentFreeBytes < Math.min(totalRequiredBytes, perNodeUsableBytes) * 0.35) {
    warnings.push("Current free memory is low; close other workloads before trusting this plan.");
  }
  if (contextTokens > 32768) {
    warnings.push("Long context can dominate memory through KV cache; lower --context for testing.");
  }
  if (diskFree && diskFree < diskRequiredBytes && (verdict === "cold" || verdict === "fallback")) {
    warnings.push(`Cold mode likely needs about ${formatGb(diskRequiredBytes)} free disk for shards and cache.`);
  }
  if (paramsB && paramsB >= 100 && verdict !== "fast") {
    warnings.push("100B+ models can be runnable while still feeling too slow for chat.");
  }

  return {
    model: modelId,
    source: estimateSource,
    paramsB: paramsB || null,
    quant,
    contextTokens,
    nodeCount,
    network: network.label,
    mode,
    verdict,
    estimatedSpeed: tokenSpeed,
    memory: {
      modelWeights: modelSizeBytes,
      kvCache: kvCacheBytes,
      runtimeOverhead: runtimeOverheadBytes,
      coldRuntimeOverhead: coldRuntimeOverheadBytes,
      totalRequired: totalRequiredBytes,
      perNodeUsable: perNodeUsableBytes,
      aggregateUsable: aggregateUsableBytes,
      coldPeak: coldPeakBytes,
      diskFree: diskFree || null,
    },
    layerPlan: {
      estimatedLayers: layerCount,
      layerShardSize: layerBytes,
      diskRequired: diskRequiredBytes,
    },
    reasons,
    warnings,
    next,
  };
}

function printPlan(estimate) {
  console.log(`Model:       ${estimate.model}`);
  console.log(`Source:      ${estimate.source}`);
  console.log(`Mode:        ${estimate.mode}`);
  console.log(`Verdict:     ${estimate.verdict}`);
  console.log(`Speed:       ${estimate.estimatedSpeed}`);
  console.log(`Network:     ${estimate.network}`);
  console.log("");
  console.log("Memory estimate:");
  console.log(`  Weights:      ${formatGb(estimate.memory.modelWeights)}`);
  console.log(`  KV cache:     ${formatGb(estimate.memory.kvCache)} @ ${estimate.contextTokens} tokens`);
  console.log(`  Overhead:     ${formatGb(estimate.memory.runtimeOverhead)}`);
  console.log(`  Cold overhead:${formatGb(estimate.memory.coldRuntimeOverhead)}`);
  console.log(`  Required:     ${formatGb(estimate.memory.totalRequired)}`);
  console.log(`  Per node:     ${formatGb(estimate.memory.perNodeUsable)} usable`);
  console.log(`  Aggregate:    ${formatGb(estimate.memory.aggregateUsable)} usable`);
  console.log(`  Cold peak:    ${formatGb(estimate.memory.coldPeak)}`);
  if (estimate.memory.diskFree) console.log(`  Disk free:    ${formatGb(estimate.memory.diskFree)}`);
  console.log("");
  console.log("Layer plan:");
  console.log(`  Layers:       ${estimate.layerPlan.estimatedLayers}`);
  console.log(`  Shard size:   ${formatGb(estimate.layerPlan.layerShardSize)}`);
  console.log(`  Disk target:  ${formatGb(estimate.layerPlan.diskRequired)}`);
  console.log("");
  printList("Why", estimate.reasons);
  printList("Warnings", estimate.warnings);
  printList("Next", estimate.next);
}

function printList(label, values) {
  if (values.length === 0) return;
  console.log(`${label}:`);
  values.forEach((value) => console.log(`  - ${value}`));
  console.log("");
}

function parseParamsFlag(input) {
  const value = parseFlagFrom(input, "--params", null);
  if (!value) return null;
  const match = String(value).match(/(\d+(?:\.\d+)?)/);
  return match ? Number(match[1]) : null;
}

function inferParamsB(modelId) {
  const matches = [...String(modelId).matchAll(/(\d+(?:\.\d+)?)\s*b/gi)];
  if (matches.length === 0) return null;
  return Number(matches[matches.length - 1][1]);
}

function normalizeQuant(value) {
  const clean = String(value || "q4").toLowerCase();
  if (clean.includes("fp16")) return "fp16";
  if (clean.includes("bf16")) return "bf16";
  if (clean.includes("fp8")) return "fp8";
  if (clean.includes("q8") || clean.includes("int8") || clean === "8") return "q8";
  if (clean.includes("q6") || clean === "6") return "q6";
  if (clean.includes("q5") || clean === "5") return "q5";
  if (clean.includes("q4") || clean === "4") return "q4";
  if (clean.includes("q3") || clean === "3") return "q3";
  if (clean.includes("q2") || clean === "2") return "q2";
  return "q4";
}

function estimateWeightBytes(paramsB, quant) {
  const bytesPerParam = QUANT_BYTES[quant] || QUANT_BYTES.q4;
  const containerFactor = quant === "q4" ? 1.28 : quant.startsWith("q") ? 1.18 : 1.06;
  return Math.ceil(paramsB * 1_000_000_000 * bytesPerParam * containerFactor);
}

function estimateKvCacheBytes(paramsB, contextTokens) {
  if (!paramsB) return Math.ceil(contextTokens * 1.5 * 1024 * 1024);
  const perTokenMb = Math.min(8, Math.max(0.45, Math.sqrt(paramsB / 7) * 0.75));
  return Math.ceil(contextTokens * perTokenMb * 1024 * 1024);
}

function estimateLayerCount(paramsB, modelId) {
  const lower = String(modelId).toLowerCase();
  if (lower.includes("405b")) return 126;
  if (!paramsB) return 80;
  if (paramsB <= 8) return 32;
  if (paramsB <= 14) return 40;
  if (paramsB <= 35) return 60;
  if (paramsB <= 80) return 80;
  if (paramsB <= 160) return 96;
  return 126;
}

function bestAcceleratorBytes(hw, usableSystemBytes) {
  const gpuBytes = hw.gpus.reduce((max, gpu) => Math.max(max, gpu.memoryBytes || 0), 0);
  if (gpuBytes > 0) return Math.floor(gpuBytes * 0.9);
  if (hw.platform === "darwin" && hw.accelerators.includes("Metal")) return usableSystemBytes;
  return 0;
}

function estimateUsableSystemBytes(rawBytes) {
  const reserve = Math.max(4 * GB, rawBytes * 0.22);
  return Math.max(0, rawBytes - reserve);
}

function parseOptionalSize(value) {
  if (!value) return null;
  const match = String(value).trim().match(/^(\d+(?:\.\d+)?)\s*(tb|gb|gib|mb|mib|b)?$/i);
  if (!match) throw new Error(`invalid size "${value}". Use values like 16gb or 8192mb.`);
  const amount = Number(match[1]);
  const unit = (match[2] || "gb").toLowerCase();
  if (unit === "tb") return Math.ceil(amount * 1024 * GB);
  if (unit === "gb" || unit === "gib") return Math.ceil(amount * GB);
  if (unit === "mb" || unit === "mib") return Math.ceil(amount * 1024 * 1024);
  return Math.ceil(amount);
}

function diskFreeBytes(targetPath) {
  try {
    const raw = execFileSync("df", ["-k", targetPath], { encoding: "utf8", timeout: 3000 });
    const line = raw.trim().split("\n").pop();
    const parts = line.trim().split(/\s+/);
    const availableKb = Number(parts[3]);
    return Number.isFinite(availableKb) ? availableKb * 1024 : null;
  } catch {
    return null;
  }
}

async function doctor() {
  const nodeMajor = Number(process.versions.node.split(".")[0]);
  const hw = await detectHardware();
  const models = await scanModels();

  console.log(`Node.js:     ${process.version} ${nodeMajor >= 18 ? "ok" : "upgrade required"}`);
  console.log(`Home:        ${HOME}`);
  console.log(`Platform:    ${hw.platform}/${hw.arch}`);
  console.log(`Tier:        ${hw.tier}`);
  console.log(`Models:      ${models.length}`);
  console.log(`Port ${DEFAULT_PORT}: ${await portOpen(DEFAULT_PORT) ? "in use" : "available"}`);
}

function portOpen(port) {
  return new Promise((resolve) => {
    const req = http.get(`http://127.0.0.1:${port}/health`, { timeout: 300 }, (res) => {
      res.resume();
      resolve(res.statusCode === 200);
    });
    req.on("timeout", () => {
      req.destroy();
      resolve(false);
    });
    req.on("error", () => resolve(false));
  });
}

async function serve(input) {
  const port = Number(parseFlagFrom(input, "--port", DEFAULT_PORT));
  const host = String(parseFlagFrom(input, "--host", "127.0.0.1"));
  if (input.includes("--detach")) return detach(port, host);

  const state = readState();
  const pod = activePod(state);
  if (!pod) {
    throw new Error("no active pod. Run \"netitgo pod create <name>\" first.");
  }

  pod.port = port;
  writeState(state);

  const commandHub = createCommandHub();
  const server = http.createServer((req, res) => {
    handleRequest(req, res, commandHub).catch((error) => {
      sendJson(res, 500, { error: { message: error.message } });
    });
  });
  server.on("upgrade", (req, socket, head) => handleCommandUpgrade(req, socket, head, commandHub));

  server.listen(port, host, () => {
    console.log(`NetItGo pod "${pod.name}" is online`);
    console.log(`Endpoint: http://${host}:${port}/v1`);
    console.log(`Command:  ws://${host}:${port}/command`);
    if (host === "0.0.0.0") {
      console.log(`LAN worker: http://${localLanAddress()}:${port}`);
      console.log("Remote calls require this API key as a bearer token.");
    }
    console.log(`API key:  ${pod.apiKey}`);
    console.log(`Browser:  http://127.0.0.1:${port}/browser-worker.html`);
    console.log("Press Ctrl+C to stop.");
  });
}

function detach(port, host = "127.0.0.1") {
  const logPath = path.join(LOG_DIR, "netitgo.log");
  const child = spawn(process.execPath, [process.argv[1], "serve", "--port", String(port), "--host", host], {
    detached: true,
    stdio: ["ignore", fs.openSync(logPath, "a"), fs.openSync(logPath, "a")],
  });
  child.unref();
  console.log(`NetItGo started in background on ${host}:${port}`);
  console.log(`Logs: ${logPath}`);
}

function localLanAddress() {
  const nets = os.networkInterfaces();
  for (const interfaces of Object.values(nets)) {
    for (const item of interfaces || []) {
      if (item.family === "IPv4" && !item.internal) return item.address;
    }
  }
  return "127.0.0.1";
}

function parseFlagFrom(input, flag, fallback) {
  const index = input.indexOf(flag);
  if (index === -1) return fallback;
  return input[index + 1] || fallback;
}

function isLoopbackRequest(req) {
  const address = req.socket.remoteAddress || "";
  return address === "127.0.0.1" || address === "::1" || address === "::ffff:127.0.0.1";
}

function requestToken(req) {
  const auth = req.headers.authorization || "";
  const authValue = Array.isArray(auth) ? auth[0] : auth;
  const bearer = authValue.match(/^Bearer\s+(.+)$/i);
  const headerToken = req.headers["x-netitgo-api-key"] || "";
  return bearer?.[1] || (Array.isArray(headerToken) ? headerToken[0] : headerToken) || "";
}

function isAuthorized(req) {
  const pod = activePod(readState());
  const token = requestToken(req);
  if (!pod?.apiKey || !token || token.length !== pod.apiKey.length) return false;
  return crypto.timingSafeEqual(Buffer.from(token), Buffer.from(pod.apiKey));
}

function sendAuthError(res) {
  return sendJson(res, 401, {
    error: {
      message: "NetItGo remote API calls require Authorization: Bearer <pod-api-key>.",
    },
  });
}

function createCommandHub() {
  const connections = new Map();
  const workers = new Map();
  const requesters = new Map();
  const jobs = new Map();
  const recentActivity = [];
  let sequence = 0;
  let api;

  function status() {
    return {
      workers: workers.size,
      requesters: requesters.size,
      jobs: jobs.size,
      readyWorkers: [...workers.values()].filter((worker) => !worker.busy).length,
      workerList: [...workers.values()].map((worker) => ({
        id: worker.id,
        name: worker.name,
        busy: worker.busy,
        connectedAt: worker.connectedAt,
        lastSeenAt: worker.lastSeenAt,
        capabilities: publicWorkerCapabilities(worker.capabilities),
      })),
      recentActivity,
    };
  }

  function addActivity(item) {
    recentActivity.unshift(item);
    recentActivity.splice(20);
  }

  function updateActivity(jobId, patch) {
    const item = recentActivity.find((activity) => activity.jobId === jobId);
    if (item) Object.assign(item, patch);
  }

  function addConnection(socket, req) {
    const connection = {
      id: `conn_${randomToken(8)}`,
      socket,
      req,
      role: "unknown",
      name: "anonymous",
      connectedAt: new Date().toISOString(),
      lastSeenAt: new Date().toISOString(),
      busy: false,
      capabilities: {},
      buffer: Buffer.alloc(0),
      hub: api,
    };
    connections.set(connection.id, connection);
    sendWs(connection, {
      type: "welcome",
      connectionId: connection.id,
      message: "Connected to NetItGo public browser commons command broker.",
      warning: "Only submit non-sensitive prompts. Public prompt previews and role assignments may be visible to participants.",
      status: status(),
    });

    socket.on("data", (chunk) => receiveWsData(connection, chunk));
    socket.on("close", () => removeConnection(connection));
    socket.on("error", () => removeConnection(connection));
  }

  function receiveMessage(connection, payload) {
    let message;
    try {
      message = JSON.parse(payload);
    } catch {
      sendWs(connection, { type: "error", message: "Invalid JSON message." });
      return;
    }

    connection.lastSeenAt = new Date().toISOString();
    if (message.type === "hello") return registerConnection(connection, message);
    if (message.type === "worker-status") return updateWorkerStatus(connection, message);
    if (message.type === "ask") return createJob(connection, message);
    if (message.type === "job-started") return markJobStarted(connection, message);
    if (message.type === "result") return finishAssignment(connection, message);
    if (message.type === "ping") return sendWs(connection, { type: "pong", status: status() });

    sendWs(connection, { type: "error", message: `Unknown message type "${message.type || "missing"}".` });
  }

  function registerConnection(connection, message) {
    connection.role = message.role === "worker" ? "worker" : message.role === "requester" ? "requester" : "observer";
    connection.name = String(message.name || connection.role || "anonymous").slice(0, 80);
    connection.capabilities = message.capabilities || {};

    if (connection.role === "worker") {
      workers.set(connection.id, connection);
      requesters.delete(connection.id);
      sendWs(connection, {
        type: "registered",
        role: "worker",
        workerId: connection.id,
        status: status(),
      });
      broadcastStats();
      return;
    }

    if (connection.role === "requester") {
      requesters.set(connection.id, connection);
      workers.delete(connection.id);
      sendWs(connection, {
        type: "registered",
        role: "requester",
        requesterId: connection.id,
        status: status(),
      });
      broadcastStats();
      return;
    }

    sendWs(connection, { type: "registered", role: "observer", status: status() });
  }

  function updateWorkerStatus(connection, message) {
    if (connection.role !== "worker") {
      sendWs(connection, { type: "error", message: "Only workers can send worker-status." });
      return;
    }
    connection.capabilities = { ...connection.capabilities, ...(message.capabilities || {}) };
    connection.busy = Boolean(message.busy);
    connection.lastSeenAt = new Date().toISOString();
    broadcastStats();
  }

  function createJob(connection, message) {
    if (connection.role !== "requester") registerConnection(connection, { role: "requester", name: message.name });

    const prompt = String(message.prompt || "").trim();
    if (!prompt) {
      sendWs(connection, { type: "error", message: "Missing prompt." });
      return;
    }

    const availableWorkers = [...workers.values()].filter((worker) => !worker.busy && worker.socket.writable);
    if (availableWorkers.length === 0) {
      sendWs(connection, {
        type: "error",
        code: "no_workers",
        message: "No public browser workers are connected. Open /participate on another machine.",
        status: status(),
      });
      return;
    }

    const roles = normalizeRoles(message.roles);
    const selectedWorkers = availableWorkers.slice(0, Math.max(1, Math.min(roles.length, availableWorkers.length)));
    const jobId = `job_${Date.now().toString(36)}_${sequence++}`;
    const createdAt = new Date().toISOString();
    const job = {
      id: jobId,
      requesterId: connection.id,
      prompt,
      createdAt,
      assignments: new Map(),
      results: [],
      timeout: null,
    };
    jobs.set(jobId, job);
    addActivity({
      jobId,
      type: "ask",
      status: "queued",
      requester: connection.name,
      promptPreview: publicPromptPreview(prompt),
      roles,
      assignedWorkers: selectedWorkers.map((worker) => worker.name),
      resultCount: 0,
      createdAt,
      completedAt: null,
    });

    selectedWorkers.forEach((worker, index) => {
      const assignmentId = `${jobId}_${index}`;
      const role = roles[index % roles.length];
      worker.busy = true;
      job.assignments.set(assignmentId, {
        id: assignmentId,
        workerId: worker.id,
        workerName: worker.name,
        role,
        status: "assigned",
        assignedAt: new Date().toISOString(),
      });
      sendWs(worker, {
        type: "job",
        jobId,
        assignmentId,
        role,
        prompt,
        maxNewTokens: Number(message.maxNewTokens || 256),
        requester: connection.name,
        warning: "Run only non-sensitive public commons prompts.",
      });
    });

    const timeoutMs = Math.max(10_000, Math.min(Number(message.timeoutMs || 120_000), 300_000));
    job.timeout = setTimeout(() => completeJob(jobId, "timeout"), timeoutMs);

    sendWs(connection, {
      type: "job-queued",
      jobId,
      assigned: [...job.assignments.values()].map(publicAssignment),
      publicActivity: recentActivity[0],
      visibility: "This is Public Browser Commons. Prompt previews are visible to connected public participants.",
      status: status(),
    });
    broadcastStats();
  }

  function markJobStarted(connection, message) {
    const job = jobs.get(message.jobId);
    if (!job) return;
    const assignment = job.assignments.get(message.assignmentId);
    if (!assignment) return;
    assignment.status = "running";
    assignment.startedAt = new Date().toISOString();
    updateActivity(job.id, { status: "running" });
    sendToRequester(job, {
      type: "worker-started",
      jobId: job.id,
      assignment: publicAssignment(assignment),
    });
  }

  function finishAssignment(connection, message) {
    const job = jobs.get(message.jobId);
    if (!job) {
      connection.busy = false;
      broadcastStats();
      return;
    }
    const assignment = job.assignments.get(message.assignmentId);
    if (!assignment) return;

    assignment.status = message.ok === false ? "failed" : "complete";
    assignment.completedAt = new Date().toISOString();
    connection.busy = false;
    const result = {
      assignmentId: assignment.id,
      workerId: connection.id,
      workerName: connection.name,
      role: assignment.role,
      ok: message.ok !== false,
      model: String(message.model || connection.capabilities.model || "browser-model").slice(0, 120),
      content: String(message.content || "").slice(0, 200000),
      elapsedMs: Number(message.elapsedMs || 0),
      completedAt: assignment.completedAt,
    };
    job.results.push(result);
    updateActivity(job.id, {
      status: "receiving_results",
      resultCount: job.results.length,
    });
    sendToRequester(job, {
      type: "result",
      jobId: job.id,
      result,
      completed: job.results.length,
      expected: job.assignments.size,
    });

    if (job.results.length >= job.assignments.size) completeJob(job.id, "complete");
    broadcastStats();
  }

  function completeJob(jobId, reason) {
    const job = jobs.get(jobId);
    if (!job) return;
    clearTimeout(job.timeout);
    for (const assignment of job.assignments.values()) {
      const worker = workers.get(assignment.workerId);
      if (worker && assignment.status !== "complete" && assignment.status !== "failed") worker.busy = false;
    }
    sendToRequester(job, {
      type: "complete",
      jobId: job.id,
      reason,
      results: job.results,
      summary: summarizePublicResults(job.results),
    });
    updateActivity(job.id, {
      status: reason,
      resultCount: job.results.length,
      completedAt: new Date().toISOString(),
    });
    jobs.delete(jobId);
    broadcastStats();
  }

  function sendToRequester(job, message) {
    const requester = requesters.get(job.requesterId) || connections.get(job.requesterId);
    if (requester) sendWs(requester, message);
  }

  function broadcastStats() {
    const payload = { type: "stats", status: status() };
    for (const connection of connections.values()) sendWs(connection, payload);
  }

  function removeConnection(connection) {
    connections.delete(connection.id);
    workers.delete(connection.id);
    requesters.delete(connection.id);
    for (const job of jobs.values()) {
      for (const assignment of job.assignments.values()) {
        if (assignment.workerId === connection.id && assignment.status !== "complete") {
          assignment.status = "lost";
          job.results.push({
            assignmentId: assignment.id,
            workerId: connection.id,
            workerName: connection.name,
            role: assignment.role,
            ok: false,
            model: "disconnected",
            content: "Worker disconnected before returning a result.",
            elapsedMs: 0,
            completedAt: new Date().toISOString(),
          });
        }
      }
      if (job.results.length >= job.assignments.size) completeJob(job.id, "worker_disconnected");
    }
    broadcastStats();
  }

  api = { addConnection, receiveMessage, status };
  return api;
}

function normalizeRoles(value) {
  if (Array.isArray(value)) return value.map((item) => String(item).trim()).filter(Boolean).slice(0, 8);
  if (typeof value === "string") return value.split(",").map((item) => item.trim()).filter(Boolean).slice(0, 8);
  return ["participant"];
}

function publicAssignment(assignment) {
  return {
    id: assignment.id,
    workerId: assignment.workerId,
    workerName: assignment.workerName,
    role: assignment.role,
    status: assignment.status,
    assignedAt: assignment.assignedAt,
    startedAt: assignment.startedAt || null,
    completedAt: assignment.completedAt || null,
  };
}

function publicPromptPreview(prompt) {
  return String(prompt || "")
    .replace(/\s+/g, " ")
    .trim()
    .slice(0, 220);
}

function publicWorkerCapabilities(capabilities = {}) {
  return {
    model: capabilities.model || null,
    webgpu: Boolean(capabilities.webgpu),
    modelLoaded: Boolean(capabilities.modelLoaded),
    maxNewTokens: capabilities.maxNewTokens || null,
    device: capabilities.device || "browser",
  };
}

function summarizePublicResults(results) {
  const successful = results.filter((result) => result.ok && result.content);
  if (successful.length === 0) return "No browser worker returned a successful answer.";
  if (successful.length === 1) return successful[0].content;
  return successful
    .map((result) => `## ${result.role} from ${result.workerName}\n${result.content}`)
    .join("\n\n");
}

function handleCommandUpgrade(req, socket, _head, commandHub) {
  const url = new URL(req.url || "/", "http://127.0.0.1");
  if (url.pathname !== "/command") {
    socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
    socket.destroy();
    return;
  }

  const key = req.headers["sec-websocket-key"];
  if (!key) {
    socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
    socket.destroy();
    return;
  }

  const accept = crypto
    .createHash("sha1")
    .update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`)
    .digest("base64");
  socket.write(
    [
      "HTTP/1.1 101 Switching Protocols",
      "Upgrade: websocket",
      "Connection: Upgrade",
      `Sec-WebSocket-Accept: ${accept}`,
      "",
      "",
    ].join("\r\n"),
  );
  commandHub.addConnection(socket, req);
}

function receiveWsData(connection, chunk) {
  connection.buffer = Buffer.concat([connection.buffer, chunk]);
  while (connection.buffer.length >= 2) {
    const first = connection.buffer[0];
    const second = connection.buffer[1];
    const opcode = first & 0x0f;
    const masked = (second & 0x80) === 0x80;
    let length = second & 0x7f;
    let offset = 2;

    if (length === 126) {
      if (connection.buffer.length < offset + 2) return;
      length = connection.buffer.readUInt16BE(offset);
      offset += 2;
    } else if (length === 127) {
      if (connection.buffer.length < offset + 8) return;
      const high = connection.buffer.readUInt32BE(offset);
      const low = connection.buffer.readUInt32BE(offset + 4);
      length = high * 2 ** 32 + low;
      offset += 8;
    }

    const maskOffset = offset;
    if (masked) offset += 4;
    if (connection.buffer.length < offset + length) return;

    let payload = connection.buffer.subarray(offset, offset + length);
    if (masked) {
      const mask = connection.buffer.subarray(maskOffset, maskOffset + 4);
      payload = Buffer.from(payload.map((byte, index) => byte ^ mask[index % 4]));
    }
    connection.buffer = connection.buffer.subarray(offset + length);

    if (opcode === 0x8) {
      connection.socket.end();
      return;
    }
    if (opcode === 0x9) {
      sendWsRaw(connection.socket, payload, 0x0a);
      continue;
    }
    if (opcode === 0x1) {
      connection.hub?.receiveMessage(connection, payload.toString("utf8"));
    }
  }
}

function sendWs(connection, payload) {
  if (!connection.socket.writable) return;
  const data = JSON.stringify(payload);
  sendWsRaw(connection.socket, Buffer.from(data, "utf8"), 0x1);
}

function sendWsRaw(socket, payload, opcode = 0x1) {
  const length = payload.length;
  let header;
  if (length < 126) {
    header = Buffer.from([0x80 | opcode, length]);
  } else if (length <= 0xffff) {
    header = Buffer.alloc(4);
    header[0] = 0x80 | opcode;
    header[1] = 126;
    header.writeUInt16BE(length, 2);
  } else {
    header = Buffer.alloc(10);
    header[0] = 0x80 | opcode;
    header[1] = 127;
    header.writeUInt32BE(0, 2);
    header.writeUInt32BE(length, 6);
  }
  socket.write(Buffer.concat([header, payload]));
}

async function handleRequest(req, res, commandHub = null) {
  const url = new URL(req.url || "/", "http://127.0.0.1");

  if (req.method === "OPTIONS") {
    cors(res);
    res.writeHead(204);
    res.end();
    return;
  }

  if (req.method === "GET" && url.pathname === "/browser-worker.html") {
    return sendHtml(res, 200, browserWorkerHtml(activePod(readState())));
  }

  if (req.method === "GET" && url.pathname === "/health") {
    return sendJson(res, 200, { ok: true, service: "netitgo", version: VERSION });
  }

  if (req.method === "GET" && url.pathname === "/api/public-network/status") {
    return sendJson(res, 200, {
      ok: true,
      service: "netitgo-public-browser-commons",
      version: VERSION,
      commandEndpoint: "/command",
      status: commandHub ? commandHub.status() : null,
      warning: "Public browser commons is best-effort, observable, and should only receive non-sensitive prompts.",
    });
  }

  if (req.method === "GET" && url.pathname === "/api/capabilities") {
    if (!isLoopbackRequest(req) && !isAuthorized(req)) {
      return sendJson(res, 200, {
        ok: true,
        service: "netitgo",
        version: VERSION,
        authRequired: true,
        endpoints: ["/health"],
      });
    }

    const hw = await detectHardware();
    const models = await scanModels();
    const state = readState();
    const pod = activePod(state);
    return sendJson(res, 200, {
      ok: true,
      service: "netitgo",
      version: VERSION,
      node: {
        nodeId: state.nodeId,
        fingerprint: state.identity.fingerprint,
        hostname: os.hostname(),
        platform: hw.platform,
        arch: hw.arch,
        tier: hw.tier,
        accelerators: hw.accelerators,
      },
      pod: publicPod(pod),
      inventory: {
        models,
        backends: summarizeBackends(models, hw),
      },
      endpoints: ["/api/council/role", "/v1/chat/completions"],
    });
  }

  if (req.method === "GET" && url.pathname === "/api/status") {
    if (!isLoopbackRequest(req) && !isAuthorized(req)) return sendAuthError(res);
    const hw = await detectHardware();
    const models = await scanModels();
    const state = readState();
    return sendJson(res, 200, {
      state: publicNodeState(state),
      pod: publicPod(activePod(state)),
      hardware: hw,
      models,
    });
  }

  if (req.method === "GET" && url.pathname === "/api/control-plane") {
    if (!isLoopbackRequest(req) && !isAuthorized(req)) return sendAuthError(res);
    const hw = await detectHardware();
    const models = await scanModels();
    const state = readState();
    const pod = activePod(state);
    return sendJson(res, 200, {
      node: {
        nodeId: state.nodeId,
        fingerprint: state.identity.fingerprint,
        hostname: os.hostname(),
        platform: hw.platform,
        arch: hw.arch,
        tier: hw.tier,
      },
      pod: publicPod(pod),
      inventory: {
        models,
        backends: summarizeBackends(models, hw),
      },
    });
  }

  if (req.method === "GET" && url.pathname === "/api/plan") {
    if (!isLoopbackRequest(req) && !isAuthorized(req)) return sendAuthError(res);
    const hw = await detectHardware();
    const models = await scanModels();
    const state = readState();
    const pod = activePod(state);
    const model = url.searchParams.get("model") || models[0]?.id || "qwen3.5:122b";
    const paramsB = url.searchParams.get("params")
      ? Number(String(url.searchParams.get("params")).replace(/b$/i, ""))
      : inferParamsB(model);
    const networkKey = (url.searchParams.get("network") || "local").toLowerCase();
    const matchedModel = models.find((item) => item.id === model);
    const estimate = buildInferencePlan({
      modelId: model,
      matchedModel,
      paramsB,
      quant: normalizeQuant(url.searchParams.get("quant") || "q4"),
      contextTokens: Number(url.searchParams.get("context") || "8192"),
      nodeCount: Number(url.searchParams.get("nodes") || pod?.members?.length || 1),
      network: NETWORK_PROFILES[networkKey] || NETWORK_PROFILES.local,
      hw,
      sizeOverride: parseOptionalSize(url.searchParams.get("size")),
      nodeMemoryOverride: parseOptionalSize(url.searchParams.get("nodeMemory")),
      gpuMemoryOverride: parseOptionalSize(url.searchParams.get("gpuMemory")),
    });
    return sendJson(res, 200, estimate);
  }

  if (req.method === "POST" && url.pathname === "/api/council") {
    if (!isLoopbackRequest(req) && !isAuthorized(req)) return sendAuthError(res);
    const body = await readBody(req);
    const result = await runCouncil({
      prompt: body.prompt || messagesToPrompt(body.messages || []),
      roles: body.roles || DEFAULT_COUNCIL_ROLES,
      requestedModels: body.models || null,
      aggregatorModel: body.aggregator || null,
      timeoutMs: body.timeoutMs || 45000,
    });
    return sendJson(res, 200, result);
  }

  if (req.method === "POST" && url.pathname === "/api/council/role") {
    if (!isLoopbackRequest(req) && !isAuthorized(req)) return sendAuthError(res);
    const body = await readBody(req);
    const participant = await runCouncilRole({
      role: body.role || "participant",
      prompt: body.prompt || messagesToPrompt(body.messages || []),
      modelId: body.model || null,
      timeoutMs: body.timeoutMs || 45000,
    });
    return sendJson(res, 200, participant);
  }

  if (req.method === "POST" && url.pathname === "/api/browser-seat/submit") {
    if (!isLoopbackRequest(req) && !isAuthorized(req)) return sendAuthError(res);
    const body = await readBody(req);
    const entry = storeBrowserSeat(body);
    return sendJson(res, 200, { ok: true, entry });
  }

  if (req.method === "GET" && url.pathname === "/api/browser-seat/submissions") {
    if (!isLoopbackRequest(req) && !isAuthorized(req)) return sendAuthError(res);
    return sendJson(res, 200, { ok: true, submissions: readBrowserSeats() });
  }

  if (req.method === "GET" && url.pathname === "/v1/models") {
    if (!isLoopbackRequest(req) && !isAuthorized(req)) return sendAuthError(res);
    const models = await scanModels();
    return sendJson(res, 200, {
      object: "list",
      data: [
        {
          id: "netitgo-council",
          object: "model",
          owned_by: "netitgo",
        },
        ...models.map((model) => ({
          id: model.id,
          object: "model",
          owned_by: model.source,
        })),
      ],
    });
  }

  if (req.method === "POST" && url.pathname === "/v1/chat/completions") {
    if (!isLoopbackRequest(req) && !isAuthorized(req)) return sendAuthError(res);
    const body = await readBody(req);
    return completeChat(res, body);
  }

  sendJson(res, 404, { error: { message: "not found" } });
}

function summarizeBackends(models, hw) {
  const backendSet = new Set(models.map((model) => model.backend || model.source));
  if (hw.platform === "darwin") backendSet.add("Metal");
  if (hw.accelerators.includes("CUDA")) backendSet.add("CUDA");
  return [...backendSet].sort();
}

async function completeChat(res, body) {
  const state = readState();
  const pod = activePod(state);
  const models = await scanModels();
  const model = body.model || models[0]?.id || "netitgo-preview";

  if (body.stream) return streamPreview(res, model, pod);

  if (model === "netitgo-council" || body.council) {
    const councilConfig = body.council || {};
    const result = await runCouncil({
      prompt: messagesToPrompt(body.messages || []),
      roles: councilConfig.roles || body.roles || DEFAULT_COUNCIL_ROLES,
      requestedModels: councilConfig.models || null,
      aggregatorModel: councilConfig.aggregator || null,
      timeoutMs: councilConfig.timeoutMs || 45000,
    });
    return sendJson(res, 200, openAiResponse("netitgo-council", result.synthesis, { council: result }));
  }

  const ollama = models.find((item) => item.source === "ollama" && item.id === model);
  if (ollama) {
    const forwarded = await askOllama(model, body.messages || []);
    if (forwarded) return sendJson(res, 200, openAiResponse(model, forwarded));
  }

  const content = [
    `NetItGo pod "${pod?.name || "local"}" accepted the request.`,
    "This preview gateway is online and OpenAI-compatible.",
    "Attach Ollama or GGUF models to route real local inference through the pod.",
    "Run netitgo plan <model> to estimate fast, distributed, cold, or fallback execution.",
  ].join(" ");

  sendJson(res, 200, openAiResponse(model, content));
}

function askOllama(model, messages, options = {}) {
  return new Promise((resolve) => {
    const payload = JSON.stringify({
      model,
      messages,
      stream: false,
      options: {
        temperature: options.temperature ?? 0.35,
      },
    });
    const req = http.request(
      "http://127.0.0.1:11434/api/chat",
      {
        method: "POST",
        headers: {
          "content-type": "application/json",
          "content-length": Buffer.byteLength(payload),
        },
        timeout: options.timeoutMs || 20000,
      },
      (res) => {
        let body = "";
        res.on("data", (chunk) => {
          body += chunk;
        });
        res.on("end", () => {
          try {
            const parsed = JSON.parse(body);
            resolve(parsed.message?.content || null);
          } catch {
            resolve(null);
          }
        });
      },
    );
    req.on("timeout", () => {
      req.destroy();
      resolve(null);
    });
    req.on("error", () => resolve(null));
    req.write(payload);
    req.end();
  });
}

function authHeaders(token) {
  return token ? { authorization: `Bearer ${token}`, "x-netitgo-api-key": token } : {};
}

function getJson(endpoint, timeoutMs = 5000, headers = {}) {
  return new Promise((resolve, reject) => {
    const url = new URL(endpoint);
    const req = http.request(
      url,
      {
        method: "GET",
        headers,
        timeout: timeoutMs,
      },
      (res) => {
        let responseBody = "";
        res.on("data", (chunk) => {
          responseBody += chunk;
        });
        res.on("end", () => {
          if (res.statusCode >= 400) {
            reject(new Error(`HTTP ${res.statusCode}`));
            return;
          }
          try {
            resolve(JSON.parse(responseBody));
          } catch (error) {
            reject(error);
          }
        });
      },
    );
    req.on("timeout", () => {
      req.destroy();
      reject(new Error("request timed out"));
    });
    req.on("error", reject);
    req.end();
  });
}

function postJson(endpoint, body, timeoutMs = 45000, headers = {}) {
  return new Promise((resolve, reject) => {
    const url = new URL(endpoint);
    const payload = JSON.stringify(body);
    const req = http.request(
      url,
      {
        method: "POST",
        headers: {
          ...headers,
          "content-type": "application/json",
          "content-length": Buffer.byteLength(payload),
        },
        timeout: timeoutMs,
      },
      (res) => {
        let responseBody = "";
        res.on("data", (chunk) => {
          responseBody += chunk;
        });
        res.on("end", () => {
          if (res.statusCode >= 400) {
            reject(new Error(`HTTP ${res.statusCode}: ${responseBody.slice(0, 180)}`));
            return;
          }
          try {
            resolve(JSON.parse(responseBody));
          } catch (error) {
            reject(error);
          }
        });
      },
    );
    req.on("timeout", () => {
      req.destroy();
      reject(new Error("request timed out"));
    });
    req.on("error", reject);
    req.write(payload);
    req.end();
  });
}

function messagesToPrompt(messages) {
  return messages
    .map((message) => `${message.role || "user"}: ${message.content || ""}`)
    .join("\n")
    .trim();
}

function streamPreview(res, model, pod) {
  cors(res);
  res.writeHead(200, {
    "content-type": "text/event-stream",
    "cache-control": "no-cache",
    connection: "keep-alive",
  });

  const id = `chatcmpl_${randomToken(10)}`;
  const tokens = [
    "NetItGo ",
    "pod ",
    `"${pod?.name || "local"}" `,
    "is ",
    "online. ",
    "Local-first ",
    "routing ",
    "is ",
    "ready.",
  ];

  for (const token of tokens) {
    res.write(
      `data: ${JSON.stringify({
        id,
        object: "chat.completion.chunk",
        created: Math.floor(Date.now() / 1000),
        model,
        choices: [{ index: 0, delta: { content: token }, finish_reason: null }],
      })}\n\n`,
    );
  }

  res.write(
    `data: ${JSON.stringify({
      id,
      object: "chat.completion.chunk",
      created: Math.floor(Date.now() / 1000),
      model,
      choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
    })}\n\n`,
  );
  res.write("data: [DONE]\n\n");
  res.end();
}

function openAiResponse(model, content, extra = {}) {
  return {
    id: `chatcmpl_${randomToken(10)}`,
    object: "chat.completion",
    created: Math.floor(Date.now() / 1000),
    model,
    choices: [
      {
        index: 0,
        message: {
          role: "assistant",
          content,
        },
        finish_reason: "stop",
      },
    ],
    ...extra,
  };
}

function readBody(req) {
  return new Promise((resolve, reject) => {
    let body = "";
    req.on("data", (chunk) => {
      body += chunk;
      if (body.length > 2_000_000) {
        reject(new Error("request body too large"));
        req.destroy();
      }
    });
    req.on("end", () => {
      try {
        resolve(body ? JSON.parse(body) : {});
      } catch {
        reject(new Error("invalid JSON body"));
      }
    });
  });
}

function readBrowserSeats() {
  try {
    if (!fs.existsSync(BROWSER_SEATS_FILE)) return [];
    const parsed = JSON.parse(fs.readFileSync(BROWSER_SEATS_FILE, "utf8"));
    return Array.isArray(parsed) ? parsed : [];
  } catch {
    return [];
  }
}

function writeBrowserSeats(seats) {
  fs.writeFileSync(BROWSER_SEATS_FILE, `${JSON.stringify(seats.slice(-100), null, 2)}\n`);
}

function storeBrowserSeat(body) {
  const entry = {
    id: `seat_${randomToken(8)}`,
    submittedAt: new Date().toISOString(),
    role: String(body.role || "browser").slice(0, 80),
    prompt: String(body.prompt || "").slice(0, 12000),
    model: String(body.model || "gemma-4-E2B-it-webgpu").slice(0, 120),
    ok: body.ok !== false,
    content: String(body.content || "").slice(0, 200000),
    elapsedMs: Number(body.elapsedMs || 0),
    hardware: body.hardware || null,
  };
  const seats = readBrowserSeats();
  seats.push(entry);
  writeBrowserSeats(seats);
  return entry;
}

function escapeHtml(value) {
  return String(value)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}

function browserWorkerHtml(pod) {
  const podName = escapeHtml(pod?.name || "local");
  return `<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>NetItGo Browser Seat</title>
  <style>
    :root {
      color-scheme: dark;
      --bg: #050609;
      --panel: #0a0f12;
      --line: rgba(223, 255, 233, 0.14);
      --text: #f4f8f4;
      --muted: #a8b5ad;
      --neon: #45ff4d;
      --cyan: #6edcff;
      font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    }
    * { box-sizing: border-box; }
    body {
      margin: 0;
      min-width: 320px;
      background:
        radial-gradient(circle at 72% 12%, rgba(69, 255, 77, 0.14), transparent 34%),
        linear-gradient(135deg, #050609 0%, #071014 62%, #04100b 100%);
      color: var(--text);
    }
    main {
      width: min(1180px, calc(100% - 40px));
      margin: 0 auto;
      padding: 40px 0 56px;
    }
    header {
      display: grid;
      grid-template-columns: minmax(0, 1fr) auto;
      gap: 24px;
      align-items: end;
      min-height: 170px;
      border-bottom: 1px solid var(--line);
    }
    h1 {
      margin: 0 0 14px;
      font-size: 72px;
      line-height: 0.96;
      letter-spacing: 0;
    }
    .eyebrow {
      margin: 0 0 18px;
      color: var(--neon);
      font-size: 12px;
      font-weight: 800;
      text-transform: uppercase;
      letter-spacing: 0;
    }
    p {
      margin: 0;
      color: var(--muted);
      line-height: 1.55;
    }
    .status {
      display: grid;
      gap: 8px;
      min-width: 260px;
      padding: 18px;
      background: rgba(255,255,255,0.035);
      border: 1px solid var(--line);
      border-radius: 8px;
    }
    .status strong { color: var(--text); }
    .workspace {
      display: grid;
      grid-template-columns: minmax(0, 0.9fr) minmax(360px, 1fr);
      gap: 28px;
      padding-top: 32px;
    }
    .panel {
      padding: 22px;
      background: rgba(5, 7, 9, 0.82);
      border: 1px solid var(--line);
      border-radius: 8px;
    }
    label {
      display: grid;
      gap: 8px;
      margin-bottom: 16px;
      color: var(--muted);
      font-size: 13px;
      font-weight: 700;
    }
    input, textarea, select {
      width: 100%;
      color: var(--text);
      background: #050709;
      border: 1px solid rgba(255,255,255,0.12);
      border-radius: 6px;
      padding: 12px;
      font: inherit;
    }
    textarea {
      min-height: 170px;
      resize: vertical;
    }
    .actions {
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      margin-top: 18px;
    }
    button {
      min-height: 44px;
      padding: 0 16px;
      color: #041007;
      background: var(--neon);
      border: 0;
      border-radius: 6px;
      font-weight: 800;
      cursor: pointer;
    }
    button.secondary {
      color: var(--text);
      background: rgba(255,255,255,0.07);
      border: 1px solid rgba(255,255,255,0.12);
    }
    button:disabled {
      opacity: 0.48;
      cursor: wait;
    }
    pre {
      min-height: 420px;
      margin: 0;
      padding: 18px;
      overflow: auto;
      white-space: pre-wrap;
      color: #e8fff0;
      background: #050709;
      border: 1px solid rgba(69,255,77,0.18);
      border-radius: 8px;
      line-height: 1.55;
    }
    .log {
      margin-top: 14px;
      color: var(--cyan);
      font-family: "SFMono-Regular", Consolas, Menlo, monospace;
      font-size: 13px;
      min-height: 20px;
    }
    @media (max-width: 860px) {
      header, .workspace { grid-template-columns: 1fr; }
      h1 { font-size: 42px; }
      main { width: min(100% - 28px, 1180px); }
    }
  </style>
</head>
<body>
  <main>
    <header>
      <div>
        <p class="eyebrow">NetItGo browser WebGPU seat</p>
        <h1>Run one council role in this tab.</h1>
        <p>This page loads Gemma 4 E2B through Transformers.js and WebGPU, runs a role prompt locally, then submits only text back to the NetItGo chair.</p>
      </div>
      <div class="status">
        <strong>Pod: ${podName}</strong>
        <span id="gpuStatus">Checking WebGPU...</span>
        <span>Model: onnx-community/gemma-4-E2B-it-ONNX</span>
      </div>
    </header>

    <section class="workspace">
      <form class="panel" id="workerForm">
        <label>Chair endpoint
          <input id="endpoint" value="" />
        </label>
        <label>Pod API key for LAN/private mesh use
          <input id="apiKey" type="password" autocomplete="off" placeholder="pk_netitgo_..." />
        </label>
        <label>Role
          <select id="role">
            <option value="skeptic">skeptic</option>
            <option value="architect">architect</option>
            <option value="implementer">implementer</option>
            <option value="security">security</option>
            <option value="cost">cost</option>
            <option value="researcher">researcher</option>
            <option value="eli10">eli10</option>
            <option value="chinese">chinese</option>
          </select>
        </label>
        <label>Prompt
          <textarea id="prompt">How should NetItGo beat public decentralized AI agent networks?</textarea>
        </label>
        <label>Max new tokens
          <input id="tokens" type="number" min="32" max="1024" value="256" />
        </label>
        <div class="actions">
          <button id="loadButton" type="button">Load Gemma 4 E2B</button>
          <button id="runButton" type="button" class="secondary">Run role</button>
          <button id="submitButton" type="button" class="secondary">Submit to chair</button>
        </div>
        <div class="log" id="log"></div>
      </form>

      <div>
        <pre id="output">Role output will appear here.</pre>
      </div>
    </section>
  </main>

  <script type="module">
    import {
      AutoProcessor,
      Gemma4ForConditionalGeneration,
      TextStreamer
    } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1";

    var modelId = "onnx-community/gemma-4-E2B-it-ONNX";
    var processor = null;
    var model = null;
    var lastResult = "";
    var startedAt = 0;
    var endpoint = document.getElementById("endpoint");
    var apiKey = document.getElementById("apiKey");
    var role = document.getElementById("role");
    var prompt = document.getElementById("prompt");
    var output = document.getElementById("output");
    var logEl = document.getElementById("log");
    var gpuStatus = document.getElementById("gpuStatus");
    var loadButton = document.getElementById("loadButton");
    var runButton = document.getElementById("runButton");
    var submitButton = document.getElementById("submitButton");

    endpoint.value = window.location.origin;
    gpuStatus.textContent = navigator.gpu ? "WebGPU available" : "WebGPU not available in this browser";

    function log(message) {
      logEl.textContent = message;
    }

    function rolePrompt(value) {
      var roles = {
        architect: "You are a senior systems architect. Focus on interfaces, failure modes, and build order.",
        skeptic: "You are a skeptical technical reviewer. Challenge assumptions and name what could break.",
        implementer: "You are a pragmatic implementation lead. Convert the idea into concrete steps.",
        security: "You are a security engineer. Focus on privacy, trust boundaries, authentication, and abuse.",
        cost: "You are a cost and operations analyst. Focus on cost, energy, maintenance, and utilization.",
        researcher: "You are a research analyst. Compare the idea to known systems and identify what to validate.",
        eli10: "Explain the idea clearly as if the reader is 10 years old, without being condescending.",
        chinese: "请用中文回答。重点解释价值、风险和可执行路径。"
      };
      return roles[value] || "Give a distinct, useful perspective.";
    }

    async function loadModel() {
      if (!navigator.gpu) {
        log("This browser does not expose WebGPU.");
        return;
      }
      loadButton.disabled = true;
      log("Loading processor and q4f16 WebGPU model. First load can take a while.");
      processor = await AutoProcessor.from_pretrained(modelId);
      model = await Gemma4ForConditionalGeneration.from_pretrained(modelId, {
        dtype: "q4f16",
        device: "webgpu",
        progress_callback: function(info) {
          if (info.status === "progress_total") log("Loading model: " + Math.round(info.progress) + "%");
        }
      });
      log("Model ready.");
      loadButton.disabled = false;
    }

    async function runRole() {
      if (!model || !processor) await loadModel();
      if (!model || !processor) return;
      runButton.disabled = true;
      output.textContent = "";
      lastResult = "";
      startedAt = Date.now();
      var messages = [
        { role: "system", content: rolePrompt(role.value) + "\\nBe concise, concrete, and preserve disagreement." },
        { role: "user", content: prompt.value }
      ];
      var chatPrompt = processor.apply_chat_template(messages, {
        enable_thinking: false,
        add_generation_prompt: true
      });
      var inputs = await processor(chatPrompt, null, null, { add_special_tokens: false });
      var streamer = new TextStreamer(processor.tokenizer, {
        skip_prompt: true,
        skip_special_tokens: true,
        callback_function: function(text) {
          lastResult += text;
          output.textContent = lastResult;
        }
      });
      var outputs = await model.generate({
        ...inputs,
        max_new_tokens: Number(document.getElementById("tokens").value || 256),
        do_sample: false,
        streamer: streamer
      });
      if (!lastResult) {
        var decoded = processor.batch_decode(
          outputs.slice(null, [inputs.input_ids.dims.at(-1), null]),
          { skip_special_tokens: true }
        );
        lastResult = decoded[0] || "";
        output.textContent = lastResult;
      }
      log("Role finished in " + (Date.now() - startedAt) + "ms.");
      runButton.disabled = false;
    }

    async function submitResult() {
      if (!lastResult.trim()) {
        log("Run the role before submitting.");
        return;
      }
      submitButton.disabled = true;
      var headers = { "content-type": "application/json" };
      if (apiKey.value.trim()) headers.authorization = "Bearer " + apiKey.value.trim();
      var response = await fetch(endpoint.value.replace(/\\/$/, "") + "/api/browser-seat/submit", {
        method: "POST",
        headers: headers,
        body: JSON.stringify({
          role: role.value,
          prompt: prompt.value,
          model: modelId,
          ok: true,
          content: lastResult,
          elapsedMs: Date.now() - startedAt,
          hardware: {
            webgpu: Boolean(navigator.gpu),
            userAgent: navigator.userAgent
          }
        })
      });
      log(response.ok ? "Submitted to chair." : "Submit failed: HTTP " + response.status);
      submitButton.disabled = false;
    }

    loadButton.addEventListener("click", loadModel);
    runButton.addEventListener("click", runRole);
    submitButton.addEventListener("click", submitResult);
  </script>
</body>
</html>`;
}

function cors(res) {
  res.setHeader("access-control-allow-origin", "*");
  res.setHeader("access-control-allow-methods", "GET,POST,OPTIONS");
  res.setHeader("access-control-allow-headers", "authorization,content-type,x-netitgo-api-key");
}

function sendJson(res, statusCode, payload) {
  cors(res);
  res.writeHead(statusCode, { "content-type": "application/json" });
  res.end(`${JSON.stringify(payload, null, 2)}\n`);
}

function sendHtml(res, statusCode, html) {
  cors(res);
  res.writeHead(statusCode, { "content-type": "text/html; charset=utf-8" });
  res.end(html);
}

function printHelp() {
  console.log(`NetItGo CLI ${VERSION}`);
  console.log("");
  console.log("Usage:");
  console.log("  netitgo node identity            Show this node's public identity");
  console.log("  netitgo pod create <name>        Create a private AI pod");
  console.log("  netitgo pod join <invite-url>    Join a pod from an invite");
  console.log("  netitgo pod invite               Print active pod invite");
  console.log("  netitgo pod members              List active pod members");
  console.log("  netitgo pod worker add <name> <url> [--token <key>] Register a council worker endpoint");
  console.log("  netitgo pod workers --check     Verify worker capability and auth");
  console.log("  netitgo policy show              Show active pod policy");
  console.log("  netitgo policy set <key> <value> Update local pod policy");
  console.log("  netitgo start [--port 8877]      Start the local API gateway");
  console.log("  netitgo start --host 0.0.0.0     Expose this node as a LAN council worker");
  console.log("  netitgo start --detach           Start gateway in the background");
  console.log("  netitgo status                   Show node and pod status");
  console.log("  netitgo system-info              Detect local hardware");
  console.log("  netitgo models list              List local models");
  console.log("  netitgo plan <model>             Estimate fast, distributed, cold, or fallback mode");
  console.log("  netitgo council <prompt>         Ask a private council of local small models");
  console.log("  netitgo browser                  Print the WebGPU browser seat URL");
  console.log("  netitgo api-key                  Print the active API key");
  console.log("  netitgo doctor                   Check local readiness");
  console.log("");
  console.log("OpenAI-compatible endpoint:");
  console.log(`  http://127.0.0.1:${DEFAULT_PORT}/v1`);
  console.log("");
  console.log("Public browser commons preview:");
  console.log(`  ws://127.0.0.1:${DEFAULT_PORT}/command`);
  console.log("  /participate connects browser WebGPU workers to this broker.");
  console.log("  /ask sends non-sensitive prompts to connected browser workers.");
}

NETITGO_CLI_SOURCE
chmod +x "$BIN_PATH"

add_path_line() {
  PROFILE_FILE="$1"
  mkdir -p "$(dirname "$PROFILE_FILE")"
  touch "$PROFILE_FILE"
  if ! grep -q "$INSTALL_DIR" "$PROFILE_FILE"; then
    {
      echo ""
      echo "$PROFILE_MARKER"
      echo "export PATH=\"$INSTALL_DIR:\$PATH\""
    } >> "$PROFILE_FILE"
  fi
}

SHELL_NAME="$(basename "${SHELL:-}")"
if [ "$SHELL_NAME" = "zsh" ]; then
  add_path_line "$HOME/.zshrc"
else
  add_path_line "$HOME/.bashrc"
fi

echo "NetItGo CLI installed to $BIN_PATH"
echo "Run this now if your shell cannot find netitgo yet:"
echo "  export PATH=\"$INSTALL_DIR:\$PATH\""
echo ""
echo "Next:"
echo "  netitgo pod create my-team"
echo "  netitgo start"
