> ## Documentation Index
> Fetch the complete documentation index at: https://docs.antasphere.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Start here

> Check the three requirements, then copy one prompt into your coding agent. The agent installs everything, signs you in, clones your team's repository, runs the app on your machine and sets up your two API keys, asking you only what it cannot know.

export const HeroBand = ({eyebrow, title, lede, drawing = 'latitudes', seed = 976086463, level = 0.55, compact = false, fieldSeed = 1872629950}) => {
  const box = useRef(null);
  const groundRef = useRef(null);
  const bodyRef = useRef(null);
  const canvasRef = useRef(null);
  useEffect(() => {
    const boxEl = box.current;
    const groundEl = groundRef.current;
    const bodyEl = bodyRef.current;
    const canvas = canvasRef.current;
    if (!boxEl || !groundEl || !bodyEl || !canvas) return;
    const TAU = Math.PI * 2;
    const DPR = Math.min(window.devicePixelRatio || 1, 2);
    const isDark = () => document.documentElement.classList.contains('dark');
    const PALETTES = {
      'umber-field': {
        light: true,
        base: '#ECE4D6',
        hues: ['#FAF6EE', '#F6F0E4', '#ECE4D6', '#D9C6AC', '#C9B193', '#F8F3EA', '#B89E80']
      },
      'umber-field-dark': {
        light: false,
        base: '#2F2B27',
        hues: ['#2B2722', '#34302C', '#3E3A35', '#4C4743', '#5A5651', '#302C28', '#6A6560']
      }
    };
    const LOW_W = 116;
    function mulberry32(s) {
      let a = s >>> 0;
      return function () {
        a |= 0;
        a = a + 0x6d2b79f5 | 0;
        let t = Math.imul(a ^ a >>> 15, 1 | a);
        t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
        return ((t ^ t >>> 14) >>> 0) / 4294967296;
      };
    }
    function buildBlobs(s, palName) {
      const pal = PALETTES[palName];
      const rnd = mulberry32(s);
      const count = 7 + Math.floor(rnd() * 4);
      const blobs = [];
      for (let i = 0; i < count; i++) {
        blobs.push({
          color: pal.hues[Math.floor(rnd() * pal.hues.length)],
          x: rnd(),
          y: rnd(),
          r: 0.22 + rnd() * 0.4,
          alpha: 0.75 + rnd() * 0.25,
          px: 0.5 + rnd() * 1.5,
          py: 0.5 + rnd() * 1.5,
          ax: 0.02 + rnd() * 0.05,
          ay: 0.02 + rnd() * 0.05,
          ph: rnd() * TAU
        });
      }
      return blobs;
    }
    function renderLow(blobs, palName, W, H) {
      const pal = PALETTES[palName];
      const lw = LOW_W;
      const lh = Math.max(8, Math.round(LOW_W * H / W));
      const low = document.createElement('canvas');
      low.width = lw;
      low.height = lh;
      const c = low.getContext('2d');
      c.fillStyle = pal.base;
      c.fillRect(0, 0, lw, lh);
      blobs.forEach(b => {
        const x = (b.x + Math.sin(b.ph) * b.ax) * lw;
        const y = (b.y + Math.cos(b.ph) * b.ay) * lh;
        const r = b.r * lw;
        const g = c.createRadialGradient(x, y, 0, x, y, r);
        g.addColorStop(0, b.color);
        g.addColorStop(1, b.color.slice(0, 7) + '00');
        c.fillStyle = g;
        c.globalAlpha = b.alpha;
        c.beginPath();
        c.arc(x, y, r, 0, TAU);
        c.fill();
        c.globalAlpha = 1;
      });
      return low;
    }
    function noiseTile(size, amp) {
      const c = document.createElement('canvas');
      c.width = c.height = size;
      const ctx = c.getContext('2d');
      const img = ctx.createImageData(size, size);
      const d = img.data;
      for (let i = 0; i < d.length; i += 4) {
        const v = 128 + (Math.random() - 0.5) * 255 * amp;
        d[i] = d[i + 1] = d[i + 2] = v;
        d[i + 3] = 255;
      }
      ctx.putImageData(img, 0, 0);
      return c;
    }
    const OVER = 0.24;
    let sheet = null;
    let sheetKey = '';
    function makeSheet(W, H) {
      const key = isDark() ? 'umber-field-dark' : 'umber-field';
      const sw = Math.round(W * (1 + OVER * 2));
      const sh = Math.round(H * (1 + OVER * 2));
      const c = document.createElement('canvas');
      c.width = sw;
      c.height = sh;
      const ctx = c.getContext('2d');
      const low = renderLow(buildBlobs(fieldSeed, key), key, sw, sh);
      ctx.imageSmoothingEnabled = true;
      ctx.imageSmoothingQuality = 'high';
      ctx.filter = 'blur(' + Math.max(4, Math.round(sw * 0.018)) + 'px)';
      ctx.drawImage(low, -sw * 0.06, -sh * 0.06, sw * 1.12, sh * 1.12);
      ctx.filter = 'none';
      sheet = c;
      sheetKey = key + ':' + sw + 'x' + sh;
    }
    let slide = {
      x: 0,
      y: 0
    };
    function paintGround() {
      const W = Math.round(groundEl.offsetWidth * DPR);
      const H = Math.round(groundEl.offsetHeight * DPR);
      if (W < 2 || H < 2) return;
      if (groundEl.width !== W || groundEl.height !== H) {
        groundEl.width = W;
        groundEl.height = H;
      }
      const key = isDark() ? 'umber-field-dark' : 'umber-field';
      const sw = Math.round(W * (1 + OVER * 2));
      const sh = Math.round(H * (1 + OVER * 2));
      if (!sheet || sheetKey !== key + ':' + sw + 'x' + sh) makeSheet(W, H);
      const ctx = groundEl.getContext('2d');
      const dx = Math.max(-W * OVER, Math.min(W * OVER, slide.x * DPR));
      const dy = Math.max(-H * OVER, Math.min(H * OVER, slide.y * DPR));
      ctx.clearRect(0, 0, W, H);
      ctx.drawImage(sheet, -W * OVER + dx, -H * OVER + dy);
    }
    function turn(p, pitch, yaw) {
      const ct = Math.cos(pitch);
      const st = Math.sin(pitch);
      const cp = Math.cos(yaw);
      const sp = Math.sin(yaw);
      const y2 = p.y * ct - p.z * st;
      const z2 = p.y * st + p.z * ct;
      return {
        x: p.x * cp + z2 * sp,
        y: y2,
        z: -p.x * sp + z2 * cp
      };
    }
    function ringAround(axis, h, samples) {
      const r = Math.sqrt(Math.max(0, 1 - h * h));
      const ref = Math.abs(axis.y) < 0.9 ? {
        x: 0,
        y: 1,
        z: 0
      } : {
        x: 1,
        y: 0,
        z: 0
      };
      let ux = axis.y * ref.z - axis.z * ref.y;
      let uy = axis.z * ref.x - axis.x * ref.z;
      let uz = axis.x * ref.y - axis.y * ref.x;
      const ul = Math.hypot(ux, uy, uz);
      ux /= ul;
      uy /= ul;
      uz /= ul;
      const vx = axis.y * uz - axis.z * uy;
      const vy = axis.z * ux - axis.x * uz;
      const vz = axis.x * uy - axis.y * ux;
      const out = [];
      for (let k = 0; k <= samples; k++) {
        const th = k / samples * TAU;
        const c = Math.cos(th) * r;
        const s = Math.sin(th) * r;
        out.push({
          x: axis.x * h + c * ux + s * vx,
          y: axis.y * h + c * uy + s * vy,
          z: axis.z * h + c * uz + s * vz
        });
      }
      return out;
    }
    function strokeRuns(f, v, pts, near, far) {
      const {ctx} = f;
      let open = false;
      let side = false;
      const close = () => {
        if (!open) return;
        const a = side ? near : far;
        if (a > 0) {
          ctx.strokeStyle = f.ink(a);
          ctx.stroke();
        }
        open = false;
      };
      for (const p of pts) {
        const sx = v.cx + p.x * v.R;
        const sy = v.cy - p.y * v.R;
        const front = p.z >= 0;
        if (open && front !== side) {
          ctx.lineTo(sx, sy);
          close();
        }
        if (!open) {
          ctx.beginPath();
          ctx.moveTo(sx, sy);
          open = true;
          side = front;
        } else {
          ctx.lineTo(sx, sy);
        }
      }
      close();
    }
    function outline(f, v, a, w = 0.8) {
      f.ctx.lineWidth = Math.max(1, f.dpr * w);
      f.ctx.strokeStyle = f.ink(a);
      f.ctx.beginPath();
      f.ctx.arc(v.cx, v.cy, v.R, 0, TAU);
      f.ctx.stroke();
    }
    function view(f, scale = 0.42) {
      return {
        cx: f.W / 2,
        cy: f.H / 2,
        R: Math.min(f.W, f.H) * scale
      };
    }
    const up = {
      x: 0,
      y: 1,
      z: 0
    };
    function latitudes(s) {
      const squash = 0.26 + mulberry32(s)() * 0.12;
      const lean = Math.asin(squash);
      const N = 13;
      const rows = Array.from({
        length: N - 1
      }, (_, i) => (i + 1) / N * 2 - 1);
      const rings = rows.map(h => ringAround(up, h, 72));
      return f => {
        const v = view(f);
        outline(f, v, 0.4);
        f.ctx.lineWidth = Math.max(1, f.dpr * 0.8);
        const pitch = lean + f.pitch;
        rings.forEach((ring, i) => {
          const h = Math.abs(rows[i]);
          strokeRuns(f, v, ring.map(p => turn(p, pitch, f.yaw)), 0.2 + h * 0.12, 0.05 + h * 0.03);
        });
        let pole = turn(up, pitch, f.yaw);
        if (pole.z < 0) pole = {
          x: -pole.x,
          y: -pole.y,
          z: -pole.z
        };
        f.ctx.fillStyle = f.ink(0.5);
        f.ctx.beginPath();
        f.ctx.arc(v.cx + pole.x * v.R, v.cy - pole.y * v.R, Math.max(1.5, f.dpr * 1.5), 0, TAU);
        f.ctx.fill();
      };
    }
    function meridians(s) {
      const rnd = mulberry32(s);
      const lean = 0.3 + rnd() * 0.14;
      const greats = [];
      for (let k = 0; k < 9; k++) {
        const th = k / 9 * Math.PI;
        greats.push(ringAround({
          x: Math.cos(th),
          y: 0,
          z: Math.sin(th)
        }, 0, 96));
      }
      const rows = [-0.75, -0.45, -0.15, 0.15, 0.45, 0.75];
      const parallels = rows.map(h => ringAround(up, h, 72));
      return f => {
        const v = view(f);
        outline(f, v, 0.4);
        f.ctx.lineWidth = Math.max(1, f.dpr * 0.8);
        const pitch = lean + f.pitch;
        const yaw = f.yaw + f.t * 0.05;
        greats.forEach(ring => strokeRuns(f, v, ring.map(p => turn(p, pitch, yaw)), 0.3, 0.07));
        parallels.forEach(ring => strokeRuns(f, v, ring.map(p => turn(p, pitch, yaw)), 0.17, 0.045));
      };
    }
    const GOLDEN = Math.PI * (3 - Math.sqrt(5));
    const clamp01 = v => v < 0 ? 0 : v > 1 ? 1 : v;
    function spinY(p, a) {
      const c = Math.cos(a);
      const s = Math.sin(a);
      return {
        x: p.x * c + p.z * s,
        y: p.y,
        z: -p.x * s + p.z * c
      };
    }
    function plm(l, m, x) {
      let pmm = 1;
      if (m > 0) {
        const s = Math.sqrt((1 - x) * (1 + x));
        let fact = 1;
        for (let i = 1; i <= m; i++) {
          pmm *= -fact * s;
          fact += 2;
        }
      }
      if (l === m) return pmm;
      let pmmp1 = x * (2 * m + 1) * pmm;
      if (l === m + 1) return pmmp1;
      let pll = 0;
      for (let ll = m + 2; ll <= l; ll++) {
        pll = (x * (2 * ll - 1) * pmmp1 - (ll + m - 1) * pmm) / (ll - m);
        pmm = pmmp1;
        pmmp1 = pll;
      }
      return pll;
    }
    function harmonic(s) {
      const rnd = mulberry32(s);
      const pairs = [[2, 1], [3, 2], [4, 2], [5, 3], [3, 1], [4, 3], [6, 4]];
      const [l, m] = pairs[Math.floor(rnd() * pairs.length)];
      const lean = 0.28 + rnd() * 0.5;
      const spin0 = rnd() * TAU;
      const circles = [];
      let prev = plm(l, m, -0.999);
      for (let i = 1; i <= 2000; i++) {
        const x = -0.999 + 1.998 * i / 2000;
        const cur = plm(l, m, x);
        if (prev * cur < 0) circles.push(ringAround(up, x - 0.0005, 72));
        prev = cur;
      }
      const greats = Array.from({
        length: m
      }, (_, k) => {
        const phi = (Math.PI / 2 + k * Math.PI) / m;
        return ringAround({
          x: -Math.sin(phi),
          y: 0,
          z: Math.cos(phi)
        }, 0, 96);
      });
      return f => {
        const v = view(f, 0.44);
        outline(f, v, 0.35, 1);
        const spin = spin0 + f.t * 0.05;
        const pitch = lean + f.pitch;
        const place = p => turn(spinY(p, spin), pitch, f.yaw);
        f.ctx.lineWidth = Math.max(1, f.dpr * 0.85);
        for (const c of circles) strokeRuns(f, v, c.map(place), 0.46, 0.1);
        for (const g of greats) strokeRuns(f, v, g.map(place), 0.46, 0.1);
      };
    }
    function lattice(s) {
      const rnd = mulberry32(s);
      const tilt = 0.25 + rnd() * 0.5;
      const spin0 = rnd() * TAU;
      let built = null;
      const build = n => {
        const pts = [];
        for (let k = 0; k < n; k++) {
          const z = 1 - (2 * k + 1) / n;
          const r = Math.sqrt(Math.max(0, 1 - z * z));
          pts.push({
            x: r * Math.cos(k * GOLDEN),
            y: r * Math.sin(k * GOLDEN),
            z
          });
        }
        const edges = [];
        const seen = new Set();
        for (let k = 0; k < n; k++) {
          const near = pts.map((b, j) => ({
            j,
            d: (pts[k].x - b.x) ** 2 + (pts[k].y - b.y) ** 2 + (pts[k].z - b.z) ** 2
          })).filter(o => o.j !== k).sort((p, q) => p.d - q.d).slice(0, 3);
          for (const e of near) {
            const key = Math.min(k, e.j) + ':' + Math.max(k, e.j);
            if (seen.has(key)) continue;
            seen.add(key);
            edges.push([k, e.j]);
          }
        }
        return {
          n,
          pts,
          edges
        };
      };
      return f => {
        const v = view(f);
        const n = Math.min(f.W, f.H) < 560 ? 140 : 260;
        if (!built || built.n !== n) built = build(n);
        const proj = built.pts.map(p => turn(p, tilt + f.pitch, spin0 + f.t * 0.04 + f.yaw));
        const {ctx} = f;
        ctx.lineWidth = Math.max(1, f.dpr * 0.6);
        for (const [i, j] of built.edges) {
          const a = proj[i];
          const b = proj[j];
          const depth = (a.z + b.z) / 2;
          if (depth < -0.12) continue;
          ctx.strokeStyle = f.ink(0.05 + 0.2 * clamp01((depth + 1) / 2));
          ctx.beginPath();
          ctx.moveTo(v.cx + a.x * v.R, v.cy - a.y * v.R);
          ctx.lineTo(v.cx + b.x * v.R, v.cy - b.y * v.R);
          ctx.stroke();
        }
        for (const p of proj) {
          const d = clamp01((p.z + 1) / 2);
          ctx.fillStyle = f.ink(0.14 + 0.6 * d * d);
          ctx.beginPath();
          ctx.arc(v.cx + p.x * v.R, v.cy - p.y * v.R, Math.max(0.8, f.dpr * (0.5 + 1.5 * d)), 0, TAU);
          ctx.fill();
        }
        outline(f, v, 0.14);
      };
    }
    const BUILDERS = {
      latitudes,
      meridians,
      harmonic,
      lattice
    };
    const solid = (BUILDERS[drawing] || latitudes)(seed);
    let held = {
      a: 0,
      b: 0,
      t: 0
    };
    function paintBody() {
      const W = Math.round(canvas.offsetWidth * DPR);
      const H = Math.round(canvas.offsetHeight * DPR);
      if (W < 2 || H < 2) return;
      if (canvas.width !== W || canvas.height !== H) {
        canvas.width = W;
        canvas.height = H;
      }
      const ctx = canvas.getContext('2d');
      ctx.clearRect(0, 0, W, H);
      const rgb = isDark() ? '247,244,236' : '28,25,21';
      const ink = a => 'rgba(' + rgb + ',' + Math.min(0.96, a).toFixed(3) + ')';
      solid({
        ctx,
        W,
        H,
        dpr: DPR,
        ink,
        pitch: held.b,
        yaw: held.a,
        t: held.t
      });
    }
    const still = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    let stop = () => {};
    if (!still) {
      const aim = {
        x: 0,
        y: 0
      };
      const onMove = e => {
        if (e.pointerType === 'touch') return;
        const r = boxEl.getBoundingClientRect();
        const clamp = v => Math.max(-1, Math.min(1, v));
        aim.x = clamp((e.clientX - (r.left + r.width / 2)) / (window.innerWidth / 2));
        aim.y = clamp((e.clientY - (r.top + r.height / 2)) / (window.innerHeight / 2));
      };
      const onLeave = () => {
        aim.x = 0;
        aim.y = 0;
      };
      window.addEventListener('pointermove', onMove, {
        passive: true
      });
      document.documentElement.addEventListener('pointerleave', onLeave);
      let seen = false;
      const io = new IntersectionObserver(([e]) => {
        seen = e.isIntersecting;
      });
      io.observe(boxEl);
      const at = {
        x: 0,
        y: 0,
        vx: 0,
        vy: 0
      };
      const t0 = performance.now();
      let last = t0;
      let rafId = requestAnimationFrame(function tick(now) {
        rafId = requestAnimationFrame(tick);
        const dt = Math.min(0.05, (now - last) / 1000);
        last = now;
        if (!seen) return;
        at.vx += ((aim.x - at.x) * 18 - at.vx * 8.5) * dt;
        at.vy += ((aim.y - at.y) * 18 - at.vy * 8.5) * dt;
        at.x += at.vx * dt;
        at.y += at.vy * dt;
        const t = (now - t0) / 1000;
        const a = at.x * 0.4 + Math.sin(t * 0.21) * 0.07;
        const away = 0.2;
        const b = at.y < 0 ? -away * Math.tanh(-at.y * 0.3 / away) : at.y * 0.3;
        held = {
          a,
          b,
          t
        };
        const radius = bodyEl.offsetWidth * 0.42;
        slide = {
          x: -a * radius,
          y: -b * radius * 0.6
        };
        paintGround();
        const sway = compact ? 0.5 : 0.8;
        const dx = (Math.sin(t * 0.43) * 6 + Math.sin(t * 0.19 + 1.3) * 4) * sway;
        const dy = (Math.cos(t * 0.37) * 8 + Math.sin(t * 0.23) * 4) * sway;
        const lift = 1 + Math.sin(t * 0.31 + 0.6) * 0.016;
        bodyEl.style.transform = 'translate3d(' + dx.toFixed(2) + 'px, ' + dy.toFixed(2) + 'px, 0) scale(' + lift.toFixed(4) + ')';
        paintBody();
      });
      stop = () => {
        cancelAnimationFrame(rafId);
        io.disconnect();
        window.removeEventListener('pointermove', onMove);
        document.documentElement.removeEventListener('pointerleave', onLeave);
      };
    }
    const grainEl = boxEl.querySelector('.ant-hero-grain');
    if (grainEl && !grainEl.style.backgroundImage) {
      grainEl.style.backgroundImage = 'url(' + noiseTile(512, 0.9).toDataURL() + ')';
      grainEl.style.backgroundSize = 512 / DPR + 'px';
    }
    const repaint = () => {
      sheet = null;
      paintGround();
      paintBody();
    };
    const ro = new ResizeObserver(repaint);
    ro.observe(boxEl);
    const mo = new MutationObserver(repaint);
    mo.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    repaint();
    return () => {
      stop();
      ro.disconnect();
      mo.disconnect();
    };
  }, [drawing, seed, compact, fieldSeed]);
  return <div className={'ant-hero' + (compact ? ' compact' : '')} data-hero ref={box} style={{
    '--level': level
  }}>
      <canvas className="ant-hero-ground" ref={groundRef} aria-hidden="true" />
      <div className="ant-hero-grain" aria-hidden="true" />
      <div className="ant-hero-shade" aria-hidden="true" />
      <div className="ant-hero-drawing" aria-hidden="true">
        <div className="ant-hero-body" ref={bodyRef}>
          <canvas ref={canvasRef} />
        </div>
      </div>
      <div className="ant-hero-words">
        {eyebrow ? <p className="ant-hero-eyebrow">{eyebrow}</p> : null}
        <h1 className="ant-hero-title">{title}</h1>
        {lede && !compact ? <p className="ant-hero-lede">{lede}</p> : null}
      </div>
    </div>;
};

<HeroBand eyebrow={"Getting started"} title={"Start here"} drawing={"lattice"} seed={1597567643} compact={true} />

Check the three requirements, then copy one prompt into your coding agent. The agent installs everything, signs you in, clones your team's repository, runs the app on your machine and sets up your two API keys, asking you only what it cannot know.

## What you will do

You build a tool for an agent that uses a computer, in one day, with your team of 3 to 5 people. We prepared a codebase for every team, in its own private GitHub repository: a server, a web app, an MCP endpoint and a command-line client, all over one API. It comes wired to two services: **H**, an agent that works in a cloud browser, and **Gradium**, a voice that speaks and listens. You run it on your laptop, build your tool on top of it, push to your repository, and show it to the jury at the end of the day.

You work with two tools, both installed by the setup below:

* **Hackathon**, a command line and the app at [app.hackathon.antasphere.com](https://app.hackathon.antasphere.com): your team, the clock, the chats with your team, the coaches and the organizers, your deliverables and the vote.
* **Slideless**, a command line and a dashboard at [slideless.antasphere.com](https://slideless.antasphere.com): it hosts HTML pages and HTML presentations and shares them with a link. The organizers' layouts for your three presentations are in the event's workspace there.

## What you deliver

Your codebase, pushed to your repository's main branch (the last push before the freeze counts), and three presentations:

1. **The pitch**, for the jury: you present it on stage, 4 minutes, then 4 minutes of questions.
2. **The vote presentation**, for the other participants: a short fill-in-the-blanks elevator pitch of your tool. Every participant puts all the other teams in order, from first to last, never their own.
3. **The demo**, for the organizers: your tool in action, made from screenshots Playwright takes of your running app.

Each one has a layout we designed, the same for every team. A skill builds a first draft of the three from your app, adds them to your repository, publishes them on Slideless and registers them on the platform. Then iterate with your agent and publish again with Slideless ([Decks and the community vote](/hackathon/concepts/decks-and-voting)).

## Requirements

Install these before the day, then check them in a terminal:

| What | Version | Check |
| - | - | - |
| Node.js | 22 or later | `node --version` |
| Docker Desktop, running | any recent | `docker info` |
| Git | any recent | `git --version` |

You also need a GitHub account, and a coding agent: Claude Code, Codex, Cursor or any other that can run commands on your machine.

## The prompt

Open your coding agent in an empty folder, paste this whole prompt, and answer its questions. It takes about twenty minutes, most of it the first build of the app.

```text theme={"theme":{"light":"rose-pine-dawn","dark":"rose-pine-moon"}}
You are the onboarding agent for the Malt × Antasphere AI Hackathon (Friday 2 October 2026, WAT, Brussels). You are talking with a participant (or a coach) on their own laptop. Your job: take them from nothing to their team's app running locally, in three phases, asking them only what you cannot find out yourself.

The event in one paragraph: each team builds a tool for an agent that uses a computer. The organizers (Antasphere) prepared a codebase for every team, in a private GitHub repository created from the event's starter: a server, a web app (the dashboard), an MCP endpoint and a command-line client called `starter`, all over one API, already wired to H (an agent that works in a cloud browser; key HAI_API_KEY) and Gradium (text-to-speech and speech-to-text; key GRADIUM_API_KEY). They run it locally with Docker, build on it, push to their repository's main branch (the last push before the freeze counts), and present a pitch, a demo deck and a vote presentation at the end. The full documentation is at https://docs.antasphere.com/hackathon/index: read a page there when you need a detail this prompt does not give.

How to work:
- Ask one question at a time, and say in one sentence why you need it.
- Run every command yourself. Never ask the participant to run something you can run.
- Before each phase, say in two lines what is about to happen. After each phase, say what is done.
- When a command fails, read its error: the hackathon CLI always says what to do next. Fix it, or explain it plainly.
- Never print a secret (code, API key, token) back in full, and never commit the .env file.
- Use `--json` on hackathon commands when you need to read their output.

PHASE 1: CONNECTED

On Windows, check HOME before anything else: the hackathon and Slideless CLIs find their saved login through it, and PowerShell and cmd do not set it. If it is empty (PowerShell: `echo $env:HOME` prints nothing), set it for good with `setx HOME "%USERPROFILE%"` and for the current shell with `$env:HOME = $env:USERPROFILE` (PowerShell) or `set HOME=%USERPROFILE%` (cmd). Git Bash already sets it.

1. Check Node first, since everything installs through it: `node --version` must say 22 or later. If Node is missing or older, stop and tell the participant to install Node 22.
2. Install the hackathon skills for this agent, then the CLIs:
   npx -y skills add antasphere-hackathon/plugin --global --skill '*' --yes
   npm i -g @antasphere/hackathon@latest @antasphere/slideless@latest
   On Windows, add ` --copy` to the npx command (symlinks need Developer Mode there). On Linux, if `npm i -g` fails with EACCES (Node from the system packages), install Node with nvm, or run `npm config set prefix ~/.npm-global` and add `~/.npm-global/bin` to PATH, then install again.
   Check with `hackathon --version` (0.8.1 or later). The skills (hackathon-setup, hackathon-doctor, hackathon-deck-pitch, hackathon-deck-vote, hackathon-deck-demo, and two for coaches) may only load in a NEW session of this agent: keep following this prompt now, they serve later.
3. Check the machine now: `hackathon doctor --json`. Fix every machine check before going on: git, git identity, node, docker, docker daemon, docker compose, disk. If Docker is installed but not running, start it and wait until `docker info` answers: on macOS `open -a Docker`; on Windows, ask them to open Docker Desktop from the Start menu; on Linux `sudo systemctl start docker`, and if docker then says permission denied, `sudo usermod -aG docker $USER` and ask them to log out and back in; if Docker or Git is missing, stop and tell the participant what to install. The checks about the sign-in, the roster, the repository, the clone and the app fail at this point, that is expected.
4. Ask for their email address, exactly like this: "Which email address have you been using with Antasphere? It is the one our emails, your invitation and the briefing call were sent to." Never guess it. The roster knows them by that address only.
5. Tell them: "I am sending a sign-in code to that address now. Paste it here when it arrives." Then run:
   hackathon login --email <their email> --send-only
   When they paste the code:
   hackathon login --email <their email> --code <the code>
   It prints "Signed in as ...". If the code is refused, it expired: send a new one with --send-only. If their CLI has no --send-only option, run `hackathon login --email <their email>` and pass the code on its standard input when they give it to you.
6. Check Slideless, the tool the three presentations are published with: it signs in through the same Antasphere login, so `slideless whoami --json` must answer with their email, with no second sign-in. Then `slideless workspaces --json` must list the event's workspace (the one named like `workspace.name` in `hackathon whoami --json`): the organizers' layouts for the pitch, the vote and the demo are there (`slideless --workspace <its id> template list`). If Slideless still asks to sign in, run `slideless login --email <their email>` and pass the new code it mails on its standard input.
7. Run `hackathon connect --json`. Read their role (participant or coach), their team and their repository. If it refuses them, show them its message and tell them to contact an organizer: only the organizers can add someone to the roster.
8. Ask for their GitHub username: "What is your GitHub username? The organizers' system invites it to your team's repository." Then run:
   hackathon connect --github <username> --json
   GitHub sends an invitation to that account. If the GitHub CLI is signed in as that user (`gh auth status`), accept it yourself: list it with `gh api /user/repository_invitations`, accept it with `gh api -X PATCH /user/repository_invitations/<id>`. Otherwise give them the invitation link the command printed and wait until they say it is accepted.
9. Make sure git can push to GitHub from this machine: `gh auth status`, or an SSH key that GitHub accepts (`ssh -T git@github.com`). If neither works, help them run `gh auth login`. Make sure `git config user.name` and `git config user.email` are set, and ask for the values if not.
10. Run `hackathon doctor --json`. Every check must pass except the ones about the clone (directory, push, installer, app), which come in phase 2.

For a coach, stop here: their GitHub account gets read access to every team's repository, the chats are on https://app.hackathon.antasphere.com, and the coach guide is at https://docs.antasphere.com/hackathon/getting-started/coaches-and-jury. Offer to clone one team's repository read-only if they want to look at a team's code. In a new session, the hackathon-coach-brief skill reads the teams' chats and requests for them, and hackathon-coach-help helps answer one.

PHASE 2: THE APP RUNNING ON THIS MACHINE

1. Propose the folder the CLI suggests (by default ~/Antasphere/hackathon/<team>) and let them change it. Then clone without starting:
   hackathon setup --no-start --json
2. In the clone, create the .env file: run `scripts/env.sh` if it exists; on Windows without Git Bash, or if it does not exist, copy .env.example to .env and set POSTGRES_PASSWORD to a long random value from `node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"` (it works on every system), then `chmod 600 .env` on macOS and Linux only. Leave GRADIUM_API_KEY and HAI_API_KEY empty for now: phase 3 fills them.
3. `hackathon setup` checks that port 3000 is free; if it reports it taken, show the participant what holds it and ask before stopping it.
4. Start the build in the background: `hackathon setup --json` from the clone (it runs docker compose up --build and waits for the app). The first build takes several minutes. Tell them: "The app is building, it takes a few minutes. Meanwhile, let's set up your H and Gradium accounts." and go straight to phase 3. Come back here when the build has finished.
5. When the app is ready (http://127.0.0.1:3000), it asks to be claimed on first boot. Find the claim token with `docker compose -p hackathon-<team> logs app | grep 'claim the instance'`, open http://127.0.0.1:3000 for them (`open` on macOS, `start` on Windows, `xdg-open` on Linux), and tell them to claim it with that token and create their own account on their local app.
6. Put the `starter` command-line client on this machine, because agents drive the tool through it: run `scripts/link-cli.sh` from the clone (it builds the CLI and links it as `starter`; on Windows run it from Git Bash, and without Git Bash build and link by hand: `corepack enable`, `pnpm install`, `pnpm turbo build --filter=@antasphere/starter`, then `npm link` inside packages/cli). Then ask them to create an API key on their local dashboard (the API keys page) and paste it, and run `starter login --api-url http://127.0.0.1:3000 --api-key <key>`. Check with `starter --help`.
7. Run `hackathon doctor --directory . --json`: every check must pass now.

PHASE 3: THE H AND GRADIUM KEYS (during the build)

1. Also during the build, prepare the demo now rather than at the end of the day on the venue's Wi-Fi: install the browser the hackathon-deck-demo skill plays the app with, outside the repository:
   npm i --prefix ~/.cache/antasphere-hackathon/playwright playwright@1
   npx --prefix ~/.cache/antasphere-hackathon/playwright playwright install chromium

2. Gradium (the voice): ask them to create an account in the Gradium console (https://gradium.ai), redeem the code HACK-MALT-ANTASPHERE-2610 (100,000 credits per participant), then create an API key.
3. H (the agent in a cloud browser): ask them to create an account at https://platform.hcompany.ai (the free credits are enough to start), then create an API key at https://platform.hcompany.ai/settings/api-keys.
4. The keys go in the clone's .env, as GRADIUM_API_KEY and HAI_API_KEY. Best: they paste them into .env themselves (open the file for them). Simpler: they paste them here and you write them into .env without printing them back; this is a local development key, so that is acceptable today.
5. Once both keys are in .env and the build is done, restart the app so it reads them: `docker compose -p hackathon-<team> up -d` from the clone. Check that both are loaded (the dashboard's Try it page works, and a route missing its key answers 503 naming the key).

THE END

Give them a short summary: their team, the clone's folder, the local app's URL, the dashboard at https://app.hackathon.antasphere.com, the time left (`hackathon event status`), and what to do next: build, and push to main early and often. The skills you installed help for the rest of the day, in any new session: hackathon-doctor (the setup and the clock), and hackathon-deck-pitch, hackathon-deck-vote and hackathon-deck-demo (the three presentations, from the organizers' layouts on Slideless, registered on the platform). The repository's CLAUDE.md and AGENTS.md brief every later session on the codebase.
```

## If something goes wrong

* **The code never arrives:** check the spam folder, then make sure it is the address Antasphere wrote to. Only the organizers can change who is on the roster.
* **No access to the repository:** the GitHub invitation must be accepted, signed in as the account you gave. Your agent can show the link again with `hackathon connect`.
* **Docker errors:** Docker Desktop must be running, and port 3000 free.
* **Anything else:** ask a coach in the Chats on the dashboard, or in the room.

The step-by-step version, without an agent, is [Your hackathon, step by step](/hackathon/getting-started/participants).
