# Fisshing integrations

This directory contains a dependency free JavaScript starter kit for the Fisshing v2 API. It needs Node 20 or newer for the built-in `fetch` API. It has no npm install step.

Download the client and examples from the public static path:

```sh
curl -fsSLO https://fisshing.net/integrations/client.mjs
curl -fsSLO https://fisshing.net/integrations/verify-webhook.mjs
curl -fsSLO https://fisshing.net/integrations/receiver.mjs
curl -fsSLO https://fisshing.net/integrations/agent-task.mjs
curl -fsSLO https://fisshing.net/integrations/companion.mjs
```

Keep an API key in a server environment variable. Do not put a key in browser code, a public repository, or a URL.
To replace and revoke the current key, open Profile → API Key and press Shift+R twice.

```js
import {FisshingClient} from "./client.mjs";

const fisshing = new FisshingClient({
  baseUrl: process.env.FISSHING_BASE_URL,
  apiKey: process.env.FISSHING_API_KEY,
});

const catalog = await fisshing.catalog();
const state = await fisshing.state();
```

`baseUrl` defaults to `https://fisshing.net`. Public `catalog()` and `world()` calls do not send the API key. All other client methods require `apiKey` and send it as a Bearer header. The client sets `redirect: "error"`, so a redirect cannot move the key to another origin. It does not retry requests. Set `timeoutMs: null` to disable the default 10 second request timeout, or pass an `AbortSignal` in the request options.

The client maps these methods to v2 routes:

| Method | Route | Access |
| --- | --- | --- |
| `catalog()` | `GET /api/v2/catalog` | Public |
| `world()` | `GET /api/v2/world` | Public |
| `state()` | `GET /api/v2/me/state` | Key |
| `inventory()` | `GET /api/v2/me/fish` | Key |
| `collection()` | `GET /api/v2/me/collection` | Key |
| `notify(payload)` | `POST /api/v2/notify` | Key |
| `status({active})` | `POST /api/v2/status` | Key |
| `enableBot()` | `POST /api/v2/bot/enable` | Key |
| `action(name)` | `POST /api/v2/actions` | Key |
| `webhooks()` | `GET /api/v2/webhooks` | Key |
| `createWebhook(payload)` or `create(payload)` | `POST /api/v2/webhooks` | Key |
| `deleteWebhook(id)` or `delete(id)` | `DELETE /api/v2/webhooks/:id` | Key |
| `deliveries({query})` | `GET /api/v2/deliveries` | Key |
| `testWebhook(id)` | `POST /api/v2/webhooks/:id/test` | Key |
| `replay(deliveryId)` | `POST /api/v2/deliveries/:id/replay` | Key |

Request failures raise `FisshingApiError` with `status`, `data`, `url`, and an optional request id. Network failures raise `FisshingClientError`; a timeout raises `FisshingTimeoutError`.
The API allows 20 requests per second per key and 2 notification requests per second. Back off when a request returns 429.

## Webhooks

Fisshing sends the `x-fisshing-signature: sha256=<hex>` header. Verify the raw bytes before parsing JSON:

```js
import {verifyWebhookSignature} from "./verify-webhook.mjs";

const rawBody = Buffer.from(await request.arrayBuffer());
const valid = verifyWebhookSignature({
  secret: process.env.FISSHING_WEBHOOK_SECRET,
  body: rawBody,
  signature: request.headers.get("x-fisshing-signature"),
});
if (!valid) throw new Error("invalid webhook signature");
const event = JSON.parse(rawBody.toString("utf8"));
```

Events use an envelope with an `evt_` id, `schema_version: 2`, `event`, `player_id`, `timestamp`, and `data`. `EventDeduper` gives a local receiver a bounded, in-memory repeat check. It is not durable. A production receiver must store event ids in a durable store before it treats a delivery as complete.

Run the local receiver with a secret in the environment:

```sh
FISSHING_WEBHOOK_SECRET='replace-me' node receiver.mjs
```

The receiver binds to `127.0.0.1`, accepts `POST /webhook`, verifies the raw body, limits bodies to 64 KiB, and returns a duplicate response for a repeated event id. Set `FISSHING_WEBHOOK_HOST`, `FISSHING_WEBHOOK_PORT`, or `FISSHING_WEBHOOK_PATH` when needed. Fisshing needs a public HTTPS webhook server for delivery; use a trusted tunnel or proxy to expose a local receiver.

## Agent Desk

The [Claude Code plugin](./claude-plugin/README.md) sends task status and pending questions to Profile → Agent Desk. You can read the question and send an answer from the game. The plugin returns that answer through the native `AskUserQuestion` hook. It needs no sidecar.

Download [the plugin archive](./fisshing-claude-plugin.tar.gz) and follow its README. Each agent gets a separate token. Remove an agent in the desk to revoke that token. Approve commands and file changes in your agent’s own app.

Use the [Codex plugin](./fisshing-codex/README.md) for status hooks and native question tools. Use the [OpenCode plugin](./opencode-plugin/README.md) for its native question reply API. Each guide includes install steps and supported versions.

## Examples

`companion.mjs` reads the collection only:

```sh
FISSHING_API_KEY='replace-me' node companion.mjs
FISSHING_API_KEY='replace-me' node companion.mjs --json
```

`agent-task.mjs` tries to mark a task active, runs one child command without a shell, sends a valid short success or failure notification, and clears the active status in `finally`. Status and notification calls are best effort, so an offline API does not change the child exit code:

```sh
FISSHING_API_KEY='replace-me' node agent-task.mjs -- node ./my-agent.mjs
```

The command output is inherited by the child process. The helper never prints the API key. `action()` reports the API response only; an accepted action does not confirm that the game state changed.

## Current limits

The v2 routes require the matching Fisshing server release. The server owns delivery history and retry state; `replay(deliveryId)` asks the server to queue a new attempt. This static kit does not store delivery history or run delivery retries.

The current scripting compiler still needs a backend fix for boolean `or` and `not` guards and for scalar guard comparisons used by some setup examples. This kit does not change script behavior.
