ai-profile
Show the evidence behind your AI collaboration.
ai-profile turns explicit Git provenance across your local repositories into
privacy-safe GitHub Profile cards and a provider-filterable dashboard with a
provider ledger—without uploading source code or guessing
attribution.
Explore the live dashboard → · Generate yours in four commands
- Explicit evidence: attribution comes from
AI-*trailers and verified AI co-author identities—not source-code style. - Local-first privacy: CLI scanning, aggregation, refresh, and rendering happen on your machine; repository identities stay out of public assets.
- Profile-ready: one render produces theme-aware SVG cards, a self-contained dashboard, and a machine-readable public summary.
ai-profile is not an AI code detector. Commits with no explicit evidence
stay unknown (shown as Unattributed in the UI); they are never silently
counted as human or assigned to a provider. Add AI-* trailers to future
commits when you want their AI participation recorded.
A real GitHub Profile example
This card contains the maintainer's real public aggregate data. Select it to open the provider-filterable dashboard; the card shows explicit provider participation and evidence totals when the Git history declares AI activity.
The card shows sustained AI collaboration (active AI days and a 12-week flat activity matrix), breadth across AI providers, and the explicit evidence totals behind every number. It is a record of declared Git evidence, not a skill score.
Why ai-profile?
- Generic GitHub statistics answer how active a developer is.
- Line-level attribution tools answer which lines a tool changed.
ai-profileanswers how AI participated and what evidence supports that claim across repositories, while keeping the analysis local.
It complements those tools; it does not replace them or infer historical AI use when Git evidence is absent.
Install
Requires Python 3.11 or newer and Git 2.17 or newer. The wheel-onboarding workflow tests Ubuntu, Windows, and macOS on Python 3.12; the full suite also tests Python 3.11–3.14 on Ubuntu. Repositories must use Git's SHA-1 object format.
python -m pip install --upgrade ai-profile-cli
aiprofile --version
The package name is ai-profile-cli; the installed command is aiprofile.
If your shell has not refreshed its script path, use:
python -m aiprofile --version
Quickstart
Run these commands from the root of one of your repositories:
aiprofile init
aiprofile scan .
aiprofile aggregate
aiprofile render
Use python -m aiprofile in place of aiprofile for any command if needed.
initcreates the local configuration and seeds your identity fromgit config user.email.scanreads commits reachable from the repository's currentHEADand records commits authored by your configured identities.aggregateprints the exact privacy-safe statistics eligible for publication.renderwrites exactly eight files todist/:
badge-dark.svg heatmap-light.svg summary-dark.svg
badge-light.svg profile.json summary-light.svg
dashboard.html heatmap-dark.svg
Existing commits without explicit AI evidence will appear as unknown.
Large unknown counts are an honest result, not a failed scan. Add trailers to
future commits when you want their AI participation credited.
Configure identities and repository privacy
The configuration file is:
- macOS/Linux:
~/.aiprofile/config.json - Windows:
%USERPROFILE%\.aiprofile\config.json($HOME\.aiprofile\config.jsonin PowerShell) - custom automation:
$AIPROFILE_HOME/config.json
Only commits authored by an address in identities count. After init,
close other aiprofile processes, open the file as UTF-8 JSON, and add every
address you use—including GitHub noreply addresses:
{
"identities": [
"you@example.com",
"12345678+username@users.noreply.github.com"
],
"repositories": [],
"salt": "keep-the-existing-generated-value"
}
Keep the generated salt unchanged. After a scan, each repository entry also
contains path, repository_uid, and publication_level. Change only
publication_level when adjusting privacy:
On Windows, save with an editor's UTF-8 (no BOM) option. Windows
PowerShell 5.1's Set-Content -Encoding utf8 adds a BOM that strict JSON
readers, including this Public Beta, reject. VS Code's default UTF-8
encoding is suitable; confirm the file still parses before replacing your
backup.
{
"path": "/local/path/created-by-scan",
"repository_uid": "keep-the-existing-generated-value",
"publication_level": "aggregate_only"
}
The allowed values are:
aggregate_only— default; include totals but withhold repository identity and daily dates.full— include identity-redacted daily aggregate activity in date views.excluded— omit that repository entirely.
aiprofile scan --full /path/to/repository is the supported opt-in from
aggregate_only to full. It does not mean the repository is public on
GitHub, and a later scan without --full does not downgrade it. To reduce
publication, edit only the existing entry's publication_level, save valid
UTF-8 JSON, then run:
aiprofile aggregate
aiprofile render
If configuration parsing fails, restore the previous valid JSON; do not
delete or regenerate the salt, path, or repository_uid. Always review
aggregate before publishing.
Automate daily updates
Choose one path. Keep private and aggregate_only repositories on your own
machine; use GitHub Actions only when every source repository is already
public.
# Refresh every configured, non-excluded repository now.
aiprofile refresh --out dist
aiprofile refresh --out dist --dry-run
# Or install one daily local job (05:37 in the machine's local time).
aiprofile schedule install --profile-repo /path/to/USERNAME --time 05:37
aiprofile schedule status
aiprofile schedule remove
Refresh every configured repository
refresh rescans each configured path once, then aggregates and writes the
same eight files. Aliases do not cause duplicate scans, and excluded
repositories remain excluded. A scan, configuration, privacy, or rendering
failure publishes no new generation. --dry-run lists which of the eight
files would change without changing configuration, publication policy,
recorded database/WAL content, or output assets. It may create or use the
advisory lock, and SQLite may update transient -shm coordination bytes while
reading committed WAL content; neither is published data.
If two configuration entries resolve to the same path but carry different
repository_uid values, refresh refuses the whole run before scanning or
reading cached aggregates. Keep the UID created by scan; do not copy a UID
between entries or invent a replacement.
Only one refresh can use an AIPROFILE_HOME at a time. On the rare filesystem
failure where output rollback is incomplete, the CLI says that partial assets
or recovery backups may remain; inspect the output before committing it.
Private or local repositories: native scheduler
schedule install creates an OS-native user job: Task Scheduler on Windows,
launchd on macOS, or a systemd user timer on Linux. It refreshes
<profile-repo>/dist daily and, by default, stages only the eight generated
paths, commits only byte changes, and pushes through the repository's existing
Git authentication only while the remote still equals the captured parent.
Push mode requires one fetch destination and the same single push destination;
multiple or different push URLs fail closed before refresh. The captured
destination is bound to a fixed alias inside a private, isolated Git context;
push and verification never put the URL in argv and cannot be redirected by
later repository, global, insteadOf, or pushInsteadOf changes. Supported
destinations are credential-free HTTPS, SSH/SCP, Git, file URLs, or local
paths without query strings or fragments. Local paths are resolved from the
Profile repository before the isolated context is created. Shallow and partial
clones are rejected before refresh because scheduled publication requires a
complete local Git history. Use a credential manager, askpass,
or SSH agent; embedded passwords and authorization headers are intentionally
unsupported, and ambient proxy variables are not forwarded. The tool does not persist or log credentials. Add
--no-push to create and advance the local exact-eight commit without pushing
it to the remote; use --dry-run to preview installation without changing
scheduler state.
The user scheduler and machine must be available. Windows and systemd can recover missed runs according to their native settings; launchd does not replay a run missed while the machine was powered off. Detached branches, changed branch state, protected branches, and rejected pushes fail closed. Scheduler commits are mechanical and intentionally do not run user commit hooks or signing.
Before a push-capable run, a clean local checkout is safely fast-forwarded when
the recorded remote branch is ahead and is a verified descendant of local
HEAD; the fetch uses the same isolated destination transport as publication.
Deliberate local commits, dirty checkouts, rewinds, deletions, and diverged or
unstable remote tips still fail closed for manual synchronization. A failed
push is retried from the same private pending commit only after an interrupted
branch update and the exact-eight index are safely recovered, and while its
branch, parent, tree, and remote remain unchanged. The actual push uses an
exact-old lease tied to that parent and verifies the remote at the immutable
commit before reporting success or clearing retry state. The private retry
record binds a SHA-256 commitment of the destination, never the URL itself.
Different homes targeting one Profile are
serialized by that Profile's Git metadata lock, and generated bytes are
verified against the completed refresh before the commit is created.
Install the scheduler from a Python environment that will remain in place.
If you move, remove, or upgrade that interpreter or virtual environment, run
schedule install again and confirm schedule status.
Public repositories: GitHub Actions
For a Profile sourced entirely from public repositories:
- Copy
docs/templates/profile-refresh-caller.ymlto.github/workflows/profile-refresh.ymlin the Profile repository. - Edit the explicit public
owner/repolist. Add the identity-email payload as the repository secretAIPROFILE_IDENTITIES; never put it inwith:. - In Settings → Pages, choose GitHub Actions as the source, then run the workflow once with Actions → Daily ai-profile refresh → Run workflow.
The template runs daily at 05:37 UTC and also supports manual dispatch. It
pins the reusable workflow to commit
9c4f276cb437f1866a2c1b407efe54d3790ce811, installs exactly
ai-profile-cli==0.7.0, rejects non-public sources before scanning, and
deploys Pages from the exact published-sha produced in that run. It uses
only GITHUB_TOKEN; no PAT fallback is provided. GitHub-hosted automation is
not local-first processing: it clones only the public repositories you list
and treats them as full. Use the local scheduler if any source is private or
aggregate_only.
Branch protection can reject the direct asset commit. Scheduled workflows in
public repositories can be disabled after 60 days without repository
activity, forks require Actions to be enabled, and organization Actions
allowlists must permit the pinned actions and reusable workflow. A commit made
with GITHUB_TOKEN does not trigger ordinary push workflows or a Pages build,
so the caller performs an explicit, same-run Pages deployment. Use one caller,
not a matrix of overlapping refresh jobs.
Publish to your GitHub Profile
Run aiprofile render --out dist inside your USERNAME/USERNAME Profile
repository, commit dist/, and place this clickable card in its README:
<a href="https://USERNAME.github.io/USERNAME/dist/dashboard.html">
<picture>
<source media="(max-width: 600px) and (prefers-color-scheme: dark)" srcset="dist/badge-dark.svg">
<source media="(max-width: 600px)" srcset="dist/badge-light.svg">
<source media="(prefers-color-scheme: dark)" srcset="dist/summary-dark.svg">
<img alt="Open my interactive AI collaboration dashboard"
src="dist/summary-light.svg">
</picture>
</a>
Add the heatmap with the same <picture> pattern using
heatmap-{light,dark}.svg. GitHub READMEs do not execute JavaScript, so the
SVG remains the Profile view and the link opens dashboard.html.
For manual publication, host the dashboard with GitHub Pages as follows. If you use the daily Action above, keep GitHub Actions as the Pages source and skip these branch-source steps.
- Push
README.mdanddist/to the Profile repository'smainbranch. - Open Settings → Pages.
- Under Build and deployment, choose Deploy from a branch.
- Select main, / (root), then Save.
- Open
https://USERNAME.github.io/USERNAME/dist/dashboard.html.
A new Pages deployment can return 404 for a few minutes. Wait for the Pages
workflow to finish, then reload the exact case-sensitive URL. If it still
returns 404, confirm that Pages uses main and / (root) and that
dist/dashboard.html exists in the pushed commit.
What gets generated
The static, synthetic previews below demonstrate the complete output family:
The summary card, heatmap, badge, and dashboard share one flat Signal Console system: calm paper-and-ink surfaces, a blue collaboration signal, a warm evidence cue, a status line that labels the generation date as a snapshot, and precise alignment that improves reading without turning activity into a 3D score or decorative scene.
The six SVGs are static and GitHub-ready. dashboard.html is a self-contained
provider-filterable view with the provider ledger, plus light, dark, and system
themes. It loads no
external script, font, tracker, or API. profile.json contains the same
validated public aggregate contract used by every renderer.
Declare AI participation
Known AI co-author identities are recognized automatically. For other tools,
or richer attribution, add one contiguous AI-* trailer block after a blank
line in the commit message:
feat: add aggregation service
AI-Provider: Anthropic
AI-Model: Claude-Sonnet
AI-Tool: Claude-Code
AI-Role: implementation, documentation
AI-Mode: AI-Assisted
AI-Reviewed-By: Human
Repeat AI-Provider: without a blank line between actor groups to declare
another AI actor in the same commit. One commit can therefore equal one
unique AI-attributed commit and multiple actor presences. Use
AI-Mode: Human-Only only for an explicitly human-only commit.
Provider declarations are normalized to a canonical provider identity
(typically company-oriented). The dashboard may show the more recognizable
product display name instead: AI-Provider: Anthropic is shown as Claude.
This is a label-only mapping; it does not change aggregation or counts.
When AI-Model is present, its canonical value is mapped to a small
schema-owned family vocabulary (for example, Claude-Sonnet → Claude) and
retained as validated, machine-readable evidence in profile.json. Family
commit counts remain intentionally non-exclusive, while actor presences and
active days remain separate measures. Missing model declarations stay
Unknown; raw model strings never enter public assets.
Privacy
- Scanning, aggregation, refresh, and rendering make no network calls and
send no telemetry. The optional local scheduler may run
git pushthrough a credential manager, askpass, or SSH agent;ai-profiledoes not persist or log credentials. - The optional public Action runs on a GitHub-hosted runner and clones only explicit public repositories. Identity emails are passed as a secret and are not written to public assets or default workflow logs.
- Public assets contain the UTC generation date. They may also contain
aggregate counts, public provider names, and evidence totals; repository
activity dates appear only for
fullrepositories. - Public assets never contain repository names or paths, organization names, branches, commit SHAs or messages, raw trailers, prompts, or email addresses.
aggregateis the publication preview. Review it before committing any generated asset.- Aggregate-only output is identity redaction, not anonymity. Repeated publication can reveal when totals changed.
- Never publish or sync
.aiprofile; it contains private paths, identities, repository identifiers, and a local salt.
Read the complete privacy model.
Metrics and current limitations
- AI-attributed commits are unique commits with at least one explicit AI actor presence.
- Actor presences count distinct provider/tool participation inside a commit. Provider totals can exceed unique commit totals.
- Model-family rows in
profile.jsoncount explicit canonicalAI-Modelevidence. A commit can contribute to more than one family row; this does not inflate the unique AI-attributed commit headline. - Unknown remains separate from human.
- Evidence quality is
verified > declared > imported > inferred > unknown. - Scans cover commits reachable from the current
HEAD, not every branch. - Git repositories using SHA-256 object format are not supported yet.
- Bot-authored commits are outside the author-identity scan unless the bot address is deliberately configured; some bot-plus-human co-author histories may therefore be absent.
- Historical AI use without explicit Git evidence stays unknown. Source-code style is never used to reconstruct attribution.
Help, contributing, and license
Run aiprofile --help or aiprofile COMMAND --help. Use
GitHub Issues for bugs and
feature requests; report vulnerabilities through the
security policy.
Contributions are welcome; see CONTRIBUTING.md. Licensed under the MIT License; vendored icon notices are in THIRD_PARTY_NOTICES.md.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ai_profile_cli-0.8.0.tar.gz.
File metadata
- Download URL: ai_profile_cli-0.8.0.tar.gz
- Upload date:
- Size: 1.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
730415902f91365c5d00b6668c0d133bab5a1904baf23a82712492abb5d5f1a2
|
|
| MD5 |
8d14b3790700d35fd4043b63c8fe1b18
|
|
| BLAKE2b-256 |
e38ed5a23c746695ad7d5df180333e2cce548cb6440fbd1412a6ffdc18b9c046
|
Provenance
The following attestation bundles were made for ai_profile_cli-0.8.0.tar.gz:
Publisher:
publish.yml on WenyuChiou/ai-profile
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ai_profile_cli-0.8.0.tar.gz -
Subject digest:
730415902f91365c5d00b6668c0d133bab5a1904baf23a82712492abb5d5f1a2 - Sigstore transparency entry: 2576031613
- Sigstore integration time:
-
Permalink:
WenyuChiou/ai-profile@af5a002aa75b2f23d7f212c45882b08907f3e79b -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/WenyuChiou
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@af5a002aa75b2f23d7f212c45882b08907f3e79b -
Trigger Event:
push
-
Statement type:
File details
Details for the file ai_profile_cli-0.8.0-py3-none-any.whl.
File metadata
- Download URL: ai_profile_cli-0.8.0-py3-none-any.whl
- Upload date:
- Size: 165.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9cc06f2052a642bd198fa00d728c75b72fce061dad24c51b72feddf84b07c89e
|
|
| MD5 |
c61f4a32f9869077913c7cd557d63086
|
|
| BLAKE2b-256 |
f0179357937991c0a3548195925b5eb1e82ce58eae6a30cf7a554d27437269e6
|
Provenance
The following attestation bundles were made for ai_profile_cli-0.8.0-py3-none-any.whl:
Publisher:
publish.yml on WenyuChiou/ai-profile
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ai_profile_cli-0.8.0-py3-none-any.whl -
Subject digest:
9cc06f2052a642bd198fa00d728c75b72fce061dad24c51b72feddf84b07c89e - Sigstore transparency entry: 2576032405
- Sigstore integration time:
-
Permalink:
WenyuChiou/ai-profile@af5a002aa75b2f23d7f212c45882b08907f3e79b -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/WenyuChiou
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@af5a002aa75b2f23d7f212c45882b08907f3e79b -
Trigger Event:
push
-
Statement type: