Repository navigation
Add autter wrapped: a year of AI-assisted coding as a share card - #82
Merged
Merged
Conversation
`autter badge` prints the share of added lines written by AI over the last 90 days (or --since / --all-time) and a paste-ready Markdown snippet. The number is sum(ai_accepted) / sum(git_diff_added_lines) over commits with an authorship note, the same ratio each uploaded commit summary carries as ai_percent; commits without a note are counted so the output can report coverage. --json prints a shields.io endpoint payload (schemaVersion, label, message, color, plus the figures) and --write stores it, by default in .autter/badge.json, so a committed file can back the badge without an account. When the repository is linked to Autter and the user is signed in, the snippet uses the live badge behind POST /cli/repo-badge-link; otherwise it falls back to a static shields.io badge within 5s. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`autter wrapped [--year] [--author|--all-authors] [--json] [--include-prompt-text] [--no-color]` walks the year's commits, reads each authorship note and folds the per-commit stats into one summary: AI share overall and by month, top agent and model, the most-prompted file, the longest prompt, the longest streak and the busiest weekday. The terminal output is a deterministic 60-column card for screenshots; `--json` prints the `wrapped/1.0.0` contract the share-image endpoint will consume. Prompt text never leaves the machine: the longest prompt is measured from the local transcript cache only (new `resolve_cas_messages_local`), and its first 120 characters are included only with `--include-prompt-text`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The backend mounts CLI routes under /api/cli, like every other CLI endpoint, so the badge link request goes there too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`--share` posts the exact `--json` summary to `/api/cli/wrapped-share` with the CLI access token (org-scoped when the repository's org has one), prints the page link after the card and opens it at a terminal. `--hide-repo` keeps the repository name off the page; `--unshare` takes a page down by link or token. Nothing is sent in local mode or without a sign-in, and the prompt snippet is stripped from the body unless `--include-prompt-text` was given. Server refusals map to one line each. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The 3.0.0 release PR carries the Wrapped entry in its own section; changelog/wrapped.md and the README paragraph stay. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…wrapped Both branches register a command in the same places; the dispatch table and help registry now carry badge and wrapped. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
sagnik11
added a commit
that referenced
this pull request
Nov 3, 2026
Bumps the version to 3.0.0 in Cargo.toml, Cargo.lock and npm/package.json, and adds the 3.0.0 changelog section for `autter badge` (#83) and `autter wrapped` (#82), with the badge's feature doc. Merging tags v3.0.0 and the Release workflow publishes. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
autter wrapped [--year <yyyy>] [--author <email> | --all-authors] [--json] [--include-prompt-text] [--no-color] [--ignore <pat>]: a Spotify-Wrapped-style summary of the current user's AI-assisted coding in this repository, drawn as a screenshot-friendly 60-column terminal card, with--jsonfor the share-image pipeline.src/authorship/wrapped.rs): walks the year's non-merge commits viagit log(capped at 5,000, reported incapped), keeps the chosen author (defaultgit user.email), reads each note withread_authorship, computesCommitStatswithstats_for_commit_stats_with_authorshipundereffective_ignore_patterns, and folds them into aWrappedSummary.src/commands/wrapped.rs): flag parsing throughpre_parse, pretty JSON, and the card: block-digit headline percentage, a 12-bar monthly chart in eighth blocks, top agent/model, most-prompted file, longest prompt, streak, busiest day, totals and anautter.dev/wrappedfooter. Usesui::Headerabove the card andui::railfor the empty state (fewer than 3 noted commits). HonorsNO_COLOR/--no-colorkeyed on stdout; ASCII fallback for terminals without Unicode.cas_bridge::resolve_cas_messages_local(cache + sync queue, never the network;resolve_cas_messagesnow calls it first). Transcripts not on this machine are skipped.snippetisnullunless--include-prompt-text, and then holds only the first 120 characters.transcript_paths_by_prompt_key/first_user_prompts_by_pathinpost_commit.rsarepub(crate)as requested. Wrapped does not end up needing them: working logs are deleted after commit, so for committed prompts the only local source of text is the CAS cache keyed by each record'smessages_url.Terminal card (fixture repo,
--no-color):JSON schema (
autter wrapped --json), the share-image contract{ "schema_version": "wrapped/1.0.0", "autter_version": "2.4.0", "year": 2026, "author": "you@company.com", // null with --all-authors "all_authors": false, "commits": 94, // non-merge commits in the year by the author "commits_with_authorship": 90, // of which carry an authorship note "capped": false, // true when the walk stopped at commit_cap "commit_cap": 5000, "added_lines": 5430, // sum of git_diff_added_lines "ai_lines": 3940, // sum of ai_accepted "human_lines": 1200, // known-human attested "untracked_lines": 290, // no attestation (incl. commits without notes) "ai_percent": 72.56, // ai_lines / added_lines * 100, 2 decimals "by_month": [ // always 12 entries, month 1..12 { "month": 1, "ai_accepted": 120, "added_lines": 400, "ai_percent": 30.0, "commits": 9 } ], "top_agent": { "name": "claude", "lines": 3100 }, // null when no AI lines "top_model": { "name": "claude-opus-5-5", "lines": 2400 }, // null when no AI lines "agents": [ { "name": "claude", "lines": 3100 } ], // ranked desc by lines, then name "models": [ { "name": "claude-opus-5-5", "lines": 2400 } ], "most_prompted_file": { "path": "src/x.rs", "prompts": 42 }, // distinct prompt/session ids on the file; null if none "files": [ { "path": "src/x.rs", "prompts": 42 } ], // top 5 "prompts_total": 310, // distinct prompt/session ids across the year "prompts_with_transcript": 280, // of which have a transcript in the local cache "longest_prompt": { // null when no transcript is cached locally "chars": 1834, "words": 312, "agent": "claude", "model": "claude-opus-5-5", "commit": "<full sha>", "snippet": null // first 120 chars only with --include-prompt-text }, "busiest_day": "Tuesday", // weekday with the most AI lines; null when none "streak_days": 14 // longest run of consecutive days with an AI commit }Notes on the definitions:
ai_percentuses the same definition asai_percentinupload_commit_authorship_summary:ai_accepted / git_diff_added_lines. Commits in the window with no note count toward the denominator as untracked.%adwith--date=format:), so the output does not depend on the machine's timezone.CommitStats.tool_model_breakdownkeys (tool::model, split on the first::), the same splitautter stats -iuses.Tests
tests/integration/wrapped.rs: a fixture year (2031) built with explicitmock_known_human/mock_aicheckpoints, one Claude-preset commit with a local transcript, and commits dated into January, March and July withGIT_AUTHOR_DATE/GIT_COMMITTER_DATE, plus a commit by another author. Asserts the full JSON contract (percent, all 12 month buckets, top agent/model, ranked lists, most-prompted file,longest_promptwithsnippet: nullby default and the 120-char snippet with--include-prompt-text, busiest day, streak), author scoping, other years, the empty state, bad arguments, and an insta snapshot of the--no-colorcard.cargo fmt --check,cargo clippy --all-targets -- -D warningsandcargo test wrapped(daemon mode) pass locally.Sharing (second commit)
The backend and share page live in the monorepo (
POST /api/cli/wrapped-share,DELETE /api/cli/wrapped-share/:token, page atapp.autter.dev/wrapped/<token>, sample at/wrapped/demo). This PR adds the CLI side insrc/api/wrapped_share.rs:--share: prints the card (or the JSON) as usual, then POSTs{ "wrapped": <the exact --json object>, "repo_url"?: <normalized default remote>, "include_prompt_text": bool, "hide_repo": bool }with the CLI access token as Bearer. The token is resolved like the commit-summary upload:resolve_org_for_repo_cached+access_token_for_org, elseApiContext::new(None).snippetis stripped from the body unless--include-prompt-textwas given. On{ token, url, image_url }it printsShared at <url>(stderr with--json, so stdout stays the contract) and opens the page in the browser at a terminal.--hide-repo: sendshide_repo: true; usage error without--share.--unshare <url-or-token>: DELETEs by token; accepts a full/wrapped/<token>URL.platform_sync_enabled_now), when not signed in, or when the year has too few noted commits; each case is one line on stderr and exit 1. Server statuses map to one line each: 401 → runautter login, 403 → not a member of the org, 413 → too large, 422 → nothing to share yet, 400 → the server'serror, 404 on unshare → not yours or already removed.ApiContextgains adeleterequest to matchget/post_json.Tests: mockito unit tests for the request body builder (snippet stripping,
repo_urlomission), token parsing, status mapping, and the POST/DELETE calls; integration tests that run the binary against a mockito server withAUTTER_API_BASE_URLand a seeded file credential store, covering flag validation, local mode sending nothing, no sign-in sending nothing, the POST with Bearer header and partial-body match (card and--jsonforms), a 403, and unshare by link and by token including the 404.🤖 Generated with Claude Code