NPC Library

Build your own NPC

Any script can become a coworker: a backup job, a deploy pipeline, a sensor, the coffee machine. Push its status and it walks into your office — and you can even draw the pixel-art machine it works at.

  1. 1

    Get a key

    In your office, open Connect AI coworkers and create a personal agent key (team admins can also create team keys in Settings → AI coworkers). Save it once on your machine with npx @nocville/npc init or put it in the NOCVILLE_API_KEY environment variable.

  2. 2

    Say hello with one request

    An NPC is created the first time something pushes a status for a new serviceId. Try it — your NPC appears in the office straight away:

    Terminal
    curl -X POST https://nocville.com/api/v1/status \
      -H "Authorization: Bearer $NOCVILLE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"serviceId":"backup-bot","kind":"backup-bot","name":"Backup Bot",
           "status":"healthy","mood":"happy","message":"Last backup 2h ago"}'

    status is healthy, warning, critical or unknown; message is its speech bubble; kind names its type so your team can send every NPC of that type to one room. Push "offline": true when it shuts down and it leaves immediately.

  3. 3

    Turn it into a program

    The @nocville/npc package runs the loop for you: it checks on a schedule, sends one push per cycle, keeps the NPC alive between checks and says goodbye on Ctrl-C.

    Node.js
    // backup-bot.mjs — npm i @nocville/npc
    import { PollingNPC, parseArgs, resolveConfig } from '@nocville/npc';
    
    class BackupBot extends PollingNPC {
      get name() { return 'backup-bot'; }          // also its type ("kind")
      get defaultDisplayName() { return 'Backup Bot'; }
    
      async collect() {
        const hoursAgo = await hoursSinceLastBackup(); // your check
        const late = hoursAgo > 26;
        return {
          status: late ? 'critical' : 'healthy',
          message: late ? `No backup for ${hoursAgo}h!` : `Last backup ${hoursAgo}h ago`,
          metrics: { hoursAgo },
          screenData: {
            layout: 'dashboard',
            values: [{ label: 'LAST', value: `${hoursAgo}h`, color: late ? '#ef4444' : '#22c55e' }],
          },
        };
      }
    }
    
    // Uses the key saved by `npx @nocville/npc setup` / `init`, or --api-key / NOCVILLE_API_KEY.
    const config = resolveConfig(parseArgs(process.argv).flags, { serviceId: 'backup-bot' });
    await new BackupBot({ ...config, pollInterval: 60_000 }).start(); // Ctrl-C → leaves the office

    Prefer Python? The nocville package pushes the same status:

    Python
    # pip install nocville
    from nocville import NocvilleClient
    
    client = NocvilleClient(api_key="YOUR_KEY", service_id="backup-bot")
    try:
        client.push_status("healthy", message="Last backup 2h ago",
                           name="Backup Bot", kind="backup-bot")
    finally:
        client.shutdown()  # pushes offline so the NPC leaves
  4. 4

    Give it a screen

    Add a companion and send screenData each cycle to put a screen beside your NPC: a dashboard-wall of values with sparklines, a log-terminal, a ticker-tape, a status-light and more. Values can carry a colour and a trend arrow.

  5. 5

    Draw its own pixel-art device

    Make your NPC a machine of its own — a server rack with blinking lights, a printer, a little robot arm. Use the pixel-device companion and send your art as the screen data:

    • Draw at 32 pixels per tile: a 2×3-tile device is 64×96, the same scale as the office furniture.
    • Use a transparent background so it stands on the floor like real furniture, and keep to about 32 colours.
    • Animate it by stacking up to 16 frames top to bottom in one PNG (1–12 fps).
    • Swap the image when its state changes — green lights when healthy, flashing red when critical.
    • Set busy: 'seek-companion' and your NPC walks over and works at it. It stays there when activity is 'busy' or 'critical'. Avoid activity: 'waiting' for degraded states: waiting freezes the NPC where it stands and raises its hand, so it never reaches its device. Send 'busy' for warning instead.
    Node.js
    import { PollingNPC, pixelArtFromPng, pixelDevice } from '@nocville/npc';
    
    class BackupBot extends PollingNPC {
      get name() { return 'backup-bot'; }
      // A 2×3-tile device beside the NPC: draw it at 64×96 (32px per tile).
      get companion() { return pixelDevice({ w: 2, h: 3, label: 'backups' }); }
      // While busy, the NPC walks over and works at its device.
      get behavior() { return { busy: 'seek-companion' }; }
    
      async collect() {
        const ok = await lastBackupOk();
        return {
          status: ok ? 'healthy' : 'critical',
          message: ok ? 'Backups OK' : 'Backup failed!',
          metrics: {},
          // Two 64×96 frames stacked in one PNG → blinking LEDs at 2 fps.
          screenData: pixelArtFromPng(ok ? 'art/rack-ok.png' : 'art/rack-alarm.png', {
            frameHeight: 96,
            fps: 2,
          }),
        };
      }
    }
  6. 6

    Run it and place it

    Keep the program running (a terminal, pm2, a systemd service or a container). In the office, click your NPC and choose which room it works in — tick the box to send every NPC of the same kind there too.

  7. 7

    Share it

    Built something your teammates — or other teams — would enjoy? Submit it to the NPC Library for review, or suggest an idea you'd like someone to build.

Let your AI coding tool build it

Copy this prompt into Claude Code, Codex, Cursor or any AI coding assistant. Replace the “What I want this NPC to do” section with your idea — it contains everything the AI needs to build, draw and run your NPC.

Prompt
You are helping me build a custom NPC for Nocville (https://nocville.com), a pixel-art virtual office where each NPC is a character that represents a service, job or tool and shows its status. Build it as a small, long-running program that I can run on my machine or a server.

## What I want this NPC to do
<Describe it here: what it watches or does, what "healthy", "warning" and "critical" mean, how often to check, what it should show on its screen or device.>

## How Nocville NPCs work
- An NPC appears in my team's office the first time its program pushes a status, and leaves when the program pushes `offline: true` (clean shutdown).
- A push is `POST https://nocville.com/api/v1/status` with header `Authorization: Bearer <API key>` and a JSON body. Pushes are rate-limited to 60 per minute per serviceId: send ONE composed push per cycle (every 15–60 s is typical), never one per event.
- Body fields:
  - `serviceId` (required, ≤100 chars): stable id; one NPC per serviceId.
  - `status` (required): `healthy` | `warning` | `critical` | `unknown`.
  - `mood` (required): `happy` | `concerned` | `stressed` | `idle` (usually healthy→happy, warning→concerned, critical→stressed).
  - `message` (≤500 chars): one short line shown in its speech bubble and card.
  - `name` (≤32 chars): banner name. `kind`: lowercase slug for the NPC's type (e.g. `backup-bot`) — teams use it to send all NPCs of a type to one room.
  - `metrics`: flat object of numbers/strings shown on its card.
  - `alertConfig.heartbeatInterval` (seconds, 5–3600): the NPC greys out after 2× this without a push, then leaves ~10 minutes later. Push at least that often.
  - `behavior` hints: `activity` (`busy` | `idle` | `waiting` | `critical`; omit it to derive one from status/mood), `busy` (`seek-desk` | `wander` | `stay` | `seek-companion`), `idle` (`seek-lounge` | `seek-desk` | `wander` | `stay`), `idlePose` (`sit` | `sleep` | `stand`).
  - What each `activity` does: `busy` goes to the `busy` target (its desk, its own device with `seek-companion`, or equipment); `critical` moves exactly like busy and adds the alert visuals; `idle` follows the `idle` hint; `waiting` FREEZES the NPC wherever it is standing and raises its hand — it does not walk anywhere. Use `waiting` only for "blocked on a human" (e.g. an agent awaiting approval), never for a degraded/warning service: send `busy` for warning so it stays at its device, and `critical` for critical.
  - `companion`: an object shown beside the NPC: `{ "screenType": ..., "size": { "w": 1-6, "h": 1-6 } (tiles), "placement": "freestanding" | "adjacent-wall" | "on-desk", "label": "..." }`. Built-in screen types: `dashboard-wall`, `log-terminal`, `ticker-tape`, `crt-monitor`, `status-light`, `gauge-panel`, `commit-board`, `welcome-sign`, `web-view`, and `pixel-device` (my own pixel art).
  - `screenData`: what the companion shows: `{ "layout": "...", "values": [{ "label", "value", "color"?, "trend"?: "up"|"down"|"flat", "sparkline"?: number[] }] }`.
  - `links`: up to a few `{ "label", "url" }` (http/https) shown on its card.
- Use the official SDK instead of raw HTTP when possible. Node: `npm i @nocville/npc` — subclass `PollingNPC`, implement `get name()` and `async collect()` returning `{ status, mood?, message, metrics, screenData?, behavior? }`; override `get companion()`, `get behavior()`, `get archetype()` and `get defaultDisplayName()` as needed; build the config with `resolveConfig(parseArgs(process.argv).flags, { serviceId: '<id>' })` and call `.start()` (it handles Ctrl-C, the offline push and heartbeats). Python: `pip install nocville`, `NocvilleClient(api_key=..., service_id=...)`, `push_status(...)` with keyword fields (`kind=`, `behavior=`, `companion=`), and `shutdown()` in a `finally` block.
- The API key: read it from the `NOCVILLE_API_KEY` environment variable or the saved config (`npx @nocville/npc init`). Never hard-code it or print it.

## Custom pixel-art device (optional, encouraged)
- Set `companion` to `{ "screenType": "pixel-device", "size": { "w": W, "h": H }, "placement": "freestanding" }` and push the art as `screenData` with `layout: "pixel-image"`.
- Art is 32 pixels per tile (a 2×3-tile device is 64×96 px), in the style of cozy 16-bit office pixel art: dark outline, 3–4 shades per material, light from the top-left, a transparent background. Keep to ≤32 colours.
- Animate with up to 16 frames stacked vertically in one PNG (e.g. blinking LEDs), 1–12 fps. Change the art to show state (green lights when healthy, red when critical) — push a new image only when the state changes.
- In Node use `pixelArtFromPng(path, { frameHeight, fps })` and `pixelDevice({ w, h, label })` from `@nocville/npc`. Raw format, if you encode it yourself: values `fmt` = `idx8;<W>x<H>;p<colours>[;t0][;f<frames>;fps<n>]` (`t0` = palette index 0 is transparent), `palette` = base64 of RGB bytes, `pixels` = base64 of one palette index per pixel, frames top to bottom; each value ≤220,000 chars.
- Set `behavior.busy` to `seek-companion` so the NPC walks over and works at its device while busy (and critical). Don't send `activity: "waiting"` for a degraded state — that freezes it away from the device.

## Deliverables
1. The program (well-structured, typed where the language allows, with timeouts on every network call, no secrets in logs, and a clean shutdown that pushes offline).
2. If it has a device: the PNG art (or code that generates it) plus a small preview I can open.
3. A README with how to install, configure and run it (including as a background service), and what each status means.
Test it against my office with a short run before finishing.