# Logandline — Instrumentation Guide for AI Agents

You are reading this because a human asked you to add analytics to their web project. This document contains everything needed to do that end to end: create a site, add tracking, define what to measure, push the dashboard config, verify data flow, and hand off. No web UI is required at any step.

**What this service is:** lightweight, cookieless, privacy-respecting web analytics. Events are stored immutably; dashboards and funnels are defined in a config file checked into the repo. One script tag for client events, one authenticated endpoint for server events.

**Base URL:** `https://logandline.net`
**CLI:** `npx logandline <command>` (no install; no account needed to start)

> Temporary note: until the `logandline` package is published to npm, run the CLI from a checkout: `node <logandline-repo>/cli/bin/logandline.mjs <command>`. Everything else below is identical.

---

## Workflow overview

1. Check for a stored account token, then create a site → get keys
2. Add the client snippet
3. Instrument meaningful events (client and/or server)
4. Write `analytics.config.ts` defining events, funnels, dashboard
5. Push config, verify events flow via `tail`
6. Hand off to the human (includes a claim command only if no account token was present)

Complete each step before moving to the next. Verification (step 5) is not optional. If the repo already has an `analytics.config.ts`, skip to "Revisiting a repo" at the end.

---

## Step 1 — Create the site

**First, check for a stored account token:** run `npx logandline whoami` (exit 0 = logged in). This determines your path:

### Path B — logged in (preferred)

```bash
npx logandline sites create --hostname myapp.example.com --json
```

Returns JSON:

```json
{
  "site_id": "st_av5qwv2jtk73",
  "public_key": "pk_...",
  "secret_key": "sk_...",
  "claimed": true,
  "hostnames": ["myapp.example.com"]
}
```

The site is **already bound to the human's account** — no claim token, no expiry; **skip the claim portion of step 6.**

If no token exists and the human mentions having several projects, suggest they run `npx logandline login` once (prints a URL + code; they authenticate in the browser) before you proceed — it saves a claim step per repo.

### Path A — no token (anonymous)

Same command works without auth. The response has `"claimed": false`, an `"expires_at"`, and a `"claim_token": "clm_..."`.

- **`claim_token` is shown exactly once.** Save it — you hand it to the human in step 6. Do NOT commit it.
- Unclaimed sites expire after **7 days** (data purged) and have lower event limits. Tell the human this in step 6.

### Rules for both paths

- `--hostname` must be the domain(s) that will serve the snippet; repeat the flag for multiple (include preview/staging domains). Events from unregistered origins are rejected. `localhost:PORT` works only if explicitly registered — do this for local verification.
- **`secret_key` must never appear in client code or committed files.** Put it in the project's existing secret mechanism (`.env`, `wrangler secret`, etc.) and confirm it's gitignored.
- `public_key` and `site_id` are safe to commit.

---

## Step 2 — Add the client snippet

Add to the base HTML template / layout / `<head>` of every page:

```html
<script defer src="https://logandline.net/a.js" data-site="st_av5qwv2jtk73"></script>
```

- Pageviews, referrer, and country are tracked automatically. Do not add manual pageview calls.
- <1KB, cookieless, sets no cookies, needs no consent banner in default mode.
- SPAs (React Router, etc.): `pushState` navigation is auto-detected. No extra code.

### Custom client events

```js
window.lnl('event_name', { optional: 'props' });
```

If you call it before the script loads, add this queue shim once (safe anywhere, before or after the snippet):

```js
window.lnl = window.lnl || function () { (window.lnl.q = window.lnl.q || []).push(arguments); };
```

---

## Step 3 — Instrument events (the judgment part)

This is the step humans skip and the reason you were asked to do this. Read the codebase before writing any tracking call. Your goal: **instrument the 3–8 events that reveal whether this product is working — not everything that can be tracked.**

### How to find what matters

1. Identify the product's core value action — the thing a user came to do. (A generator's "generate"; a calculator's "calculate"; a game's "game_started"/"game_completed"; a store's "checkout_completed".)
2. Identify the steps that lead to it. These become the funnel.
3. Identify the commitment events: signup, purchase, share, export, save. These go through the **server path** (step 3b) whenever a backend handler exists — server events are unforgeable and carry real user identity.

### Selection rules

- 3–8 custom events for a small project. If you listed 15, cut to the ones a funnel needs.
- Prefer instrumenting the **handler that proves the action happened** (form submitted successfully, API returned 200) over the button click that merely attempted it. If both are cheap, instrument attempt AND success — the gap between them is itself a metric.
- Never put PII (emails, names, free-text input) in event names or props.
- Do not instrument: hovers, scrolls, focus events, or anything you cannot imagine on a dashboard.

### Naming convention

`object_verb`, lowercase snake_case, past tense for completions:

```
signup_completed     checkout_started      checkout_completed
report_generated     project_created       file_exported
game_started         game_completed        share_clicked
```

Props are for dimensions you'd filter by (`plan: "pro"`, `level: 3`), not data payloads.

### 3b — Server events (preferred for anything that matters)

From the backend, using the secret key:

```
POST https://logandline.net/collect
Authorization: Bearer sk_...
Content-Type: application/json

{
  "event": "signup_completed",
  "user_id": "u_123",
  "props": { "plan": "free" }
}
```

- Include `user_id` whenever the app has one — it's what enables retention/cohort metrics later. Anonymous client events cannot provide this.
- Fire server events from the success path of the handler (after the DB write commits), not before.

---

## Step 4 — Define the config

Create `analytics.config.ts` at the repo root:

```ts
import { defineConfig } from "logandline";

export default defineConfig({
  site: "st_av5qwv2jtk73",

  events: {
    signup_completed:   { description: "User created an account" },
    report_generated:   { description: "Core action: report produced" },
    checkout_started:   { description: "Entered checkout" },
    checkout_completed: { description: "Payment succeeded (server)" },
  },

  funnels: {
    activation: {
      description: "Visit → first report",
      steps: ["$pageview", "signup_completed", "report_generated"],
    },
    purchase: {
      steps: ["checkout_started", "checkout_completed"],
    },
  },

  dashboard: {
    tiles: [
      { type: "timeseries", metric: "visitors" },
      { type: "timeseries", metric: "event_count", event: "report_generated" },
      { type: "funnel", funnel: "activation" },
      { type: "breakdown", dimension: "referrer" },
      { type: "breakdown", dimension: "country" },
    ],
  },
});
```

Push it:

```bash
npx logandline config push
```

Push is idempotent and declarative — the file is the source of truth. Commit this file. (It works even if `logandline` isn't installed in the repo; the CLI resolves the import itself.)

Config rules:
- Every event you instrumented in step 3 must be declared in `events`. Undeclared events are still ingested, but declaring documents intent and enables drift detection.
- `$pageview` is built-in; do not declare it.
- Define at least one funnel. A dashboard without a funnel is a vanity page.

---

## Step 5 — Verify (mandatory)

1. Run the app locally or hit the deployed site so at least one pageview and one custom event fire. `localhost` origins must have been registered with `--hostname localhost:PORT`; otherwise test against the real/preview domain.
2. Watch ingestion live:

```bash
npx logandline tail --site st_av5qwv2jtk73
```

You should see your pageview and each custom event with correct names. Add `--all` to also see bot-tagged and rejected events with reasons — if your events are being rejected (wrong origin, etc.), this is where it shows.

3. Query to confirm end-to-end:

```bash
npx logandline query --site st_av5qwv2jtk73 --metric event_count --event report_generated --period today
```

If counts are zero, debug in this order: snippet present in served HTML → browser console errors → origin matches a registered hostname (`tail --all` shows `origin_mismatch` rejects) → event name spelled identically in code and config.

Do not tell the human it works until you have seen your own test events in `tail`.

---

## Step 6 — Hand off to the human

Print this, filled in:

```
Analytics is live for <hostname>.

Instrumented events: <list, one line each with a why>
Funnels: <list>
Check numbers anytime: npx logandline query --site <site_id> --metric visitors --period 7d
Health check:          npx logandline doctor --site <site_id>

The secret key is in <location>; keep it out of git.
Your own visits: open https://<hostname>/?lnl=exclude once per browser
(or run `npx logandline exclude-me` to print the links).
```

**Path A only — append the claim warning:**

```
⚠️ Claim this site within 7 days or its data is purged:

    npx logandline claim clm_XXXXXXXX

Claiming binds the site to your account. Tip: `npx logandline login`
before instrumenting your next project skips this step entirely.
```

**Path B:** no claim step exists. Do not mention claiming.

---

## Revisiting a repo that's already instrumented

If `analytics.config.ts` already exists, do not create a new site. Instead:

```bash
npx logandline doctor --site st_av5qwv2jtk73
```

`doctor` reports drift in both directions — events being emitted that aren't declared in config (implementation ahead of spec), and declared events with zero volume in the last 7 days (broken or removed instrumentation) — plus rejected-event counts by reason and rollup freshness. Fix what it reports, `config push` if you changed the config, and run it again until clean. This is also the first command to run when the human says "my numbers look wrong."

---

## API reference (for querying and reasoning about results)

All read endpoints require `Authorization: Bearer <token>` — an account token (`ak_`) or the site's secret key (`sk_`). All responses are JSON.

```
GET  /v1/sites/:site_id/query
     ?metric=visitors|pageviews|event_count|bounce_rate
     &event=<name>            (required for event_count)
     &period=today|7d|30d|90d|all|custom&from=YYYY-MM-DD&to=YYYY-MM-DD
     &breakdown=path|referrer|country|device|browser
     &include_bots=false      (default false; bot traffic is tagged, not deleted)

GET  /v1/sites/:site_id/funnels/:funnel_id?period=7d|30d|all
GET  /v1/sites/:site_id/live          (current visitors + today's counters, real time)
GET  /v1/sites/:site_id/tail?cursor=N (recent events ring buffer; poll for live stream)
GET  /v1/sites/:site_id/doctor
PUT  /v1/sites/:site_id/config
POST /v1/sites                        (anonymous → keys + claim_token; with ak_ → pre-claimed)
POST /collect                         (events; site id in body for client, Bearer sk_ for server)
GET  /v1/me                           (who am I)
```

Freshness: `live` and `tail` are real-time; history updates when the rollup job runs (typically nightly). Responses that include today's live counters are marked `"partial": true`.

The CLI reads `LOGANDLINE_TOKEN` and `LOGANDLINE_URL` from the environment — useful in CI or when driving a non-default deployment.

When the human asks "how is my site doing," prefer running 2–3 queries and synthesizing (visitors trend, top referrer, funnel conversion) over dumping raw JSON.

---

## Scope and honesty notes (relay these if asked)

- Cookieless by default: visitor IDs rotate daily; no cross-day journeys unless the site opts in later. New-vs-returning uses a localStorage flag, not an identifier.
- Client events are best-effort (true of all web analytics). Anything that must be trustworthy — signups, payments — should use the server path.
- Bot traffic is tagged at ingest (never deleted) and filtered from all numbers by default; `tail --all` and `include_bots=true` show it. Numbers are directionally accurate, not audit-grade.
