.fyi
Early access

Install quibble with your coding agent

quibble ships a recipe your coding agent follows: it finds your framework, asks which tracker, mounts the endpoint behind your sign-in, and finishes with a test note. You choose whether it makes the changes, shows you a plan first, or only guides you.

Matches quibble 2.0.0. It never asks for your tracker token: you create that yourself.

Give it to your agent

  1. Download quibble-install.zip and unzip it in your app's repository.
  2. Claude Code: put the folder in .claude/skills/, then ask "add quibble to this app".
  3. Copilot, Codex or Cursor: put the folder in .agents/ and add the lines from AGENTS.snippet.md to your AGENTS.md.
  4. Or just point it here: ask your agent to follow quibble.fyi/agents/quibble-install/INSTALL.md.

The recipe your agent follows

You are adding quibble to the developer's web app: a feedback button that files each note as an issue in their own tracker. This file is the whole recipe. Follow it in order, and don't improvise code that a step gives you: the snippets here are the ones quibble supports.

quibble version these steps match: see VERSION next to this file.

0. Ask how hands-on to be — once, before anything else

Ask the developer to pick one, and repeat it back:

  1. Do it, ask at forks. You make the changes and only stop for real choices (tracker, repository, how users sign in).
  2. Plan, then apply. You read the app and show every change as a diff; you apply nothing until they approve.
  3. Guide me. You explain each step and check their work; you never edit a file.

Before every edit, say which mode you're in. If you're unsure whether something is a fork, treat it as one and ask.

Rules that hold in every mode

  • Never handle the tracker token. The developer creates it and puts it in place. You may write a placeholder line (QUIBBLE_GITHUB_TOKEN= with nothing after it) into .env.example, never a value into .env, a commit, a log or the chat.
  • Never weaken the app's authentication. quibble's endpoint creates issues, so it must sit behind the app's sign-in, like the rest of the app. If you can't tell whether it will, stop and ask.
  • Touch only the files a step names, and list every file you changed at the end.
  • Don't guess a framework. If detection is ambiguous, ask.

1. Find out what you're working with

Read, don't ask, where you can:

  • Framework: look at imports and the dependency file (pyproject.toml, requirements*.txt, Pipfile) for fastapi, django, flask, starlette, litestar. Python frameworks other than these, and non-Python apps, go to Any other stack.
  • Package manager: uv.lock → uv add; poetry.lock → poetry add; Pipfile → pipenv install; otherwise pip and the requirements file.
  • Where pages are rendered: the base template (Jinja base.html, a Django templates/base.html) or the function that returns HTML.
  • How users sign in: a reverse proxy in front (Cloudflare Access, oauth2-proxy, an SSO gateway), the app's own login, or nothing. Note which headers the proxy sets.

Tell the developer what you found in three or four lines before going on.

2. Choose the tracker and where notes go

Ask: which tracker, and which repository or project? Then tell them exactly which token to create (they create it; you don't):

Tracker QUIBBLE_TRACKER Target (QUIBBLE_GITHUB_REPO) Also set Token to create
GitHub (leave unset) owner/repo — Fine-grained PAT on that repo: Issues: Read and write. Add Contents: Read and write only if they want screenshots stored in the repo. Easiest: set QUIBBLE_GITHUB_REPO, run quibble doctor, and give the developer the link it prints: it opens GitHub's form already filled in, so they only tick the repos.
GitLab gitlab group/project QUIBBLE_TRACKER_API=https://<host>/api/v4 Personal or project access token with the api scope.
Gitea / Forgejo gitea owner/repo QUIBBLE_TRACKER_API=https://<host>/api/v1 Token with write:issue.
Jira Cloud jira project key, e.g. IN QUIBBLE_TRACKER_API=https://<site>.atlassian.net, QUIBBLE_ISSUE_TYPE=Task Atlassian API token; the value is [email protected]:API_TOKEN. Give that account Browse, Create issues, Transition issues, Create attachments in the project.
Linear linear team key, e.g. ENG — Restricted API key (lin_api_…) with Read + Write on that one team.

Once quibble is installed (step 3.1), quibble env --tracker <name> prints these lines ready for .env.example, with the token empty and its scope spelled out. Use that rather than writing them by hand.

In production, prefer a mounted secret file: QUIBBLE_GITHUB_TOKEN_FILE=/run/secrets/… instead of QUIBBLE_GITHUB_TOKEN. The variable is named GITHUB for history; it holds the token for whichever tracker is set.

Several repositories? Ask whether notes should go to one place or be routed:

  • A fixed list: QUIBBLE_REPOS=org/main,org/billing (the default stays QUIBBLE_GITHUB_REPO).
  • Routed by page: QUIBBLE_ROUTES=/admin/billing=org/billing,/svc=org/svc.
  • A repo the token can't reach gets its own credential: QUIBBLE_REPOS=org/main,other-org/repo@OTHER_TOKEN (the @ names a variable, never a secret).
  • On GitHub, every repo an org's token can reach: QUIBBLE_REPOS_ORG=org, narrowed with QUIBBLE_REPOS_INCLUDE / QUIBBLE_REPOS_EXCLUDE.

3. Install

  1. Add the dependency, with the framework's extra: quibble[fastapi], quibble[django], quibble[flask], quibble[starlette] or quibble[litestar]. Until quibble is on PyPI the developer's early-access instructions give the install line; use exactly that.

  2. Settings. Add the output of quibble env --tracker <name> (plus --repos org/a,org/b for further targets) to .env.example. Its secrets are empty; tell the developer to fill in .env (or their secret store) themselves.

  3. Mount the endpoint where the app is created. Print quibble's own code for the framework and paste it unchanged:

    quibble snippet --framework fastapi    # or django, flask, starlette, litestar
    quibble snippet --framework django --multi-repo   # several repositories
    

    It prints the imports, the mount and where they go. Don't rewrite it. On Django keep the re_path(r"^quibble/?", …) exactly: the widget posts to the bare /quibble, and a path("quibble/", …) mount rejects every note.

  4. Add the button. Put the output of render_widget() just before </body> in the base template, so every page has it. Django: quibble.render_widget("/quibble"). In Jinja, pass it in as a variable and render it with |safe; in Django, mark it safe. A single-page app (React, Vue, Svelte, Next.js) whose pages aren't rendered by the server: install @quibble-fyi/widget and call mount({ api: "/quibble" }) where the app's root component mounts. Call unmount() when it unmounts, or on routes that shouldn't show the widget. Don't add the script tag as well.

4. Sign-in and who filed it

  • Check the endpoint is behind sign-in. /quibble must be reachable only by people who can already use the app. If the app's routes are protected by a decorator or middleware that the new mount skips, say so and ask how to protect it.
  • Name the reporter. quibble reads the person from a header set by the proxy in front:
    • Cloudflare Access: nothing to set.
    • Anything else: QUIBBLE_IDENTITY_HEADERS=X-Auth-Request-Email,X-Forwarded-User (the headers that proxy actually sends, first match wins).
    • No proxy: offer QUIBBLE_ASK_EMAIL=on (and QUIBBLE_ASK_NAME=on). Tell the developer those notes are marked self-declared, and that anonymous submitters share one rate limit.

5. Check it works

  1. Run quibble doctor with the app's environment. Fix every ✗ before going on. quibble doctor --fix creates the labels quibble needs. Add --json to read the result without parsing the text.
  2. Start the app and run quibble doctor --url <the app's address>. It checks that quibble is mounted, that it refuses a caller who hasn't signed in, and that the button is on the page. If the developer gives you a signed-in header (a session cookie), add --header 'Cookie: …'; never ask for a password, and never print the header.
  3. File one test note: quibble send-test prints the new issue's link. (Or sign in, open any page and file "quibble install test — safe to close" from the button.)
  4. Confirm the issue appeared in the tracker, then close it (quibble send-test --close does both).
  5. Tell the developer how to switch quibble off without removing it: QUIBBLE_ENABLED=off mounts no routes at all.

If it doesn't work:

  • A note files as "anonymous": the identity header isn't reaching the app; recheck step 4 against the headers the proxy really sends.
  • CERTIFICATE_VERIFY_FAILED: this Python has no trusted certificates (common with python.org installers on macOS). Run its "Install Certificates" command, or set SSL_CERT_FILE to a CA bundle.
  • Django: every note is rejected (404): the mount isn't re_path(r"^quibble/?", …).

6. Hand over

Summarise in a short list: the mode used, the tracker and target, every file changed, the settings they still have to fill in, and the doctor result. Mention that their coding agent can read and act on quibbles through the tracker's own tools (see docs/ai-agents.md).

Any other stack

quibble's server side is Python. For an app in any other language, run it beside the app with quibble serve, but only on one machine: a desktop app, or local development. quibble serve listens on loopback only, and it isn't for a server on a network.

  1. Install quibble where the app runs (step 3.1, with no framework extra) and add the step 2 settings to .env.example, secrets empty, as before.
  2. Add QUIBBLE_ALLOWED_ORIGINS set to the exact origin of the page that shows the widget: the dev server's (http://localhost:5173) or the webview's (tauri://localhost). Never *.
  3. Have the app start quibble serve --port 8787. Generate a random QUIBBLE_SERVE_KEY for each launch, pass it to serve in its environment, and give it to the page as window.QUIBBLE_KEY. Without the key, any program on the machine can file notes.
  4. Load the widget in the page: <script src="http://127.0.0.1:8787/quibble.js"></script>.
  5. For a desktop app with one user, set QUIBBLE_REPORTER to that user. Otherwise offer QUIBBLE_ASK_EMAIL=on.
  6. Check it as in step 5: run quibble doctor, then file one test note.

For a server on a network in any other language, explain that quibble's server side needs a Python host or the backend contract, and stop. Don't write an unofficial adapter.