{"id":51395001,"url":"https://github.com/aidanht/promptly-daemon","last_synced_at":"2026-07-04T02:02:22.737Z","repository":{"id":368323103,"uuid":"1283379491","full_name":"AidanHT/promptly-daemon","owner":"AidanHT","description":"promptlyd + promptly — the local Rust telemetry daemon and CLI for the Promptly prompt-engineering arena. Auto-captures AI coding usage; submits device-signed ranked runs.","archived":false,"fork":false,"pushed_at":"2026-06-30T03:31:13.000Z","size":285,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-30T05:06:13.438Z","etag":null,"topics":["claude-code","cli","daemon","opentelemetry","prompt-engineering","rust","telemetry"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AidanHT.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-28T21:36:49.000Z","updated_at":"2026-06-30T03:31:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/AidanHT/promptly-daemon","commit_stats":null,"previous_names":["aidanht/promptly-daemon"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/AidanHT/promptly-daemon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AidanHT%2Fpromptly-daemon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AidanHT%2Fpromptly-daemon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AidanHT%2Fpromptly-daemon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AidanHT%2Fpromptly-daemon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AidanHT","download_url":"https://codeload.github.com/AidanHT/promptly-daemon/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AidanHT%2Fpromptly-daemon/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35107463,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude-code","cli","daemon","opentelemetry","prompt-engineering","rust","telemetry"],"created_at":"2026-07-04T02:02:21.951Z","updated_at":"2026-07-04T02:02:22.731Z","avatar_url":"https://github.com/AidanHT.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# promptly + promptlyd\n\n[![CI](https://github.com/AidanHT/promptly-daemon/actions/workflows/ci.yml/badge.svg)](https://github.com/AidanHT/promptly-daemon/actions/workflows/ci.yml)\n[![Security audit](https://github.com/AidanHT/promptly-daemon/actions/workflows/security-audit.yml/badge.svg)](https://github.com/AidanHT/promptly-daemon/actions/workflows/security-audit.yml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n\nThe local capture stack for **[Promptly](https://trypromptly.vercel.app)** — a\ncompetitive prompt-engineering arena where engineers solve hard coding challenges\nthrough an AI harness and are scored on prompt efficiency (tokens, turns, model\ncost, execution speed).\n\nThis repository ships two Rust binaries that run on the player's machine:\n\n- **`promptlyd`** — a local telemetry **daemon**. It auto-captures your AI coding\n  usage, normalizes every source into one schema, and serves it over a\n  **localhost-only** HTTP API.\n- **`promptly`** — the player's terminal **CLI**. It fetches a challenge\n  workspace, runs a scored capture session, tests locally, watches live token\n  burn, scores with parity to the server, pairs your device, and submits a run for\n  ranked grading.\n\nEverything is captured **locally**. The only thing that ever leaves your machine\nis a **redacted, device-signed** submission that you explicitly send.\n\n---\n\n## Install\n\n### 1. One-line install script (no Rust toolchain needed)\n\nDownloads the prebuilt `promptly` + `promptlyd` binaries for your platform from\nthe latest [GitHub release](https://github.com/AidanHT/promptly-daemon/releases)\nand drops them on your PATH.\n\n**macOS / Linux**\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/AidanHT/promptly-daemon/main/install.sh | sh\n```\n\n**Windows (PowerShell)**\n\n```powershell\nirm https://raw.githubusercontent.com/AidanHT/promptly-daemon/main/install.ps1 | iex\n```\n\nOverride the version or location with `PROMPTLY_VERSION` / `PROMPTLY_INSTALL_DIR`.\n\n### 2. With Cargo (the Rust package manager)\n\nIf you already have a [Rust toolchain](https://rustup.rs), install both binaries\nstraight from this repo — no release download, always builds from source:\n\n```sh\ncargo install --git https://github.com/AidanHT/promptly-daemon promptly promptlyd\n```\n\n(This is a Cargo **workspace** with two packages, so both names are listed\nexplicitly.)\n\n### 3. Prebuilt binaries (manual)\n\nGrab the archive for your platform from the\n[releases page](https://github.com/AidanHT/promptly-daemon/releases/latest),\nunpack it, and move `promptly` / `promptlyd` somewhere on your PATH. Prebuilt\ntargets:\n\n| Platform | Target |\n| --- | --- |\n| Linux (x86-64) | `x86_64-unknown-linux-gnu` |\n| macOS (Apple Silicon) | `aarch64-apple-darwin` |\n| macOS (Intel) | `x86_64-apple-darwin` |\n| Windows (x86-64) | `x86_64-pc-windows-msvc` |\n\n\u003e **Roadmap:** Homebrew tap, Scoop bucket, and a `crates.io` publish (so plain\n\u003e `cargo install promptly` works) are planned. Until then, options 1 and 2 above\n\u003e are the quickest paths.\n\n### Updating\n\nAlready installed? Upgrade both binaries in place:\n\n```sh\npromptly update          # fetch the latest release and swap promptly + promptlyd\npromptly update --check  # just report whether a newer version exists\n```\n\nIt resolves the latest release for your platform, stops the daemon if it's\nrunning, and replaces `promptly` + `promptlyd`. If you installed from source\ninstead, re-run `cargo install --git https://github.com/AidanHT/promptly-daemon promptly promptlyd --force`.\n\n`promptly` also checks GitHub for a newer release about once a day and prints a\none-line notice when one is available (interactive terminals only — never in\nscripts or CI; `promptly doctor` shows the same status). Set\n`PROMPTLY_NO_UPDATE_CHECK=1` to disable it.\n\n---\n\n## Quick start\n\n```sh\npromptly pair                 # one-time: link this device to your Promptly account\n\n# The fast path — fetch the level, launch the daemon, and begin capturing at once:\npromptly play \u003clevel-slug\u003e\ncd \u003clevel-slug\u003e\n#   ...solve the challenge with your AI harness (Claude Code, etc.)...\npromptly submit               # redact + package + device-signed ranked upload\n```\n\nPrefer it step by step? `promptly init \u003clevel\u003e`, `cd` in, then `promptly start` —\nthe background daemon launches automatically (no second terminal). `promptly watch`\nfollows live token burn, `promptly stop` ends the session, and `promptly up` /\n`promptly down` start and stop the daemon yourself if you'd rather manage it.\n\nPlaying on `localhost`? It just works — see [Local vs production](#local-vs-production).\n\nFrom a source checkout you can also run the daemon directly with the bundled\nlaunchers (`./run.sh` / `./run.ps1`), though you rarely need to — the CLI starts it\nfor you.\n\n---\n\n## What it captures\n\n1. **Claude Code — native OpenTelemetry.** An embedded OTLP/HTTP receiver ingests\n   Claude Code's `api_request` log events (model, token counts, `cost_usd`,\n   `duration_ms`, `prompt.id`). High-confidence (`otel`). It speaks **OTLP/HTTP\n   with JSON**, so the harness bootstrap sets\n   `OTEL_EXPORTER_OTLP_PROTOCOL=http/json` and points\n   `OTEL_EXPORTER_OTLP_ENDPOINT` at the daemon's loopback port.\n2. **Claude Code — JSONL session logs.** A watcher tails\n   `~/.claude/projects/\u003cencoded-cwd\u003e/*.jsonl`, parsing `assistant` usage and\n   thinking blocks. Fallback/supplement (`jsonl`), and the source of\n   thinking-token detail.\n3. **Best-effort adapters.** Cursor (`state.vscdb`, read-only + immutable), OpenAI\n   Codex CLI (`~/.codex/sessions` rollout JSONL), and GitHub Copilot Chat (VS Code\n   `chatSessions/*.json`). These are reverse-engineered and version-fragile, so\n   they degrade gracefully, mark inferred counts `estimated`, **never write to or\n   lock** the editor's files, and report their detection state on `/health` for\n   `promptly doctor`.\n\nWhen OTEL and JSONL observe the same turn they are **correlated, not just\nde-duplicated**: the normalized turn carries an `agreement` marker (a tampering\nsignal), and OTEL values are authoritative — JSONL never silently overrides them.\n\n### Verified-eligible captures\n\nThe ranked **verified** badge is reserved for the one capture path whose telemetry\nis authenticated, cross-checked, and tamper-evident end to end. Every other capture\nstill ranks — it simply carries no badge (`unverified`), never a penalty.\n\n| Capture | Local signal | Verified-eligible? |\n| --- | --- | --- |\n| Claude Code — native OTEL (consented, online) | `otel` | **Yes** — with a server-issued nonce + attested baseline |\n| Claude Code — JSONL logs only | `jsonl` | No — ranks `unverified` |\n| Cursor / Codex / Copilot adapters | `estimated` | No — reverse-engineered, ranks `unverified` |\n| Any harness started offline (local nonce) | — | No — ranks `unverified` |\n| Any capture with a tampering fingerprint | — | `suspect` (held for review) |\n\nOnly OTEL-backed Claude Code qualifies because it is the only source the daemon can\n(1) **authenticate** (a per-session ingest token the receiver requires), (2)\n**corroborate** (OTEL↔JSONL agreement), and (3) **bind** into a device-signed v3\nturn chain the server verifies. Adapters read another tool's logs after the fact\nwith no such guarantees, so they can't earn the badge — by design, not omission.\n`promptly submit` prints the projected tier before you confirm.\n\n## Session scoping\n\nCapture only counts toward a level **between an explicit start and stop**, and\nonly for the bound workspace — so unrelated AI usage never inflates an attempt. A\n`promptly start` binds the session to the workspace's `.promptly/manifest.json`\nlevel and, before capturing anything:\n\n- runs the **baseline integrity check** (resets a tampered workspace to the\n  canonical starter, after a backup),\n- issues the **attempt nonce** (the anti-replay guard; offline caps at\n  `unverified`, a server-issued nonce lifts the cap), and\n- with explicit consent, bootstraps the OTEL env into the **project**\n  `.claude/settings.json` (declining falls back to JSONL-only).\n\nA `stop` reopens to a `start` (resume) without re-checking the baseline, so an\nin-progress attempt is never reset out from under you.\n\n## Commands\n\n```\n# CLI (promptly) — global: --api-url URL | --api-port 8765 | --no-color\npromptly pair                 # device-authorization flow → 90-day device token\npromptly play \u003clevel\u003e         # fetch + launch the daemon + start capturing, in one step\npromptly init \u003clevel\u003e         # download the starter kit; start the solve clock\npromptly start | stop | reset # bound capture session (auto-starts the daemon)\npromptly restart [\u003clevel\u003e]    # discard this attempt; re-fetch the level fresh in place\npromptly up | down            # start / stop the background daemon explicitly\npromptly test                 # run public tests (local-first; remote fallback)\npromptly watch                # live per-turn token burn + projected score\npromptly score                # projected score, parity with the server\npromptly doctor               # diagnose daemon / OTEL / web app / manifest / runtime\npromptly submit               # redact + package + device-signed ranked upload\npromptly update               # upgrade promptly + promptlyd to the latest release\npromptly help                 # grouped overview of every command\n\n# Daemon (promptlyd) — auto-managed by the CLI; you rarely run it directly\npromptlyd run [--workspace DIR] [--api-port 8765] [--otlp-port 4318] [--web-origin ORIGIN]…\npromptlyd status [--api-port 8765]                 # connected / capturing / idle\npromptlyd install [--workspace DIR] …              # register as a background OS service\npromptlyd uninstall\n```\n\nThe CLI manages the daemon for you: `promptly start`, `watch`, and `play`\nauto-launch `promptlyd` in the background scoped to your level (and `promptly down`\nstops it), so you never need a second terminal. `promptlyd run` is still the\nforeground entrypoint a service manager invokes, and `promptlyd install` registers\nit as a systemd **user** service (Linux), a launchd **agent** (macOS), or a logon\n**scheduled task** (Windows) if you'd rather run it always-on.\n\n## Local HTTP API (loopback only)\n\nRead (consumed by the web HUD):\n\n- `GET /health` — status, version, uptime, OTLP endpoint, adapter detection,\n  recent errors.\n- `GET /session` — the active session binding (bound level, nonce, window), token\n  totals, captured turns, and provenance signals.\n- `GET /stream` — Server-Sent Events, one event per normalized turn.\n- `GET /session/preflight` — what a `start` would do, without side effects.\n\nControl (driven by the `promptly` CLI):\n\n- `POST /session/start` `{confirm_reset, consent_bootstrap}` — begin/resume.\n- `POST /session/stop` — end the session and revert the harness settings.\n- `POST /session/reset` — restore the workspace to the canonical starter.\n- `POST /shutdown` — stop the daemon gracefully (the `promptly down` / level-switch path).\n\nCORS only allows **GET** from loopback dev origins and the configured deployed\nPromptly origin(s); the mutating routes additionally require the CLI's\n`X-Promptly-Control` header. A public HTTPS Promptly page reaching `127.0.0.1`\nalso gets Chrome's Private Network Access preflight answered.\n\n## Local vs production\n\n**Local dev just works.** Loopback origins (`http://localhost:3000`,\n`http://127.0.0.1:3000`) are always allowed, and the CLI defaults to\n`http://localhost:3000`, so a local web app + `promptlyd run` + `promptly …` need\nno configuration.\n\n**Playing against the deployed app** needs two things, both wired by default:\n\n- **The web HUD** reads the daemon from the browser. The canonical production\n  origin (`https://trypromptly.vercel.app`) is allowed by the daemon's CORS **by\n  default** — no flag needed. A custom domain or preview deploy is added with\n  `PROMPTLY_WEB_ORIGIN` (comma/space-separated) or repeated `--web-origin`. It is\n  always an **exact** origin, never a wildcard.\n- **The CLI** (`pair`, `init`, `submit`, remote `test`) talks to the web app.\n  Point it at production with `PROMPTLY_API_URL=https://trypromptly.vercel.app`\n  (or `--api-url`). `promptly doctor` prints the resolved URL and whether it's\n  local-dev or production.\n\n## State\n\nAll under `~/.promptly/` (override the home dirs for testing with\n`PROMPTLY_DATA_DIR` / `PROMPTLY_CLAUDE_HOME`):\n\n- `session.json` — the session marker: bound level, workspace, attempt nonce,\n  `code_reset_count`, and the bootstrap state needed to revert.\n- `checkpoint.json` — crash-recovery checkpoint (turns, per-file JSONL offsets,\n  dedup set), keyed by session id. A restart resumes without losing or\n  double-counting turns. Machine-local; never synced.\n- `credentials.json` — the paired device token + Ed25519 signing seed, `0600`\n  (owner-only). 90-day expiry + one-command revocation bound the damage.\n- `cache/\u003clevel\u003e/v\u003cn\u003e/` — the pristine canonical starter, cached on a verified\n  start so a later tampered start can be reset offline.\n- `promptlyd.lock` / `session.lock` — single-instance and single-session guards.\n- `\u003cworkspace\u003e/.promptly/backup/\u003cts\u003e/` — your files, backed up before any reset.\n\n## Security\n\n- **Loopback only.** Both servers bind `127.0.0.1`; the daemon is never exposed\n  off-machine.\n- **Local capture, explicit upload.** Nothing leaves your machine until you run\n  `promptly submit`, and that payload is **redacted** (provider keys, bearer\n  tokens, PEM blocks, `secret=`-style assignments) before it is signed and sent.\n- **Read-only adapters.** The Cursor / Codex / Copilot adapters open editor state\n  read-only and immutable; they never write to or lock your editor's files.\n- **Device-signed runs.** A ranked submission is signed by a per-device Ed25519\n  key created at pairing; the credential file is owner-only and the token expires.\n\n### Anti-cheat: earning (and not faking) the verified badge\n\nThe scoring rewards efficiency (fewer tokens/turns), so the incentive is to\n*under-report* work or *fabricate* a clean capture. These layers make the verified\nbadge unfakeable rather than trusting the client:\n\n- **Verified is a server decision over signed evidence.** The badge requires a\n  v3 device-signed turn chain with a server-issued nonce, an attested kit baseline,\n  every turn OTEL/JSONL-sourced and non-estimated, at least one OTEL-backed turn,\n  and coherent timing. The server reads only what was *signed*, so an edited wire\n  field (e.g. a Copilot capture relabelled `otel`) can't earn it — it lands\n  `unverified`, and a broken/replayed chain lands `suspect`.\n- **Authenticated OTLP ingest.** The receiver mints a fresh per-session token,\n  writes it into the harness settings, and rejects any post that doesn't present it\n  — and *all* posts while idle or JSONL-only. No other loopback process can inject\n  fabricated `api_request` turns to inflate or forge a run.\n- **Attested kit baseline.** A fresh start verifies the local (player-editable)\n  `manifest.json` baseline against the server's authoritative hash and refuses a\n  stale or tampered kit, so a pre-solved workspace can't be anchored to a forged\n  starter. Offline starts are unattested and cap at `unverified`.\n- **Signed, tamper-evident provenance.** Each turn signs its confidence, source\n  set, and timestamp; the terminal entry signs a capture summary (nonce origin,\n  baseline attestation + reset count, bulk-paste count) and the OTEL↔JSONL\n  cross-source agreement. Implausible pacing (backwards timestamps, impossible\n  bursts) is flagged before upload and re-checked server-side.\n\n## Build from source\n\n```sh\ncargo run -p promptlyd -- run        # foreground daemon\ncargo run -p promptly  -- doctor     # CLI\ncargo test                           # unit + integration tests (both crates)\ncargo clippy --all-targets -- -D warnings\ncargo fmt\n```\n\nCross-platform release binaries are built and attached to a GitHub release on a\n`v*` tag (`.github/workflows/release.yml`). See [CONTRIBUTING.md](./CONTRIBUTING.md)\nfor the `vendor/` parity fixtures and how scoring stays in lockstep with the web\napp.\n\n## License\n\n[MIT](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faidanht%2Fpromptly-daemon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faidanht%2Fpromptly-daemon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faidanht%2Fpromptly-daemon/lists"}