@aaa-sentinel/ai-usage 0.1.0 → 0.1.2
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/README.md +272 -69
- package/dist/cli.js +19 -3
- package/package.json +42 -42
package/README.md
CHANGED
|
@@ -4,90 +4,307 @@ Reports the usage of an **individual Cursor, Claude Code, GitHub Copilot or Code
|
|
|
4
4
|
AAA Sentinel, so a personal subscription shows which models it is spent on instead of only a
|
|
5
5
|
plan percentage. The prompt, the code and the conversation stay on the laptop.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- [Who does what](#who-does-what)
|
|
8
|
+
- [What leaves your machine](#what-actually-leaves-your-machine)
|
|
9
|
+
- [Setup, step by step](#setup-step-by-step)
|
|
10
|
+
- [Automatic start](#automatic-start)
|
|
11
|
+
- [Per tool](#per-tool)
|
|
12
|
+
- [Reading the figures](#reading-the-figures)
|
|
13
|
+
- [Troubleshooting](#troubleshooting)
|
|
14
|
+
- [Update and remove](#update-and-remove)
|
|
15
|
+
- [Environment variables](#environment-variables)
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
- An individual AI connection in AAA Sentinel (Cursor, Anthropic, Copilot or OpenAI) and the
|
|
11
|
-
enrollment token it issues. The token is shown once; rotating it invalidates the previous one.
|
|
12
|
-
- For Copilot only: the [GitHub CLI](https://cli.github.com), already signed in (`gh auth login`)
|
|
13
|
-
to the account that holds the Copilot plan.
|
|
17
|
+
## Who does what
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
**The person who manages the organization** adds an individual AI connection in AAA Sentinel
|
|
20
|
+
(Cursor, Anthropic for Claude Code, Copilot, or OpenAI for Codex) with the email of the person who
|
|
21
|
+
uses the tool. In *View connection → Usage agent* they choose **Enroll** and **Copy**: the token is
|
|
22
|
+
shown only once. They send it through a private channel, such as a password manager or a direct
|
|
23
|
+
message deleted afterwards — never a team channel, a ticket or an email with others in copy.
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
**The person who uses the tool** follows the steps below on every computer where they use it, with
|
|
26
|
+
the same token. No administrator rights are needed. After step 5 there is nothing to start by hand.
|
|
27
|
+
|
|
28
|
+
If the account is yours, you do both parts.
|
|
29
|
+
|
|
30
|
+
## What actually leaves your machine
|
|
31
|
+
|
|
32
|
+
One request per day of usage, with **the day, the model and the counters** — input, output,
|
|
33
|
+
cache-read and cache-write tokens, and the number of turns — plus a random identifier generated
|
|
34
|
+
on this machine that only tells two laptops apart. Each run also asks the server which tool the
|
|
35
|
+
connection is for; that request carries nothing but the token.
|
|
36
|
+
|
|
37
|
+
Not the prompt, not the code, not the conversation, not your email, not project names and not the
|
|
38
|
+
path of a transcript. The CLI reads only the tool its connection is for: a connection enrolled for
|
|
39
|
+
Cursor never opens a Claude Code transcript, and `gh` is not called unless the connection is
|
|
40
|
+
Copilot.
|
|
41
|
+
|
|
42
|
+
The figures are not converted to money and are not added to billed or estimated spend.
|
|
43
|
+
|
|
44
|
+
## Setup, step by step
|
|
45
|
+
|
|
46
|
+
Pick the commands for the terminal you use. Install the agent **on the same system where the AI
|
|
47
|
+
tool runs**: if you open Cursor or Claude Code on Windows, use a Windows terminal even if you also
|
|
48
|
+
have WSL.
|
|
49
|
+
|
|
50
|
+
| Terminal | How to open it |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| PowerShell (Windows) | Start → type `PowerShell` → Windows PowerShell, PowerShell 7 or a PowerShell tab in Windows Terminal. Not as administrator |
|
|
53
|
+
| CMD (Windows) | Start → type `cmd` → Command Prompt. Not as administrator |
|
|
54
|
+
| Git Bash (Windows) | Comes with Git for Windows. Start, or right-click a folder → *Open Git Bash here* |
|
|
55
|
+
| WSL | Your distribution from Start, or `wsl` in PowerShell. Only if the AI tool runs inside WSL |
|
|
56
|
+
| macOS | Applications → Utilities → Terminal, or iTerm2. zsh, bash and fish all work |
|
|
57
|
+
| Linux | Any terminal in your desktop session, as your own user (not root) |
|
|
58
|
+
|
|
59
|
+
### 1. Node.js 20 or later
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
node --version
|
|
63
|
+
npm --version
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The first line must start with `v20` or higher. If not:
|
|
67
|
+
|
|
68
|
+
| System | Install the LTS |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| Windows | `winget install OpenJS.NodeJS.LTS`, or the installer from nodejs.org. Then open a new terminal |
|
|
71
|
+
| macOS | `brew install node`, or the installer from nodejs.org |
|
|
72
|
+
| Linux, WSL | Many distributions ship old versions. Use [nvm](https://github.com/nvm-sh/nvm): run `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh \| bash`, open a new terminal, then `nvm install --lts`. Inside WSL, the Windows Node does not count |
|
|
73
|
+
|
|
74
|
+
### 2. Install the agent
|
|
75
|
+
|
|
76
|
+
```sh
|
|
18
77
|
npm install -g @aaa-sentinel/ai-usage
|
|
19
78
|
```
|
|
20
79
|
|
|
21
|
-
|
|
80
|
+
Check that the command exists:
|
|
81
|
+
|
|
82
|
+
| Terminal | Command |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| PowerShell | `Get-Command sentinel-usage` |
|
|
85
|
+
| CMD | `where sentinel-usage` |
|
|
86
|
+
| Git Bash, WSL, macOS, Linux | `command -v sentinel-usage` |
|
|
22
87
|
|
|
23
|
-
|
|
24
|
-
|
|
88
|
+
If it is not found, **close the terminal and open a new one** — the npm folder is added to `PATH`
|
|
89
|
+
when the shell starts. If it is still missing:
|
|
90
|
+
|
|
91
|
+
- **Windows**: the folder printed by `npm prefix -g` must be in your user `PATH` (Settings →
|
|
92
|
+
System → About → Advanced system settings → Environment Variables).
|
|
93
|
+
- **macOS, Linux, WSL**: add it to your shell, then open a new terminal.
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
# bash / zsh (use ~/.zshrc on macOS)
|
|
97
|
+
echo 'export PATH="$(npm prefix -g)/bin:$PATH"' >> ~/.bashrc
|
|
98
|
+
# fish
|
|
99
|
+
fish_add_path (npm prefix -g)/bin
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- **PowerShell says running scripts is disabled**: type `sentinel-usage.cmd` instead of
|
|
103
|
+
`sentinel-usage` in every command. It behaves the same.
|
|
104
|
+
|
|
105
|
+
Do not use `sudo` to install or to log in: the token and the automatic start would end up in the
|
|
106
|
+
root account and would not see your usage.
|
|
107
|
+
|
|
108
|
+
### 3. The token
|
|
109
|
+
|
|
110
|
+
Get it from whoever manages the organization, or, if you manage the connection: *AI connections →
|
|
111
|
+
the connection's card → View connection → Usage agent → Enroll → Copy*. If a token already existed
|
|
112
|
+
and you do not have it, choose **Rotate token**: the previous one stops working on every computer.
|
|
113
|
+
|
|
114
|
+
Each token belongs to one connection — one person and one tool. Treat it like a password.
|
|
115
|
+
|
|
116
|
+
### 4. Connect this computer
|
|
117
|
+
|
|
118
|
+
With the token copied to the clipboard, run the line for your terminal. The token is read from the
|
|
119
|
+
clipboard, so it stays out of the shell history and out of any file.
|
|
120
|
+
|
|
121
|
+
| Terminal | Command |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| PowerShell | `Get-Clipboard \| sentinel-usage login` |
|
|
124
|
+
| CMD | `powershell -NoProfile -Command Get-Clipboard \| sentinel-usage login` |
|
|
125
|
+
| Git Bash | `cat /dev/clipboard \| sentinel-usage login` |
|
|
126
|
+
| WSL | `powershell.exe -NoProfile -Command Get-Clipboard \| sentinel-usage login` |
|
|
127
|
+
| macOS | `pbpaste \| sentinel-usage login` |
|
|
128
|
+
| Linux (Wayland) | `wl-paste \| sentinel-usage login` (needs `wl-clipboard`) |
|
|
129
|
+
| Linux (X11) | `xclip -selection clipboard -o \| sentinel-usage login` (needs `xclip`) |
|
|
130
|
+
|
|
131
|
+
Expected answer (the CLI prints its messages in Spanish):
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
Agente instalado. El arranque subirá el uso; no hace falta lanzarlo en cada sesión.
|
|
25
135
|
```
|
|
26
136
|
|
|
27
|
-
|
|
28
|
-
|
|
137
|
+
`login` stores the token, installs the Cursor hook and schedules the automatic upload. If any step
|
|
138
|
+
fails, nothing is left installed and you can run it again.
|
|
139
|
+
|
|
140
|
+
**Without a clipboard** (for example over SSH), create `token.txt` in the current folder with the
|
|
141
|
+
token as its only line, then:
|
|
142
|
+
|
|
143
|
+
| Terminal | Commands |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| PowerShell | `Get-Content -Raw -Encoding utf8 token.txt \| sentinel-usage login` then `Remove-Item token.txt` |
|
|
146
|
+
| CMD | `sentinel-usage login < token.txt` then `del token.txt` |
|
|
147
|
+
| Git Bash, WSL, macOS, Linux | `sentinel-usage login < token.txt` then `rm token.txt` |
|
|
148
|
+
|
|
149
|
+
- On Windows, Notepad adds `.txt` and File Explorer hides extensions, so the file may end up as
|
|
150
|
+
`token.txt.txt`. Save it with the name `token` only, or check the real name with `dir`.
|
|
151
|
+
- On macOS, TextEdit saves rich text by default: use *Format → Make Plain Text* before saving.
|
|
152
|
+
|
|
153
|
+
The token is never accepted as an argument — it would stay in the shell history — and it is never
|
|
154
|
+
read from the keyboard, so it is not echoed.
|
|
155
|
+
|
|
156
|
+
### 5. Check that it works
|
|
157
|
+
|
|
158
|
+
Use the tool for a moment (one question is enough), then upload without waiting for the next hour:
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
sentinel-usage report
|
|
29
162
|
```
|
|
30
163
|
|
|
31
|
-
|
|
32
|
-
|
|
164
|
+
```text
|
|
165
|
+
Uso aceptado hasta 2026-10-16.
|
|
166
|
+
```
|
|
33
167
|
|
|
34
|
-
`
|
|
35
|
-
Scheduler on Windows, `launchd` on macOS, a `systemd` user timer on Linux). That task runs
|
|
36
|
-
`sentinel-usage report`. You do not start anything when you sit down to work.
|
|
168
|
+
`No hay uso nuevo que subir.` means you have not used the tool since `login`. Then:
|
|
37
169
|
|
|
38
|
-
|
|
170
|
+
```sh
|
|
171
|
+
sentinel-usage status
|
|
172
|
+
```
|
|
39
173
|
|
|
40
|
-
|
|
174
|
+
```text
|
|
175
|
+
agente: instalado
|
|
176
|
+
arranque: sí
|
|
177
|
+
herramienta: claude_code
|
|
178
|
+
último día aceptado: 2026-10-16
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`herramienta` is the tool this connection reads: `cursor`, `claude_code`, `github_copilot` or
|
|
182
|
+
`codex`. In AAA Sentinel the connection's card no longer says *Silent agent* and shows the last
|
|
183
|
+
day, the turns and the tokens.
|
|
184
|
+
|
|
185
|
+
## Automatic start
|
|
186
|
+
|
|
187
|
+
`login` schedules `sentinel-usage report` at sign-in and every hour while a session is open.
|
|
188
|
+
|
|
189
|
+
| System | What is created |
|
|
41
190
|
| --- | --- |
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
191
|
+
| Windows | Two Task Scheduler tasks, `AAA Sentinel AI usage` and `AAA Sentinel AI usage logon`, for your user only, without administrator rights. They run while you are signed in |
|
|
192
|
+
| macOS | A LaunchAgent, `com.aaa-sentinel.ai-usage`, in `~/Library/LaunchAgents`. Run `login` from the Terminal of your session, not over SSH |
|
|
193
|
+
| Linux | A systemd user timer, `aaa-sentinel-ai-usage.timer`. Needs a session with a systemd user instance |
|
|
194
|
+
| WSL | Same as Linux, and WSL needs systemd enabled (below). Runs while WSL is open |
|
|
195
|
+
|
|
196
|
+
To enable systemd in WSL, add this to `/etc/wsl.conf` (with `sudo`) and run `wsl --shutdown` from
|
|
197
|
+
PowerShell:
|
|
198
|
+
|
|
199
|
+
```ini
|
|
200
|
+
[boot]
|
|
201
|
+
systemd=true
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`sentinel-usage status` says `arranque: sí` when the automatic start is in place.
|
|
205
|
+
|
|
206
|
+
## Per tool
|
|
207
|
+
|
|
208
|
+
- **Cursor.** `login` adds a hook to `~/.cursor/hooks.json` that writes one small file per turn to
|
|
209
|
+
a local spool. If Cursor was open, quit it completely and reopen it. If the hook carries token
|
|
210
|
+
counters they are reported per model; the stored input excludes cache, which is sent separately.
|
|
211
|
+
If the counters are missing, the turn is counted and no token number is invented.
|
|
212
|
+
- **Claude Code.** Nothing to configure. The usage counters of the session files in
|
|
213
|
+
`~/.claude/projects` (or under `CLAUDE_CONFIG_DIR`) are read — Claude Code in the terminal, VS
|
|
214
|
+
Code or JetBrains, as long as it runs on this computer with this user. If Claude Code runs in WSL,
|
|
215
|
+
a dev container or a remote machine over SSH, install the agent there. A cost figure that
|
|
216
|
+
sometimes comes with the session is not uploaded. A turn here is one call to the model, so a
|
|
217
|
+
reply that uses tools counts several.
|
|
218
|
+
- **GitHub Copilot.** Needs the [GitHub CLI](https://cli.github.com) signed in to the account that
|
|
219
|
+
holds the Copilot plan: `gh auth login`, then `gh auth status` must show that account
|
|
220
|
+
(`winget install GitHub.cli` on Windows, `brew install gh` on macOS). Premium requests by model
|
|
221
|
+
are read through `gh`; GitHub does not report tokens. This is account usage, not device usage, so
|
|
222
|
+
two laptops do not double it. If `gh` answers 401 or 403, an empty row is not written.
|
|
223
|
+
- **Codex.** Session counters, only when Codex is signed in with the ChatGPT subscription
|
|
224
|
+
(`codex login` → sign in with ChatGPT). If its auth file shows an API key, nothing is attributed
|
|
225
|
+
to the individual account: that spend belongs to the organization connection.
|
|
226
|
+
|
|
227
|
+
Each user on a computer reports **one** connection: a second `login` with another token replaces
|
|
228
|
+
the first. Usage from before `login` is never uploaded.
|
|
229
|
+
|
|
230
|
+
## Reading the figures
|
|
231
|
+
|
|
232
|
+
What the organization sees on the connection's card, for the plan's current renewal period:
|
|
233
|
+
|
|
234
|
+
| Figure | Meaning |
|
|
235
|
+
| --- | --- |
|
|
236
|
+
| Turns | Calls to the model. Cursor: a chat or agent turn. Copilot: premium requests |
|
|
237
|
+
| Input | New tokens sent to the model, **excluding cache** |
|
|
238
|
+
| Cache read | Context the provider already had stored (open code, conversation, instructions) and the model reads again each turn. Usually the largest figure; each token costs a fraction of an input token |
|
|
239
|
+
| Cache write | New context stored for later turns. Costs about the same as, or a little more than, input |
|
|
240
|
+
| Output | What the model generates. The most expensive kind of token |
|
|
241
|
+
|
|
242
|
+
Coding tools resend the same context every turn and the provider serves it from cache, so a
|
|
243
|
+
working day can show a few thousand input tokens and millions read from cache. That is normal, not
|
|
244
|
+
a measurement error. Add input, cache read and cache write to see how much text the model processed.
|
|
245
|
+
|
|
246
|
+
*Silent agent* means the token exists but no usage arrived this period — not a zero. Usage arrives
|
|
247
|
+
hourly while the person is signed in; if the computer was off, it arrives when it is switched on
|
|
248
|
+
again. An Anthropic connection measures Claude Code, not the claude.ai chat; an OpenAI connection
|
|
249
|
+
measures Codex, not the ChatGPT chat.
|
|
250
|
+
|
|
251
|
+
## Troubleshooting
|
|
252
|
+
|
|
253
|
+
| You see | What to do |
|
|
254
|
+
| --- | --- |
|
|
255
|
+
| `sentinel-usage` not recognized / `command not found` | Open a new terminal. Then see step 2 about `PATH`. On Windows you can run the full path: the `npm prefix -g` folder plus `sentinel-usage.cmd` |
|
|
256
|
+
| PowerShell: *running scripts is disabled* | Use `sentinel-usage.cmd`, or `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` if your company allows it |
|
|
257
|
+
| PowerShell: *The '<' operator is reserved for future use* | PowerShell cannot redirect files with `<`; use the PowerShell line from step 4 |
|
|
258
|
+
| `EACCES` from npm on macOS/Linux | Do not use `sudo`. Install Node with nvm, or `npm config set prefix ~/.npm-global`, add `~/.npm-global/bin` to `PATH`, install again |
|
|
259
|
+
| `EBADENGINE`, or `SyntaxError` when running | Node is older than 20. Update it (step 1) |
|
|
260
|
+
| *Redirige el token a la entrada estándar…* | `login` received no token. Use a full command from step 4 |
|
|
261
|
+
| *El token no tiene el formato de un alta de uso.* | The clipboard or file does not hold just the token. Copy it again right before the command, or rotate a new one |
|
|
262
|
+
| Windows: `token.txt` not found | It was saved as `token.txt.txt`. Check with `dir` |
|
|
263
|
+
| Exit code `3` — *No se pudo registrar el arranque* | Windows: a company policy may block Task Scheduler, ask IT. macOS: run `login` in the Terminal of your session, not over SSH. Linux/WSL: no systemd user instance, see [Automatic start](#automatic-start). Nothing is left installed |
|
|
264
|
+
| Exit code `4` — *El servidor no reconoce el token* | The token was rotated or the connection was revoked or removed. Get a new token and repeat step 4 |
|
|
265
|
+
| *El uso de esta herramienta no corresponde a esta conexión…* | The token was enrolled for a different tool. Use the token of the connection that matches this machine's tool |
|
|
266
|
+
| Exit code `5` | Copilot: install `gh` and `gh auth login` to the account that holds Copilot |
|
|
267
|
+
| `sentinel-usage: fetch failed` | No route to `https://api.aaa-sentinel.com` on port 443: proxy or firewall. Ask IT. For npm behind a proxy: `npm config set proxy …` and `npm config set https-proxy …` |
|
|
268
|
+
| The card stays on *Silent agent* | No usage this period yet. Use the tool, run `sentinel-usage report` and read its message |
|
|
46
269
|
|
|
47
270
|
| Exit code | Meaning |
|
|
48
271
|
| --- | --- |
|
|
49
272
|
| `0` | Done, including "there is no new usage to upload" |
|
|
273
|
+
| `1` | Unexpected error, usually the network |
|
|
50
274
|
| `2` | Wrong usage: unknown command, malformed token, or a base URL that is not `https` |
|
|
51
275
|
| `3` | The startup task could not be registered or removed |
|
|
52
276
|
| `4` | The server did not accept a day, or no longer recognizes the token |
|
|
53
277
|
| `5` | Copilot: `gh` is missing or lacks permission to read the usage |
|
|
54
278
|
|
|
55
|
-
|
|
279
|
+
## Update and remove
|
|
56
280
|
|
|
57
|
-
|
|
281
|
+
```sh
|
|
282
|
+
npm install -g @aaa-sentinel/ai-usage@latest
|
|
283
|
+
```
|
|
58
284
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
on this machine that only tells two laptops apart.
|
|
285
|
+
No need to log in again after an update. To remove the agent, **log out first**, then uninstall —
|
|
286
|
+
otherwise the scheduled task keeps pointing to a program that no longer exists:
|
|
62
287
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
288
|
+
```sh
|
|
289
|
+
sentinel-usage logout
|
|
290
|
+
npm uninstall -g @aaa-sentinel/ai-usage
|
|
291
|
+
```
|
|
66
292
|
|
|
67
|
-
|
|
293
|
+
Removing the agent does not delete usage already sent. If you switch Node versions with nvm, fnm or
|
|
294
|
+
Volta, install the package in the new version and log in again with a rotated token: the automatic
|
|
295
|
+
start uses the Node that `login` ran with.
|
|
68
296
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
-
|
|
75
|
-
comes with the session is not uploaded. A turn here is one call to the model, so a reply that
|
|
76
|
-
uses tools counts several.
|
|
77
|
-
- **GitHub Copilot.** Premium requests by model for the personal account, read through `gh`.
|
|
78
|
-
GitHub does not report tokens. This is account usage, not device usage, so two laptops do not
|
|
79
|
-
double it. The GitHub token is not read in order to send it. If `gh` answers 401 or 403, an
|
|
80
|
-
empty row is not written.
|
|
81
|
-
- **Codex.** Session counters, only when Codex is signed in with the ChatGPT subscription. If its
|
|
82
|
-
auth file shows an API key, nothing is attributed to the individual account: that spend belongs
|
|
83
|
-
to the organization connection.
|
|
84
|
-
|
|
85
|
-
Usage from before `login` is never uploaded: it predates the enrollment and the server would
|
|
86
|
-
reject it.
|
|
297
|
+
| Command | What it does |
|
|
298
|
+
| --- | --- |
|
|
299
|
+
| `sentinel-usage login` | Stores the token read from standard input, installs the Cursor hook and registers the startup task. If any step fails, nothing is left installed |
|
|
300
|
+
| `sentinel-usage report` | Uploads the usage that is new since the last accepted day. Run by the startup task; safe to run by hand |
|
|
301
|
+
| `sentinel-usage status` | Says whether the agent and the startup task are installed, which tool this connection reports, and the last accepted day. It does not print the token |
|
|
302
|
+
| `sentinel-usage logout` | Removes the startup task, the Cursor hook and the token from this machine |
|
|
87
303
|
|
|
88
304
|
## Environment variables
|
|
89
305
|
|
|
90
|
-
None is required.
|
|
306
|
+
None is required. The automatic start only sees your permanent user variables on Windows; on
|
|
307
|
+
macOS and Linux it does not read the variables of your shell.
|
|
91
308
|
|
|
92
309
|
| Variable | Meaning |
|
|
93
310
|
| --- | --- |
|
|
@@ -97,23 +314,9 @@ None is required.
|
|
|
97
314
|
| `SENTINEL_USAGE_CLAUDE_DIR` | Claude Code projects folder. Defaults to `projects` under `CLAUDE_CONFIG_DIR` or `~/.claude` |
|
|
98
315
|
| `SENTINEL_USAGE_CODEX_HOME` | Codex folder. Defaults to `CODEX_HOME` or `~/.codex` |
|
|
99
316
|
|
|
100
|
-
The token is stored in `usage.json` inside `SENTINEL_USAGE_HOME
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
**`sentinel-usage` is not found after installing.** Open a new terminal: the global `npm` folder
|
|
105
|
-
is added to `PATH` when the shell starts.
|
|
106
|
-
|
|
107
|
-
**`report` exits with 4 and says the server does not recognize the token.** The token was rotated
|
|
108
|
-
or the connection was removed. Enroll again in the portal and repeat `login` with the new token.
|
|
109
|
-
|
|
110
|
-
**`report` says the usage does not belong to this connection.** The token was enrolled for a
|
|
111
|
-
different tool than the one in use on this machine. Nothing is uploaded; enroll the token from
|
|
112
|
-
the connection that matches the tool.
|
|
113
|
-
|
|
114
|
-
**The connection shows a silent agent in the portal.** The token exists and this renewal period
|
|
115
|
-
has no usage yet. That is not a zero — use the tool and wait for the next hourly run, or run
|
|
116
|
-
`sentinel-usage report` by hand.
|
|
317
|
+
The token is stored in `usage.json` inside `SENTINEL_USAGE_HOME`. On macOS and Linux the file is
|
|
318
|
+
readable only by your user (mode 600); on Windows it inherits the permissions of your profile
|
|
319
|
+
folder.
|
|
117
320
|
|
|
118
321
|
## License
|
|
119
322
|
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { execFile } from "node:child_process";
|
|
5
|
-
import { homedir } from "node:os";
|
|
5
|
+
import { homedir, userInfo } from "node:os";
|
|
6
6
|
import { dirname as dirname2, join as join5 } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { promisify } from "node:util";
|
|
@@ -40,6 +40,7 @@ function createLauncher(options) {
|
|
|
40
40
|
const command = formatCommand(args);
|
|
41
41
|
const uid = options.uid ?? 0;
|
|
42
42
|
const windowsCmd = join(scriptDir, "run.cmd");
|
|
43
|
+
const logonXml = join(scriptDir, "logon.xml");
|
|
43
44
|
return {
|
|
44
45
|
async install() {
|
|
45
46
|
if (platform === "win32") {
|
|
@@ -53,7 +54,21 @@ ${args.map(cmdArg).join(" ")}\r
|
|
|
53
54
|
try {
|
|
54
55
|
await run(exec, "schtasks", ["/Create", "/F", "/TN", TASK_HOURLY, "/SC", "HOURLY", "/ST", "00:00", "/IT", "/TR", tr]);
|
|
55
56
|
hourlyCreated = true;
|
|
56
|
-
|
|
57
|
+
const user = xmlEscape(options.windowsUser ?? "");
|
|
58
|
+
const xml = `<?xml version="1.0" encoding="UTF-16"?>\r
|
|
59
|
+
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">\r
|
|
60
|
+
<Triggers><LogonTrigger><Enabled>true</Enabled><UserId>${user}</UserId></LogonTrigger></Triggers>\r
|
|
61
|
+
<Principals><Principal id="Author"><UserId>${user}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals>\r
|
|
62
|
+
<Settings><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>false</StopIfGoingOnBatteries></Settings>\r
|
|
63
|
+
<Actions Context="Author"><Exec><Command>${xmlEscape(windowsCmd)}</Command></Exec></Actions>\r
|
|
64
|
+
</Task>\r
|
|
65
|
+
`;
|
|
66
|
+
writeFileSync(logonXml, Buffer.from(`\uFEFF${xml}`, "utf16le"));
|
|
67
|
+
try {
|
|
68
|
+
await run(exec, "schtasks", ["/Create", "/F", "/TN", TASK_LOGON, "/XML", logonXml]);
|
|
69
|
+
} finally {
|
|
70
|
+
rmSync(logonXml, { force: true });
|
|
71
|
+
}
|
|
57
72
|
} catch (err) {
|
|
58
73
|
if (previousCmd === null && hourlyCreated) await exec("schtasks", ["/Delete", "/TN", TASK_HOURLY, "/F"]);
|
|
59
74
|
if (previousCmd === null) rmSync(windowsCmd, { force: true });
|
|
@@ -1319,7 +1334,8 @@ async function main() {
|
|
|
1319
1334
|
args: reportArgs(),
|
|
1320
1335
|
homeDir: homedir(),
|
|
1321
1336
|
scriptDir: dirname2(paths.configPath),
|
|
1322
|
-
...uid !== void 0 ? { uid } : {}
|
|
1337
|
+
...uid !== void 0 ? { uid } : {},
|
|
1338
|
+
windowsUser: process.env.USERDOMAIN ? `${process.env.USERDOMAIN}\\${userInfo().username}` : userInfo().username
|
|
1323
1339
|
});
|
|
1324
1340
|
if (command === "login") {
|
|
1325
1341
|
if (tokenArg) {
|
package/package.json
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@aaa-sentinel/ai-usage",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Report individual Cursor, Claude Code, Copilot and Codex usage to AAA Sentinel; prompts and tokens stay on the laptop",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
"sentinel-usage": "dist/cli.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"dist"
|
|
12
|
-
],
|
|
13
|
-
"keywords": [
|
|
14
|
-
"cursor",
|
|
15
|
-
"claude-code",
|
|
16
|
-
"github-copilot",
|
|
17
|
-
"codex",
|
|
18
|
-
"finops",
|
|
19
|
-
"ai-usage"
|
|
20
|
-
],
|
|
21
|
-
"publishConfig": {
|
|
22
|
-
"access": "public"
|
|
23
|
-
},
|
|
24
|
-
"homepage": "https://app.aaa-sentinel.com/developer/ai-usage",
|
|
25
|
-
"bugs": {
|
|
26
|
-
"url": "https://app.aaa-sentinel.com/contact"
|
|
27
|
-
},
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=20"
|
|
30
|
-
},
|
|
31
|
-
"scripts": {
|
|
32
|
-
"build": "esbuild src/cli.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/cli.js --banner:js=\"#!/usr/bin/env node\" && esbuild src/hook.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/hook.js",
|
|
33
|
-
"prepublishOnly": "npm run build",
|
|
34
|
-
"test": "vitest run"
|
|
35
|
-
},
|
|
36
|
-
"devDependencies": {
|
|
37
|
-
"@types/node": "^22.0.0",
|
|
38
|
-
"esbuild": "^0.24.0",
|
|
39
|
-
"typescript": "^5.7.0",
|
|
40
|
-
"vitest": "^2.0.0"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@aaa-sentinel/ai-usage",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Report individual Cursor, Claude Code, Copilot and Codex usage to AAA Sentinel; prompts and tokens stay on the laptop",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"sentinel-usage": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cursor",
|
|
15
|
+
"claude-code",
|
|
16
|
+
"github-copilot",
|
|
17
|
+
"codex",
|
|
18
|
+
"finops",
|
|
19
|
+
"ai-usage"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://app.aaa-sentinel.com/developer/ai-usage",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://app.aaa-sentinel.com/contact"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "esbuild src/cli.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/cli.js --banner:js=\"#!/usr/bin/env node\" && esbuild src/hook.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/hook.js",
|
|
33
|
+
"prepublishOnly": "npm run build",
|
|
34
|
+
"test": "vitest run"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"esbuild": "^0.24.0",
|
|
39
|
+
"typescript": "^5.7.0",
|
|
40
|
+
"vitest": "^2.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|