{"id":"e16d3bfa-9a0e-4dcc-9b73-a8e6815aafd8","entityType":"agent","slug":"clawhub-gilesdawe-tokei-agent","name":"tokei-agent","canonicalUrl":"https://www.xpersona.co/agent/clawhub-gilesdawe-tokei-agent","canonicalPath":"/agent/clawhub-gilesdawe-tokei-agent","generatedAt":"2026-10-11T03:55:54.420Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:24:02.556Z","emptyReason":null},"description":"Tokei (tokei.io) is a pre-launch, waitlist and giveaway platform — launch pages, competition giveaways, sweepstakes, referral and viral-loop campaigns, Product Hunt launch drives, Gleam-style and KickoffLabs-style entry pages. This CLI controls them from the command line — list, clone, update, publish and unpublish pages, upload images and video, set prizes, reward tiers and deadlines, restyle pages, and read stats, leaderboards, top referrers, signups, survey responses, winner selections and the webhook event catalog, plus manage webhooks (all 5 events) — all via the Tokei v1 REST API.","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.2K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s17f49saxv9xfh8dt4h21w9pgs8ax0fm:tokei-agent","sourceUrl":"https://clawhub.ai/gilesdawe/tokei-agent","homepage":"https://clawhub.ai/gilesdawe/skills/tokei-agent","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/gilesdawe/tokei-agent","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/gilesdawe/skills/tokei-agent","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":62,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"tokei-agent technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:24:02.556Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:24:02.556Z","emptyReason":null},"stars":null,"forks":null,"downloads":1206,"packageName":null,"latestVersion":"0.3.6","tractionLabel":"1.2K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:24:02.491Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T01:24:02.556Z","lastCrawledAt":"2026-10-11T01:24:02.491Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T01:24:02.491Z","lastVerifiedAt":null,"highlights":[{"version":"0.3.6","createdAt":"2026-09-04T20:16:23.598Z","changelog":"- Removes deprecated skill-card.md file. - Updates dependencies in package.json. - Adjusts and expands tests in __tests__ directory. - Updates internal HTTP, MCP, and media handling logic. - Expands and clarifies documentation in SKILL.md.","fileCount":23,"zipByteSize":92184},{"version":"0.3.5","createdAt":"2026-08-28T02:03:56.756Z","changelog":"- Removed deprecated skill-card.md file. - Updated documentation in SKILL.md for clarity and completeness. - Updated CHANGELOG.md and package.json for version 0.3.5. - Minor test adjustments in src/__tests__/mcp.test.ts. - Small code and logic updates in src/index.ts and src/mcp.ts. - No breaking changes to core CLI usage or API envelope.","fileCount":23,"zipByteSize":90452},{"version":"0.3.4","createdAt":"2026-08-21T02:40:32.539Z","changelog":"tokei-agent 0.3.4 - Improved and updated documentation in SKILL.md, README.md, and CHANGELOG.md. - Minor updates to code and tests for maintainability. - Removed obsolete file: skill-card.md.","fileCount":23,"zipByteSize":87216},{"version":"0.3.3","createdAt":"2026-08-04T17:17:55.196Z","changelog":"tokei-agent 0.3.3 - Added full webhook event catalog and management, supporting all 5 event types. - Introduced a fourth hard rule: require explicit human approval before actions that affect entrants or alter public visibility. - Expanded documentation for the new rule and updated usage guidelines. - Added CHANGELOG.md; removed outdated skill-card.md. - Improved test coverage and clarified instructions in the README. - Minor dependency and code quality updates.","fileCount":23,"zipByteSize":84843},{"version":"0.3.2","createdAt":"2026-08-02T22:29:20.423Z","changelog":"- Added new referrals:top command to fetch top referrers for a campaign. - Updated documentation and examples to include the new referrals:top command and its JSON output structure. - Clarified and expanded README and SKILL.md, including notes about output envelope and updated command coverage. - Removed deprecated skill-card.md file. - General maintenance updates across code and tests.","fileCount":22,"zipByteSize":78001},{"version":"0.3.1","createdAt":"2026-08-02T16:53:45.239Z","changelog":"- Added human-friendly UI banner and summary when run in an interactive terminal; JSON output is now reserved for non-TTY (pipes, subprocesses, CI, or when TOKEI_OUTPUT=json). - Updated documentation to clarify the new output behavior and how agents/subprocesses always receive pure JSON. - Improved internal structure with new UI handling files and related tests. - Removed obsolete skill-card.md documentation.","fileCount":22,"zipByteSize":76789},{"version":"0.3.0","createdAt":"2026-07-26T22:04:46.015Z","changelog":"**Big update: Adds media upload support and revises workflow documentation.** - New `media:upload` command for uploading images and video, with an allowlisted URL returned for use on pages. - Strict enforcement: all media uploaded to pages must use `media:upload`—direct file paths or third-party URLs are rejected. - Extended documentation covering media handling, workflow patterns, and key error/plan checks. - Homepage updated, expanded README and SKILL.md, and improved field and workflow explanations. - Removed obsolete file (`skill-card.md`).","fileCount":20,"zipByteSize":56240},{"version":"0.2.2","createdAt":"2026-07-25T14:10:44.562Z","changelog":"- Added new test file: `src/__tests__/version.test.ts` - Updated documentation in README and SKILL.md, including new notes on exit codes for Node 24 / Windows - Improved and clarified sample output for `templates:list` in SKILL.md, with usage guidance and new illustrative section on skin appearances - Minor code changes across core files and tests - Removed obsolete `skill-card.md` file","fileCount":18,"zipByteSize":41478}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17f49saxv9xfh8dt4h21w9pgs8ax0fm:tokei-agent","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s17f49saxv9xfh8dt4h21w9pgs8ax0fm:tokei-agent` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/gilesdawe/tokei-agent before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-gilesdawe-tokei-agent/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-gilesdawe-tokei-agent/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-gilesdawe-tokei-agent/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-gilesdawe-tokei-agent/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-gilesdawe-tokei-agent/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-gilesdawe-tokei-agent/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T03:55:54.416Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-gilesdawe-tokei-agent/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-gilesdawe-tokei-agent/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-gilesdawe-tokei-agent/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-gilesdawe-tokei-agent/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T01:24:02.556Z","emptyReason":null},"readme":"Skill: tokei-agent\n\nOwner: gilesdawe\n\nSummary: Tokei (tokei.io) is a pre-launch, waitlist and giveaway platform — launch pages, competition giveaways, sweepstakes, referral and viral-loop campaigns, Product Hunt launch drives, Gleam-style and KickoffLabs-style entry pages. This CLI controls them from the command line — list, clone, update, publish and unpublish pages, upload images and video, set prizes, reward tiers and deadlines, restyle pages, and read stats, leaderboards, top referrers, signups, survey responses, winner selections and the webhook event catalog, plus manage webhooks (all 5 events) — all via the Tokei v1 REST API.\n\nTags: latest:0.3.6\n\nVersion history:\n\nv0.3.6 | 2026-09-04T20:16:23.598Z | auto\n\n- Removes deprecated skill-card.md file.\n- Updates dependencies in package.json.\n- Adjusts and expands tests in __tests__ directory.\n- Updates internal HTTP, MCP, and media handling logic.\n- Expands and clarifies documentation in SKILL.md.\n\nv0.3.5 | 2026-08-28T02:03:56.756Z | auto\n\n- Removed deprecated skill-card.md file.\n- Updated documentation in SKILL.md for clarity and completeness.\n- Updated CHANGELOG.md and package.json for version 0.3.5.\n- Minor test adjustments in src/__tests__/mcp.test.ts.\n- Small code and logic updates in src/index.ts and src/mcp.ts.\n- No breaking changes to core CLI usage or API envelope.\n\nv0.3.4 | 2026-08-21T02:40:32.539Z | auto\n\ntokei-agent 0.3.4\n\n- Improved and updated documentation in SKILL.md, README.md, and CHANGELOG.md.\n- Minor updates to code and tests for maintainability.\n- Removed obsolete file: skill-card.md.\n\nv0.3.3 | 2026-08-04T17:17:55.196Z | auto\n\ntokei-agent 0.3.3\n\n- Added full webhook event catalog and management, supporting all 5 event types.\n- Introduced a fourth hard rule: require explicit human approval before actions that affect entrants or alter public visibility.\n- Expanded documentation for the new rule and updated usage guidelines.\n- Added CHANGELOG.md; removed outdated skill-card.md.\n- Improved test coverage and clarified instructions in the README.\n- Minor dependency and code quality updates.\n\nv0.3.2 | 2026-08-02T22:29:20.423Z | auto\n\n- Added new referrals:top command to fetch top referrers for a campaign.\n- Updated documentation and examples to include the new referrals:top command and its JSON output structure.\n- Clarified and expanded README and SKILL.md, including notes about output envelope and updated command coverage.\n- Removed deprecated skill-card.md file.\n- General maintenance updates across code and tests.\n\nv0.3.1 | 2026-08-02T16:53:45.239Z | auto\n\n- Added human-friendly UI banner and summary when run in an interactive terminal; JSON output is now reserved for non-TTY (pipes, subprocesses, CI, or when TOKEI_OUTPUT=json).\n- Updated documentation to clarify the new output behavior and how agents/subprocesses always receive pure JSON.\n- Improved internal structure with new UI handling files and related tests.\n- Removed obsolete skill-card.md documentation.\n\nv0.3.0 | 2026-07-26T22:04:46.015Z | auto\n\n**Big update: Adds media upload support and revises workflow documentation.**\n\n- New `media:upload` command for uploading images and video, with an allowlisted URL returned for use on pages.\n- Strict enforcement: all media uploaded to pages must use `media:upload`—direct file paths or third-party URLs are rejected.\n- Extended documentation covering media handling, workflow patterns, and key error/plan checks.\n- Homepage updated, expanded README and SKILL.md, and improved field and workflow explanations.\n- Removed obsolete file (`skill-card.md`).\n\nv0.2.2 | 2026-07-25T14:10:44.562Z | auto\n\n- Added new test file: `src/__tests__/version.test.ts`\n- Updated documentation in README and SKILL.md, including new notes on exit codes for Node 24 / Windows\n- Improved and clarified sample output for `templates:list` in SKILL.md, with usage guidance and new illustrative section on skin appearances\n- Minor code changes across core files and tests\n- Removed obsolete `skill-card.md` file\n\nv0.2.1 | 2026-07-22T23:39:18.301Z | auto\n\n- Added support for listing and cloning from named platform templates; new `templates:list` command and `pages:clone --template <slug>` flag.\n- `pages:get` output and docs now include more fields, with clarified descriptions and new writeable fields via `pages:update`.\n- Improved status handling for `pages:list`, clarifying vocabulary and mapping.\n- Updated and expanded SKILL.md documentation for new commands, options, and field explanations.\n- Removed legacy `skill-card.md` file.\n- Added `server.json`; updated tests and source files for new features.\n\nv0.2.0 | 2026-07-20T23:21:37.406Z | auto\n\n- Added SKILL.md with comprehensive documentation for tokei-agent.\n- Introduced clear terminology mapping between API and UI concepts.\n- Detailed environment variable setup and error handling conventions.\n- Listed and explained all available commands for listing, updating, and managing Tokei campaigns, entries, stats, and webhooks.\n- Described support for running as a local MCP server for use as an MCP tool.\n\nArchive index:\n\nArchive v0.3.6: 23 files, 92184 bytes\n\nFiles: bin/tokei-agent.mjs (3225b), CHANGELOG.md (10016b), LICENSE (1073b), package.json (821b), README.md (9637b), server.json (1116b), skill-card.md (2661b), SKILL.md (49762b), src/__tests__/args.test.ts (1720b), src/__tests__/commands.test.ts (36931b), src/__tests__/http.test.ts (6498b), src/__tests__/mcp.test.ts (39214b), src/__tests__/media.test.ts (11228b), src/__tests__/ui.test.ts (17797b), src/__tests__/version.test.ts (1827b), src/args.ts (1352b), src/http.ts (6361b), src/index.ts (33029b), src/mcp.ts (34901b), src/media.ts (5141b), src/ui.ts (19384b), tsconfig.json (259b), _meta.json (130b)\n\nFile v0.3.6:SKILL.md\n\n---\nname: tokei-agent\ndescription: Tokei (tokei.io) is a pre-launch, waitlist and giveaway platform — launch pages, competition giveaways, sweepstakes, referral and viral-loop campaigns, Product Hunt launch drives, Gleam-style and KickoffLabs-style entry pages. This CLI controls them from the command line — list, clone, update, publish and unpublish pages, upload images and video, set prizes, reward tiers and deadlines, restyle pages, and read stats, leaderboards, top referrers, signups, survey responses, winner selections and the webhook event catalog, plus manage webhooks (all 5 events) — all via the Tokei v1 REST API.\nhomepage: https://tokei.io/agent\nmetadata: {\"openclaw\":{\"emoji\":\"⏱️\",\"requires\":{\"bins\":[],\"env\":[\"TOKEI_API_KEY\"]}}}\n---\n\n# tokei-agent\n\n`tokei-agent` is a zero-dependency CLI for the Tokei v1 REST API (`https://tokei.io/api/v1`). Every command prints JSON to stdout, so pipe it to `jq` or parse it directly.\n\n## Install tokei-agent if it isn't already there\n\n```sh\nnpm install -g tokei-agent\n# or run it without installing:\nnpx tokei-agent --help\n```\n\nRequires Node 22+. npm release: https://www.npmjs.com/package/tokei-agent — official website: https://tokei.io — agent docs: https://tokei.io/agent — API reference: https://tokei.io/docs/api\n\n---\n\n## ⚠️ Four hard rules (read first)\n\n**Rule 1 — Run `tokei-agent me` before anything else.** It proves the key is live and reports the account's **plan**. API access requires an active subscription or lifetime plan — **trial accounts get `403` on every command**, so a whole workflow can fail on its first call for a reason no other command explains.\n\n> `me` does **not** report the key's scope. There is no way to read a key's scope from the API — you discover a read-only key by getting `403 FORBIDDEN` on your first write. If the task involves changing anything, ask the human up front whether their key is read+write.\n\n**Rule 2 — Every media URL you write to a page MUST come from `tokei-agent media:upload`.** Raw filesystem paths (`hero.png`) and third-party URLs (`https://example.com/hero.png`) are **rejected** — the seven media fields on `pages:update` are guarded by a closed host allowlist that only accepts the app's own storage (and `res.cloudinary.com`). Always:\n\n```sh\nHERO=$(tokei-agent media:upload ./hero.png | jq -r '.data.public_url')\ntokei-agent pages:update \"$PAGE_ID\" --image-video \"$HERO\"\n```\n\nEvery `--image-video` / `--og-image` / `--background-image` example below assumes a `public_url` obtained this way — never a local file.\n\n**Rule 3 — List fields replace wholesale; always read before you write.** `prizes` (max 20) and `reward_thresholds` (max 50) are **not** merged — whatever array you send becomes the entire list, so sending one prize deletes the other nineteen. The pattern is always `pages:get` → modify the array → `pages:update` with the complete list.\n\n**Rule 4 — Get explicit human approval before any action that emails people or changes anything public.** Publishing a page, creating a webhook that fires on live events, sending entries/notifications, or anything else visible to entrants or third parties needs a human sign-off first — this CLI does not gate those calls for you, so you are the gate. Read-only commands (`me`, `pages:list`, `pages:get`, `stats`, `leaderboard`, `referrals:top`, `entries:list`, `surveys:list`, `winners:list`, `webhooks:list`, `templates:list`, `actions:catalog`, `events:catalog`) need no such approval.\n\n---\n\n## Terminology mapping (read this first)\n\nThe API and the UI use different words for the same objects. Do not treat these as different things.\n\n| API says               | UI / humans say    | Notes                                                            |\n| ---------------------- | ------------------ | ---------------------------------------------------------------- |\n| `contest`              | page, campaign     | The core object. `contestId` in paths = the page's id.           |\n| `promotion`            | page, campaign     | Same object again — `POST /promotions` creates it, reads live under `/contests/{id}`. |\n| `entry`                | signup, subscriber | One person joining a page.                                       |\n| `entries:create`       | add a signup       |                                                                  |\n\nCLI command names use the UI words (`pages:list`, `pages:update`); the JSON they return uses the API words (`contest`, `promotion`).\n\n## Setup\n\n| Env var         | Required | Meaning                                                                          |\n| --------------- | -------- | -------------------------------------------------------------------------------- |\n| `TOKEI_API_KEY` | Yes      | Sent as `Authorization: Bearer <key>`. Create one at tokei.io → Dashboard → Settings → API Keys. |\n| `TOKEI_API_URL` | No       | Base URL override (default `https://tokei.io`).                                  |\n\nKeys have a scope: **read-only** or **read+write**. Write commands need a read+write key; a read-only key gets `403`. Keys can also carry an expiry — an expired key gets `401`. Prefer a read-only key unless the task actually changes something.\n\n## Core workflow\n\nThe fundamental pattern, end to end:\n\n1. **Verify** — confirm the key and the plan (Rule 1)\n2. **Discover** — list your pages, and the platform's named starting points\n3. **Create** — clone a template (or one of your own pages) into a new draft\n4. **Prepare** — upload media and get back allowlisted URLs (Rule 2)\n5. **Shape** — PATCH copy, dates, prizes, appearance and media onto the page\n6. **Publish** — flip the draft live (needs a future `end_date`)\n7. **Monitor** — stats, leaderboard, top referrers, signups\n8. **Automate** — subscribe a webhook instead of polling\n\n```sh\n# 1. Verify — do this first, always\ntokei-agent me\n\n# 2. Discover\ntokei-agent pages:list --status active\ntokei-agent templates:list\n\n# 3. Create (returns a draft page)\nPAGE=$(tokei-agent pages:clone --title \"Spring Launch Waitlist\" \\\n  --template product-hunt | jq -r '.data.id')\n\n# 4. Prepare media (Rule 2 — never pass a raw path)\nHERO=$(tokei-agent media:upload ./hero.png | jq -r '.data.public_url')\n\n# 5. Shape\ntokei-agent pages:update \"$PAGE\" \\\n  --description \"Join the list for early access.\" \\\n  --template showcase --dark-mode true --primary-color \"#7d78c6\" \\\n  --image-video \"$HERO\"\n\n# 6. Publish (end_date must be in the future — set it in the same call if unset)\ntokei-agent pages:publish \"$PAGE\" --data '{\"end_date\":\"2026-09-01T00:00:00Z\"}'\n\n# 7. Monitor\ntokei-agent stats \"$PAGE\"\ntokei-agent leaderboard \"$PAGE\" --per-page 10\ntokei-agent referrals:top \"$PAGE\" --per-page 10\ntokei-agent entries:list \"$PAGE\"\n\n# 8. Automate — events:catalog lists all 5 subscribable events and their payloads\ntokei-agent webhooks:create --url https://yourserver.com/webhooks/tokei \\\n  --events entry.created,winner.selected\n```\n\nConfirm every write by reading it back with `pages:get \"$PAGE\"` — everything writable is also readable.\n\n## Output envelope, exit codes\n\n- stdout: the API's JSON body, augmented with a top-level `\"rate_limit\"` object: `{\"limit\": n, \"remaining\": n, \"reset\": <unix epoch seconds>}`, or `null` when the headers were absent (e.g. network failure).\n- **Agents always get this JSON.** From 0.3.1 the CLI renders a human banner and summary *only* when stdout is an interactive terminal. A subprocess, pipe, redirect, CI environment or the `mcp` transport has no TTY, so the JSON envelope below is what you will receive, byte for byte. If you ever need to force it explicitly, set `TOKEI_OUTPUT=json`.\n- Exit code `0` — success (HTTP 2xx).\n- Exit code `1` — API or network error. The API's JSON error body (same envelope, with `rate_limit`) is still printed on stdout; pure network failures print `{\"ok\": false, \"error\": {\"type\": \"network_error\", \"message\": ...}}`.\n- Exit code `2` — usage error (bad flags, missing arguments, missing `TOKEI_API_KEY`). Printed as JSON on **stderr**: `{\"ok\": false, \"error\": {\"type\": \"usage_error\", \"message\": ...}}`. Nothing was sent to the API.\n\n**The shape, so you can `jq` it without guessing.** Success always nests the payload under `data` — it is never a bare top-level array:\n\n```jsonc\n// single-object reads (pages:get, me, media:upload, pages:clone, …)\n{ \"success\": true, \"data\": { … }, \"rate_limit\": { … } }\n\n// list reads (pages:list, leaderboard, referrals:top, entries:list,\n//             surveys:list, templates:list, webhooks:list)\n// referrals:top adds a sibling \"totals\" object next to data + pagination\n{ \"success\": true, \"data\": [ … ],\n  \"pagination\": { \"page\": 1, \"per_page\": 20, \"total_pages\": 3, \"total_count\": 47 },\n  \"rate_limit\": { … } }\n\n// errors\n{ \"success\": false,\n  \"error\": { \"code\": \"VALIDATION_ERROR\", \"message\": \"…\", \"status\": 422,\n             \"details\": [{ \"field\": \"end_date\", \"message\": \"…\" }] },\n  \"rate_limit\": { … } }\n```\n\nSo the idioms are:\n\n```sh\ntokei-agent pages:list | jq -r '.data[0].id'                          # first page's id\ntokei-agent pages:list | jq -r '.data[] | select(.status==\"active\") | .id'\ntokei-agent pages:list --per-page 100 | jq '.pagination.total_pages'  # more to fetch?\ntokei-agent pages:get \"$PAGE\" | jq -r '.data.public_url'\ntokei-agent media:upload ./hero.png | jq -r '.data.public_url'\ntokei-agent pages:update \"$PAGE\" --title x | jq -r '.error.details[]?.field'\n```\n\n`templates:list`, `me` and `winners:list` are unpaginated — they return `data` with no `pagination` key (`winners:list`'s `data` is still an array, just capped rather than paged; see its entry below). `events:catalog` and `actions:catalog` return `data` as an object (or one entry, with `--type`), not an array at all.\n\n> **Known issue — exit codes on Node 24 / Windows (fixed in 0.3.0).** On 0.2.2 and earlier the CLI could print its correct JSON output and then abort during process exit, corrupting the exit code (`$LASTEXITCODE` read `-1073740791` / `0xC0000409` on success and failure alike). On an affected version, judge a run by the JSON on stdout, not by the exit status — or upgrade. (Historical labelling slip: the 0.3.0 tarball misreported `--version` as `0.2.2`; 0.3.1+ reports correctly.)\n\n## Commands (read — any key)\n\n**`me`** — verify the key, see plan and API usage. Returns `user_id`, `email`, `plan`, `active_contests` and an `api_usage` block (`requests_today`, `daily_limit`, `rate_limit_per_minute`). Not the key's scope — see Rule 1.\n\n```sh\ntokei-agent me\n```\n\n**`pages:list`** — list your pages. Flags: `--status draft|active|completed|deleted`, `--mode competition|gamification|sharing_only`, `--page <n>`, `--per-page <1-100>`. The filter values are exactly the values a page's `status` field can hold, so what you read back is what you can filter on. There is no `ended` or `paused` status; those were accepted by older builds and always returned nothing.\n\n`status` — both the field and the filter — is the **effective** status, derived from the stored value **and the dates**: a page whose `end_date` has passed reads and filters as `completed`, and one whose `start_date` is still in the future reads as `draft`. A creator can mark a page completed by hand, but nothing does so when `end_date` passes, so before 2026-07-27 an ended page nobody closed manually reported `active` indefinitely. `status: \"active\"` now genuinely means live — trust it.\n\n```sh\ntokei-agent pages:list --status active --per-page 20\n```\n\n**`pages:get <contestId>`** — one page, full object. `title` is the **visible page headline**, not the internal dashboard name, so what you read is what a visitor sees. The object also carries:\n\n| Field | Meaning |\n| ----- | ------- |\n| `total_entries` | Count of entry **actions** (`contest_entries` rows) — not people, and not points. **Never narrate this as \"signups.\"** |\n| `total_points_awarded` | Sum of points earned across all entry actions. Not a headcount either. |\n| `status` | The **effective** status (see `pages:list` above) — `completed` once `end_date` passes, whatever is stored. Safe to report as \"live\" / \"finished\" directly. |\n| `days_left` | Whole days remaining, computed from `end_date` at read time; **`0` means it has ended**, and the partial final day rounds up so a page closing tonight reads `1`. Only falls back to the stored column when there is no `end_date` at all. Before 2026-07-27 this replayed a stale stored value and could say `30` on a page with a day to go. |\n| `entry_methods[].points` | What each action **actually awards**, including any per-action override the owner configured. Safe to quote to a user. |\n| `description`, `prizes`, `reward_thresholds` | Everything `pages:update` can write, so you can read-modify-write. |\n| `public_url` | The live page URL — no second call needed. **Can be `null`** when the page has no slug yet (`contest_url` null); check before handing it to your user as a link. |\n| `primary_color` | Brand colour as a CSS value (e.g. `#7d78c6`); `null` means the template default. `settings.color` is a deprecated alias of it. |\n| `card_width` | `max-w-2xl` \\| `max-w-3xl` \\| `max-w-4xl` \\| `max-w-7xl`; `null` renders as `max-w-2xl`. |\n| `settings.template` | Page skin: `basic-new`, `showcase` or `future`. |\n| `image_video` | Hero media — an image **or** a video URL. May carry dimension hints as query params. Writable via `pages:update --image-video` (get the URL from `media:upload`). |\n| `secondary_image` … `fifth_image`, `background_image`, `og_image` | The page's other media slots; `null` when unset. Each writable via its own `pages:update` flag — see the media flags note under `pages:update` below. |\n| `campaign_name`, `project_name` | **Read-only.** `project_name` is the small subheading under the headline; `campaign_name` is the internal dashboard name and never appears on the page. |\n\n`primary_color`, `card_width`, `settings.template` and all seven media fields (`image_video`, `secondary_image`, `third_image`, `fourth_image`, `fifth_image`, `background_image`, `og_image`) are now writable via `pages:update` (see below); `dark_mode_enabled` writes through `--dark-mode` there too, though it isn't itself a field in this table. `campaign_name`/`project_name` remain read-only always. If your user wants a headcount (\"how many people entered\"), that's neither `total_entries` nor `total_points_awarded` — use `stats`'s `unique_participants` instead.\n\n```sh\ntokei-agent pages:get 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90\n```\n\n**`stats <contestId>`** — aggregated analytics for a page.\n\n```sh\ntokei-agent stats 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90\n```\n\n**`leaderboard <contestId>`** — participants ranked by points. Flags: `--page`, `--per-page <1-100>`.\n\n```sh\ntokei-agent leaderboard 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 --per-page 10\n```\n\n**`referrals:top <contestId>`** — the page's top referrers, ranked by converted referrals then total referrals. Flags: `--page`, `--per-page <1-100>`.\n\nEach row carries `referrer_id`, `referral_code`, `full_name`, `email`, `total_referrals`, `converted_referrals` and `bonus_points_earned`. Alongside `data` and `pagination` the response adds a `totals` object: `total_referrers`, `total_referrals`, `total_clicks`, `converted_clicks`, `click_conversion_rate` (a percentage, one decimal place).\n\nThree things to know before you report these numbers:\n\n- Only entrants who have actually referred someone appear. Every participant is issued a referral code, so an unfiltered list would be almost entirely zero rows.\n- `converted_referrals` counts referred people who went on to complete at least one entry action *other than* sharing — it is the ranking key, and it is the number worth reporting as \"referrals that worked\".\n- `bonus_points_earned` is a **count of bonus entries, not a points total**, despite the name. The name matches the underlying data and the dashboard, so it is kept for consistency; do not present it as points.\n\n```sh\ntokei-agent referrals:top 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 --per-page 10\n```\n\n**`winners:list <contestId>`** — selection-run history for a page, newest first, each run with its persisted winners nested. Read-only, no query params, no pagination (contest-scale run/winner counts, capped at 100 runs — headroom, not a pagination story). Finalize-via-API is deliberately out of scope (human-approval policy, Rule 4) — this is how an agent looks back at what a run actually selected, not how it draws one. Each run carries `id`, `created_at`, `seed`, `algorithm_version`, `status`, `requested_by_email`, `finalized_at`, `total_candidates`, `total_winners_selected`, `winners_count` and a `winners` array; each winner carries `id`, `contest_user_id`, `email`, `full_name`, `entry_points`, `created_at`, `country_name`, `city`, `prize_tier`, `prize_description`, `prize_value`, `selected_at`, `notified_at`, `notification_method`, `verified`, `claimed_at`.\n\n```sh\ntokei-agent winners:list 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90\n```\n\n**`entries:list <contestId>`** — signups for a page. Flags: `--page`, `--per-page <1-100>`, `--email <addr>` (exact-match filter).\n\n```sh\ntokei-agent entries:list 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 --email fan@example.com\n```\n\n**`surveys:list <contestId>`** — survey responses. Flags: `--page`, `--per-page <1-100>`.\n\n```sh\ntokei-agent surveys:list 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 --page 2\n```\n\n**`webhooks:list`** — list webhook subscriptions. Reading them needs no write scope (only `webhooks:create`/`webhooks:delete` do). Flags: `--page`, `--per-page <1-100>`. Watch `failure_count`: a subscription is auto-disabled after 10 consecutive failed deliveries.\n\n```sh\ntokei-agent webhooks:list\n```\n\n**`templates:list`** — the platform's named starting points, for cloning with `pages:clone --template <slug>`. Same list for every key — not scoped to your account (it's platform content, not the caller's). No flags, no pagination.\n\n```sh\ntokei-agent templates:list\n```\n\nExample response — an **excerpt** of the real menu at the time of writing (15 templates live, 5 shown). It grows as the platform publishes templates, so call `templates:list` and use the slugs it returns; never hardcode one.\n\n```json\n{\n  \"success\": true,\n  \"data\": [\n    {\n      \"id\": \"0e4de06c-8915-4e1c-ba3f-48f6c9a098f2\",\n      \"slug\": \"collect-email-list\",\n      \"name\": \"Collect email list — registration-first opt-in subscriber page\",\n      \"skin\": \"future\",\n      \"entry_method_count\": 0\n    },\n    {\n      \"id\": \"06743256-2e8e-4ede-a431-f17866fae1f6\",\n      \"slug\": \"competition-starter\",\n      \"name\": \"Starter — Gleam-style competition giveaway (X, Instagram, TikTok, Facebook entries)\",\n      \"skin\": \"basic-new\",\n      \"entry_method_count\": 6\n    },\n    {\n      \"id\": \"c0cc71a0-1ff5-46ab-8a23-c801aec30337\",\n      \"slug\": \"instagram-engagement\",\n      \"name\": \"Instagram engagement — follow & share photo giveaway\",\n      \"skin\": \"showcase\",\n      \"entry_method_count\": 3\n    },\n    {\n      \"id\": \"6ca0bdbc-23d8-4c79-a894-857eea485fbe\",\n      \"slug\": \"secret-codes\",\n      \"name\": \"Secret Code — unlock entries with a code (QR codes, receipts, printed inserts, events)\",\n      \"skin\": \"basic-new\",\n      \"entry_method_count\": 0\n    },\n    {\n      \"id\": \"88fde228-8baf-4b31-9b0e-cc243b3cc83d\",\n      \"slug\": \"steam-promotion\",\n      \"name\": \"A futuristic Steam template for Adding to Steam Wishlists and Playing Steam Games.\",\n      \"skin\": \"future\",\n      \"entry_method_count\": 6\n    }\n  ],\n  \"rate_limit\": { \"limit\": 60, \"remaining\": 59, \"reset\": 1753000000 }\n}\n```\n\nRows come back sorted by `slug`, unpaginated. The other ten at the time of writing: `discord-community`, `facebook-promotion`, `family-friends`, `prelaunch-vips`, `product-hunt`, `survey-system`, `tiktok-growth`, `twitch-growth`, `x-followers`, `youtube-contest`.\n\n`skin` is the page skin (`basic-new`/`showcase`/`future`) — the same vocabulary `pages:update --template` writes. `entry_method_count` is how many entry actions the template ships with. For agents: **list templates first, then clone by slug** — don't guess a slug. A `0` there is not always a stub — two templates legitimately report `0` because their action lives on the page rather than in `entry_methods`: `secret-codes` (the single action *is* the secret code) and `survey-system` (a mandatory survey plus a photo upload). Cloning `secret-codes` gives you the code input switched on but **no codes** — codes are stored per page and are never copied, so the owner adds their own in the dashboard.\n\nWhat the skins look like — use this to match the user's reference point:\n\n- `basic-new` (\"Basic\") — the classic giveaway card: entry actions in a clean vertical list, soft pastel styling. This is the format popularized by Gleam — when a user asks for a Gleam-style (or KickoffLabs-style) campaign, this is the closest match. The platform default.\n- `showcase` — a dynamic two-column layout with warm colors and platform-styled buttons. Product-forward — the natural fit for a Product Hunt-style launch page.\n- `future` — a dark, immersive game-style skin for bold, high-impact pages (gaming and tech audiences).\n\n**`actions:catalog`** — every entry-action type Tokei supports: label, description, default points, platform, whether it's trust-based/verifiable, and — for the 26 types writable as an `entry_methods` row — the exact `config` fields `pages:update` accepts for that type. This is the **authoritative** per-type reference; nothing below restates it. Same list for every key (not scoped to your account, no pagination). Flags: `--type <actionType>` (a value matching no action type is `400`).\n\n```sh\ntokei-agent actions:catalog --type twitter_follow\n```\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"label\": \"Follow on X\",\n    \"defaultPoints\": 3,\n    \"platform\": \"twitter\",\n    \"trustBased\": false,\n    \"isEntryMethodRow\": true,\n    \"fields\": [\n      { \"key\": \"username\", \"type\": \"string\", \"required\": true,\n        \"note\": \"X/Twitter handle. Checked for presence only — no format validation.\" }\n    ],\n    \"needs\": \"Requires participant X/Twitter OAuth to be configured on this deployment...\"\n  }\n}\n```\n\nA field's `group` marks alternatives — write the FIRST field of the group; the rest are claim-validator aliases that satisfy validation but render a dead button on their own (each carries a `note` saying so). `isEntryMethodRow: false` means the type is not writable as an `entry_methods` row at all (it's enabled via a contest setting or a dedicated route instead) — `needs` says which.\n\n**`events:catalog`** — every webhook event Tokei's delivery engine understands: description, `payloadSchema` (the exact shape of the `data` field a subscriber receives), `emitSites` and whether it's `subscribable`. This is the **authoritative** payload reference for `webhooks:create` — read it before assuming a field exists in a delivery. Same list for every key (not scoped to your account, no pagination). Flags: `--type <eventName>` (a value matching no event type is `400`). All 5 events are `subscribable: true` this stage — every one has a real emit site (see Webhooks below).\n\n```sh\ntokei-agent events:catalog --type winner.selected\n```\n\n## Commands (write — need a read+write key)\n\n**`pages:clone`** — create a page by cloning one you own (`--source <promotionId>`), a named platform template by slug (`--template <slug>` — get slugs from `templates:list`), or omit both to clone the platform starter template. `--template` and `--source` are **alternatives, not combinable** — sending both is `422`; a `--template` slug matching no template is `404`. Template, theme, and entry methods copy verbatim from the source — keep one polished master page per shape and clone it. Capped at **20 API-created pages per account per UTC day** (429 with `Retry-After`). Flags: `--title` (required), `--source`, `--template`, `--description`, `--prize`, `--end-date <iso>`, `--campaign-url`, `--image-url`, `--status draft|active`, `--idempotency-key`, `--data`.\n\n```sh\ntokei-agent pages:clone --title \"Spring Launch Waitlist\" \\\n  --source 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 \\\n  --prize \"Lifetime license\" --end-date 2026-09-01T00:00:00Z \\\n  --idempotency-key spring-launch-2026\n```\n\nCloning from a named template instead — list first, then clone by slug:\n\n```sh\ntokei-agent templates:list\ntokei-agent pages:clone --title \"Spring Launch Waitlist\" --template product-hunt \\\n  --prize \"Lifetime license\" --end-date 2026-09-01T00:00:00Z\n```\n\n`--status active` makes the page live immediately at the returned `public_url`; the default is `draft`. Reuse an `--idempotency-key` and you get `409` with the existing page's id in `error.details` instead of a duplicate.\n\n**`media:upload <file>`** — upload an image or video and get back a `public_url` to feed into `pages:update`'s media flags (below). Two HTTP calls under the hood, both handled for you: (1) request a short-lived signed upload ticket from Tokei; (2) `PUT` the file bytes straight to a Supabase Storage host with **no `Authorization` header** — the signing token lives in that URL's own query string. Nothing is stored until step 2 succeeds, so a step-1 failure never burns anything. Content type is inferred from the file extension (`.jpg`/`.jpeg`/`.png`/`.gif`/`.webp`/`.mp4`/`.webm`/`.mov`); override it with `--content-type <type>` for an extensionless or misnamed file. `application/pdf` is not accepted — only image and video. Prints `{public_url, path, content_type, filename, size_bytes}` under `data`.\n\n```sh\ntokei-agent media:upload ./hero.png\ntokei-agent pages:update 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 \\\n  --image-video https://media.tokei.io/api-uploads/u1/<uuid>.png\n```\n\nThings that surprise agents:\n\n- **The 5MB bucket cap applies to the signed-upload bucket itself, not to image vs. video specifically — so video must be ≤5MB too.** This is thin for video; a real clip is likely to exceed it. There is no larger-upload path through this API today.\n- **The 50/day cap counts tickets issued (step 1), not completed uploads.** A step-1 call that never gets PUT still burns quota — don't loop calling `media:upload` speculatively.\n- **A file over 5MB fails at step 2 with a `413` from Supabase Storage, not from Tokei.** The CLI surfaces this as `{\"ok\": false, \"error\": {\"type\": \"upload_failed\", \"stage\": \"storage_put\", \"status\": 413, \"message\": \"...\"}}` — see the error-semantics note below; it is a different shape from the usual `error.code` envelope because it isn't a Tokei API response.\n- **The stored object name is always a server-generated UUID.** The `filename` you send (or that the CLI infers from your local path) is validated and echoed back in the response, but never used to name anything — so reusing a filename never collides or overwrites.\n\n**`pages:update <contestId>`** — PATCH a page. Simple fields via flags: `--title`, `--description`, `--start-date <iso>`, `--end-date <iso>`, `--template basic-new|showcase|future|simple`, `--custom-css <css>`, `--dark-mode true|false`, `--primary-color \"#7d78c6\"`, `--card-width narrow|medium|wide`, and the seven media flags below. Prizes, reward tiers, nulls, or a full body via `--data`. At least one field required; unknown fields are rejected (422). `prizes` (max 20) and `reward_thresholds` (max 50) each **replace the existing list wholesale** — read the current lists with `pages:get` first, modify, and send the complete list back (Rule 3). A future `start_date` pauses new entries until then; setting `end_date` recomputes `days_left` — but a page whose winners have already been drawn rejects any `end_date` write, **setting or clearing**, with `409 WINNERS_ALREADY_SELECTED`. Clearing the date via `--data '{\"end_date\": null}'` also requires the page to be unpublished: an **active** page must keep a deadline (otherwise it renders as live forever while every signup is rejected), so clearing one returns `422` on field `end_date` — send `{\"status\": \"draft\", \"end_date\": null}` together, or a replacement date. `--title` sets the visible page headline (and the dashboard name), and `--description` accepts basic rich-text HTML which is sanitized on write, so unsupported tags come back stripped.\n\nAppearance flags: `--template` is the page skin (`basic-new`, `showcase` or `future`), stored verbatim as `settings.template` — see the skin guide under `templates:list` for what each looks like and when to pick it. `--dark-mode` is a plain creator-side toggle (column `dark_mode_enabled`) — there is no visitor `prefers-color-scheme` behavior, so this is the only thing that controls it. `--primary-color` must be a hex colour only (3, 4, 6 or 8 digits, e.g. `\"#7d78c6\"`) — other CSS colour formats (`rgb()`, named colours) are rejected because the value is interpolated into the page's server-rendered `<style>` tag; to reset it to the template default, send `--data '{\"primary_color\": null}'` (there is no flag spelling for null). `--card-width` accepts the friendly names `narrow`/`medium`/`wide`/`xl` **or** the raw stored Tailwind class directly (`max-w-2xl`/`max-w-3xl`/`max-w-4xl`/`max-w-7xl`); friendly names map `narrow`→`max-w-2xl`, `medium`→`max-w-3xl`, `wide`→`max-w-4xl`, `xl`→`max-w-7xl`, and **reads always return the stored class, never the friendly name** — PATCH `--card-width wide` and the next `pages:get` returns `\"max-w-4xl\"`.\n\n**`template: \"simple\"` is the Custom template** (launched 2026-08-10) — bare structural markup the creator styles via `custom_css`, applied on both the hosted page and the widget embed via `--tokei-*` custom properties and `.tokei-simple-*` class hooks. `--custom-css` is a string, max 20KB, server-sanitised on write — `@import`, `<` anywhere, and URLs outside `data:`/Cloudinary/tokei.io are rejected with a `422 VALIDATION_ERROR` naming the reason, so read-back may differ from what was sent; send `--data '{\"custom_css\": null}'` to clear it (there is no flag spelling for null). `pages:get` returns the stored `custom_css` for any page that has one.\n\nMedia flags: `--image-video <url>` (the hero — an image **or** a video), `--secondary-image <url>`, `--third-image <url>`, `--fourth-image <url>`, `--fifth-image <url>` (the additional layout block slots), `--background-image <url>` (interpolated into the page's CSS), `--og-image <url>` (social-share preview). All seven accept only an `https` URL on the app's own Supabase public storage or `res.cloudinary.com` — exactly what `media:upload`'s `public_url` produces, so the normal flow is `media:upload` then `pages:update` with the URL it printed (Rule 2). There is no flag spelling for clearing a media field to `null`; use `--data '{\"og_image\": null}'`.\n\n```sh\ntokei-agent pages:update 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 \\\n  --title \"Now with 3 prize tiers\" \\\n  --data '{\"prizes\":[{\"name\":\"AirPods Pro\",\"winners\":1,\"value\":249,\"currency\":\"USD\"},{\"name\":\"Sticker pack\",\"winners\":50}]}'\n```\n\n```sh\ntokei-agent pages:update 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 \\\n  --template showcase --dark-mode true --primary-color \"#7d78c6\"\n```\n\n**`entry_methods`** — reachable only via `--data` (max 30 rows, ~64KB body cap on this route only, raised from the shared 10KB to fit it). Six things to know:\n\n1. **Full-array replace, same as Rule 3 — always `pages:get` → modify → `pages:update`.** There is no per-row patch: `[]` clears every entry method, and omitting the field leaves the stored array untouched. Two row shapes: an **action row** `{id?, actionType, label, points?, config?, requireVerification?}` — `actionType` must be one of the 26 writable types from `actions:catalog` — or a **link row** with no `actionType`: `{id?, label, points?, link, actionsRequired?}` — a plain http(s) button to any URL, for anything the catalog has no action for; `actionsRequired` (0-20, link rows only) hides the row until the entrant has completed that many other actions. Unknown keys (`icon`, `config.type`, the ten legacy `<platform>Config` duplicates) are stripped, not stored, so echoing back a row you just read is always safe. `icon`/`config.type` are always server-derived from `actionType`; whatever you send for them is ignored.\n2. **`points` may not render as sent.** `0` means unset — the renderer falls back to the type's default. Any type with an `entryValueSettingKey` (see `actions:catalog`) has its displayed points overridden by `settings.<type>_entry_value` when the owner has configured one, and Product Hunt (`producthunt_follow`, `producthunt_vote`) and all three Steam types go further — their points are **hard-substituted with the platform default at render**, unconditionally. Also: **`pages:get` already returns the EFFECTIVE points** (overrides applied), not the raw stored value, so an untouched `pages:get` → `pages:update` round-trip silently persists that effective number into storage in place of whatever was originally configured.\n3. **Some types need a deployment prerequisite — warn the human before adding one.** All 5 `twitter_*` types and `linkedin_share`/`linkedin_post` need participant OAuth configured on this deployment; `discord_join` needs Discord OAuth and has no working verification at all, so its points are awarded on trust regardless; all 3 `steam_*` types need Steam login. `actions:catalog`'s `needs` field says this per type — `null` means no prerequisite. Facebook, Instagram, TikTok, Twitch, Product Hunt, `linkedin_follow`, `linkedin_company_follow` and link rows have none — safe to add without asking.\n4. Draft/publish semantics are unchanged by any of this. Unknown keys are stripped on the way in, never stored.\n5. **Link-row rules:** a label that doesn't start with `\"Visit Our \"` renders as a non-clickable generic row; the clickable, credited path also requires the page to have a `campaign_url` configured.\n6. **Rows of one `actionType` are capped, and can carry a stable `id`.** At most 5 rows may share one `actionType` — but only for `twitter_follow`, `instagram_follow` and `facebook_visit_page`; every other type, including one that reads as duplicate-eligible in `actions:catalog`, is capped at 1. Exceeding it is `422 VALIDATION_ERROR`: `Too many \"twitter_follow\" entry methods (max 5 per promotion).` for the three types above, `Only one \"tiktok_follow\" entry method is allowed per promotion.` for everything else. `id` (optional, `[A-Za-z0-9_-]`, max 64 chars) gives a row stable identity across a `pages:get` → modify → `pages:update` round-trip once a page holds several rows of one type — never required, and no pre-existing row has one. Two rows sharing an `id` is also `422`: `Duplicate entry method id \"<id>\" (already used at index <n>).`\n\n```sh\ntokei-agent pages:get \"$PAGE\" | jq '.data.entry_methods'   # always read first (Rule 3)\n\n# Trust-based (no prerequisite) + conditional (needs X/Twitter OAuth — warn the human\n# first) + a link row, sent together as the COMPLETE replacement array\ntokei-agent pages:update \"$PAGE\" --data '{\"entry_methods\":[\n  {\"actionType\":\"tiktok_follow\",\"label\":\"Follow us on TikTok\",\"points\":3,\"config\":{\"username\":\"tokei\"}},\n  {\"actionType\":\"twitter_follow\",\"label\":\"Follow on X\",\"points\":3,\"config\":{\"username\":\"tokei\"}},\n  {\"label\":\"Visit Our Store\",\"points\":1,\"link\":\"https://example.com/store\"}\n]}'\n```\n\n**`pages:publish <contestId>`** — sugar over `pages:update` that PATCHes `{\"status\": \"active\"}`. Requires an `end_date` in the future — either already stored on the page, or sent in the same call via `--data`. If neither is true, this returns `422 VALIDATION_ERROR` with `details: [{\"field\": \"status\", \"message\": \"Publishing requires an end_date in the future...\"}]`. Re-publishing an already-active page is a no-op and skips that check. Still accepts `--data` for any other field, merged under the fixed `status` (a `--data` `status` value would override it, but there's no reason to send one here).\n\n```sh\ntokei-agent pages:publish 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90\n```\n\nThe one-call ergonomic path, when the page has no future `end_date` yet — set it and publish together:\n\n```sh\ntokei-agent pages:publish 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 \\\n  --data '{\"end_date\":\"2026-09-01T00:00:00Z\"}'\n```\n\n**`pages:unpublish <contestId>`** — sugar over `pages:update` that PATCHes `{\"status\": \"draft\"}`. Safe for pages with entrants: entries and entrants are left untouched, and it only blocks new signups. **This is the single most important thing to tell your user: a draft page still renders publicly at its URL — unpublishing hides nothing, it only stops new entries.** If they want the page actually gone from public view, unpublishing is not that; there is no way to destroy or hide a page through this API (the `status` field only ever accepts `draft`/`active` — `deleted` is rejected).\n\n```sh\ntokei-agent pages:unpublish 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90\n```\n\n**`entries:create <contestId>`** — add a signup. Flags: `--email` (required), `--name`, `--action-type` (default `api_import`), `--points`, `--value`; `metadata` via `--data`.\n\n```sh\ntokei-agent entries:create 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 \\\n  --email fan@example.com --name \"Ada Lovelace\" --points 10 --value \"Order #12345\"\n```\n\nA duplicate email for the same page returns `409` — the person is already signed up; usually safe to treat as success.\n\n**`webhooks:create`** — subscribe an HTTPS endpoint. Flags: `--url` (required), `--events <e1,e2>` (required — one or more of the 5 subscribable events: `entry.created`, `contest.ended`, `winner.selected`, `daily_bonus.claimed`, `referral.converted`; run `events:catalog` for what each payload contains and where it fires from), `--data`. Capped at **10 active subscriptions per account** — the eleventh returns `400 BAD_REQUEST` telling you to delete one first. Get explicit human approval before creating a live webhook (Rule 4) — it starts firing on real events immediately.\n\n```sh\ntokei-agent webhooks:create --url https://yourserver.com/webhooks/tokei --events entry.created,winner.selected\n```\n\nThe response contains the `whsec_` signing secret **exactly once** — it cannot be retrieved again (the CLI also prints a stderr warning). Store it immediately; deliveries are HMAC-SHA256 signed in the `X-TOKEI-Signature` header, expect a 2xx within 10s, and retry with backoff (5s, 30s, 5min).\n\n> **Creator (per-contest) webhooks are separate from this API.** A page owner can also subscribe a webhook from the Tokei dashboard, per contest — those are created via a session-auth route (`src/app/api/promotion/[contestId]/webhooks/route.ts`), not this CLI/API, and are curated to a fixed trio (`entry.created`, `winner.selected`, `contest.ended`) with no event-picker UI. `daily_bonus.claimed` and `referral.converted` stay developer-API-only, reachable only via `webhooks:create` above.\n\n**`webhooks:delete <webhookId>`** — remove a subscription.\n\n```sh\ntokei-agent webhooks:delete 9f1b2a3c-4d5e-6f70-8192-a3b4c5d6e7f8\n```\n\n## MCP server\n\n`tokei-agent mcp` runs a local MCP server over stdio (newline-delimited JSON-RPC) exposing every command above as an MCP tool — no extra install, zero dependencies. Tool names swap `:` for `_` (`pages:list` → `pages_list`); inputs use the API's wire field names directly (`contest_id`, `per_page`, `prizes`, …) instead of flags, so nested bodies need no `--data`. Results carry the same envelope (`rate_limit` included) as text content, with `isError` set on API failures — the error semantics table below applies unchanged. Register it with an MCP client, e.g. Claude Code:\n\n```sh\nclaude mcp add tokei --env TOKEI_API_KEY=tokei_k_... -- npx -y tokei-agent mcp\n```\n\n## `--data` semantics\n\nWrite commands accept `--data '<json>'` or `--data @file.json` for the raw request body (must be a JSON object). Individual field flags are merged **on top of** `--data` — **flags win** on conflict. The merged body is sent to the API untouched: the CLI does no schema validation, so the API's `422` response with per-field `error.details` **is** the validation. Fields only reachable via `--data`: `prizes`, `reward_thresholds`, and nulls (e.g. `{\"end_date\": null}` clears the end date — only on a page that is not active) on `pages:update`; `metadata` and `marketing_consent` on `entries:create` (`marketing_consent: true` records the participant's consent and is what enables syncing them to the connected email provider — send it only when consent was genuinely collected).\n\n## Error semantics — what to DO per status\n\n| Status | `error.code`          | Meaning                                                                 | Agent action                                                                                     |\n| ------ | --------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |\n| 400    | `BAD_REQUEST`         | Invalid query parameters, or the 10-active-webhooks cap.                | Fix the request. Do not retry as-is.                                                             |\n| 401    | `UNAUTHORIZED`        | Missing, invalid, revoked, or **expired** key.                          | Stop and ask the human for a fresh key. Never retry.                                             |\n| 403    | `FORBIDDEN`           | Key is valid but: trial plan (no API access), or the key **lacks the write scope** for a write command. | Do not retry. Ask the human for a read+write key (or a plan upgrade). Not an ownership error.    |\n| 404    | `NOT_FOUND`           | Resource doesn't exist **or is not owned by this key's account** — ownership failures are masked as 404. `pages:clone --template <slug>` also 404s when the slug matches no template (`error.details` has `field: \"template\"`). | Verify the id via `pages:list` / `webhooks:list`, or the slug via `templates:list`. Do not retry. For `webhooks:delete`, a 404 usually means already deleted — safe to treat as success. |\n| 409    | `CONFLICT`            | `entries:create`: email already entered. `pages:clone`: idempotency key already used (existing page's id is in `error.details`). | Safe to treat as success in both cases — the desired state already exists. Do not retry.          |\n| 409    | `WINNERS_ALREADY_SELECTED` | `pages:update`: `end_date` was set **or cleared** on a page whose winners have already been drawn. | Do not retry. Tell the human the draw is final — they must clear the winner selection in the dashboard first. Other fields still patch fine; just omit `--end-date`. |\n| 413    | `PAYLOAD_TOO_LARGE`   | Body over the 10KB limit (`pages:update` alone is raised to 64KB, to fit a full `entry_methods` array). | Shrink the body. Do not retry as-is.                                                             |\n| 422    | `VALIDATION_ERROR`    | Body failed validation; `error.details` is an array of `{field, message}`. | Fix exactly the listed fields, then retry once.                                                  |\n| 429    | `RATE_LIMIT_EXCEEDED` | Per-minute limit, the 20/day clone cap, or the 50/day media-ticket cap. | Wait `Retry-After` seconds (in the response headers), then retry with exponential backoff. On the two daily caps, trust `Retry-After` — `rate_limit` in the envelope reports the per-minute window, which will still look healthy. |\n| 5xx    | `INTERNAL_ERROR`      | Server fault.                                                           | Retry with exponential backoff, max 2–3 attempts, then report to the human.                      |\n| (none) | `network_error`       | Request never reached the API (DNS, timeout, refused).                  | Retry with backoff a couple of times, then report.                                               |\n\n`media:upload` step 1 (the ticket request) uses the table above as normal — an oversize declared `size_bytes` or a rejected `content_type` comes back as the usual `422 VALIDATION_ERROR`, and the 50/day ticket cap as the usual `429 RATE_LIMIT_EXCEEDED`. Step 2 (the `PUT` of the actual bytes) is **not a Tokei API call** — it hits Supabase Storage directly, so a failure there is a different, `error.code`-less shape: `{\"ok\": false, \"error\": {\"type\": \"upload_failed\", \"stage\": \"storage_put\", \"status\": <n>, \"message\": \"...\"}}`. In practice `status` is `413` — the real file exceeded the 5MB bucket cap despite an honest `size_bytes` declaration (or the declaration undersold it). Do not retry the same ticket; shrink the file and run `media:upload` again from scratch.\n\n## Self-throttling\n\nEvery successful and failed API response includes `rate_limit` in the envelope (from the `X-RateLimit-*` headers). Limits are per account: Subscriber 60 read / 30 write per minute, Lifetime 120 / 60. When `rate_limit.remaining` is low, slow down; when it's 0, sleep until `rate_limit.reset` (Unix epoch seconds) before the next call. Don't burn the budget discovering a 429 — read the envelope you already have.\n\n## Common gotchas\n\n1. **`TOKEI_API_KEY` not set** — exit code `2`, JSON on stderr, nothing sent to the API. Export the key first.\n2. **Trial plan gets `403` on everything** — API access needs an active subscription or a lifetime plan. Run `me` first (Rule 1) so this surfaces once, not on every command.\n3. **Read-only key gets `403` on writes, and nothing tells you in advance** — `me` doesn't report scope. If the task changes anything, confirm the key is read+write with the human before starting.\n4. **Media must go through `media:upload` first (Rule 2)** — raw filenames and external URLs are rejected by the host allowlist on all seven media fields. No exceptions, not even for a quick test.\n5. **5MB applies to video too**, and the 50/day media cap **counts tickets issued, not uploads** — never call `media:upload` speculatively in a loop.\n6. **`prizes` and `reward_thresholds` replace the whole list (Rule 3)** — `pages:get` first or you will silently delete the other entries.\n7. **A draft page still renders publicly at its URL** — `pages:unpublish` blocks new signups, it does not hide anything. Say this out loud to the user; they will assume otherwise.\n8. **Publishing needs a future `end_date`** — otherwise `422` on field `status`. Set it in the same call: `pages:publish <id> --data '{\"end_date\":\"...\"}'`.\n9. **Clearing `end_date` needs `status: draft` in the same body**, and is refused outright with `409 WINNERS_ALREADY_SELECTED` once winners are drawn.\n10. **Appearance values don't always read back as written** — `--card-width wide` reads back as `max-w-4xl`. That's correct, not a failed write.\n11. **`--primary-color` is hex only** — `rgb()` and named colours are rejected, because the value is interpolated into a server-rendered `<style>` tag.\n12. **`404` can mean \"not yours\"** — ownership failures are masked as not-found. Confirm the id with `pages:list` before assuming the page is gone.\n13. **`--status` takes `draft|active|completed|deleted`** — `ended` and `paused` are not real values; older builds accepted them and always returned an empty list.\n14. **`--data` is not validated locally** — it is sent untouched and the API's `422` with `error.details` is the validation. Read `.error.details[].field`.\n15. **Template slugs change** — always `templates:list` first; never hardcode a slug.\n\n## Quick reference\n\n```sh\n# Setup — required before anything else\nexport TOKEI_API_KEY=tokei_k_...      # read-only unless you need to write\ntokei-agent me                        # verify key + plan FIRST (Rule 1)\n\n# Read (any key)\ntokei-agent pages:list --status active --per-page 20\ntokei-agent pages:get <contestId>\ntokei-agent stats <contestId>\ntokei-agent leaderboard <contestId> --per-page 10\ntokei-agent referrals:top <contestId> --per-page 10\ntokei-agent entries:list <contestId> --email fan@example.com\ntokei-agent surveys:list <contestId> --page 2\ntokei-agent winners:list <contestId>\ntokei-agent webhooks:list\ntokei-agent templates:list\ntokei-agent actions:catalog\ntokei-agent events:catalog\n\n# Create (write key)\ntokei-agent pages:clone --title \"T\" --template <slug>          # 20/day cap\ntokei-agent pages:clone --title \"T\" --source <promotionId>     # or clone your own\ntokei-agent media:upload ./hero.png                            # ≤5MB, 50 tickets/day\n\n# Update (write key) — read back with pages:get to confirm\ntokei-agent pages:update <id> --title \"T\" --description \"<p>D</p>\"\ntokei-agent pages:update <id> --start-date <iso> --end-date <iso>\ntokei-agent pages:update <id> --template showcase --dark-mode true \\\n  --primary-color \"#7d78c6\" --card-width wide\ntokei-agent pages:update <id> --image-video <public_url> --og-image <public_url>\ntokei-agent pages:update <id> --data '{\"prizes\":[...]}'        # replaces the list\ntokei-agent pages:update <id> --data '{\"og_image\": null}'      # nulls need --data\ntokei-agent pages:update <id> --data '{\"entry_methods\":[...]}' # replaces the list, max 30, 64KB cap\n\n# Publish state (write key)\ntokei-agent pages:publish <id> --data '{\"end_date\":\"2026-09-01T00:00:00Z\"}'\ntokei-agent pages:unpublish <id>                               # still renders publicly!\n\n# Signups + webhooks (write key)\ntokei-agent entries:create <id> --email a@b.com --name \"Ada\" --points 10\ntokei-agent webhooks:create --url https://you.com/hook --events entry.created  # secret shown ONCE\ntokei-agent webhooks:delete <webhookId>\n\n# Other\ntokei-agent mcp                       # MCP stdio server (all 21 commands as tools)\ntokei-agent --help                    # full flag reference\ntokei-agent --version\n```\n\nFile v0.3.6:README.md\n\n# tokei-agent\n\nControl your [Tokei](https://tokei.io) pre-launch and waitlist campaigns from the command line — and from AI agents like Claude Code and OpenClaw. Wraps the Tokei v1 REST API with JSON-only output, zero runtime dependencies.\n\n## Install\n\n```sh\nnpm install -g tokei-agent\n# or run without installing:\nnpx tokei-agent --help\n```\n\nRequires Node 22+.\n\n### Claude Code\n\nInstall it as a plugin and the `tokei-agent` skill comes with it — Claude then\nknows the whole command surface without being told:\n\n```\n/plugin marketplace add gilesdawe/tokei-agent\n/plugin install tokei-agent@tokei\n```\n\nSet `TOKEI_API_KEY` (see [Quick start](#quick-start)) and you're done. The CLI\nitself still runs via `npx`, so there is nothing else to install.\n\n## Quick start\n\n1. Create an API key at [tokei.io](https://tokei.io) → Dashboard → Settings → API Keys. Pick **read-only** unless you need to change things; API access requires an active subscription or lifetime plan.\n\n2. Export it — the syntax differs by shell:\n\n```sh\nexport TOKEI_API_KEY=tokei_k_...          # bash / zsh\n```\n\n```fish\nset -x TOKEI_API_KEY tokei_k_...          # fish\n```\n\n```powershell\n$env:TOKEI_API_KEY = \"tokei_k_...\"        # PowerShell\n```\n\n3. Try it:\n\n```sh\ntokei-agent me                       # verify the key, see plan + API usage\ntokei-agent pages:list --status active\ntokei-agent stats <contestId>        # analytics for one page\n```\n\nEvery command prints JSON to stdout with a top-level `rate_limit` object. Exit codes: `0` success, `1` API/network error, `2` usage error (JSON on stderr).\n\n> **Interactive output (0.3.1+).** When stdout is an interactive terminal, the CLI renders a banner and a human-readable summary instead of raw JSON. When stdout is a pipe, a redirect, CI, or the `mcp` transport, it prints exactly the JSON it always has — so agents, scripts and MCP clients are unaffected. Set `TOKEI_OUTPUT=json` to force JSON at a terminal too; `NO_COLOR` disables colour and animation, and `TOKEI_NO_ANIM=1` keeps the colour but stops the movement.\n\n> **Known issue — exit codes on Node 24 / Windows (fixed in 0.3.0).** On 0.2.2 and earlier the CLI could print its correct JSON output and then abort during process exit, corrupting the exit code (`$LASTEXITCODE` read `-1073740791` / `0xC0000409` on success and failure alike). On an affected version, judge a run by the JSON on stdout, not by the exit status — or upgrade. (Historical labelling slip: the 0.3.0 tarball misreported `--version` as `0.2.2`; 0.3.1+ reports correctly.)\n\n## Commands\n\nRead (any key):\n\n| Command                    | Does                                                        |\n| -------------------------- | ----------------------------------------------------------- |\n| `me`                       | Verify the key; account, plan, API usage                    |\n| `pages:list`               | List pages — `--status`, `--mode`, `--page`, `--per-page`   |\n| `pages:get <contestId>`    | One page in full (prizes, reward tiers, public URL)         |\n| `stats <contestId>`        | Aggregated analytics                                        |\n| `leaderboard <contestId>`  | Participants ranked by points                               |\n| `referrals:top <contestId>`| Top referrers ranked by conversions, plus referral totals   |\n| `winners:list <contestId>` | Selection-run history, newest first, with each run's winners nested |\n| `entries:list <contestId>` | Signups — filter with `--email`                             |\n| `surveys:list <contestId>` | Survey responses                                            |\n| `webhooks:list`            | List webhook subscriptions                                  |\n| `templates:list`           | The platform's named starting points, for `pages:clone --template` |\n| `actions:catalog`          | Every entry-action type Tokei supports — `--type <actionType>`     |\n| `events:catalog`           | Every webhook event Tokei's delivery engine understands — `--type <eventName>` |\n\nWrite (needs a read+write key):\n\n| Command                       | Does                                                                     |\n| ----------------------------- | ------------------------------------------------------------------------ |\n| `pages:clone`                 | Create a page by cloning one you own, a named template, or the starter. 20/day cap |\n| `media:upload <file>`         | Upload an image or video, get back a `public_url` for `pages:update`. ≤5MB per file (video too) |\n| `pages:update <contestId>`    | Update title, description, dates, prizes, reward tiers, appearance (incl. the Custom template: `--template simple` / `--custom-css`), media, and entry actions (`entry_methods`, incl. custom-link rows) |\n| `pages:publish <contestId>`   | Take a page live (needs a future `end_date`)                             |\n| `pages:unpublish <contestId>` | Back to draft — blocks new signups, but the page still renders publicly  |\n| `entries:create <contestId>`  | Add a signup                                                             |\n| `webhooks:create`             | Subscribe an HTTPS endpoint (`whsec_` secret shown once — save it!)      |\n| `webhooks:delete <webhookId>` | Remove a subscription                                                    |\n\nWrite commands take simple fields as flags and full/nested bodies via `--data '<json>'` or `--data @file.json` (flags win on conflict). Run `tokei-agent --help` for every flag, or see [SKILL.md](./SKILL.md) — the agent-oriented reference bundled in this package, with worked examples and error-handling guidance.\n\n```sh\ntokei-agent pages:clone --title \"Spring Launch Waitlist\" --source <promotionId>\ntokei-agent pages:update <contestId> --end-date 2026-09-01T00:00:00Z\ntokei-agent media:upload ./hero.png\ntokei-agent pages:update <contestId> --image-video <public_url from the upload above>\ntokei-agent actions:catalog --type twitter_follow                  # see what a type accepts\ntokei-agent pages:update <contestId> \\\n  --data '{\"entry_methods\":[{\"actionType\":\"tiktok_follow\",\"label\":\"Follow us on TikTok\",\"points\":3,\"config\":{\"username\":\"tokei\"}}]}'\n```\n\n## MCP server\n\nThe package doubles as a local MCP server (stdio): every command above becomes an MCP tool (`pages_list`, `pages_update`, `stats`, …) for Claude Code, Claude Desktop, and other MCP clients.\n\n```sh\nclaude mcp add tokei --env TOKEI_API_KEY=tokei_k_... -- npx -y tokei-agent mcp\n```\n\nOr in JSON config:\n\n```json\n{\n  \"mcpServers\": {\n    \"tokei\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"tokei-agent\", \"mcp\"],\n      \"env\": { \"TOKEI_API_KEY\": \"tokei_k_...\" }\n    }\n  }\n}\n```\n\n## Environment\n\n| Variable        | Required | Meaning                                          |\n| --------------- | -------- | ------------------------------------------------ |\n| `TOKEI_API_KEY` | Yes      | Sent as `Authorization: Bearer <key>`            |\n| `TOKEI_API_URL` | No       | Base URL override (default `https://tokei.io`)   |\n\n## Agents, human in the loop\n\nTokei is built so agents draft and humans approve: give monitoring agents a read-only key, reserve read+write keys for agents that genuinely need to change things, and set key expiry. New webhook subscriptions created via the API trigger a security notification to the account owner.\n\n## Privacy Policy\n\nFull policy: https://tokei.io/privacy\n\n### Data collection practices\n\n`tokei-agent` is a thin client for the Tokei v1 REST API. It collects no data of\nits own: there is no telemetry, no analytics, no crash reporting, and no\nphone-home of any kind.\n\nIt reads exactly two inputs from your environment — `TOKEI_API_KEY` and the\noptional `TOKEI_API_URL` — plus the arguments you pass on the command line (or\nthe arguments an MCP client passes to a tool call). For `media:upload` it also\nreads the bytes of the local file you name.\n\n### Usage and storage\n\nYour API key is used solely as an `Authorization: Bearer` header on requests to\nyour Tokei account. Command arguments become the request path, query string, or\nJSON body. Nothing is written to disk: the CLI creates no config file, cache,\ncredential store, or log file, and holds nothing after the process exits.\n\nAPI responses — which can include entrant email addresses, survey answers, and\nanalytics — are printed as JSON to stdout. From that point they are handled by\nwhatever invoked the CLI (your shell, your scripts, or your AI agent and its\nconversation history). Treat that output as the personal data it is.\n\n### Third-party sharing\n\nNothing is sold or shared with third parties. Data is transmitted only to:\n\n- **Tokei** (`https://tokei.io`, or the host you set in `TOKEI_API_URL`) — every\n  command, to serve your request against your own account.\n- **Tokei's object storage provider** — `media:upload` only. The API returns a\n  short-lived signed upload URL and the CLI PUTs your file bytes straight to it;\n  the file becomes a public asset on your promotion page.\n\nThe CLI contacts no other host.\n\n### Data retention\n\nThe CLI retains nothing. Data held in your Tokei account — promotions, entries,\nsurvey responses — is retained under the Tokei privacy policy above, and you can\nrequest access or deletion there. API keys are created, scoped, expired, and\nrevoked by you at Dashboard → Settings → API Keys; revoking a key immediately\nstops all access through it.\n\n### Contact information\n\nTokei — https://tokei.io/privacy — support@tokei.io\n\n## Docs\n\n- API reference: https://tokei.io/docs/api\n- OpenAPI spec: https://tokei.io/openapi.json\n- Agent skill reference: [SKILL.md](./SKILL.md)\n\n## License\n\nMIT\n\nFile v0.3.6:_meta.json\n\n{\n  \"ownerId\": \"kn71v6w9gadrqg1bvex24zx4ex8aw3qd\",\n  \"slug\": \"tokei-agent\",\n  \"version\": \"0.3.6\",\n  \"publishedAt\": 1788552983598\n}\n\nFile v0.3.6:CHANGELOG.md\n\n# Changelog\n\nAll notable changes to `tokei-agent`. Dates are the npm publish times.\n\nThis file was seeded on 2026-08-02, after 0.3.2 shipped, by reconstructing the\nhistory from `npm view tokei-agent time` and the commits that touched `cli/`.\nEntries before 0.3.3 are therefore summaries written after the fact; entries\nfrom 0.3.3 on are written as part of the release.\n\nThe format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).\nThis package is pre-1.0: minor versions may carry breaking changes, though none\nhas so far.\n\n## [Unreleased]\n\n## [0.3.5] — 2026-08-28\n\n### Fixed\n\n- **MCP tool calls no longer forward undeclared arguments.** `callTool` built the\n  request body as `{ ...fixedBody, ...args }` and validated only that *required*\n  arguments were present, so any extra field a caller invented rode through to the\n  API. Because `PATCH /api/v1/contests/{id}` replaces `prizes` wholesale, a call of\n  `pages_publish { contest_id, prizes: [] }` **wiped the page's prize list** — under\n  a tool annotated `destructiveHint: false`. Arguments are now allowlisted against\n  the tool's own `inputSchema.properties` before the merge; undeclared fields are\n  dropped, and the tool result now names the ones it ignored rather than\n  discarding them in silence. Declared arguments still win over `fixedBody`.\n\n  One documented field was caught by this and is declared as part of the same\n  release: `entries_create` accepted `marketing_consent`\n  (`docs/using-custom-apis.md`) without advertising it, so the allowlist would\n  have dropped it. See Added below.\n- **`entries_create` was silently discarding `marketing_consent`.** The v1 route\n  accepts it and the field gates whether an entrant is ever synced to the\n  creator's connected email provider, so an import of consented entrants would\n  have returned 201 on every row and synced none of them.\n\n### Added\n\n- `entries_create` declares `marketing_consent`. Set it to `true` only when the\n  participant genuinely gave marketing consent elsewhere; it records the consent\n  and its timestamp, and is what enables the email-provider sync.\n\n### Changed\n\n- `pages_update`'s description now states that `status` is not settable there and\n  points at `pages_publish` / `pages_unpublish`. `status` stays undeclared on\n  purpose — `pages_publish` enforces a future-`end_date` check that a raw\n  `status: \"active\"` would bypass.\n- `webhooks_delete` now declares `idempotentHint: true`, and its description states\n  that the subscription's queued and historical `webhook_deliveries` rows are\n  deleted with it (`ON DELETE CASCADE`), so past delivery attempts are not\n  retrievable afterwards.\n- The MCP server instructions and the `pages_unpublish` description are reworded\n  from instructing the agent (\"slow down\", \"Tell your user this…\") to describing\n  what the API does. No behavioural change.\n\n## [0.3.4] — 2026-08-21\n\n### Added\n\n- `pages:update --template simple` — the Custom template: bare structural\n  markup the creator styles entirely via `--custom-css`, rather than a fixed\n  layout.\n- `pages:update --custom-css <css>` — creator CSS for the Custom template,\n  applied on both the hosted page and the widget embed via `--tokei-*` custom\n  properties and `.tokei-simple-*` class hooks. Max 20 KB; server-sanitised,\n  with unsafe constructs rejected by a 422 naming the reason.\n- `--card-width` gains `xl` and `max-w-7xl`, in both the CLI flag and the\n  `pages_update` MCP tool's schema.\n- `entry_methods` — the page's action buttons — is now settable through\n  `pages:update --data` and the `pages_update` MCP tool, alongside `prizes`\n  and `reward_thresholds`. Replaces the whole list wholesale: an action row\n  (`actionType`, `label`, `points?`, `config?`, `requireVerification?`) or a\n  custom-link row (`label`, `points?`, `link`, `actionsRequired?`) with no\n  `actionType`.\n\n## [0.3.3] — 2026-08-03\n\n### Added\n\n- `events:catalog` (+ `--type`) and the `events_catalog` MCP tool — every\n  webhook event Tokei can send, with its payload schema, description and emit\n  sites, fetched from the API rather than bundled so the CLI can never drift\n  from the platform. **21 commands, 21 MCP tools.**\n- `winners:list <contestId>` and the `winners_list` MCP tool — read-only\n  selection-run history with the winners of each run. Unpaginated, capped at the\n  100 most recent runs. Selecting winners stays a human action in the dashboard;\n  the CLI can only read the result.\n- **All five webhook events are now subscribable**, not just `entry.created`:\n  `contest.ended`, `winner.selected`, `daily_bonus.claimed` and\n  `referral.converted` now fire on the live platform and are accepted by\n  `webhooks:create --events`. Previously they were catalogued but rejected with\n  a 422.\n- **Installable as a Claude Code plugin** — a fifth distribution channel\n  alongside npm, skills.sh, ClawHub and the MCP registry:\n\n  ```\n  /plugin marketplace add gilesdawe/tokei-agent\n  /plugin install tokei-agent@tokei\n  ```\n\n  The skill then loads automatically as `tokei-agent:tokei-agent`, with no\n  `npx skills add` step. Adds `.claude-plugin/plugin.json` and\n  `.claude-plugin/marketplace.json` to the repo. Not included in the npm\n  tarball — plugin users install from GitHub.\n- The ANSI wordmark now renders above `--help` at an interactive terminal.\n  Previously `--help` returned before the terminal UI was created, so the first\n  command most people run was undecorated. Suppressed — byte for byte — for\n  pipes, redirects, CI, `TERM=dumb`, `TOKEI_OUTPUT=json` and terminals narrower\n  than 60 columns, so parsed output is unchanged.\n- `SKILL.md` gains a fourth hard rule: get explicit human approval before any\n  action that emails people or changes anything public, with the read-only\n  commands listed as exempt.\n\n### Fixed\n\n- The `webhooks_create` MCP tool advertised `entry.created` as the only valid\n  value for `events`, in both its JSON-schema `enum` and its description. MCP\n  clients read that schema as the contract, so an agent had no way to subscribe\n  to the four events this release activates. The CLI's own `webhooks:create` was\n  never affected.\n\n### Changed\n\n- This changelog now ships in the tarball. It was added 27 minutes after 0.3.2\n  published, so 0.3.3 is the first release to carry it.\n- `SKILL.md`'s `templates:list` sample is refreshed against the live 14-template\n  menu and marked as an excerpt. It was always illustrative — call the endpoint,\n  never hardcode a slug.\n\n## [0.3.2] — 2026-08-02\n\n### Added\n\n- `referrals:top <contestId>` and the `referrals_top` MCP tool — top referrers\n  ranked by conversions, plus click/conversion totals. Lists only entrants who\n  have actually referred someone. **19 commands, 19 MCP tools.**\n\n### Fixed\n\n- The interactive-output note in `README.md` / `SKILL.md` said \"0.3.2+\"; the\n  feature shipped in 0.3.1.\n\n## [0.3.1] — 2026-08-02\n\n### Added\n\n- `actions:catalog` (+ `--type`) and the matching MCP tool — every entry-action\n  type Tokei supports, fetched from the API rather than bundled, so the CLI can\n  never drift from the platform.\n- Interactive terminal output: the ANSI wordmark on `me`, plus a step line,\n  animated spinner and one-line summary elsewhere. Inert unless a real terminal\n  is attached — `TOKEI_OUTPUT=json`, `NO_COLOR` and `TOKEI_NO_ANIM=1` all\n  control it.\n\n### Fixed\n\n- `--version` now reports the real version. The 0.3.0 tarball was built from a\n  tree still labelled 0.2.2 and reported that instead.\n\n## [0.3.0] — 2026-07-26\n\n### Added\n\n- `media:upload <file>` and the `media_upload` MCP tool — two-step signed-ticket\n  upload (request a ticket, PUT the bytes), returning a `public_url`.\n- Seven media flags on `pages:update`: `--image-video`, `--secondary-image`,\n  `--third-image`, `--fourth-image`, `--fifth-image`, `--background-image`,\n  `--og-image`.\n\n### Fixed\n\n- Exit-code corruption on Node 24 / Windows: the CLI printed correct JSON and\n  then aborted during process exit, so `$LASTEXITCODE` read `-1073740791`\n  (`0xC0000409`) on success and failure alike.\n\n### Known issue\n\n- This tarball reports `--version` as `0.2.2`. Fixed in 0.3.1.\n\n## [0.2.2] — 2026-07-25\n\n### Changed\n\n- Documented the exit-code contract: `0` success, `1` API/network error,\n  `2` usage error.\n\n## [0.2.1] — 2026-07-22\n\n### Added\n\n- `templates:list` and clone-by-slug (`pages:clone --template <slug>`).\n- `pages:publish` / `pages:unpublish` (status draft ⇄ active; publishing\n  requires a future `end_date`).\n- Appearance flags on `pages:update`: `--template`, `--dark-mode`,\n  `--primary-color`, `--card-width`.\n\n## [0.2.0] — 2026-07-20\n\n### Added\n\n- `mcp` — the bundled MCP stdio server, exposing every command as an MCP tool\n  for Claude Code, Claude Desktop and other MCP clients.\n\n## [0.1.0] — 2026-07-20\n\n### Added\n\n- Write commands: `pages:clone`, `pages:update`, `entries:create`,\n  `webhooks:create`, `webhooks:delete`.\n- `SKILL.md` and `README.md`, making the package usable as an agent skill.\n\n## [0.0.1] — 2026-07-20\n\n### Added\n\n- First publish. Read commands (`me`, `pages:list`, `pages:get`, `stats`,\n  `leaderboard`, `entries:list`, `surveys:list`, `webhooks:list`), `TOKEI_API_KEY`\n  auth, JSON-only stdout with a top-level `rate_limit` object, zero runtime\n  dependencies.\n\n[unreleased]: https://github.com/gilesdawe/tokei-agent/compare/v0.3.5...HEAD\n[0.3.5]: https://www.npmjs.com/package/tokei-agent/v/0.3.5\n[0.3.4]: https://www.npmjs.com/package/tokei-agent/v/0.3.4\n[0.3.3]: https://www.npmjs.com/package/tokei-agent/v/0.3.3\n[0.3.2]: https://www.npmjs.com/package/tokei-agent/v/0.3.2\n[0.3.1]: https://www.npmjs.com/package/tokei-agent/v/0.3.1\n[0.3.0]: https://www.npmjs.com/package/tokei-agent/v/0.3.0\n[0.2.2]: https://www.npmjs.com/package/tokei-agent/v/0.2.2\n[0.2.1]: https://www.npmjs.com/package/tokei-agent/v/0.2.1\n[0.2.0]: https://www.npmjs.com/package/tokei-agent/v/0.2.0\n[0.1.0]: https://www.npmjs.com/package/tokei-agent/v/0.1.0\n[0.0.1]: https://www.npmjs.com/package/tokei-agent/v/0.0.1\n\nFile v0.3.6:skill-card.md\n\n## Description:\n\ntokei-agent lets agents and CLI users manage Tokei waitlist, launch, giveaway, referral, webhook, media, and analytics workflows through the Tokei v1 REST API.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[gilesdawe](https://clawhub.ai/user/gilesdawe)\n\n### License/Terms of Use:\n\nMIT\n\n## Use Case:\n\nDevelopers and campaign operators use this skill to let agents inspect and manage Tokei pre-launch, waitlist, giveaway, referral, media, analytics, and webhook workflows from CLI or MCP surfaces.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill can give an agent high-impact control over Tokei account resources, including publishing pages, creating webhooks, adding entries, and uploading media.\n\nMitigation: Use a pinned package version, prefer read-only and short-lived Tokei keys, and require explicit human approval before write actions, public changes, email-affecting actions, or unattended media_upload calls.\n\nRisk: Credentials and API destination choices can expand account exposure, especially when TOKEI_API_URL is overridden.\n\nMitigation: Keep TOKEI_API_KEY scoped to the task, rotate or expire keys after use, and avoid setting TOKEI_API_URL unless the destination is fully trusted.\n\nRisk: API responses and uploaded files can include personal or campaign-sensitive data that may be exposed through agent transcripts, logs, or public campaign assets.\n\nMitigation: Review command outputs before sharing, limit logging of entrant data and survey responses, and inspect local files before uploading them as campaign media.\n\n## Reference(s):\n\n- [Tokei Agent Docs](https://tokei.io/agent)\n- [Tokei API Reference](https://tokei.io/docs/api)\n- [Tokei OpenAPI Specification](https://tokei.io/openapi.json)\n- [tokei-agent npm Package](https://www.npmjs.com/package/tokei-agent)\n- [ClawHub Skill Page](https://clawhub.ai/gilesdawe/skills/tokei-agent)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Configuration, Guidance]\n\n**Output Format:** [Markdown guidance with shell commands; commands return JSON from the Tokei API.]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Requires TOKEI_API_KEY; write workflows may require a read+write key and explicit human approval.]\n\n## Skill Version(s):\n\n0.3.6 (source: release evidence, package.json, server.json)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v0.3.6:package.json\n\n{\n  \"name\": \"tokei-agent\",\n  \"version\": \"0.3.6\",\n  \"description\": \"Control your Tokei (tokei.io) pre-launch and waitlist campaigns from AI agents and the command line.\",\n  \"type\": \"module\",\n  \"bin\": {\n    \"tokei-agent\": \"bin/tokei-agent.mjs\"\n  },\n  \"engines\": {\n    \"node\": \">=22\"\n  },\n  \"files\": [\n    \"bin\",\n    \"dist\",\n    \"CHANGELOG.md\",\n    \"README.md\",\n    \"SKILL.md\"\n  ],\n  \"keywords\": [\n    \"tokei\",\n    \"waitlist\",\n    \"prelaunch\",\n    \"agent\",\n    \"cli\",\n    \"mcp\",\n    \"claude\",\n    \"ai-agents\"\n  ],\n  \"homepage\": \"https://tokei.io\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/gilesdawe/tokei-agent.git\"\n  },\n  \"mcpName\": \"io.github.gilesdawe/tokei-agent\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"build\": \"tsc -p tsconfig.json\",\n    \"test\": \"node bin/tokei-agent.mjs --help\"\n  }\n}\n\nFile v0.3.6:server.json\n\n{\n  \"$schema\": \"https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json\",\n  \"name\": \"io.github.gilesdawe/tokei-agent\",\n  \"description\": \"Control your Tokei (tokei.io) pre-launch and waitlist campaigns from AI agents and the command line.\",\n  \"version\": \"0.3.6\",\n  \"repository\": {\n    \"url\": \"https://github.com/gilesdawe/tokei-agent\",\n    \"source\": \"github\"\n  },\n  \"packages\": [\n    {\n      \"registryType\": \"npm\",\n      \"identifier\": \"tokei-agent\",\n      \"version\": \"0.3.6\",\n      \"transport\": {\n        \"type\": \"stdio\"\n      },\n      \"environmentVariables\": [\n        {\n          \"name\": \"TOKEI_API_KEY\",\n          \"description\": \"Tokei API key (starts tokei_k_). Create one at tokei.io -> Dashboard -> Settings -> API Keys. Read-only keys cover every read command; write access is opt-in.\",\n          \"isRequired\": true,\n          \"isSecret\": true\n        },\n        {\n          \"name\": \"TOKEI_API_URL\",\n          \"description\": \"Override the API base URL. Defaults to https://tokei.io; rarely needed.\",\n          \"isRequired\": false,\n          \"isSecret\": false\n        }\n      ]\n    }\n  ]\n}\n\nFile v0.3.6:tsconfig.json\n\n{\n  \"compilerOptions\": {\n    \"module\": \"NodeNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"target\": \"ES2023\",\n    \"strict\": true,\n    \"outDir\": \"dist\",\n    \"rootDir\": \"src\",\n    \"declaration\": true\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"src/__tests__\"]\n}\n\nFile v0.3.6:LICENSE\n\nMIT License\n\nCopyright (c) 2026 Tokei (tokei.io)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nArchive v0.3.5: 23 files, 90452 bytes\n\nFiles: bin/tokei-agent.mjs (3225b), CHANGELOG.md (10016b), LICENSE (1073b), package.json (821b), README.md (9637b), server.json (1116b), skill-card.md (2344b), SKILL.md (48938b), src/__tests__/args.test.ts (1720b), src/__tests__/commands.test.ts (36931b), src/__tests__/http.test.ts (6498b), src/__tests__/mcp.test.ts (39352b), src/__tests__/media.test.ts (9693b), src/__tests__/ui.test.ts (17797b), src/__tests__/version.test.ts (1827b), src/args.ts (1352b), src/http.ts (4824b), src/index.ts (32595b), src/mcp.ts (34322b), src/media.ts (6013b), src/ui.ts (19384b), tsconfig.json (259b), _meta.json (130b)\n\nFile v0.3.5:SKILL.md\n\n---\nname: tokei-agent\ndescription: Tokei (tokei.io) is a pre-launch, waitlist and giveaway platform — launch pages, competition giveaways, sweepstakes, referral and viral-loop campaigns, Product Hunt launch drives, Gleam-style and KickoffLabs-style entry pages. This CLI controls them from the command line — list, clone, update, publish and unpublish pages, upload images and video, set prizes, reward tiers and deadlines, restyle pages, and read stats, leaderboards, top referrers, signups, survey responses, winner selections and the webhook event catalog, plus manage webhooks (all 5 events) — all via the Tokei v1 REST API.\nhomepage: https://tokei.io/agent\nmetadata: {\"openclaw\":{\"emoji\":\"⏱️\",\"requires\":{\"bins\":[],\"env\":[\"TOKEI_API_KEY\"]}}}\n---\n\n# tokei-agent\n\n`tokei-agent` is a zero-dependency CLI for the Tokei v1 REST API (`https://tokei.io/api/v1`). Every command prints JSON to stdout, so pipe it to `jq` or parse it directly.\n\n## Install tokei-agent if it isn't already there\n\n```sh\nnpm install -g tokei-agent\n# or run it without installing:\nnpx tokei-agent --help\n```\n\nRequires Node 22+. npm release: https://www.npmjs.com/package/tokei-agent — official website: https://tokei.io — agent docs: https://tokei.io/agent — API reference: https://tokei.io/docs/api\n\n---\n\n## ⚠️ Four hard rules (read first)\n\n**Rule 1 — Run `tokei-agent me` before anything else.** It proves the key is live and reports the account's **plan**. API access requires an active subscription or lifetime plan — **trial accounts get `403` on every command**, so a whole workflow can fail on its first call for a reason no other command explains.\n\n> `me` does **not** report the key's scope. There is no way to read a key's scope from the API — you discover a read-only key by getting `403 FORBIDDEN` on your first write. If the task involves changing anything, ask the human up front whether their key is read+write.\n\n**Rule 2 — Every media URL you write to a page MUST come from `tokei-agent media:upload`.** Raw filesystem paths (`hero.png`) and third-party URLs (`https://example.com/hero.png`) are **rejected** — the seven media fields on `pages:update` are guarded by a closed host allowlist that only accepts the app's own storage (and `res.cloudinary.com`). Always:\n\n```sh\nHERO=$(tokei-agent media:upload ./hero.png | jq -r '.data.public_url')\ntokei-agent pages:update \"$PAGE_ID\" --image-video \"$HERO\"\n```\n\nEvery `--image-video` / `--og-image` / `--background-image` example below assumes a `public_url` obtained this way — never a local file.\n\n**Rule 3 — List fields replace wholesale; always read before you write.** `prizes` (max 20) and `reward_thresholds` (max 50) are **not** merged — whatever array you send becomes the entire list, so sending one prize deletes the other nineteen. The pattern is always `pages:get` → modify the array → `pages:update` with the complete list.\n\n**Rule 4 — Get explicit human approval before any action that emails people or changes anything public.** Publishing a page, creating a webhook that fires on live events, sending entries/notifications, or anything else visible to entrants or third parties needs a human sign-off first — this CLI does not gate those calls for you, so you are the gate. Read-only commands (`me`, `pages:list`, `pages:get`, `stats`, `leaderboard`, `referrals:top`, `entries:list`, `surveys:list`, `winners:list`, `webhooks:list`, `templates:list`, `actions:catalog`, `events:catalog`) need no such approval.\n\n---\n\n## Terminology mapping (read this first)\n\nThe API and the UI use different words for the same objects. Do not treat these as different things.\n\n| API says               | UI / humans say    | Notes                                                            |\n| ---------------------- | ------------------ | ---------------------------------------------------------------- |\n| `contest`              | page, campaign     | The core object. `contestId` in paths = the page's id.           |\n| `promotion`            | page, campaign     | Same object again — `POST /promotions` creates it, reads live under `/contests/{id}`. |\n| `entry`                | signup, subscriber | One person joining a page.                                       |\n| `entries:create`       | add a signup       |                                                                  |\n\nCLI command names use the UI words (`pages:list`, `pages:update`); the JSON they return uses the API words (`contest`, `promotion`).\n\n## Setup\n\n| Env var         | Required | Meaning                                                                          |\n| --------------- | -------- | -------------------------------------------------------------------------------- |\n| `TOKEI_API_KEY` | Yes      | Sent as `Authorization: Bearer <key>`. Create one at tokei.io → Dashboard → Settings → API Keys. |\n| `TOKEI_API_URL` | No       | Base URL override (default `https://tokei.io`).                                  |\n\nKeys have a scope: **read-only** or **read+write**. Write commands need a read+write key; a read-only key gets `403`. Keys can also carry an expiry — an expired key gets `401`. Prefer a read-only key unless the task actually changes something.\n\n## Core workflow\n\nThe fundamental pattern, end to end:\n\n1. **Verify** — confirm the key and the plan (Rule 1)\n2. **Discover** — list your pages, and the platform's named starting points\n3. **Create** — clone a template (or one of your own pages) into a new draft\n4. **Prepare** — upload media and get back allowlisted URLs (Rule 2)\n5. **Shape** — PATCH copy, dates, prizes, appearance and media onto the page\n6. **Publish** — flip the draft live (needs a future `end_date`)\n7. **Monitor** — stats, leaderboard, top referrers, signups\n8. **Automate** — subscribe a webhook instead of polling\n\n```sh\n# 1. Verify — do this first, always\ntokei-agent me\n\n# 2. Discover\ntokei-agent pages:list --status active\ntokei-agent templates:list\n\n# 3. Create (returns a draft page)\nPAGE=$(tokei-agent pages:clone --title \"Spring Launch Waitlist\" \\\n  --template product-hunt | jq -r '.data.id')\n\n# 4. Prepare media (Rule 2 — never pass a raw path)\nHERO=$(tokei-agent media:upload ./hero.png | jq -r '.data.public_url')\n\n# 5. Shape\ntokei-agent pages:update \"$PAGE\" \\\n  --description \"Join the list for early access.\" \\\n  --template showcase --dark-mode true --primary-color \"#7d78c6\" \\\n  --image-video \"$HERO\"\n\n# 6. Publish (end_date must be in the future — set it in the same call if unset)\ntokei-agent pages:publish \"$PAGE\" --data '{\"end_date\":\"2026-09-01T00:00:00Z\"}'\n\n# 7. Monitor\ntokei-agent stats \"$PAGE\"\ntokei-agent leaderboard \"$PAGE\" --per-page 10\ntokei-agent referrals:top \"$PAGE\" --per-page 10\ntokei-agent entries:list \"$PAGE\"\n\n# 8. Automate — events:catalog lists all 5 subscribable events and their payloads\ntokei-agent webhooks:create --url https://yourserver.com/webhooks/tokei \\\n  --events entry.created,winner.selected\n```\n\nConfirm every write by reading it back with `pages:get \"$PAGE\"` — everything writable is also readable.\n\n## Output envelope, exit codes\n\n- stdout: the API's JSON body, augmented with a top-level `\"rate_limit\"` object: `{\"limit\": n, \"remaining\": n, \"reset\": <unix epoch seconds>}`, or `null` when the headers were absent (e.g. network failure).\n- **Agents always get this JSON.** From 0.3.1 the CLI renders a human banner and summary *only* when stdout is an interactive terminal. A subprocess, pipe, redirect, CI environment or the `mcp` transport has no TTY, so the JSON envelope below is what you will receive, byte for byte. If you ever need to force it explicitly, set `TOKEI_OUTPUT=json`.\n- Exit code `0` — success (HTTP 2xx).\n- Exit code `1` — API or network error. The API's JSON error body (same envelope, with `rate_limit`) is still printed on stdout; pure network failures print `{\"ok\": false, \"error\": {\"type\": \"network_error\", \"message\": ...}}`.\n- Exit code `2` — usage error (bad flags, missing arguments, missing `TOKEI_API_KEY`). Printed as JSON on **stderr**: `{\"ok\": false, \"error\": {\"type\": \"usage_error\", \"message\": ...}}`. Nothing was sent to the API.\n\n**The shape, so you can `jq` it without guessing.** Success always nests the payload under `data` — it is never a bare top-level array:\n\n```jsonc\n// single-object reads (pages:get, me, media:upload, pages:clone, …)\n{ \"success\": true, \"data\": { … }, \"rate_limit\": { … } }\n\n// list reads (pages:list, leaderboard, referrals:top, entries:list,\n//             surveys:list, templates:list, webhooks:list)\n// referrals:top adds a sibling \"totals\" object next to data + pagination\n{ \"success\": true, \"data\": [ … ],\n  \"pagination\": { \"page\": 1, \"per_page\": 20, \"total_pages\": 3, \"total_count\": 47 },\n  \"rate_limit\": { … } }\n\n// errors\n{ \"success\": false,\n  \"error\": { \"code\": \"VALIDATION_ERROR\", \"message\": \"…\", \"status\": 422,\n             \"details\": [{ \"field\": \"end_date\", \"message\": \"…\" }] },\n  \"rate_limit\": { … } }\n```\n\nSo the idioms are:\n\n```sh\ntokei-agent pages:list | jq -r '.data[0].id'                          # first page's id\ntokei-agent pages:list | jq -r '.data[] | select(.status==\"active\") | .id'\ntokei-agent pages:list --per-page 100 | jq '.pagination.total_pages'  # more to fetch?\ntokei-agent pages:get \"$PAGE\" | jq -r '.data.public_url'\ntokei-agent media:upload ./hero.png | jq -r '.data.public_url'\ntokei-agent pages:update \"$PAGE\" --title x | jq -r '.error.details[]?.field'\n```\n\n`templates:list`, `me` and `winners:list` are unpaginated — they return `data` with no `pagination` key (`winners:list`'s `data` is still an array, just capped rather than paged; see its entry below). `events:catalog` and `actions:catalog` return `data` as an object (or one entry, with `--type`), not an array at all.\n\n> **Known issue — exit codes on Node 24 / Windows (fixed in 0.3.0).** On 0.2.2 and earlier the CLI could print its correct JSON output and then abort during process exit, corrupting the exit code (`$LASTEXITCODE` read `-1073740791` / `0xC0000409` on success and failure alike). On an affected version, judge a run by the JSON on stdout, not by the exit status — or upgrade. (Historical labelling slip: the 0.3.0 tarball misreported `--version` as `0.2.2`; 0.3.1+ reports correctly.)\n\n## Commands (read — any key)\n\n**`me`** — verify the key, see plan and API usage. Returns `user_id`, `email`, `plan`, `active_contests` and an `api_usage` block (`requests_today`, `daily_limit`, `rate_limit_per_minute`). Not the key's scope — see Rule 1.\n\n```sh\ntokei-agent me\n```\n\n**`pages:list`** — list your pages. Flags: `--status draft|active|completed|deleted`, `--mode competition|gamification|sharing_only`, `--page <n>`, `--per-page <1-100>`. The filter values are exactly the values a page's `status` field can hold, so what you read back is what you can filter on. There is no `ended` or `paused` status; those were accepted by older builds and always returned nothing.\n\n`status` — both the field and the filter — is the **effective** status, derived from the stored value **and the dates**: a page whose `end_date` has passed reads and filters as `completed`, and one whose `start_date` is still in the future reads as `draft`. A creator can mark a page completed by hand, but nothing does so when `end_date` passes, so before 2026-07-27 an ended page nobody closed manually reported `active` indefinitely. `status: \"active\"` now genuinely means live — trust it.\n\n```sh\ntokei-agent pages:list --status active --per-page 20\n```\n\n**`pages:get <contestId>`** — one page, full object. `title` is the **visible page headline**, not the internal dashboard name, so what you read is what a visitor sees. The object also carries:\n\n| Field | Meaning |\n| ----- | ------- |\n| `total_entries` | Count of entry **actions** (`contest_entries` rows) — not people, and not points. **Never narrate this as \"signups.\"** |\n| `total_points_awarded` | Sum of points earned across all entry actions. Not a headcount either. |\n| `status` | The **effective** status (see `pages:list` above) — `completed` once `end_date` passes, whatever is stored. Safe to report as \"live\" / \"finished\" directly. |\n| `days_left` | Whole days remaining, computed from `end_date` at read time; **`0` means it has ended**, and the partial final day rounds up so a page closing tonight reads `1`. Only falls back to the stored column when there is no `end_date` at all. Before 2026-07-27 this replayed a stale stored value and could say `30` on a page with a day to go. |\n| `entry_methods[].points` | What each action **actually awards**, including any per-action override the owner configured. Safe to quote to a user. |\n| `description`, `prizes`, `reward_thresholds` | Everything `pages:update` can write, so you can read-modify-write. |\n| `public_url` | The live page URL — no second call needed. **Can be `null`** when the page has no slug yet (`contest_url` null); check before handing it to your user as a link. |\n| `primary_color` | Brand colour as a CSS value (e.g. `#7d78c6`); `null` means the template default. `settings.color` is a deprecated alias of it. |\n| `card_width` | `max-w-2xl` \\| `max-w-3xl` \\| `max-w-4xl` \\| `max-w-7xl`; `null` renders as `max-w-2xl`. |\n| `settings.template` | Page skin: `basic-new`, `showcase` or `future`. |\n| `image_video` | Hero media — an image **or** a video URL. May carry dimension hints as query params. Writable via `pages:update --image-video` (get the URL from `media:upload`). |\n| `secondary_image` … `fifth_image`, `background_image`, `og_image` | The page's other media slots; `null` when unset. Each writable via its own `pages:update` flag — see the media flags note under `pages:update` below. |\n| `campaign_name`, `project_name` | **Read-only.** `project_name` is the small subheading under the headline; `campaign_name` is the internal dashboard name and never appears on the page. |\n\n`primary_color`, `card_width`, `settings.template` and all seven media fields (`image_video`, `secondary_image`, `third_image`, `fourth_image`, `fifth_image`, `background_image`, `og_image`) are now writable via `pages:update` (see below); `dark_mode_enabled` writes through `--dark-mode` there too, though it isn't itself a field in this table. `campaign_name`/`project_name` remain read-only always. If your user wants a headcount (\"how many people entered\"), that's neither `total_entries` nor `total_points_awarded` — use `stats`'s `unique_participants` instead.\n\n```sh\ntokei-agent pages:get 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90\n```\n\n**`stats <contestId>`** — aggregated analytics for a page.\n\n```sh\ntokei-agent stats 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90\n```\n\n**`leaderboard <contestId>`** — participants ranked by points. Flags: `--page`, `--per-page <1-100>`.\n\n```sh\ntokei-agent leaderboard 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 --per-page 10\n```\n\n**`referrals:top <contestId>`** — the page's top referrers, ranked by converted referrals then total referrals. Flags: `--page`, `--per-page <1-100>`.\n\nEach row carries `referrer_id`, `referral_code`, `full_name`, `email`, `total_referrals`, `converted_referrals` and `bonus_points_earned`. Alongside `data` and `pagination` the response adds a `totals` object: `total_referrers`, `total_referrals`, `total_clicks`, `converted_clicks`, `click_conversion_rate` (a percentage, one decimal place).\n\nThree things to know before you report these numbers:\n\n- Only entrants who have actually referred someone appear. Every participant is issued a referral code, so an unfiltered list would be almost entirely zero rows.\n- `converted_referrals` counts referred people who went on to complete at least one entry action *other than* sharing — it is the ranking key, and it is the number worth reporting as \"referrals that worked\".\n- `bonus_points_earned` is a **count of bonus entries, not a points total**, despite the name. The name matches the underlying data and the dashboard, so it is kept for consistency; do not present it as points.\n\n```sh\ntokei-agent referrals:top 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 --per-page 10\n```\n\n**`winners:list <contestId>`** — selection-run history for a page, newest first, each run with its persisted winners nested. Read-only, no query params, no pagination (contest-scale run/winner counts, capped at 100 runs — headroom, not a pagination story). Finalize-via-API is deliberately out of scope (human-approval policy, Rule 4) — this is how an agent looks back at what a run actually selected, not how it draws one. Each run carries `id`, `created_at`, `seed`, `algorithm_version`, `status`, `requested_by_email`, `finalized_at`, `total_candidates`, `total_winners_selected`, `winners_count` and a `winners` array; each winner carries `id`, `contest_user_id`, `email`, `full_name`, `entry_points`, `created_at`, `country_name`, `city`, `prize_tier`, `prize_description`, `prize_value`, `selected_at`, `notified_at`, `notification_method`, `verified`, `claimed_at`.\n\n```sh\ntokei-agent winners:list 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90\n```\n\n**`entries:list <contestId>`** — signups for a page. Flags: `--page`, `--per-page <1-100>`, `--email <addr>` (exact-match filter).\n\n```sh\ntokei-agent entries:list 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 --email fan@example.com\n```\n\n**`surveys:list <contestId>`** — survey responses. Flags: `--page`, `--per-page <1-100>`.\n\n```sh\ntokei-agent surveys:list 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 --page 2\n```\n\n**`webhooks:list`** — list webhook subscriptions. Reading them needs no write scope (only `webhooks:create`/`webhooks:delete` do). Flags: `--page`, `--per-page <1-100>`. Watch `failure_count`: a subscription is auto-disabled after 10 consecutive failed deliveries.\n\n```sh\ntokei-agent webhooks:list\n```\n\n**`templates:list`** — the platform's named starting points, for cloning with `pages:clone --template <slug>`. Same list for every key — not scoped to your account (it's platform content, not the caller's). No flags, no pagination.\n\n```sh\ntokei-agent templates:list\n```\n\nExample response — an **excerpt** of the real menu at the time of writing (15 templates live, 5 shown). It grows as the platform publishes templates, so call `templates:list` and use the slugs it returns; never hardcode one.\n\n```json\n{\n  \"success\": true,\n  \"data\": [\n    {\n      \"id\": \"0e4de06c-8915-4e1c-ba3f-48f6c9a098f2\",\n      \"slug\": \"collect-email-list\",\n      \"name\": \"Collect email list — registration-first opt-in subscriber page\",\n      \"skin\": \"future\",\n      \"entry_method_count\": 0\n    },\n    {\n      \"id\": \"06743256-2e8e-4ede-a431-f17866fae1f6\",\n      \"slug\": \"competition-starter\",\n      \"name\": \"Starter — Gleam-style competition giveaway (X, Instagram, TikTok, Facebook entries)\",\n      \"skin\": \"basic-new\",\n      \"entry_method_count\": 6\n    },\n    {\n      \"id\": \"c0cc71a0-1ff5-46ab-8a23-c801aec30337\",\n      \"slug\": \"instagram-engagement\",\n      \"name\": \"Instagram engagement — follow & share photo giveaway\",\n      \"skin\": \"showcase\",\n      \"entry_method_count\": 3\n    },\n    {\n      \"id\": \"6ca0bdbc-23d8-4c79-a894-857eea485fbe\",\n      \"slug\": \"secret-codes\",\n      \"name\": \"Secret Code — unlock entries with a code (QR codes, receipts, printed inserts, events)\",\n      \"skin\": \"basic-new\",\n      \"entry_method_count\": 0\n    },\n    {\n      \"id\": \"88fde228-8baf-4b31-9b0e-cc243b3cc83d\",\n      \"slug\": \"steam-promotion\",\n      \"name\": \"A futuristic Steam template for Adding to Steam Wishlists and Playing Steam Games.\",\n      \"skin\": \"future\",\n      \"entry_method_count\": 6\n    }\n  ],\n  \"rate_limit\": { \"limit\": 60, \"remaining\": 59, \"reset\": 1753000000 }\n}\n```\n\nRows come back sorted by `slug`, unpaginated. The other ten at the time of writing: `discord-community`, `facebook-promotion`, `family-friends`, `prelaunch-vips`, `product-hunt`, `survey-system`, `tiktok-growth`, `twitch-growth`, `x-followers`, `youtube-contest`.\n\n`skin` is the page skin (`basic-new`/`showcase`/`future`) — the same vocabulary `pages:update --template` writes. `entry_method_count` is how many entry actions the template ships with. For agents: **list templates first, then clone by slug** — don't guess a slug. A `0` there is not always a stub — two templates legitimately report `0` because their action lives on the page rather than in `entry_methods`: `secret-codes` (the single action *is* the secret code) and `survey-system` (a mandatory survey plus a photo upload). Cloning `secret-codes` gives you the code input switched on but **no codes** — codes are stored per page and are never copied, so the owner adds their own in the dashboard.\n\nWhat the skins look like — use this to match the user's reference point:\n\n- `basic-new` (\"Basic\") — the classic giveaway card: entry actions in a clean vertical list, soft pastel styling. This is the format popularized by Gleam — when a user asks for a Gleam-style (or KickoffLabs-style) campaign, this is the closest match. The platform default.\n- `showcase` — a dynamic two-column layout with warm colors and platform-styled buttons. Product-forward — the natural fit for a Product Hunt-style launch page.\n- `future` — a dark, immersive game-style skin for bold, high-impact pages (gaming and tech audiences).\n\n**`actions:catalog`** — every entry-action type Tokei supports: label, description, default points, platform, whether it's trust-based/verifiable, and — for the 25 types writable as an `entry_methods` row — the exact `config` fields `pages:update` accepts for that type. This is the **authoritative** per-type reference; nothing below restates it. Same list for every key (not scoped to your account, no pagination). Flags: `--type <actionType>` (a value matching no action type is `400`).\n\n```sh\ntokei-agent actions:catalog --type twitter_follow\n```\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"label\": \"Follow on X\",\n    \"defaultPoints\": 3,\n    \"platform\": \"twitter\",\n    \"trustBased\": false,\n    \"isEntryMethodRow\": true,\n    \"fields\": [\n      { \"key\": \"username\", \"type\": \"string\", \"required\": true,\n        \"note\": \"X/Twitter handle. Checked for presence only — no format validation.\" }\n    ],\n    \"needs\": \"Requires participant X/Twitter OAuth to be configured on this deployment...\"\n  }\n}\n```\n\nA field's `group` marks alternatives — write the FIRST field of the group; the rest are claim-validator aliases that satisfy validation but render a dead button on their own (each carries a `note` saying so). `isEntryMethodRow: false` means the type is not writable as an `entry_methods` row at all (it's enabled via a contest setting or a dedicated route instead) — `needs` says which.\n\n**`events:catalog`** — every webhook event Tokei's delivery engine understands: description, `payloadSchema` (the exact shape of the `data` field a subscriber receives), `emitSites` and whether it's `subscribable`. This is the **authoritative** payload reference for `webhooks:create` — read it before assuming a field exists in a delivery. Same list for every key (not scoped to your account, no pagination). Flags: `--type <eventName>` (a value matching no event type is `400`). All 5 events are `subscribable: true` this stage — every one has a real emit site (see Webhooks below).\n\n```sh\ntokei-agent events:catalog --type winner.selected\n```\n\n## Commands (write — need a read+write key)\n\n**`pages:clone`** — create a page by cloning one you own (`--source <promotionId>`), a named platform template by slug (`--template <slug>` — get slugs from `templates:list`), or omit both to clone the platform starter template. `--template` and `--source` are **alternatives, not combinable** — sending both is `422`; a `--template` slug matching no template is `404`. Template, theme, and entry methods copy verbatim from the source — keep one polished master page per shape and clone it. Capped at **20 API-created pages per account per UTC day** (429 with `Retry-After`). Flags: `--title` (required), `--source`, `--template`, `--description`, `--prize`, `--end-date <iso>`, `--campaign-url`, `--image-url`, `--status draft|active`, `--idempotency-key`, `--data`.\n\n```sh\ntokei-agent pages:clone --title \"Spring Launch Waitlist\" \\\n  --source 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 \\\n  --prize \"Lifetime license\" --end-date 2026-09-01T00:00:00Z \\\n  --idempotency-key spring-launch-2026\n```\n\nCloning from a named template instead — list first, then clone by slug:\n\n```sh\ntokei-agent templates:list\ntokei-agent pages:clone --title \"Spring Launch Waitlist\" --template product-hunt \\\n  --prize \"Lifetime license\" --end-date 2026-09-01T00:00:00Z\n```\n\n`--status active` makes the page live immediately at the returned `public_url`; the default is `draft`. Reuse an `--idempotency-key` and you get `409` with the existing page's id in `error.details` instead of a duplicate.\n\n**`media:upload <file>`** — upload an image or video and get back a `public_url` to feed into `pages:update`'s media flags (below). Two HTTP calls under the hood, both handled for you: (1) request a short-lived signed upload ticket from Tokei; (2) `PUT` the file bytes straight to a Supabase Storage host with **no `Authorization` header** — the signing token lives in that URL's own query string. Nothing is stored until step 2 succeeds, so a step-1 failure never burns anything. Content type is inferred from the file extension (`.jpg`/`.jpeg`/`.png`/`.gif`/`.webp`/`.mp4`/`.webm`/`.mov`); override it with `--content-type <type>` for an extensionless or misnamed file. `application/pdf` is not accepted — only image and video. Prints `{public_url, path, content_type, filename, size_bytes}` under `data`.\n\n```sh\ntokei-agent media:upload ./hero.png\ntokei-agent pages:update 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 \\\n  --image-video https://xyz.supabase.co/storage/v1/object/public/tokei-public/api-uploads/u1/<uuid>.png\n```\n\nThings that surprise agents:\n\n- **The 5MB bucket cap applies to the signed-upload bucket itself, not to image vs. video specifically — so video must be ≤5MB too.** This is thin for video; a real clip is likely to exceed it. There is no larger-upload path through this API today.\n- **The 50/day cap counts tickets issued (step 1), not completed uploads.** A step-1 call that never gets PUT still burns quota — don't loop calling `media:upload` speculatively.\n- **A file over 5MB fails at step 2 with a `413` from Supabase Storage, not from Tokei.** The CLI surfaces this as `{\"ok\": false, \"error\": {\"type\": \"upload_failed\", \"stage\": \"storage_put\", \"status\": 413, \"message\": \"...\"}}` — see the error-semantics note below; it is a different shape from the usual `error.code` envelope because it isn't a Tokei API response.\n- **The stored object name is always a server-generated UUID.** The `filename` you send (or that the CLI infers from your local path) is validated and echoed back in the response, but never used to name anything — so reusing a filename never collides or overwrites.\n\n**`pages:update <contestId>`** — PATCH a page. Simple fields via flags: `--title`, `--description`, `--start-date <iso>`, `--end-date <iso>`, `--template basic-new|showcase|future|simple`, `--custom-css <css>`, `--dark-mode true|false`, `--primary-color \"#7d78c6\"`, `--card-width narrow|medium|wide`, and the seven media flags below. Prizes, reward tiers, nulls, or a full body via `--data`. At least one field required; unknown fields are rejected (422). `prizes` (max 20) and `reward_thresholds` (max 50) each **replace the existing list wholesale** — read the current lists with `pages:get` first, modify, and send the complete list back (Rule 3). A future `start_date` pauses new entries until then; setting `end_date` recomputes `days_left` — but a page whose winners have already been drawn rejects any `end_date` write, **setting or clearing**, with `409 WINNERS_ALREADY_SELECTED`. Clearing the date via `--data '{\"end_date\": null}'` also requires the page to be unpublished: an **active** page must keep a deadline (otherwise it renders as live forever while every signup is rejected), so clearing one returns `422` on field `end_date` — send `{\"status\": \"draft\", \"end_date\": null}` together, or a replacement date. `--title` sets the visible page headline (and the dashboard name), and `--description` accepts basic rich-text HTML which is sanitized on write, so unsupported tags come back stripped.\n\nAppearance flags: `--template` is the page skin (`basic-new`, `showcase` or `future`), stored verbatim as `settings.template` — see the skin guide under `templates:list` for what each looks like and when to pick it. `--dark-mode` is a plain creator-side toggle (column `dark_mode_enabled`) — there is no visitor `prefers-color-scheme` behavior, so this is the only thing that controls it. `--primary-color` must be a hex colour only (3, 4, 6 or 8 digits, e.g. `\"#7d78c6\"`) — other CSS colour formats (`rgb()`, named colours) are rejected because the value is interpolated into the page's server-rendered `<style>` tag; to reset it to the template default, send `--data '{\"primary_color\": null}'` (there is no flag spelling for null). `--card-width` accepts the friendly names `narrow`/`medium`/`wide`/`xl` **or** the raw stored Tailwind class directly (`max-w-2xl`/`max-w-3xl`/`max-w-4xl`/`max-w-7xl`); friendly names map `narrow`→`max-w-2xl`, `medium`→`max-w-3xl`, `wide`→`max-w-4xl`, `xl`→`max-w-7xl`, and **reads always return the stored class, never the friendly name** — PATCH `--card-width wide` and the next `pages:get` returns `\"max-w-4xl\"`.\n\n**`template: \"simple\"` is the Custom template** (launched 2026-08-10) — bare structural markup the creator styles via `custom_css`, applied on both the hosted page and the widget embed via `--tokei-*` custom properties and `.tokei-simple-*` class hooks. `--custom-css` is a string, max 20KB, server-sanitised on write — `@import`, `<` anywhere, and URLs outside `data:`/Cloudinary/tokei.io are rejected with a `422 VALIDATION_ERROR` naming the reason, so read-back may differ from what was sent; send `--data '{\"custom_css\": null}'` to clear it (there is no flag spelling for null). `pages:get` returns the stored `custom_css` for any page that has one.\n\nMedia flags: `--image-video <url>` (the hero — an image **or** a video), `--secondary-image <url>`, `--third-image <url>`, `--fourth-image <url>`, `--fifth-image <url>` (the additional layout block slots), `--background-image <url>` (interpolated into the page's CSS), `--og-image <url>` (social-share preview). All seven accept only an `https` URL on the app's own Supabase public storage or `res.cloudinary.com` — exactly what `media:upload`'s `public_url` produces, so the normal flow is `media:upload` then `pages:update` with the URL it printed (Rule 2). There is no flag spelling for clearing a media field to `null`; use `--data '{\"og_image\": null}'`.\n\n```sh\ntokei-agent pages:update 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 \\\n  --title \"Now with 3 prize tiers\" \\\n  --data '{\"prizes\":[{\"name\":\"AirPods Pro\",\"winners\":1,\"value\":249,\"currency\":\"USD\"},{\"name\":\"Sticker pack\",\"winners\":50}]}'\n```\n\n```sh\ntokei-agent pages:update 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 \\\n  --template showcase --dark-mode true --primary-color \"#7d78c6\"\n```\n\n**`entry_methods`** — reachable only via `--data` (max 30 rows, ~64KB body cap on this route only, raised from the shared 10KB to fit it). Five things to know:\n\n1. **Full-array replace, same as Rule 3 — always `pages:get` → modify → `pages:update`.** There is no per-row patch: `[]` clears every entry method, and omitting the field leaves the stored array untouched. Two row shapes: an **action row** `{actionType, label, points?, config?, requireVerification?}` — `actionType` must be one of the 25 writable types from `actions:catalog` — or a **link row** with no `actionType`: `{label, points?, link, actionsRequired?}` — a plain http(s) button to any URL, for anything the catalog has no action for; `actionsRequired` (0-20, link rows only) hides the row until the entrant has completed that many other actions. Unknown keys (`icon`, `config.type`, the nine legacy `<platform>Config` duplicates) are stripped, not stored, so echoing back a row you just read is always safe. `icon`/`config.type` are always server-derived from `actionType`; whatever you send for them is ignored.\n2. **`points` may not render as sent.** `0` means unset — the renderer falls back to the type's default. Any type with an `entryValueSettingKey` (see `actions:catalog`) has its displayed points overridden by `settings.<type>_entry_value` when the owner has configured one, and Product Hunt (`producthunt_follow`, `producthunt_vote`) and all three Steam types go further — their points are **hard-substituted with the platform default at render**, unconditionally. Also: **`pages:get` already returns the EFFECTIVE points** (overrides applied), not the raw stored value, so an untouched `pages:get` → `pages:update` round-trip silently persists that effective number into storage in place of whatever was originally configured.\n3. **Some types need a deployment prerequisite — warn the human before adding one.** All 5 `twitter_*` types and `linkedin_share`/`linkedin_post` need participant OAuth configured on this deployment; `discord_join` needs Discord OAuth and has no working verification at all, so its points are awarded on trust regardless; all 3 `steam_*` types need Steam login. `actions:catalog`'s `needs` field says this per type — `null` means no prerequisite. Facebook, Instagram, TikTok, Twitch, Product Hunt, `linkedin_follow`, `linkedin_company_follow` and link rows have none — safe to add without asking.\n4. Draft/publish semantics are unchanged by any of this. Unknown keys are stripped on the way in, never stored.\n5. **Link-row rules:** a label that doesn't start with `\"Visit Our \"` renders as a non-clickable generic row; the clickable, credited path also requires the page to have a `campaign_url` configured.\n\n```sh\ntokei-agent pages:get \"$PAGE\" | jq '.data.entry_methods'   # always read first (Rule 3)\n\n# Trust-based (no prerequisite) + conditional (needs X/Twitter OAuth — warn the human\n# first) + a link row, sent together as the COMPLETE replacement array\ntokei-agent pages:update \"$PAGE\" --data '{\"entry_methods\":[\n  {\"actionType\":\"tiktok_follow\",\"label\":\"Follow us on TikTok\",\"points\":3,\"config\":{\"username\":\"tokei\"}},\n  {\"actionType\":\"twitter_follow\",\"label\":\"Follow on X\",\"points\":3,\"config\":{\"username\":\"tokei\"}},\n  {\"label\":\"Visit Our Store\",\"points\":1,\"link\":\"https://example.com/store\"}\n]}'\n```\n\n**`pages:publish <contestId>`** — sugar over `pages:update` that PATCHes `{\"status\": \"active\"}`. Requires an `end_date` in the future — either already stored on the page, or sent in the same call via `--data`. If neither is true, this returns `422 VALIDATION_ERROR` with `details: [{\"field\": \"status\", \"message\": \"Publishing requires an end_date in the future...\"}]`. Re-publishing an already-active page is a no-op and skips that check. Still accepts `--data` for any other field, merged under the fixed `status` (a `--data` `status` value would override it, but there's no reason to send one here).\n\n```sh\ntokei-agent pages:publish 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90\n```\n\nThe one-call ergonomic path, when the page has no future `end_date` yet — set it and publish together:\n\n```sh\ntokei-agent pages:publish 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 \\\n  --data '{\"end_date\":\"2026-09-01T00:00:00Z\"}'\n```\n\n**`pages:unpublish <contestId>`** — sugar over `pages:update` that PATCHes `{\"status\": \"draft\"}`. Safe for pages with entrants: entries and entrants are left untouched, and it only blocks new signups. **This is the single most important thing to tell your user: a draft page still renders publicly at its URL — unpublishing hides nothing, it only stops new entries.** If they want the page actually gone from public view, unpublishing is not that; there is no way to destroy or hide a page through this API (the `status` field only ever accepts `draft`/`active` — `deleted` is rejected).\n\n```sh\ntokei-agent pages:unpublish 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90\n```\n\n**`entries:create <contestId>`** — add a signup. Flags: `--email` (required), `--name`, `--action-type` (default `api_import`), `--points`, `--value`; `metadata` via `--data`.\n\n```sh\ntokei-agent entries:create 4e7a1c0e-8b2d-4f6a-9c3e-2d5b8a7f1e90 \\\n  --email fan@example.com --name \"Ada Lovelace\" --points 10 --value \"Order #12345\"\n```\n\nA duplicate email for the same page returns `409` — the person is already signed up; usually safe to treat as success.\n\n**`webhooks:create`** — subscribe an HTTPS endpoint. Flags: `--url` (required), `--events <e1,e2>` (required — one or more of the 5 subscribable events: `entry.created`, `contest.ended`, `winner.selected`, `daily_bonus.claimed`, `referral.converted`; run `events:catalog` for what each payload contains and where it fires from), `--data`. Capped at **10 active subscriptions per account** — the eleventh returns `400 BAD_REQUEST` telling you to delete one first. Get explicit human approval before creating a live webhook (Rule 4) — it starts firing on real events immediately.\n\n```sh\ntokei-agent webhooks:create --url https://yourserver.com/webhooks/tokei --events entry.created,winner.selected\n```\n\nThe response contains the `whsec_` signing secret **exactly once** — it cannot be retrieved again (the CLI also prints a stderr warning). Store it immediately; deliveries are HMAC-SHA256 signed in the `X-TOKEI-Signature` header, expect a 2xx within 10s, and retry with backoff (5s, 30s, 5min).\n\n> **Creator (per-contest) webhooks are separate from this API.** A page owner can also subscribe a webhook from the Tokei dashboard, per contest — those are created via a session-auth route (`src/app/api/promotion/[contestId]/webhooks/route.ts`), not this CLI/API, and are curated to a fixed trio (`entry.created`, `winner.selected`, `contest.ended`) with no event-picker UI. `daily_bonus.claimed` and `referral.converted` stay developer-API-only, reachable only via `webhooks:create` above.\n\n**`webhooks:delete <webhookId>`** — remove a subscription.\n\n```sh\ntokei-agent webhooks:delete 9f1b2a3c-4d5e-6f70-8192-a3b4c5d6e7f8\n```\n\n## MCP server\n\n`tokei-agent mcp` runs a local MCP server over stdio (newline-delimited JSON-RPC) exposing every command above as an MCP tool — no extra install, zero dependencies. Tool names swap `:` for `_` (`pages:list` → `pages_list`); inputs use the API's wire field names directly (`contest_id`, `per_page`, `prizes`, …) instead of flags, so nested bodies need no `--data`. Results carry the same envelope (`rate_limit` included) as text content, with `isError` set on API failures — the error semantics table below applies unchanged. Register it with an MCP client, e.g. Claude Code:\n\n```sh\nclaude mcp add tokei --env TOKEI_API_KEY=tokei_k_... -- npx -y tokei-agent mcp\n```\n\n## `--data` semantics\n\nWrite commands accept `--data '<json>'` or `--data @file.json` for the raw request body (must be a JSON object). Individual field flags are merged **on top of** `--data` — **flags win** on conflict. The merged body is sent to the API untouched: the CLI does no schema validation, so the API's `422` response with per-field `error.details` **is** the validation. Fields only reachable via `--data`: `prizes`, `reward_thresholds`, and nulls (e.g. `{\"end_date\": null}` clears the end date — only on a page that is not active) on `pages:update`; `metadata` and `marketing_consent` on `entries:create` (`marketing_consent: true` records the participant's consent and is what enables syncing them to the connected email provider — send it only when consent was genuinely collected).\n\n## Error semantics — what to DO per status\n\n| Status | `error.code`          | Meaning                                                                 | Agent action                                                                                     |\n| ------ | --------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |\n| 400    | `BAD_REQUEST`         | Invalid query parameters, or the 10-active-webhooks cap.                | Fix the request. Do not retry as-is.                                                             |\n| 401    | `UNAUTHORIZED`        | Missing, invalid, revoked, or **expired** key.                          | Stop and ask the human for a fresh key. Never retry.                                             |\n| 403    | `FORBIDDEN`           | Key is valid but: trial plan (no API access), or the key **lacks the write scope** for a write command. | Do not retry. Ask the human for a read+write key (or a plan upgrade). Not an ownership error.    |\n| 404    | `NOT_FOUND`           | Resource doesn't exist **or is not owned by this key's account** — ownership failures are masked as 404. `pages:clone --template <slug>` also 404s when the slug matches no template (`error.details` has `field: \"template\"`). | Verify the id via `pages:list` / `webhooks:list`, or the slug via `templates:list`. Do not retry. For `webhooks:delete`, a 404 usually means already deleted — safe to treat as success. |\n| 409    | `CONFLICT`            | `entries:create`: email already entered. `pages:clone`: idempotency key already used (existing page's id is in `error.details`). | Safe to treat as success in both cases — the desired state already exists. Do not retry.          |\n| 409    | `WINNERS_ALREADY_SELECTED` | `pages:update`: `end_date` was set **or cleared** on a page whose winners have already been drawn. | Do not retry. Tell the human the draw is final — they must clear the winner selection in the dashboard first. Other fields still patch fine; just omit `--end-date`. |\n| 413    | `PAYLOAD_TOO_LARGE`   | Body over the 10KB limit (`pages:update` alone is raised to 64KB, to fit a full `entry_methods` array). | Shrink the body. Do not retry as-is.                                                             |\n| 422    | `VALIDATION_ERROR`    | Body failed validation; `error.details` is an array of `{field, message}`. | Fix exactly the listed fields, then retry once.                                                  |\n| 429    | `RATE_LIMIT_EXCEEDED` | Per-minute limit, the 20/day clone cap, or the 50/day media-ticket cap. | Wait `Retry-After` seconds (in the response headers), then retry with exponential backoff. On the two daily caps, trust `Retry-After` — `rate_limit` in the envelope reports the per-minute window, which will still look healthy. |\n| 5xx    | `INTERNAL_ERROR`      | Server fault.                                                           | Retry with exponential backoff, max 2–3 attempts, then report to the human.                      |\n| (none) | `network_error`       | Request never reached the API (DNS, timeout, refused).                  | Retry with backoff a couple of times, then report.                                               |\n\n`media:upload` step 1 (the ticket request) uses the table above as normal — an oversize declared `size_bytes` or a rejected `content_type` comes back as the usual `422 VALIDATION_ERROR`, and the 50/day ticket cap as the usual `429 RATE_LIMIT_EXCEEDED`. Step 2 (the `PUT` of the actual bytes) is **not a Tokei API call** — it hits Supabase Storage directly, so a failure there is a different, `error.code`-less shape: `{\"ok\": false, \"error\": {\"type\": \"upload_failed\", \"stage\": \"storage_put\", \"status\": <n>, \"message\": \"...\"}}`. In practice `status` is `413` — the real file exceeded the 5MB bucket cap despite an honest `size_bytes` declaration (or the declaration undersold it). Do not retry the same ticket; shrink the file and run `media:upload` again from scratch.\n\n## Self-throttling\n\nEvery successful and failed API response includes `rate_limit` in the envelope (from the `X-RateLimit-*` headers). Limits are per account: Subscriber 60 read / 30 write per minute, Lifetime 120 / 60. When `rate_limit.remaining` is low, slow down; when it's 0, sleep until `rate_limit.reset` (Unix epoch seconds) before the next call. Don't burn the budget discovering a 429 — read the envelope you already have.\n\n## Common gotchas\n\n1. **`TOKEI_API_KEY` not set** — exit code `2`, JSON on stderr, nothing sent to the API. Export the key first.\n2. **Trial plan gets `403` on everything** — API access needs an active subscription or a lifetime plan. Run `me` first (Rule 1) so this surfaces once, not on every command.\n3. **Read-only key gets `403` on writes, and nothing tells you in advance** — `me` doesn't report scope. If the task changes anything, confirm the key is read+write with the human before starting.\n4. **Media must go through `media:upload` first (Rule 2)** — raw filenames and external URLs are rejected by the host allowlist on all seven media fields. No exceptions, not even for a quick test.\n5. **5MB applies to video too**, and the 50/day media cap **counts tickets issued, not uploads** — never call `media:upload` speculatively in a loop.\n6. **`prizes` and `reward_thresholds` replace the whole list (Rule 3)** — `pages:get` first or you will silently delete the other entries.\n7. **A draft page still renders publicly at its URL** — `pages:unpublish` blocks new signups, it does not hide anything. Say this out loud to the user; they will assume otherwise.\n8. **Publishing needs a future `end_date`** — otherwise `422` on field `status`. Set it in the same call: `pages:publish <id> --data '{\"end_date\":\"...\"}'`.\n9. **Clearing `end_date` needs `status: draft` in the same body**, and is refused outright with `409 WINNERS_ALREADY_SELECTED` once winners are drawn.\n10. **Appearance values don't always read back as written** — `--card-width wide` reads back as `max-w-4xl`. That's correct, not a failed write.\n11. **`--primary-color` is hex only** — `rgb()` and named colours are rejected, because the value is interpolated into a server-rendered `<style>` tag.\n12. **`404` can mean \"not yours\"** — ownership failures are masked as not-found. Confirm the id with `pages:list` before assuming the page is gone.\n13. **`--status` takes `draft|active|completed|deleted`** — `ended` and `paused` are not real values; older builds accepted them and always returned an empty list.\n14. **`--data` is not validated locally** — it is sent untouched and the API's `422` with `error.details` is the validation. Read `.error.details[].field`.\n15. **Template slugs change** — always `templates:list` first; never hardcode a slug.\n\n## Quick reference\n\n```sh\n# Setup — required before anything else\nexport TOKEI_API_KEY=tokei_k_...      # read-only unless you need to write\ntokei-agent me                        # verify key + plan FIRST (Rule 1)\n\n# Read (any key)\ntokei-agent pages:list --status active --per-page 20\ntokei-agent pages:get <contestId>\ntokei-agent stats <contestId>\ntokei-agent leaderboard <contestId> --per-page 10\ntokei-agent referrals:top <contestId> --per-page 10\ntokei-agent entries:list <contestId> --email fan@example.com\ntokei-agent surveys:list <contestId> --page 2\ntokei-agent winners:list <contestId>\ntokei-agent webhooks:list\ntokei-agent templates:list\ntokei-agent actions:catalog\ntokei-agent events:catalog\n\n# Create (write key)\ntokei-agent pages:clone --title \"T\" --template <slug>          # 20/day cap\ntokei-agent pages:clone --title \"T\" --source <promotionId>     # or clone your own\ntokei-agent media:upload ./hero.png                            # ≤5MB, 50 tickets/day\n\n# Update (write key) — read back with pages:get to confirm\ntokei-agent pages:update <id> --title \"T\" --description \"<p>D</p>\"\ntokei-agent pages:update <id> --start-date <iso> --end-date <iso>\ntokei-agent pages:update <id> --template showcase --dark-mode true \\\n  --primary-color \"#7d78c6\" --card-width wide\ntokei-agent pages:update <id> --image-video <public_url> --og-image <public_url>\ntokei-agent pages:update <id> --data '{\"prizes\":[...]}'        # replaces the list\ntokei-agent pages:update <id> --data '{\"og_image\": null}'      # nulls need --data\ntokei-agent pages:update <id> --data '{\"entry_methods\":[...]}' # replaces the list, max 30, 64KB cap\n\n# Publish state (write key)\ntokei-agent pages:publish <id> --data '{\"end_date\":\"2026-09-01T00:00:00Z\"}'\ntokei-agent pages:unpublish <id>                               # still renders publicly!\n\n# Signups + webhooks (write key)\ntokei-agent entries:create <id> --email a@b.com --name \"Ada\" --points 10\ntokei-agent webhooks:create --url https://you.com/hook --events entry.created  # secret shown ONCE\ntokei-agent webhooks:delete <webhookId>\n\n# Other\ntokei-agent mcp                       # MCP stdio server (all 21 commands as tools)\ntokei-agent --help                    # full flag reference\ntokei-agent --version\n```\n\nFile v0.3.5:README.md\n\n# tokei-agent\n\nControl your [Tokei](https://tokei.io) pre-launch and waitlist campaigns from the command line — and from AI agents like Claude Code and OpenClaw. Wraps the Tokei v1 REST API with JSON-only output, zero runtime dependencies.\n\n## Install\n\n```sh\nnpm install -g tokei-agent\n# or run without installing:\nnpx tokei-agent --help\n```\n\nRequires Node 22+.\n\n### Claude Code\n\nInstall it as a plugin and the `tokei-agent` skill comes with it — Claude then\nknows the whole command surface without being told:\n\n```\n/plugin marketplace add gilesdawe/tokei-agent\n/plugin install tokei-agent@tokei\n```\n\nSet `TOKEI_API_KEY` (see [Quick start](#quick-start)) and you're done. The CLI\nitself still runs via `npx`, so there is nothing else to install.\n\n## Quick start\n\n1. Create an API key at [tokei.io](https://tokei.io) → Dashboard → Settings → API Keys. Pick **read-only** unless you need to change things; API access requires an active subscription or lifetime plan.\n\n2. Export it — the syntax differs by shell:\n\n```sh\nexport TOKEI_API_KEY=tokei_k_...          # bash / zsh\n```\n\n```fish\nset -x TOKEI_API_KEY tokei_k_...          # fish\n```\n\n```powershell\n$env:TOKEI_API_KEY = \"tokei_k_...\"        # PowerShell\n```\n\n3. Try it:\n\n```sh\ntokei-agent me                       # verify the key, see plan + API usage\ntokei-agent pages:list --status active\ntokei-agent stats <contestId>        # analytics for one page\n```\n\nEvery command prints JSON to stdout with a top-level `rate_limit` object. Exit codes: `0` success, `1` API/network error, `2` usage error (JSON on stderr).\n\n> **Interactive output (0.3.1+).** When stdout is an interactive terminal, the CLI renders a banner and a human-readable summary instead of raw JSON. When stdout is a pipe, a redirect, CI, or the `mcp` transport, it prints exactly the JSON it always has — so agents, scripts and MCP clients are unaffected. Set `TOKEI_OUTPUT=json` to force JSON at a terminal too; `NO_COLOR` disables colour and animation, and `TOKEI_NO_ANIM=1` keeps the colour but stops the movement.\n\n> **Known issue — exit codes on Node 24 / Windows (fixed in 0.3.0).** On 0.2.2 and earlier the CLI could print its correct JSON output and then abort during process exit, corrupting the exit code (`$LASTEXITCODE` read `-1073740791` / `0xC0000409` on success and failure alike). On an affected version, judge a run by the JSON on stdout, not by the exit status — or upgrade. (Historical labelling slip: the 0.3.0 tarball misreported `--version` as `0.2.2`; 0.3.1+ reports correctly.)\n\n## Commands\n\nRead (any key):\n\n| Command                    | Does                                                        |\n| -------------------------- | ----------------------------------------------------------- |\n| `me`                       | Verify the key; account, plan, API usage                    |\n| `pages:list`               | List pages — `--status`, `--mode`, `--page`, `--per-page`   |\n| `pages:get <contestId>`    | One page in full (prizes, reward tiers, public URL)         |\n| `stats <contestId>`        | Aggregated analytics                                        |\n| `leaderboard <contestId>`  | Participants ranked by points                               |\n| `referrals:top <contestId>`| Top referrers ranked by conversions, plus referral totals   |\n| `winners:list <contestId>` | Selection-run history, newest first, with each run's winners nested |\n| `entries:list <contestId>` | Signups — filter with `--email`                             |\n| `surveys:list <contestId>` | Survey responses                                            |\n| `webhooks:list`            | List webhook subscriptions                                  |\n| `templates:list`           | The platform's named starting points, for `pages:clone --template` |\n| `actions:catalog`          | Every entry-action type Tokei supports — `--type <actionType>`     |\n| `events:catalog`           | Every webhook event Tokei's delivery engine understands — `--type <eventName>` |\n\nWrite (needs a read+write key):\n\n| Command                       | Does                                                                     |\n| ----------------------------- | ------------------------------------------------------------------------ |\n| `pages:clone`                 | Create a page by cloning one you own, a named template, or the starter. 20/day cap |\n| `media:upload <file>`         | Upload an image or video, get back a `public_url` for `pages:update`. ≤5MB per file (video too) |\n| `pages:update <contestId>`    | Update title, description, dates, prizes, reward tiers, appearance (incl. the Custom template: `--template simple` / `--custom-css`), media, and entry actions (`entry_methods`, incl. custom-link rows) |\n| `pages:publish <contestId>`   | Take a page live (needs a future `end_date`)                             |\n| `pages:unpublish <contestId>` | Back to draft — blocks new signups, but the page still renders publicly  |\n| `entries:create <contestId>`  | Add a signup                                                             |\n| `webhooks:create`             | Subscribe an HTTPS endpoint (`whsec_` secret shown once — save it!)      |\n| `webhooks:delete <webhookId>` | Remove a subscription                                                    |\n\nWrite commands take simple fields as flags and full/nested bodies via `--data '<json>'` or `--data @file.json` (flags win on conflict). Run `tokei-agent --help` for every flag, or see [SKILL.md](./SKILL.md) — the agent-oriented reference bundled in this package, with worked examples and error-handling guidance.\n\n```sh\ntokei-agent pages:clone --title \"Spring Launch Waitlist\" --source <promotionId>\ntokei-agent pages:update <contestId> --end-date 2026-09-01T00:00:00Z\ntokei-agent media:upload ./hero.png\ntokei-agent pages:update <contestId> --image-video <public_url from the upload above>\ntokei-agent actions:catalog --type twitter_follow                  # see what a type accepts\ntokei-agent pages:update <contestId> \\\n  --data '{\"entry_methods\":[{\"actionType\":\"tiktok_follow\",\"label\":\"Follow us on TikTok\",\"points\":3,\"config\":{\"username\":\"tokei\"}}]}'\n```\n\n## MCP server\n\nThe package doubles as a local MCP server (stdio): every command above becomes an MCP tool (`pages_list`, `pages_update`, `stats`, …) for Claude Code, Claude Desktop, and other MCP clients.\n\n```sh\nclaude mcp add tokei --env TOKEI_API_KEY=tokei_k_... -- npx -y tokei-agent mcp\n```\n\nOr in JSON config:\n\n```json\n{\n  \"mcpServers\": {\n    \"tokei\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"tokei-agent\", \"mcp\"],\n      \"env\": { \"TOKEI_API_KEY\": \"tokei_k_...\" }\n    }\n  }\n}\n```\n\n## Environment\n\n| Variable        | Required | Meaning                                          |\n| --------------- | -------- | ------------------------------------------------ |\n| `TOKEI_API_KEY` | Yes      | Sent as `Authorization: Bearer <key>`            |\n| `TOKEI_API_URL` | No       | Base URL override (default `https://tokei.io`)   |\n\n## Agents, human in the loop\n\nTokei is built so agents draft and humans approve: give monitoring agents a read-only key, reserve read+write keys for agents that genuinely need to change things, and set key expiry. New webhook subscriptions created via the API trigger a security notification to the account owner.\n\n## Privacy Policy\n\nFull policy: https://tokei.io/privacy\n\n### Data collection practices\n\n`tokei-agent` is a thin client for the Tokei v1 REST API. It collects no data of\nits own: there is no telemetry, no analytics, no crash reporting, and no\nphone-home of any kind.\n\nIt reads exactly two inputs from your environment — `TOKEI_API_KEY` and the\noptional `TOKEI_API_URL` — plus the arguments you pass on the command line (or\nthe arguments an MCP client passes to a tool call). For `media:upload` it also\nreads the bytes of the local file you name.\n\n### Usage and storage\n\nYour API key is used solely as an `Authorization: Bearer` header on requests to\nyour Tokei account. Command arguments become the request path, query string, or\nJSON body. Nothing is written to disk: the CLI creates no config file, cache,\ncredential store, or log file, and holds nothing after the process exits.\n\nAPI responses — which can include entrant email addresses, survey answers, and\nanalytics — are printed as JSON to stdout. From that point they are handled by\nwhatever invoked the CLI (your shell, your scripts, or your AI agent and its\nconversation history). Treat that output as the personal data it is.\n\n### Third-party sharing\n\nNothing is sold or shared with third parties. Data is transmitted only to:\n\n- **Tokei** (`https://tokei.io`, or the host you set in `TOKEI_API_URL`) — every\n  command, to serve your request against your own account.\n- **Tokei's object storage provider** — `media:upload` only. The API returns a\n  short-lived signed upload URL and the CLI PUTs your file bytes straight to it;\n  the file becomes a public asset on your promotion page.\n\nThe CLI contacts no other host.\n\n### Data retention\n\nThe CLI retains nothing. Data held in your Tokei account — promotions, entries,\nsurvey responses — is retained under the Tokei privacy policy above, and you can\nrequest access or deletion there. API keys are created, scoped, expired, and\nrevoked by you at Dashboard → Settings → API Keys; revoking a key immediately\nstops all access through it.\n\n### Contact information\n\nTokei — https://tokei.io/privacy — support@tokei.io\n\n## Docs\n\n- API reference: https://tokei.io/docs/api\n- OpenAPI spec: https://tokei.io/openapi.json\n- Agent skill reference: [SKILL.md](./SKILL.md)\n\n## License\n\nMIT\n\nFile v0.3.5:_meta.json\n\n{\n  \"ownerId\": \"kn71v6w9gadrqg1bvex24zx4ex8aw3qd\",\n  \"slug\": \"tokei-agent\",\n  \"version\": \"0.3.5\",\n  \"publishedAt\": 1787882636756\n}\n\nFile v0.3.5:CHANGELOG.md\n\n# Changelog\n\nAll notable changes to `tokei-agent`. Dates are the npm publish times.\n\nThis file was seeded on 2026-08-02, after 0.3.2 shipped, by reconstructing the\nhistory from `npm view tokei-agent time` and the commits that touched `cli/`.\nEntries before 0.3.3 are therefore summaries written after the fact; entries\nfrom 0.3.3 on are written as part of the release.\n\nThe format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).\nThis package is pre-1.0: minor versions may carry breaking changes, though none\nhas so far.\n\n## [Unreleased]\n\n## [0.3.5] — 2026-08-28\n\n### Fixed\n\n- **MCP tool calls no longer forward undeclared arguments.** `callTool` built the\n  request body as `{ ...fixedBody, ...args }` and validated only that *required*\n  arguments were present, so any extra field a caller invented rode through to the\n  API. Because `PATCH /api/v1/contests/{id}` replaces `prizes` wholesale, a call of\n  `pages_publish { contest_id, prizes: [] }` **wiped the page's prize list** — under\n  a tool annotated `destructiveHint: false`. Arguments are now allowlisted against\n  the tool's own `inputSchema.properties` before the merge; undeclared fields are\n  dropped, and the tool result now names the ones it ignored rather than\n  discarding them in silence. Declared arguments still win over `fixedBody`.\n\n  One documented field was caught by this and is declared as part of the same\n  release: `entries_create` accepted `marketing_consent`\n  (`docs/using-custom-apis.md`) without advertising it, so the allowlist would\n  have dropped it. See Added below.\n- **`entries_create` was silently discarding `marketing_consent`.** The v1 route\n  accepts it and the field gates whether an entrant is ever synced to the\n  creator's connected email provider, so an import of consented entrants would\n  have returned 201 on every row and synced none of them.\n\n### Added\n\n- `entries_create` declares `marketing_consent`. Set it to `true` only when the\n  participant genuinely gave marketing consent elsewhere; it records the consent\n  and its timestamp, and is what enables the email-provider sync.\n\n### Changed\n\n- `pages_update`'s description now states that `status` is not settable there and\n  points at `pages_publish` / `pages_unpublish`. `status` stays undeclared on\n  purpose — `pages_publish` enforces a future-`end_date` check that a raw\n  `status: \"active\"` would bypass.\n- `webhooks_delete` now declares `idempotentHint: true`, and its description states\n  that the subscription's queued and historical `webhook_deliveries` rows are\n  deleted with it (`ON DELETE CASCADE`), so past delivery attempts are not\n  retrievable afterwards.\n- The MCP server instructions and the `pages_unpublish` description are reworded\n  from instructing the agent (\"slow down\", \"Tell your user this…\") to describing\n  what the API does. No behavioural change.\n\n## [0.3.4] — 2026-08-21\n\n### Added\n\n- `pages:update --template simple` — the Custom template: bare structural\n  markup the creator styles entirely via `--custom-css`, rather than a fixed\n  layout.\n- `pages:update --custom-css <css>` — creator CSS for the Custom template,\n  applied on both the hosted page and the widget embed via `--tokei-*` custom\n  properties and `.tokei-simple-*` class hooks. Max 20 KB; server-sanitised,\n  with unsafe constructs rejected by a 422 naming the reason.\n- `--card-width` gains `xl` and `max-w-7xl`, in both the CLI flag and the\n  `pages_update` MCP tool's schema.\n- `entry_methods` — the page's action buttons — is now settable through\n  `pages:update --data` and the `pages_update` MCP tool, alongside `prizes`\n  and `reward_thresholds`. Replaces the whole list wholesale: an action row\n  (`actionType`, `label`, `points?`, `config?`, `requireVerification?`) or a\n  custom-link row (`label`, `points?`, `link`, `actionsRequired?`) with no\n  `actionType`.\n\n## [0.3.3] — 2026-08-03\n\n### Added\n\n- `events:catalog` (+ `--type`) and the `events_catalog` MCP tool — every\n  webhook event Tokei can send, with its payload schema, description and emit\n  sites, fetched from the API rather than bundled so the CLI can never drift\n  from the platform. **21 commands, 21 MCP tools.**\n- `winners:list <contestId>` and the `winners_list` MCP tool — read-only\n  selection-run history with the winners of each run. Unpaginated, capped at the\n  100 most recent runs. Selecting winners stays a human action in the dashboard;\n  the CLI can only read the result.\n- **All five webhook events are now subscribable**, not just `entry.created`:\n  `contest.ended`, `winner.selected`, `daily_bonus.claimed` and\n  `referral.converted` now fire on the live platform and are accepted by\n  `webhooks:create --events`. Previously they were catalogued but rejected with\n  a 422.\n- **Installable as a Claude Code plugin** — a fifth distribution channel\n  alongside npm, skills.sh, ClawHub and the MCP registry:\n\n  ```\n  /plugin marketplace add gilesdawe/tokei-agent\n  /plugin install tokei-agent@tokei\n  ```\n\n  The skill then loads automatically as `tokei-agent:tokei-agent`, with no\n  `npx skills add` step. Adds `.claude-plugin/plugin.json` and\n  `.claude-plugin/marketplace.json` to the repo. Not included in the npm\n  tarball — plugin users install from GitHub.\n- The ANSI wordmark now renders above `--help` at an interactive terminal.\n  Previously `--help` returned before the terminal UI was created, so the first\n  command most people run was undecorated. Suppressed — byte for byte — for\n  pipes, redirects, CI, `TERM=dumb`, `TOKEI_OUTPUT=json` and terminals narrower\n  than 60 columns, so parsed output is unchanged.\n- `SKILL.md` gains a fourth hard rule: get explicit human approval before any\n  action that emails people or changes anything public, with the read-only\n  commands listed as exempt.\n\n### Fixed\n\n- The `webhooks_create` MCP tool advertised `entry.created` as the only valid\n  value for `events`, in both its JSON-schema `enum` and its description. MCP\n  clients read that schema as the contract, so an agent had no way to subscribe\n  to the four events this release activates. The CLI's own `webhooks:create` was\n  never affected.\n\n### Changed\n\n- This changelog now ships in the tarball. It was added 27 minutes after 0.3.2\n  published, so 0.3.3 is the first release to carry it.\n- `SKILL.md`'s `templates:list` sample is refreshed against the live 14-template\n  menu and marked as an excerpt. It was always illustrative — call the endpoint,\n  never hardcode a slug.\n\n## [0.3.2] — 2026-08-02\n\n### Added\n\n- `referrals:top <contestId>` and the `referrals_top` MCP tool — top referrers\n  ranked by conversions, plus click/conversion totals. Lists only entrants who\n  have actually referred someone. **19 commands, 19 MCP tools.**\n\n### Fixed\n\n- The interactive-output note in `README.md` / `SKILL.md` said \"0.3.2+\"; the\n  feature shipped\n\nArchive v0.3.4: 23 files, 87216 bytes\n\nFiles: bin/tokei-agent.mjs (3225b), CHANGELOG.md (7648b), LICENSE (1073b), package.json (821b), README.md (9637b), server.json (1116b), skill-card.md (2600b), SKILL.md (48733b), src/__tests__/args.test.ts (1720b), src/__tests__/commands.test.ts (36931b), src/__tests__/http.test.ts (6498b), src/__tests__/mcp.test.ts (32831b), src/__tests__/media.test.ts (9693b), src/__tests__/ui.test.ts (17797b), src/__tests__/version.test.ts (1827b), src/args.ts (1352b), src/http.ts (4824b), src/index.ts (32595b), src/mcp.ts (32507b), src/media.ts (6013b), src/ui.ts (19384b), tsconfig.json (259b), _meta.json (130b)\n\nArchive v0.3.3: 23 files, 84843 bytes\n\nFiles: bin/tokei-agent.mjs (3225b), CHANGELOG.md (6587b), LICENSE (1073b), package.json (821b), README.md (9547b), server.json (1116b), skill-card.md (2662b), SKILL.md (47544b), src/__tests__/args.test.ts (1720b), src/__tests__/commands.test.ts (36931b), src/__tests__/http.test.ts (6498b), src/__tests__/mcp.test.ts (31055b), src/__tests__/media.test.ts (9693b), src/__tests__/ui.test.ts (17797b), src/__tests__/version.test.ts (1827b), src/args.ts (1352b), src/http.ts (4824b), src/index.ts (31240b), src/mcp.ts (30828b), src/media.ts (6013b), src/ui.ts (19384b), tsconfig.json (259b), _meta.json (130b)\n\nArchive v0.3.2: 22 files, 78001 bytes\n\nFiles: bin/tokei-agent.mjs (3225b), LICENSE (1073b), package.json (801b), README.md (8946b), server.json (1116b), skill-card.md (3236b), SKILL.md (43934b), src/__tests__/args.test.ts (1720b), src/__tests__/commands.test.ts (34121b), src/__tests__/http.test.ts (6498b), src/__tests__/mcp.test.ts (28615b), src/__tests__/media.test.ts (9693b), src/__tests__/ui.test.ts (15670b), src/__tests__/version.test.ts (918b), src/args.ts (1352b), src/http.ts (4824b), src/index.ts (29779b), src/mcp.ts (28842b), src/media.ts (6013b), src/ui.ts (17632b), tsconfig.json (259b), _meta.json (130b)\n\nArchive v0.3.1: 22 files, 76789 bytes\n\nFiles: bin/tokei-agent.mjs (3225b), LICENSE (1073b), package.json (801b), README.md (8918b), server.json (1116b), skill-card.md (2405b), SKILL.md (42575b), src/__tests__/args.test.ts (1720b), src/__tests__/commands.test.ts (33801b), src/__tests__/http.test.ts (6498b), src/__tests__/mcp.test.ts (28050b), src/__tests__/media.test.ts (9693b), src/__tests__/ui.test.ts (15670b), src/__tests__/version.test.ts (918b), src/args.ts (1352b), src/http.ts (4824b), src/index.ts (29240b), src/mcp.ts (28172b), src/media.ts (6013b), src/ui.ts (17632b), tsconfig.json (259b), _meta.json (130b)\n\nArchive v0.3.0: 20 files, 56240 bytes\n\nFiles: bin/tokei-agent.mjs (2106b), LICENSE (1073b), package.json (801b), README.md (5723b), server.json (1116b), skill-card.md (2594b), SKILL.md (34591b), src/__tests__/args.test.ts (1720b), src/__tests__/commands.test.ts (30354b), src/__tests__/http.test.ts (6498b), src/__tests__/mcp.test.ts (26087b), src/__tests__/media.test.ts (9693b), src/__tests__/version.test.ts (918b), src/args.ts (1352b), src/http.ts (4500b), src/index.ts (22610b), src/mcp.ts (25402b), src/media.ts (6013b), tsconfig.json (259b), _meta.json (130b)\n\nArchive v0.2.2: 18 files, 41478 bytes\n\nFiles: bin/tokei-agent.mjs (821b), LICENSE (1073b), package.json (801b), README.md (4882b), server.json (1116b), skill-card.md (2816b), SKILL.md (20130b), src/__tests__/args.test.ts (1720b), src/__tests__/commands.test.ts (28191b), src/__tests__/http.test.ts (6498b), src/__tests__/mcp.test.ts (21693b), src/__tests__/version.test.ts (918b), src/args.ts (1352b), src/http.ts (3359b), src/index.ts (18964b), src/mcp.ts (20655b), tsconfig.json (259b), _meta.json (130b)\n\nArchive v0.2.1: 17 files, 40060 bytes\n\nFiles: bin/tokei-agent.mjs (821b), LICENSE (1073b), package.json (801b), README.md (4581b), server.json (1116b), skill-card.md (2698b), SKILL.md (18931b), src/__tests__/args.test.ts (1720b), src/__tests__/commands.test.ts (28191b), src/__tests__/http.test.ts (6498b), src/__tests__/mcp.test.ts (21693b), src/args.ts (1352b), src/http.ts (3359b), src/index.ts (18964b), src/mcp.ts (20655b), tsconfig.json (259b), _meta.json (130b)\n\nArchive v0.2.0: 16 files, 33471 bytes\n\nFiles: bin/tokei-agent.mjs (821b), LICENSE (1073b), package.json (801b), README.md (4581b), skill-card.md (2797b), SKILL.md (11998b), src/__tests__/args.test.ts (1720b), src/__tests__/commands.test.ts (19836b), src/__tests__/http.test.ts (6498b), src/__tests__/mcp.test.ts (16069b), src/args.ts (1352b), src/http.ts (3359b), src/index.ts (16066b), src/mcp.ts (17209b), tsconfig.json (259b), _meta.json (130b)","readmeExcerpt":"Skill: tokei-agent Owner: gilesdawe Summary: Tokei (tokei.io) is a pre-launch, waitlist and giveaway platform — launch pages, competition giveaways, sweepstakes, referral and viral-loop campaigns, Product Hunt launch drives, Gleam-style and KickoffLabs-style entry pages. This CLI controls them from the command line — list, clone, update, publish and unpublish pages, upload images and video, set prizes, reward tiers a","codeSnippets":[],"executableExamples":[{"language":"sh","snippet":"npm install -g tokei-agent\n# or run it without installing:\nnpx tokei-agent --help"},{"language":"sh","snippet":"HERO=$(tokei-agent media:upload ./hero.png | jq -r '.data.public_url')\ntokei-agent pages:update \"$PAGE_ID\" --image-video \"$HERO\""},{"language":"sh","snippet":"# 1. Verify — do this first, always\ntokei-agent me\n\n# 2. Discover\ntokei-agent pages:list --status active\ntokei-agent templates:list\n\n# 3. Create (returns a draft page)\nPAGE=$(tokei-agent pages:clone --title \"Spring Launch Waitlist\" \\\n  --template product-hunt | jq -r '.data.id')\n\n# 4. Prepare media (Rule 2 — never pass a raw path)\nHERO=$(tokei-agent media:upload ./hero.png | jq -r '.data.public_url')\n\n# 5. Shape\ntokei-agent pages:update \"$PAGE\" \\\n  --description \"Join the list for early access.\" \\\n  --template showcase --dark-mode true --primary-color \"#7d78c6\" \\\n  --image-video \"$HERO\"\n\n# 6. Publish (end_date must be in the future — set it in the same call if unset)\ntokei-agent pages:publish \"$PAGE\" --data '{\"end_date\":\"2026-09-01T00:00:00Z\"}'\n\n# 7. Monitor\ntokei-agent stats \"$PAGE\"\ntokei-agent leaderboard \"$PAGE\" --per-page 10\ntokei-agent referrals:top \"$PAGE\" --per-page 10\ntokei-agent entries:list \"$PAGE\"\n\n# 8. Automate — events:catalog lists all 5 subscribable events and their payloads\ntokei-agent webhooks:create --url https://yourserver.com/webhooks/tokei \\\n  --events entry.created,winner.selected"},{"language":"jsonc","snippet":"// single-object reads (pages:get, me, media:upload, pages:clone, …)\n{ \"success\": true, \"data\": { … }, \"rate_limit\": { … } }\n\n// list reads (pages:list, leaderboard, referrals:top, entries:list,\n//             surveys:list, templates:list, webhooks:list)\n// referrals:top adds a sibling \"totals\" object next to data + pagination\n{ \"success\": true, \"data\": [ … ],\n  \"pagination\": { \"page\": 1, \"per_page\": 20, \"total_pages\": 3, \"total_count\": 47 },\n  \"rate_limit\": { … } }\n\n// errors\n{ \"success\": false,\n  \"error\": { \"code\": \"VALIDATION_ERROR\", \"message\": \"…\", \"status\": 422,\n             \"details\": [{ \"field\": \"end_date\", \"message\": \"…\" }] },\n  \"rate_limit\": { … } }"},{"language":"sh","snippet":"tokei-agent pages:list | jq -r '.data[0].id'                          # first page's id\ntokei-agent pages:list | jq -r '.data[] | select(.status==\"active\") | .id'\ntokei-agent pages:list --per-page 100 | jq '.pagination.total_pages'  # more to fetch?\ntokei-agent pages:get \"$PAGE\" | jq -r '.data.public_url'\ntokei-agent media:upload ./hero.png | jq -r '.data.public_url'\ntokei-agent pages:update \"$PAGE\" --title x | jq -r '.error.details[]?.field'"},{"language":"sh","snippet":"tokei-agent me"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: tokei-agent\ndescription: Tokei (tokei.io) is a pre-launch, waitlist and giveaway platform — launch pages, competition giveaways, sweepstakes, referral and viral-loop campaigns, Product Hunt launch drives, Gleam-style and KickoffLabs-style entry pages. This CLI controls them from the command line — list, clone, update, publish and unpublish pages, upload images and video, set prizes, reward tiers and deadlines, restyle pages, and read stats, leaderboards, top referrers, signups, survey responses, winner selections and the webhook event catalog, plus manage webhooks (all 5 events) — all via the Tokei v1 REST API.\nhomepage: https://tokei.io/agent\nmetadata: {\"openclaw\":{\"emoji\":\"⏱️\",\"requires\":{\"bins\":[],\"env\":[\"TOKEI_API_KEY\"]}}}\n---\n\n# tokei-agent\n\n`tokei-agent` is a zero-dependency CLI for the Tokei v1 REST API (`https://tokei.io/api/v1`). Every command prints JSON to stdout, so pipe it to `jq` or parse it directly.\n\n## Install tokei-agent if it isn't already there\n\n```sh\nnpm install -g tokei-agent\n# or run it without installing:\nnpx tokei-agent --help\n```\n\nRequires Node 22+. npm release: https://www.npmjs.com/package/tokei-agent — official website: https://tokei.io — agent docs: https://tokei.io/agent — API reference: https://tokei.io/docs/api\n\n---\n\n## ⚠️ Four hard rules (read first)\n\n**Rule 1 — Run `tokei-agent me` before anything else.** It proves the key is live and reports the account's **plan**. API access requires an active subscription or lifetime plan — **trial accounts get `403` on every command**, so a whole workflow can fail on its first call for a reason no other command explains.\n\n> `me` does **not** report the key's scope. There is no way to read a key's scope from the API — you discover a read-only key by getting `403 FORBIDDEN` on your first write. If the task involves changing anything, ask the human up front whether their key is read+write.\n\n**Rule 2 — Every media URL you write to a page MUST come from `tokei-agent media:upload`.** Raw filesystem paths (`hero.png`) and third-party URLs (`https://example.com/hero.png`) are **rejected** — the seven media fields on `pages:update` are guarded by a closed host allowlist that only accepts the app's own storage (and `res.cloudinary.com`). Always:\n\n```sh\nHERO=$(tokei-agent media:upload ./hero.png | jq -r '.data.public_url')\ntokei-agent pages:update \"$PAGE_ID\" --image-video \"$HERO\"\n```\n\nEvery `--image-video` / `--og-image` / `--background-image` example below assumes a `public_url` obtained this way — never a local file.\n\n**Rule 3 — List fields replace wholesale; always read before you write.** `prizes` (max 20) and `reward_thresholds` (max 50) are **not** merged — whatever array you send becomes the entire list, so sending one prize deletes the other nineteen. The pattern is always `pages:get` → modify the array → `pages:update` with the complete list.\n\n**Rule 4 — Get explicit human approval before any action that emails people or changes anything public.** Publishing a page, "},{"path":"README.md","content":"# tokei-agent\n\nControl your [Tokei](https://tokei.io) pre-launch and waitlist campaigns from the command line — and from AI agents like Claude Code and OpenClaw. Wraps the Tokei v1 REST API with JSON-only output, zero runtime dependencies.\n\n## Install\n\n```sh\nnpm install -g tokei-agent\n# or run without installing:\nnpx tokei-agent --help\n```\n\nRequires Node 22+.\n\n### Claude Code\n\nInstall it as a plugin and the `tokei-agent` skill comes with it — Claude then\nknows the whole command surface without being told:\n\n```\n/plugin marketplace add gilesdawe/tokei-agent\n/plugin install tokei-agent@tokei\n```\n\nSet `TOKEI_API_KEY` (see [Quick start](#quick-start)) and you're done. The CLI\nitself still runs via `npx`, so there is nothing else to install.\n\n## Quick start\n\n1. Create an API key at [tokei.io](https://tokei.io) → Dashboard → Settings → API Keys. Pick **read-only** unless you need to change things; API access requires an active subscription or lifetime plan.\n\n2. Export it — the syntax differs by shell:\n\n```sh\nexport TOKEI_API_KEY=tokei_k_...          # bash / zsh\n```\n\n```fish\nset -x TOKEI_API_KEY tokei_k_...          # fish\n```\n\n```powershell\n$env:TOKEI_API_KEY = \"tokei_k_...\"        # PowerShell\n```\n\n3. Try it:\n\n```sh\ntokei-agent me                       # verify the key, see plan + API usage\ntokei-agent pages:list --status active\ntokei-agent stats <contestId>        # analytics for one page\n```\n\nEvery command prints JSON to stdout with a top-level `rate_limit` object. Exit codes: `0` success, `1` API/network error, `2` usage error (JSON on stderr).\n\n> **Interactive output (0.3.1+).** When stdout is an interactive terminal, the CLI renders a banner and a human-readable summary instead of raw JSON. When stdout is a pipe, a redirect, CI, or the `mcp` transport, it prints exactly the JSON it always has — so agents, scripts and MCP clients are unaffected. Set `TOKEI_OUTPUT=json` to force JSON at a terminal too; `NO_COLOR` disables colour and animation, and `TOKEI_NO_ANIM=1` keeps the colour but stops the movement.\n\n> **Known issue — exit codes on Node 24 / Windows (fixed in 0.3.0).** On 0.2.2 and earlier the CLI could print its correct JSON output and then abort during process exit, corrupting the exit code (`$LASTEXITCODE` read `-1073740791` / `0xC0000409` on success and failure alike). On an affected version, judge a run by the JSON on stdout, not by the exit status — or upgrade. (Historical labelling slip: the 0.3.0 tarball misreported `--version` as `0.2.2`; 0.3.1+ reports correctly.)\n\n## Commands\n\nRead (any key):\n\n| Command                    | Does                                                        |\n| -------------------------- | ----------------------------------------------------------- |\n| `me`                       | Verify the key; account, plan, API usage                    |\n| `pages:list`               | List pages — `--status`, `--mode`, `--page`, `--per-page`   |\n| `pages:get <contestId>`    | One page in full (prizes, reward tiers, pub"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn71v6w9gadrqg1bvex24zx4ex8aw3qd\",\n  \"slug\": \"tokei-agent\",\n  \"version\": \"0.3.6\",\n  \"publishedAt\": 1788552983598\n}"},{"path":"CHANGELOG.md","content":"# Changelog\n\nAll notable changes to `tokei-agent`. Dates are the npm publish times.\n\nThis file was seeded on 2026-08-02, after 0.3.2 shipped, by reconstructing the\nhistory from `npm view tokei-agent time` and the commits that touched `cli/`.\nEntries before 0.3.3 are therefore summaries written after the fact; entries\nfrom 0.3.3 on are written as part of the release.\n\nThe format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).\nThis package is pre-1.0: minor versions may carry breaking changes, though none\nhas so far.\n\n## [Unreleased]\n\n## [0.3.5] — 2026-08-28\n\n### Fixed\n\n- **MCP tool calls no longer forward undeclared arguments.** `callTool` built the\n  request body as `{ ...fixedBody, ...args }` and validated only that *required*\n  arguments were present, so any extra field a caller invented rode through to the\n  API. Because `PATCH /api/v1/contests/{id}` replaces `prizes` wholesale, a call of\n  `pages_publish { contest_id, prizes: [] }` **wiped the page's prize list** — under\n  a tool annotated `destructiveHint: false`. Arguments are now allowlisted against\n  the tool's own `inputSchema.properties` before the merge; undeclared fields are\n  dropped, and the tool result now names the ones it ignored rather than\n  discarding them in silence. Declared arguments still win over `fixedBody`.\n\n  One documented field was caught by this and is declared as part of the same\n  release: `entries_create` accepted `marketing_consent`\n  (`docs/using-custom-apis.md`) without advertising it, so the allowlist would\n  have dropped it. See Added below.\n- **`entries_create` was silently discarding `marketing_consent`.** The v1 route\n  accepts it and the field gates whether an entrant is ever synced to the\n  creator's connected email provider, so an import of consented entrants would\n  have returned 201 on every row and synced none of them.\n\n### Added\n\n- `entries_create` declares `marketing_consent`. Set it to `true` only when the\n  participant genuinely gave marketing consent elsewhere; it records the consent\n  and its timestamp, and is what enables the email-provider sync.\n\n### Changed\n\n- `pages_update`'s description now states that `status` is not settable there and\n  points at `pages_publish` / `pages_unpublish`. `status` stays undeclared on\n  purpose — `pages_publish` enforces a future-`end_date` check that a raw\n  `status: \"active\"` would bypass.\n- `webhooks_delete` now declares `idempotentHint: true`, and its description states\n  that the subscription's queued and historical `webhook_deliveries` rows are\n  deleted with it (`ON DELETE CASCADE`), so past delivery attempts are not\n  retrievable afterwards.\n- The MCP server instructions and the `pages_unpublish` description are reworded\n  from instructing the agent (\"slow down\", \"Tell your user this…\") to describing\n  what the API does. No behavioural change.\n\n## [0.3.4] — 2026-08-21\n\n### Added\n\n- `pages:update --template simple` — the Custom template: bare structural\n  markup the creator styles enti"},{"path":"skill-card.md","content":"## Description:\n\ntokei-agent lets agents and CLI users manage Tokei waitlist, launch, giveaway, referral, webhook, media, and analytics workflows through the Tokei v1 REST API.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[gilesdawe](https://clawhub.ai/user/gilesdawe)\n\n### License/Terms of Use:\n\nMIT\n\n## Use Case:\n\nDevelopers and campaign operators use this skill to let agents inspect and manage Tokei pre-launch, waitlist, giveaway, referral, media, analytics, and webhook workflows from CLI or MCP surfaces.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill can give an agent high-impact control over Tokei account resources, including publishing pages, creating webhooks, adding entries, and uploading media.\n\nMitigation: Use a pinned package version, prefer read-only and short-lived Tokei keys, and require explicit human approval before write actions, public changes, email-affecting actions, or unattended media_upload calls.\n\nRisk: Credentials and API destination choices can expand account exposure, especially when TOKEI_API_URL is overridden.\n\nMitigation: Keep TOKEI_API_KEY scoped to the task, rotate or expire keys after use, and avoid setting TOKEI_API_URL unless the destination is fully trusted.\n\nRisk: API responses and uploaded files can include personal or campaign-sensitive data that may be exposed through agent transcripts, logs, or public campaign assets.\n\nMitigation: Review command outputs before sharing, limit logging of entrant data and survey responses, and inspect local files before uploading them as campaign media.\n\n## Reference(s):\n\n- [Tokei Agent Docs](https://tokei.io/agent)\n- [Tokei API Reference](https://tokei.io/docs/api)\n- [Tokei OpenAPI Specification](https://tokei.io/openapi.json)\n- [tokei-agent npm Package](https://www.npmjs.com/package/tokei-agent)\n- [ClawHub Skill Page](https://clawhub.ai/gilesdawe/skills/tokei-agent)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Configuration, Guidance]\n\n**Output Format:** [Markdown guidance with shell commands; commands return JSON from the Tokei API.]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Requires TOKEI_API_KEY; write workflows may require a read+write key and explicit human approval.]\n\n## Skill Version(s):\n\n0.3.6 (source: release evidence, package.json, server.json)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":2409,"uniquenessScore":41,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T01:24:02.556Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T01:24:02.556Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T03:55:54.420Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}