costgrep-cli 0.0.0-stage → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +299 -2
- package/action.yml +85 -0
- package/agents.json +20 -0
- package/package.json +15 -4
- package/scripts/costgrep.mjs +993 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright 2026 Paraphern and costgrep contributors
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,300 @@
|
|
|
1
|
-
#
|
|
1
|
+
# costgrep
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://github.com/Paraphern/costgrep/actions/workflows/test.yml)
|
|
5
|
+
[](https://github.com/Paraphern/costgrep/actions/workflows/audit.yml)
|
|
6
|
+
|
|
7
|
+
**GitHub Actions cost breakdown by who triggered the run: human vs AI agent vs bot.**
|
|
8
|
+
|
|
9
|
+
**See it on real data → [live benchmark, 6 public repos](benchmark/2026-10-02/README.md)**
|
|
10
|
+
and **[3 verifiable cases with links to the actual runs & PRs](benchmark/cases.md):**
|
|
11
|
+
in GitHub's own agentic-workflows repo, AI agents triggered **53.5% of runs and 28.8% of
|
|
12
|
+
CI spend in a single day**; a solo maintainer's repo is now **agent-majority (46% of runs,
|
|
13
|
+
57% of spend)**; in microsoft/vscode, **89% of a one-day sample's compute value ($123.61)
|
|
14
|
+
sits in runner classes GitHub bills even for public repos**.
|
|
15
|
+
Every figure ships with per-job evidence you can recompute in a spreadsheet.
|
|
16
|
+
|
|
17
|
+
Copilot coding agents, Copilot code review, Claude Code, Codex, Cursor — since June 2026
|
|
18
|
+
they all burn your Actions minutes *on top of* AI credits ([#192948], 958 downvotes).
|
|
19
|
+
GitHub's own usage views don't tell you **who** spent it. This does:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
$ node scripts/costgrep.mjs --repo actions/stale --days 30 --max-runs 50 --quiet
|
|
23
|
+
costgrep — actions/stale (last 30 days, 50 runs, 47 jobs)
|
|
24
|
+
==============================================================================
|
|
25
|
+
class runs run% minutes cost cost%
|
|
26
|
+
------------------------------------------------------------------------------
|
|
27
|
+
agent 0 0.0% 0 $0.00 0.0%
|
|
28
|
+
agent-assisted 14 28.0% 26 $0.28 36.8%
|
|
29
|
+
human 25 50.0% 24 $0.33 42.5%
|
|
30
|
+
bot 11 22.0% 16 $0.16 20.7%
|
|
31
|
+
unattributed 0 0.0% 0 $0.00 0.0%
|
|
32
|
+
------------------------------------------------------------------------------
|
|
33
|
+
total 50 66 $0.77
|
|
34
|
+
|
|
35
|
+
>>> Agents cost $0.00 (0.0% of CI spend, 0 runs / 0.0%).
|
|
36
|
+
>>> Bot runs: 11 (22.0% of all runs), bot spend $0.16.
|
|
37
|
+
>>> Top workflows by total cost:
|
|
38
|
+
$0.50 65.0% Basic validation
|
|
39
|
+
$0.10 12.4% Code scanning
|
|
40
|
+
$0.06 7.8% Licensed
|
|
41
|
+
>>> public repo: standard Linux/Windows minutes are FREE — the $ above is the list-price VALUE of this compute (what it would cost in a private repo), not money owed. NOTE: 5 macOS/larger-runner jobs ARE billed even for public repos (~$0.37).
|
|
42
|
+
>>> List-price model: NOT your invoice. Included plan minutes are consumed first; hosted phase reconciles against the billing API.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
*(Real snapshot taken 2026-10-04 with the exact command shown; rerun it and you get
|
|
46
|
+
current numbers. Note the `agent-assisted` row: 14 of 50 runs in this repo were
|
|
47
|
+
pushed by humans with an AI `Co-Authored-By` trailer — that's the class doing its job.)*
|
|
48
|
+
|
|
49
|
+
Runs **on your infrastructure** (your Actions runner, your terminal). Zero npm
|
|
50
|
+
dependencies (single-file CLI core + a plain `agents.json` for the classifier
|
|
51
|
+
lists — the core works standalone if the data file is missing), no telemetry,
|
|
52
|
+
no code/log access — workflow-run metadata only.
|
|
53
|
+
|
|
54
|
+
## Quick start (composite action)
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
# .github/workflows/agent-ci-audit.yml
|
|
58
|
+
name: costgrep audit
|
|
59
|
+
on:
|
|
60
|
+
schedule: [{cron: '0 6 * * 1'}] # weekly
|
|
61
|
+
workflow_dispatch:
|
|
62
|
+
permissions:
|
|
63
|
+
actions: read # the only permission needed
|
|
64
|
+
jobs:
|
|
65
|
+
audit:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v4 # only needed if you run a *local* copy / want the JSON in an artifact
|
|
69
|
+
- uses: YOUR_LOGIN/costgrep@v1
|
|
70
|
+
with:
|
|
71
|
+
days: '30'
|
|
72
|
+
- uses: actions/upload-artifact@v4
|
|
73
|
+
if: always()
|
|
74
|
+
with: {name: costgrep, path: costgrep.json}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
No org-admin rights, no billing access, no webhook — the repo-scoped
|
|
78
|
+
`GITHUB_TOKEN` with `actions: read` is enough.
|
|
79
|
+
|
|
80
|
+
## CLI
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
node scripts/costgrep.mjs --repo owner/name [--days 30] [--token $GITHUB_TOKEN] \
|
|
84
|
+
[--max-runs 1000] [--config my-rules.json] [--json-file report.json] \
|
|
85
|
+
[--csv-file evidence.csv] [--md-file report.md] [--gh-output $GITHUB_OUTPUT] \
|
|
86
|
+
[--no-coab] [--pr-comment N|auto] [--slack-webhook URL] [--step-summary] [--quiet] [--help]
|
|
87
|
+
# org-wide audit (still no org-admin rights — a PAT with actions:read is enough):
|
|
88
|
+
node scripts/costgrep.mjs --org my-org [--repos 20] [--max-runs 100]
|
|
89
|
+
# offline demo on a bundled fixture:
|
|
90
|
+
node scripts/costgrep.mjs --fixture-dir test/fixtures/demo-repo --days 4000
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Requires Node >= 18 (`fetch`, `node:test`, ESM — no dependencies). Note on caps:
|
|
94
|
+
`--max-runs` defaults to **1000** in the CLI and **500** in the composite action
|
|
95
|
+
(safety cap for shared runners).
|
|
96
|
+
|
|
97
|
+
`--config` (JSON) extends the classifier and overrides rates:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"agents": ["my-internal-agent-bot"],
|
|
102
|
+
"bots": ["my-ci-bot"],
|
|
103
|
+
"rates": { "actions_linux": 0.006 }
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Outputs & budget gate
|
|
108
|
+
|
|
109
|
+
The action exposes machine-readable outputs, so CI can *act* on the split —
|
|
110
|
+
not just display it (per-actor budget enforcement GitHub's budgets API doesn't have):
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
- id: costgrep
|
|
114
|
+
uses: Paraphern/costgrep@v1
|
|
115
|
+
- name: Agent budget gate (fail when agents exceed 20% of CI spend)
|
|
116
|
+
if: fromJSON(steps.costgrep.outputs['agent-share-pct']) > 20
|
|
117
|
+
run: |
|
|
118
|
+
echo "::error::AI agents burned ${{ steps.costgrep.outputs['agent-cost'] }} \
|
|
119
|
+
(${{ steps.costgrep.outputs['agent-share-pct'] }}% of CI spend) — above the 20% budget"
|
|
120
|
+
exit 1
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
| output | meaning |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `total-cost` / `total-minutes` | whole-window list-price spend / billable minutes |
|
|
126
|
+
| `agent-cost` / `agent-share-pct` / `agent-runs` | the AI-agent slice |
|
|
127
|
+
| `agent-assisted-cost` | human-triggered runs with an AI `Co-Authored-By` trailer |
|
|
128
|
+
| `bot-runs` / `bot-runs-pct` | automation share of runs |
|
|
129
|
+
|
|
130
|
+
### Ready-made gates (copy-paste)
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
# 1) Budget gate: fail CI when agents exceed 20% of spend
|
|
134
|
+
- id: costgrep
|
|
135
|
+
uses: Paraphern/costgrep@v1
|
|
136
|
+
- run: |
|
|
137
|
+
if (( $(echo "${{ steps.costgrep.outputs['agent-share-pct'] }} > 20" | bc -l) )); then
|
|
138
|
+
echo "::error::Agents at ${{ steps.costgrep.outputs['agent-cost'] }} (${{ steps.costgrep.outputs['agent-share-pct'] }}%) — above budget"
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# 2) Weekly org audit to Slack (secrets.SLACK_WEBHOOK; PAT with actions:read as CG_PAT)
|
|
143
|
+
- uses: Paraphern/costgrep@v1
|
|
144
|
+
env:
|
|
145
|
+
CG_TOKEN: ${{ secrets.CG_PAT }}
|
|
146
|
+
with:
|
|
147
|
+
token: ${{ secrets.CG_PAT }}
|
|
148
|
+
slack-webhook: ${{ secrets.SLACK_WEBHOOK }}
|
|
149
|
+
days: '7'
|
|
150
|
+
# (--org mode: run the CLI directly, see above)
|
|
151
|
+
|
|
152
|
+
# 3) Cost stamp on every PR (needs pull-requests:write)
|
|
153
|
+
- uses: Paraphern/costgrep@v1
|
|
154
|
+
with:
|
|
155
|
+
pr-comment: auto
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Reconciliation & AI credits (experimental subcommands)
|
|
159
|
+
|
|
160
|
+
For organizations that *can* provide an org token with billing rights (the one
|
|
161
|
+
place costgrep ever asks for more than `actions:read`):
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# our recomputation vs the billing API, per SKU, for a calendar month;
|
|
165
|
+
# the delta lands in an explicit "unattributed" bucket with coverage warnings
|
|
166
|
+
node scripts/costgrep.mjs reconcile --org my-org --month 2026-09 --billing-token $ORG_TOKEN
|
|
167
|
+
|
|
168
|
+
# AI credits for the same month (by model; per-user breakdown is not exposed by the API)
|
|
169
|
+
node scripts/costgrep.mjs credits --org my-org --month 2026-09 --billing-token $ORG_TOKEN
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Status, stated plainly: EXPERIMENTAL.** This code path is not yet validated
|
|
173
|
+
against a live billing account — treat every delta as a hypothesis until
|
|
174
|
+
calibrated on a real invoice (that calibration program is the 1.0 gate). Every
|
|
175
|
+
output of these subcommands carries this label; the coverage warnings say
|
|
176
|
+
exactly which repos/runs the comparison did and did not see.
|
|
177
|
+
|
|
178
|
+
## Evidence: every number is traceable
|
|
179
|
+
|
|
180
|
+
Reports are self-describing — a human can check any figure against real API data:
|
|
181
|
+
|
|
182
|
+
- **JSON** (`--json-file`): aggregates + `provenance` (which endpoints were called, the
|
|
183
|
+
exact window, fetched-vs-counted jobs, rates version, honesty flags) + `evidence[]` —
|
|
184
|
+
one row per billable job.
|
|
185
|
+
- **CSV** (`--csv-file`): the same per-job rows for any spreadsheet: run, actor, class,
|
|
186
|
+
timestamps, minutes, SKU, rate, cost.
|
|
187
|
+
- **Markdown** (`--md-file`): a shareable report with the provenance block baked in.
|
|
188
|
+
|
|
189
|
+
Verify it yourself (that's the point):
|
|
190
|
+
|
|
191
|
+
1. open the CSV and recompute `minutes × rate_usd_per_min` on any row — it matches `cost_usd`;
|
|
192
|
+
2. sum `cost_usd` — it equals the headline totals;
|
|
193
|
+
3. compare `minutes` against the GitHub usage UI for the same window (same per-job round-up);
|
|
194
|
+
4. rerun costgrep on the same repo + window — the result is deterministic (latest job attempt only);
|
|
195
|
+
5. spot-check any `run_id` via `gh api /repos/{repo}/actions/runs/{run_id}/jobs` — timestamps,
|
|
196
|
+
actor and labels in the CSV are the API's own values, unmodified.
|
|
197
|
+
|
|
198
|
+
Scope note: the CSV proves the **spend and minutes** figures. The *run-share* percentages
|
|
199
|
+
(e.g. "agents = 53.5% of runs") count all runs in the window, including ones whose jobs
|
|
200
|
+
never started — those are visible in the JSON report's `runsAnalyzed`/class totals, and
|
|
201
|
+
re-derivable directly from `GET /repos/{repo}/actions/runs`.
|
|
202
|
+
|
|
203
|
+
## How it attributes (summary — full methodology: [docs/methodology.md](docs/methodology.md))
|
|
204
|
+
|
|
205
|
+
1. **Identity = `triggering_actor`**, falling back to `actor`. Re-runs are
|
|
206
|
+
attributed to whoever re-ran them. For `workflow_run` chains `github.actor`
|
|
207
|
+
can resolve to a generic bot ([gh-aw#20586]) — `triggering_actor` is the
|
|
208
|
+
honest identity, so that's what we use.
|
|
209
|
+
2. **Classes:**
|
|
210
|
+
- `agent` — login matches an AI-agent slug list (Copilot / `copilot-swe-agent`,
|
|
211
|
+
Claude, Codex, Cursor, Gemini, Devin, Aider, Windsurf, CodeRabbit, Qodo, …
|
|
212
|
+
the list lives in [`agents.json`](agents.json) — extend it by PR, no code);
|
|
213
|
+
- `agent-assisted` — human-triggered run whose head commit carries an AI
|
|
214
|
+
`Co-Authored-By:` trailer (checked from the run's own metadata, zero extra
|
|
215
|
+
API calls; disable with `--no-coab`). Known limit, stated plainly: some
|
|
216
|
+
editors (VS Code) auto-insert the Copilot trailer — that's why this is a
|
|
217
|
+
separate class and never silently merged into `agent`;
|
|
218
|
+
- `bot` — automation (`github-actions[bot]`, Dependabot, Renovate, …) and any
|
|
219
|
+
other `type: Bot` / `[bot]` account not claimed by the agent list;
|
|
220
|
+
- `human` — `type: User`;
|
|
221
|
+
- `unattributed` — anything we can't classify. We show the bucket; we never
|
|
222
|
+
quietly re-assign it.
|
|
223
|
+
3. **Minutes:** `ceil((completed_at − started_at) / 60s)` per job, minimum 1 —
|
|
224
|
+
same per-job round-up GitHub bills by. Jobs of the latest attempt only
|
|
225
|
+
(the API default), so re-run attempts aren't double-counted. Jobs that never
|
|
226
|
+
reached a runner (no timestamps) count 0; in-progress runs are excluded and
|
|
227
|
+
reported. Jobs that hold runner timestamps but were `skipped` or had
|
|
228
|
+
zero/negative duration are billed at the 1-minute minimum and **flagged in
|
|
229
|
+
every report with their exact total** — whether GitHub bills such executions
|
|
230
|
+
identically is unverifiable without invoice access, so we surface the
|
|
231
|
+
subtrahend instead of silently deciding.
|
|
232
|
+
4. **Rates:** the published GitHub-hosted runner list prices effective
|
|
233
|
+
2026-01-01 ([docs]), keyed by the same SKU ids the billing API uses — the
|
|
234
|
+
foundation for invoice reconciliation in the hosted phase. Self-hosted = $0
|
|
235
|
+
(the $0.002/min platform fee was [postponed]); minutes still counted.
|
|
236
|
+
5. **Known limits (stated, not hidden):** list-price model — included plan
|
|
237
|
+
minutes are consumed first, so this is *gross spend at list prices*, not
|
|
238
|
+
your invoice; runner-class inference comes from job labels and falls back
|
|
239
|
+
to the standard Linux rate (flagged in every report as `rate fallback` jobs);
|
|
240
|
+
per-user AI *credits* are not visible at repo level (org billing API only —
|
|
241
|
+
hosted phase). Expected accuracy vs a real invoice: **±5–10%**, which is
|
|
242
|
+
exactly why the hosted product leads with reconciliation, not estimates.
|
|
243
|
+
|
|
244
|
+
## Why not just…? *(checked 2026-10)*
|
|
245
|
+
|
|
246
|
+
| tool | human/agent/bot split | cost numbers it gives you | what you pay | needs org admin |
|
|
247
|
+
|---|---|---|---|---|
|
|
248
|
+
| GitHub native usage views | ✗ — no actor slice; org metrics [frozen since 21.07.2026](https://github.com/orgs/community/discussions/202838) | invoice-grade billing view | included in plan | view perms on billing |
|
|
249
|
+
| Datadog CI Visibility | partial — git-author facet only, not trigger actor | span-based estimates | $8–12 / committer / mo | yes |
|
|
250
|
+
| Blacksmith Analytics | ✗ (jobs / workflows / repos) | runner minutes | bundled with runners | no |
|
|
251
|
+
| TrimCI | partial — bots excluded from *their* billing math; no AI-agent class | cost of CI failures | usage-based, per active contributor | no |
|
|
252
|
+
| **costgrep** | **✓ human / AI agent / bot, re-runs attributed** | **list-price per 2026 SKU rates (invoice reconciliation = planned hosted phase, not yet)** | **nothing — free, open source (Apache-2.0)** | **no — repo token, `actions:read`** |
|
|
253
|
+
|
|
254
|
+
**Money, stated plainly:** costgrep costs nothing and no paid tier exists today.
|
|
255
|
+
Every `$` figure it prints is the *list-price value of compute* (see the notice in
|
|
256
|
+
every report) — not money you owe GitHub and not money you pay us.
|
|
257
|
+
|
|
258
|
+
## Calibration case (do this first)
|
|
259
|
+
|
|
260
|
+
Run it on a repo where you can see the Actions usage chart for the same window:
|
|
261
|
+
|
|
262
|
+
1. `node scripts/costgrep.mjs --repo your-org/your-repo --days 30 --json-file r.json`
|
|
263
|
+
2. Compare `totalMinutes` per workflow against the GitHub UI "Usage" breakdown
|
|
264
|
+
for the same 30 days (billable minutes, per-job rounded).
|
|
265
|
+
3. Compare per-class cost shares against what you *know* about the repo
|
|
266
|
+
(scheduled jobs → bot; PRs from `copilot-swe-agent` → agent).
|
|
267
|
+
4. If your runners use unusual labels, add a `--config` rates block until
|
|
268
|
+
`rate fallback jobs` hits zero.
|
|
269
|
+
|
|
270
|
+
## Roadmap
|
|
271
|
+
|
|
272
|
+
**This repo — the CLI and the composite action — is free and open source, permanently.**
|
|
273
|
+
If a paid product ever appears, it will be the *hosted service* (continuous monitoring,
|
|
274
|
+
invoice reconciliation), not this code.
|
|
275
|
+
|
|
276
|
+
**1.0 is criteria-based, not date-based** (see [CHANGELOG](CHANGELOG.md) and
|
|
277
|
+
[docs/calibration.md](docs/calibration.md)): accuracy demonstrated on ≥5 real
|
|
278
|
+
organizations with reconciliation delta ≤5%; 100+ action installs; zero open
|
|
279
|
+
doctrine violations under a repeatable verification pass. Current state:
|
|
280
|
+
**engineering-complete to 1.0-rc — the remaining gates are market evidence.**
|
|
281
|
+
Stability guarantees: [docs/compatibility.md](docs/compatibility.md).
|
|
282
|
+
|
|
283
|
+
- **Phase B (hosted):** GitHub App + webhooks → org-wide dashboard, per-actor
|
|
284
|
+
budgets/alerts, weekly digests.
|
|
285
|
+
- **Phase C (reconciliation):** nightly join against
|
|
286
|
+
`/organizations/{org}/settings/billing/usage` — "our sum = your invoice line",
|
|
287
|
+
residual shown as an honest `unattributed` bucket; AI credits per user next
|
|
288
|
+
to minutes — the first unified agent bill.
|
|
289
|
+
|
|
290
|
+
## Privacy & license
|
|
291
|
+
|
|
292
|
+
The CLI reads workflow-run/job metadata via the public REST API and stores
|
|
293
|
+
nothing anywhere. Apache-2.0 — see [LICENSE](LICENSE). Accuracy is best-effort; see
|
|
294
|
+
the methodology above before quoting numbers to your CFO.
|
|
295
|
+
|
|
296
|
+
[#192948]: https://github.com/orgs/community/discussions/192948
|
|
297
|
+
[gh-aw#20586]: https://github.com/github/gh-aw/issues/20586
|
|
298
|
+
[docs]: https://docs.github.com/en/billing/reference/actions-runner-pricing
|
|
299
|
+
[postponed]: https://github.com/resources/insights/2026-pricing-changes-for-github-actions
|
|
300
|
+
[#202838]: https://github.com/orgs/community/discussions/202838
|
package/action.yml
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
name: 'CostGrep'
|
|
2
|
+
description: >-
|
|
3
|
+
GitHub Actions cost breakdown by who triggered the run: human vs AI agent
|
|
4
|
+
(Copilot, Claude, Codex, Cursor...) vs bot. List-price model, public
|
|
5
|
+
methodology, per-job evidence trail, runs on your runner — no data leaves
|
|
6
|
+
your infrastructure.
|
|
7
|
+
author: 'Paraphern'
|
|
8
|
+
|
|
9
|
+
inputs:
|
|
10
|
+
days:
|
|
11
|
+
description: 'Look-back window in days'
|
|
12
|
+
default: '30'
|
|
13
|
+
token:
|
|
14
|
+
description: 'GitHub token with actions:read (repo-scoped GITHUB_TOKEN is enough)'
|
|
15
|
+
default: '${{ github.token }}'
|
|
16
|
+
max-runs:
|
|
17
|
+
description: 'Safety cap on analyzed workflow runs'
|
|
18
|
+
default: '500'
|
|
19
|
+
config:
|
|
20
|
+
description: 'Optional JSON file {agents, bots, rates, coab} to extend/override defaults'
|
|
21
|
+
default: ''
|
|
22
|
+
pr-comment:
|
|
23
|
+
description: 'Optional: post the report as a PR comment (PR number, or "auto" to take it from GITHUB_REF; token needs pull-requests:write)'
|
|
24
|
+
default: ''
|
|
25
|
+
slack-webhook:
|
|
26
|
+
description: 'Optional: post the report to a Slack incoming webhook (https://hooks.slack.com/services/... URL)'
|
|
27
|
+
default: ''
|
|
28
|
+
json-file:
|
|
29
|
+
description: 'Where to write the full JSON report (relative to workspace)'
|
|
30
|
+
default: 'costgrep.json'
|
|
31
|
+
csv-file:
|
|
32
|
+
description: 'Optional: write per-job evidence CSV here (audit trail)'
|
|
33
|
+
default: ''
|
|
34
|
+
md-file:
|
|
35
|
+
description: 'Optional: write shareable Markdown report with provenance here'
|
|
36
|
+
default: ''
|
|
37
|
+
|
|
38
|
+
outputs:
|
|
39
|
+
total-cost:
|
|
40
|
+
description: 'Total list-price CI spend, USD'
|
|
41
|
+
total-minutes:
|
|
42
|
+
description: 'Total billable minutes (rounded per job, like GitHub)'
|
|
43
|
+
agent-cost:
|
|
44
|
+
description: 'List-price spend attributed to AI agents, USD'
|
|
45
|
+
agent-assisted-cost:
|
|
46
|
+
description: 'Spend from human-triggered runs whose commits carry an AI Co-Authored-By trailer, USD'
|
|
47
|
+
agent-share-pct:
|
|
48
|
+
description: 'Agents share of total CI spend, % (e.g. "6.8")'
|
|
49
|
+
agent-runs:
|
|
50
|
+
description: 'Workflow runs attributed to AI agents'
|
|
51
|
+
bot-runs:
|
|
52
|
+
description: 'Workflow runs attributed to bots'
|
|
53
|
+
bot-runs-pct:
|
|
54
|
+
description: 'Bot share of all runs, %'
|
|
55
|
+
|
|
56
|
+
runs:
|
|
57
|
+
using: 'composite'
|
|
58
|
+
steps:
|
|
59
|
+
- name: 'costgrep: human vs agent vs bot CI spend'
|
|
60
|
+
shell: bash
|
|
61
|
+
env:
|
|
62
|
+
# All user-controllable inputs go through env, never via ${{ }} inline in
|
|
63
|
+
# bash — script-injection hardening per GitHub's security guide.
|
|
64
|
+
CG_TOKEN: ${{ inputs.token }}
|
|
65
|
+
CG_REPO: ${{ github.repository }}
|
|
66
|
+
CG_DAYS: ${{ inputs.days }}
|
|
67
|
+
CG_MAX_RUNS: ${{ inputs.max-runs }}
|
|
68
|
+
CG_JSON: ${{ inputs.json-file }}
|
|
69
|
+
CG_CSV: ${{ inputs.csv-file }}
|
|
70
|
+
CG_MD: ${{ inputs.md-file }}
|
|
71
|
+
CG_CONFIG: ${{ inputs.config }}
|
|
72
|
+
CG_PR: ${{ inputs.pr-comment }}
|
|
73
|
+
CG_SLACK: ${{ inputs.slack-webhook }}
|
|
74
|
+
run: |
|
|
75
|
+
ARGS=(--repo "$CG_REPO" --days "$CG_DAYS" --max-runs "$CG_MAX_RUNS" --token "$CG_TOKEN" --json-file "$CG_JSON" --gh-output "$GITHUB_OUTPUT" --step-summary)
|
|
76
|
+
[ -n "$CG_CSV" ] && ARGS+=(--csv-file "$CG_CSV")
|
|
77
|
+
[ -n "$CG_MD" ] && ARGS+=(--md-file "$CG_MD")
|
|
78
|
+
[ -n "$CG_CONFIG" ] && ARGS+=(--config "$CG_CONFIG")
|
|
79
|
+
[ -n "$CG_PR" ] && ARGS+=("--pr-comment=$CG_PR")
|
|
80
|
+
[ -n "$CG_SLACK" ] && ARGS+=("--slack-webhook=$CG_SLACK")
|
|
81
|
+
node "${{ github.action_path }}/scripts/costgrep.mjs" "${ARGS[@]}"
|
|
82
|
+
|
|
83
|
+
branding:
|
|
84
|
+
icon: 'bar-chart-2'
|
|
85
|
+
color: 'purple'
|
package/agents.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2026-10-03",
|
|
3
|
+
"note": "Data file: extend the classifier without touching code. PRs adding entries must cite evidence (a public run/PR/doc) — see CONTRIBUTING.md. Rates live in code (money = code review).",
|
|
4
|
+
"agents": [
|
|
5
|
+
"copilot", "copilot-swe-agent", "claude", "cursor", "codex", "openai-codex",
|
|
6
|
+
"gemini", "gemini-code-assist", "jules", "devin", "aider", "windsurf",
|
|
7
|
+
"codebuff", "opencode", "goose", "factory", "sweep", "coderabbit",
|
|
8
|
+
"qodo", "greptile", "ellipsis", "codeant", "kodu", "ampcode"
|
|
9
|
+
],
|
|
10
|
+
"bots": [
|
|
11
|
+
"github-actions", "actions-user", "dependabot", "renovate", "greenkeeper",
|
|
12
|
+
"imgbot", "allcontributors", "lock", "stash", "bors", "mergify",
|
|
13
|
+
"semantic-release-bot", "codecov", "coveralls", "netlify", "vercel",
|
|
14
|
+
"github-advanced-security", "fossa", "changeset-bot"
|
|
15
|
+
],
|
|
16
|
+
"coauthorNames": [
|
|
17
|
+
"copilot", "claude", "cursor", "gemini", "codex", "devin", "aider",
|
|
18
|
+
"windsurf", "jules", "goose", "codebuff", "opencode"
|
|
19
|
+
]
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "costgrep-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.7.4",
|
|
4
|
+
"description": "GitHub Actions cost breakdown: human vs AI agent vs bot. Zero-dependency CLI + composite action.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"keywords": ["github-actions", "cost", "finops", "ci", "ai-agents", "copilot", "developer-tools", "cli"],
|
|
8
|
+
"homepage": "https://github.com/Paraphern/costgrep#readme",
|
|
9
|
+
"repository": { "type": "git", "url": "git+https://github.com/Paraphern/costgrep.git" },
|
|
10
|
+
"bugs": "https://github.com/Paraphern/costgrep/issues",
|
|
11
|
+
"bin": { "costgrep": "scripts/costgrep.mjs", "costgrep-cli": "scripts/costgrep.mjs" },
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node --test"
|
|
14
|
+
},
|
|
15
|
+
"engines": { "node": ">=18" },
|
|
16
|
+
"files": ["scripts/", "agents.json", "action.yml", "README.md", "LICENSE"]
|
|
17
|
+
}
|