estela 0.1.39 → 0.1.40
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 +53 -0
- package/dist/cli-quota.test.d.ts +2 -0
- package/dist/cli-quota.test.d.ts.map +1 -0
- package/dist/cli-quota.test.js +216 -0
- package/dist/cli-quota.test.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +138 -0
- package/dist/cli.js.map +1 -1
- package/dist/db/store.d.ts +10 -1
- package/dist/db/store.d.ts.map +1 -1
- package/dist/db/store.js +29 -0
- package/dist/db/store.js.map +1 -1
- package/dist/demo.d.ts.map +1 -1
- package/dist/demo.js +53 -0
- package/dist/demo.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +29 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/metrics/quota.d.ts +186 -0
- package/dist/metrics/quota.d.ts.map +1 -0
- package/dist/metrics/quota.js +245 -0
- package/dist/metrics/quota.js.map +1 -0
- package/dist/metrics/quota.test.d.ts +2 -0
- package/dist/metrics/quota.test.d.ts.map +1 -0
- package/dist/metrics/quota.test.js +262 -0
- package/dist/metrics/quota.test.js.map +1 -0
- package/dist/quota.d.ts +18 -0
- package/dist/quota.d.ts.map +1 -0
- package/dist/quota.js +85 -0
- package/dist/quota.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +8 -0
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +37 -0
- package/dist/server.test.js.map +1 -1
- package/package.json +1 -1
- package/web/app.css +36 -0
- package/web/app.js +61 -1
- package/web/i18n.js +28 -0
- package/web/index.html +4 -1
package/README.md
CHANGED
|
@@ -189,6 +189,35 @@ different machine, pass `--token` with the existing one, or your client ends up
|
|
|
189
189
|
with a dead link. The Free plan allows one published dashboard at a time; Pro
|
|
190
190
|
and Teams have no limit.
|
|
191
191
|
|
|
192
|
+
The dashboard's footer says "Hours filled in by Estela", linking to
|
|
193
|
+
getestela.dev. On Pro and Teams, `--no-credit` removes that line.
|
|
194
|
+
|
|
195
|
+
## Showing what you built
|
|
196
|
+
|
|
197
|
+
A badge for your project's README, with its measured hours and what the AI
|
|
198
|
+
cost:
|
|
199
|
+
|
|
200
|
+
```sh
|
|
201
|
+
estela badge # the project for the repo you're in
|
|
202
|
+
estela badge --project acme-web
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
It prints a Markdown line with a static [shields.io](https://shields.io) image,
|
|
206
|
+
something like `Built in 42 h · $18 of AI`. Nothing is hosted and nothing is
|
|
207
|
+
looked up: the numbers are baked into the URL, so run it again to update them.
|
|
208
|
+
It only counts **measured** hours, never the ones estimated from commits.
|
|
209
|
+
|
|
210
|
+
And a receipt of your week (or of your whole history) as a PNG:
|
|
211
|
+
|
|
212
|
+
```sh
|
|
213
|
+
estela receipt --week
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Measured versus estimated hours, the AI cost, what caching saved you, and the
|
|
217
|
+
most expensive project and branch. It's drawn on your machine and stays there.
|
|
218
|
+
Project and branch names are left out by default, because they're usually a
|
|
219
|
+
client's; add `--names` to include them.
|
|
220
|
+
|
|
192
221
|
## What the AI actually costs
|
|
193
222
|
|
|
194
223
|
On a flat subscription, your real spend isn't the sum of the tokens: it's the
|
|
@@ -202,6 +231,30 @@ estela ai-cost
|
|
|
202
231
|
Cache is counted separately because it's usually most of the bill — ignoring it
|
|
203
232
|
underestimated spend fivefold.
|
|
204
233
|
|
|
234
|
+
## Which project is eating your quota
|
|
235
|
+
|
|
236
|
+
On Claude Max/Pro or Codex with a subscription, the quota runs out before you
|
|
237
|
+
expect it and nothing tells you where it went. `estela quota` splits it by
|
|
238
|
+
project:
|
|
239
|
+
|
|
240
|
+
```sh
|
|
241
|
+
estela quota # current 5-hour window and this week, per project
|
|
242
|
+
estela quota --json # the same, for scripts
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Tokens (input, output, cache read and cache write) per project, for the current
|
|
246
|
+
5-hour window and for this week (Monday to today, in your time zone), plus this
|
|
247
|
+
week against the average of your previous 4 over the same stretch. Claude Code
|
|
248
|
+
and Codex are shown separately: they draw on different quotas. It's also a card
|
|
249
|
+
in the Summary tab of `estela web`, and it's free.
|
|
250
|
+
|
|
251
|
+
What it never does is tell you how much you have left: Anthropic and OpenAI
|
|
252
|
+
don't publish their plans' limits, so any "X tokens left" would be made up. The
|
|
253
|
+
5-hour window is an estimate — how its start is rounded isn't public, and
|
|
254
|
+
anything you use on claude.ai or another machine counts too but isn't on this
|
|
255
|
+
one. Copilot is left out because it doesn't record reliable tokens. Everything
|
|
256
|
+
is calculated on your machine.
|
|
257
|
+
|
|
205
258
|
## Hours no import will infer
|
|
206
259
|
|
|
207
260
|
Meetings, travel, research, and development without an agent that didn't leave
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-quota.test.d.ts","sourceRoot":"","sources":["../src/cli-quota.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
40
|
+
const node_child_process_1 = require("node:child_process");
|
|
41
|
+
const node_fs_1 = require("node:fs");
|
|
42
|
+
const node_os_1 = require("node:os");
|
|
43
|
+
const node_path_1 = require("node:path");
|
|
44
|
+
const node_test_1 = require("node:test");
|
|
45
|
+
const schema_js_1 = require("./db/schema.js");
|
|
46
|
+
const store = __importStar(require("./db/store.js"));
|
|
47
|
+
const quota_js_1 = require("./metrics/quota.js");
|
|
48
|
+
/**
|
|
49
|
+
* `estela quota`, ejecutando la CLI de verdad.
|
|
50
|
+
*
|
|
51
|
+
* Cada prueba corre con un HOME temporal: el refresco lee los transcripts de
|
|
52
|
+
* ~/.claude, y un test no puede ni leer los de quien lo ejecuta ni depender de
|
|
53
|
+
* que existan.
|
|
54
|
+
*/
|
|
55
|
+
const BIN = (0, node_path_1.join)(__dirname, "bin.js");
|
|
56
|
+
function estela(home, lang, ...args) {
|
|
57
|
+
const r = (0, node_child_process_1.spawnSync)(process.execPath, [BIN, ...args], {
|
|
58
|
+
encoding: "utf8",
|
|
59
|
+
env: { ...process.env, HOME: home, USERPROFILE: home, ESTELA_LANG: lang, NO_COLOR: "1" },
|
|
60
|
+
});
|
|
61
|
+
return { code: r.status, out: r.stdout, err: r.stderr };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Datos fijos para la cuota, válidos a cualquier hora de cualquier día.
|
|
65
|
+
*
|
|
66
|
+
* No sirve la demo: pone las sesiones de Codex por la tarde, y un lunes por
|
|
67
|
+
* la mañana la semana aún no tiene ninguna — estos tests fallaban según el
|
|
68
|
+
* momento en que se ejecutaran. Aquí todo cae a mitad de camino entre el
|
|
69
|
+
* lunes a las 00:00 y ahora, que siempre es esta semana.
|
|
70
|
+
*/
|
|
71
|
+
function sembrarCuota(dbPath) {
|
|
72
|
+
const ahora = new Date();
|
|
73
|
+
const lunes = (0, quota_js_1.weekStart)(ahora);
|
|
74
|
+
const enEstaSemana = new Date((lunes.getTime() + ahora.getTime()) / 2);
|
|
75
|
+
const haceSemanas = (n) => new Date(lunes.getTime() - n * 7 * 86_400_000 + 3600_000);
|
|
76
|
+
let n = 0;
|
|
77
|
+
const t = (agent, at, repo, cacheRead) => ({
|
|
78
|
+
agent, turnId: `q${n++}`, sessionId: "s", at, model: "m", repoPath: repo, branch: "main",
|
|
79
|
+
tokens: { input: 1, output: 10, cacheRead, cacheWrite5m: 0, cacheWrite1h: 0 }, producerVersion: null,
|
|
80
|
+
});
|
|
81
|
+
const db = (0, schema_js_1.openDatabase)(dbPath);
|
|
82
|
+
try {
|
|
83
|
+
store.upsertClient(db, { id: "c", name: "C", currency: "EUR" });
|
|
84
|
+
for (const id of ["web", "api"]) {
|
|
85
|
+
store.upsertProject(db, { id, clientId: "c", name: id, repoPaths: [`/repos/${id}`], billable: true,
|
|
86
|
+
roundingMinutes: 0, aiCostPolicy: "absorbed", kind: "client" });
|
|
87
|
+
}
|
|
88
|
+
store.saveTurns(db, [
|
|
89
|
+
// Claude Code: seis semanas de historial, así que compara.
|
|
90
|
+
t("claude-code", haceSemanas(6), "/repos/web", 100),
|
|
91
|
+
t("claude-code", enEstaSemana, "/repos/web", 3000),
|
|
92
|
+
t("claude-code", enEstaSemana, "/repos/api", 1000),
|
|
93
|
+
// Codex: empezó esta semana, así que no hay con qué comparar.
|
|
94
|
+
t("codex", enEstaSemana, "/repos/api", 500),
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
db.close();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function entorno() {
|
|
102
|
+
const home = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "estela-quota-"));
|
|
103
|
+
return { home, db: (0, node_path_1.join)(home, "estela.db") };
|
|
104
|
+
}
|
|
105
|
+
/** Un transcript de Claude Code con un turno de hace media hora, en `cwd`. */
|
|
106
|
+
function transcript(home, cwd, id, cacheRead) {
|
|
107
|
+
const dir = (0, node_path_1.join)(home, ".claude", "projects", "-proyecto");
|
|
108
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
109
|
+
const at = new Date(Date.now() - 30 * 60_000).toISOString();
|
|
110
|
+
const linea = JSON.stringify({
|
|
111
|
+
type: "assistant", timestamp: at, cwd, gitBranch: "main", version: "2.1.238", sessionId: "s1",
|
|
112
|
+
message: { id, model: "claude-sonnet-4-6",
|
|
113
|
+
usage: { input_tokens: 10, output_tokens: 100, cache_read_input_tokens: cacheRead,
|
|
114
|
+
cache_creation_input_tokens: 0 } },
|
|
115
|
+
});
|
|
116
|
+
// Dos veces la misma fila: Claude Code repite el turno en el transcript.
|
|
117
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, "sesion.jsonl"), `${linea}\n${linea}\n`);
|
|
118
|
+
}
|
|
119
|
+
(0, node_test_1.test)("sin nada guardado, lo dice y sugiere setup en vez de enseñar una tabla vacía", () => {
|
|
120
|
+
const { home, db } = entorno();
|
|
121
|
+
const r = estela(home, "es", "quota", "--db", db, "--no-refresh");
|
|
122
|
+
strict_1.default.equal(r.code, 0, r.err);
|
|
123
|
+
strict_1.default.match(r.out, /Sin turnos de agente guardados\. Empieza con: estela setup/);
|
|
124
|
+
strict_1.default.match(r.out, /no es el límite de tu plan/);
|
|
125
|
+
});
|
|
126
|
+
(0, node_test_1.test)("refresca los transcripts de esta semana antes de calcular, y un turno repetido cuenta una vez", () => {
|
|
127
|
+
const { home, db } = entorno();
|
|
128
|
+
transcript(home, "/repos/web", "msg_cuota_1", 5000);
|
|
129
|
+
const r = estela(home, "es", "quota", "--db", db, "--json");
|
|
130
|
+
strict_1.default.equal(r.code, 0, r.err);
|
|
131
|
+
const j = JSON.parse(r.out);
|
|
132
|
+
strict_1.default.equal(j.agents[0].agent, "claude-code");
|
|
133
|
+
// 10 + 100 + 5000, una sola vez aunque el transcript traiga la fila dos veces.
|
|
134
|
+
strict_1.default.equal(j.agents[0].week.total.total, 5110);
|
|
135
|
+
// Repetirlo no duplica: el turno ya está guardado.
|
|
136
|
+
const otra = JSON.parse(estela(home, "es", "quota", "--db", db, "--json").out);
|
|
137
|
+
strict_1.default.equal(otra.agents[0].week.total.total, 5110);
|
|
138
|
+
});
|
|
139
|
+
(0, node_test_1.test)("--no-refresh no lee transcripts: usa solo lo ya guardado", () => {
|
|
140
|
+
const { home, db } = entorno();
|
|
141
|
+
transcript(home, "/repos/web", "msg_cuota_2", 5000);
|
|
142
|
+
const r = estela(home, "es", "quota", "--db", db, "--no-refresh", "--json");
|
|
143
|
+
strict_1.default.equal(r.code, 0, r.err);
|
|
144
|
+
strict_1.default.deepEqual(JSON.parse(r.out).agents, []);
|
|
145
|
+
});
|
|
146
|
+
(0, node_test_1.test)("sobre la base de la demo nunca lee tus transcripts, aunque no se pase --no-refresh", () => {
|
|
147
|
+
// Mezclaría tu trabajo real con los datos inventados, justo lo que la demo
|
|
148
|
+
// promete no hacer.
|
|
149
|
+
const { home } = entorno();
|
|
150
|
+
const demo = (0, node_path_1.join)(home, "estela-demo.db");
|
|
151
|
+
(0, schema_js_1.openDatabase)(demo).close();
|
|
152
|
+
transcript(home, "/repos/web", "msg_real", 5000);
|
|
153
|
+
const r = estela(home, "es", "quota", "--db", demo, "--json");
|
|
154
|
+
strict_1.default.equal(r.code, 0, r.err);
|
|
155
|
+
const conn = (0, schema_js_1.openDatabase)(demo);
|
|
156
|
+
try {
|
|
157
|
+
strict_1.default.equal(store.firstTurnByAgent(conn).size, 0, "no se guardó ningún turno real en la demo");
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
conn.close();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
(0, node_test_1.test)("--json es JSON y nada más: ni avisos ni cabeceras en la salida", () => {
|
|
164
|
+
const { home, db } = entorno();
|
|
165
|
+
sembrarCuota(db);
|
|
166
|
+
const r = estela(home, "es", "quota", "--db", db, "--no-refresh", "--json");
|
|
167
|
+
strict_1.default.equal(r.code, 0, r.err);
|
|
168
|
+
const j = JSON.parse(r.out);
|
|
169
|
+
strict_1.default.equal(j.windowIsEstimate, true);
|
|
170
|
+
strict_1.default.equal(j.windowHours, 5);
|
|
171
|
+
strict_1.default.deepEqual(j.agents.map((a) => a.agent), ["claude-code", "codex"]);
|
|
172
|
+
// Seis semanas de Claude Code y Codex recién empezado: uno compara, el otro no.
|
|
173
|
+
strict_1.default.equal(j.agents[0].comparison.status, "ok");
|
|
174
|
+
strict_1.default.equal(j.agents[1].comparison.status, "insufficient");
|
|
175
|
+
const shares = j.agents[0].week.projects.map((p) => p.share);
|
|
176
|
+
strict_1.default.ok(Math.abs(shares.reduce((s, x) => s + x, 0) - 1) < 0.001, "los porcentajes suman el 100 %");
|
|
177
|
+
});
|
|
178
|
+
(0, node_test_1.test)("la salida en texto, en los dos idiomas, sin promesas sobre lo que queda", () => {
|
|
179
|
+
const { home, db } = entorno();
|
|
180
|
+
sembrarCuota(db);
|
|
181
|
+
const es = estela(home, "es", "quota", "--db", db, "--no-refresh").out;
|
|
182
|
+
strict_1.default.match(es, /Claude Code — esta semana \(lun \d+ \w+ → hoy\)/);
|
|
183
|
+
strict_1.default.match(es, /Proyecto\s+Entrada\s+Salida\s+Lect\. caché\s+Escr\. caché\s+Total\s+%/);
|
|
184
|
+
strict_1.default.match(es, /Sin historial suficiente para comparar: hacen falta 4 semanas completas y hay \d\./);
|
|
185
|
+
strict_1.default.match(es, /La ventana de 5 h es una estimación/);
|
|
186
|
+
const en = estela(home, "en", "quota", "--db", db, "--no-refresh").out;
|
|
187
|
+
strict_1.default.match(en, /Claude Code — this week \(Mon \w+ \d+ → today\)/);
|
|
188
|
+
strict_1.default.match(en, /Project\s+Input\s+Output\s+Cache read\s+Cache write\s+Total\s+%/);
|
|
189
|
+
strict_1.default.match(en, /The 5-hour window is an estimate/);
|
|
190
|
+
// La regla que no se rompe: nada de "te quedan" ni de cuándo se acaba.
|
|
191
|
+
for (const out of [es, en]) {
|
|
192
|
+
strict_1.default.doesNotMatch(out, /te quedan|quedan \d|se te acaba|you have \d|left until|runs out/i);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
(0, node_test_1.test)("Copilot queda fuera, y se dice en una línea", () => {
|
|
196
|
+
const { home, db } = entorno();
|
|
197
|
+
const conn = (0, schema_js_1.openDatabase)(db);
|
|
198
|
+
try {
|
|
199
|
+
store.saveTurns(conn, [{
|
|
200
|
+
agent: "copilot", turnId: "copilot:1:end", sessionId: "s", at: new Date(Date.now() - 60_000),
|
|
201
|
+
model: "gpt-4o", repoPath: "/repos/web", branch: null,
|
|
202
|
+
tokens: { input: 900, output: 100, cacheRead: 0, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
203
|
+
producerVersion: null,
|
|
204
|
+
}]);
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
conn.close();
|
|
208
|
+
}
|
|
209
|
+
const r = estela(home, "es", "quota", "--db", db, "--no-refresh");
|
|
210
|
+
strict_1.default.match(r.out, /Copilot no guarda tokens fiables y queda fuera de este cálculo\./);
|
|
211
|
+
strict_1.default.match(r.out, /Sin consumo de Claude Code ni Codex esta semana\./);
|
|
212
|
+
const j = JSON.parse(estela(home, "es", "quota", "--db", db, "--no-refresh", "--json").out);
|
|
213
|
+
strict_1.default.deepEqual(j.agents, []);
|
|
214
|
+
strict_1.default.deepEqual(j.excluded.map((e) => e.agent), ["copilot"]);
|
|
215
|
+
});
|
|
216
|
+
//# sourceMappingURL=cli-quota.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-quota.test.js","sourceRoot":"","sources":["../src/cli-quota.test.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA6CA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA6CA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAsoEpD,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -75,6 +75,9 @@ const agents_js_1 = require("./watchers/agents.js");
|
|
|
75
75
|
const git_js_1 = require("./watchers/git.js");
|
|
76
76
|
const identity_js_1 = require("./watchers/identity.js");
|
|
77
77
|
const index_js_1 = require("./i18n/index.js");
|
|
78
|
+
const dates_js_1 = require("./export/dates.js");
|
|
79
|
+
const quota_js_1 = require("./metrics/quota.js");
|
|
80
|
+
const quota_js_2 = require("./quota.js");
|
|
78
81
|
const labels_js_1 = require("./i18n/labels.js");
|
|
79
82
|
const HELP_ES = `
|
|
80
83
|
estela — registro de horas para desarrollo asistido por IA
|
|
@@ -143,6 +146,17 @@ estela — registro de horas para desarrollo asistido por IA
|
|
|
143
146
|
estela entry link --entry <id> --items PROJ-12[,AB#1234] | --clear
|
|
144
147
|
Pone a mano los tickets de un bloque. El import ya no los cambia.
|
|
145
148
|
estela ai-cost Reparto de tu cuota entre proyectos.
|
|
149
|
+
estela quota [--json] [--no-refresh]
|
|
150
|
+
¿Qué proyecto se come tu cuota de Claude Code o Codex? Tokens por
|
|
151
|
+
proyecto en la ventana de 5 h actual y en la semana (de lunes a
|
|
152
|
+
hoy), y esta semana frente a la media de tus 4 anteriores en el
|
|
153
|
+
mismo tramo. Solo consumo real: no dice cuánto te queda, porque
|
|
154
|
+
Anthropic y OpenAI no publican los límites. La ventana de 5 h es
|
|
155
|
+
una estimación: no se sabe cómo redondean su inicio, y lo que uses
|
|
156
|
+
en claude.ai u otro equipo también cuenta y Estela no lo ve.
|
|
157
|
+
Copilot queda fuera: no guarda tokens fiables. Antes de calcular
|
|
158
|
+
lee los transcripts desde el lunes (--no-refresh lo evita).
|
|
159
|
+
A diferencia de ai-cost, que reparte dinero, esto reparte tokens.
|
|
146
160
|
estela badge [--project <id>]
|
|
147
161
|
Badge para el README: horas medidas y coste de IA del proyecto.
|
|
148
162
|
Sin --project, el del repositorio en el que estás.
|
|
@@ -302,6 +316,18 @@ estela — time tracking for AI-assisted development
|
|
|
302
316
|
estela entry link --entry <id> --items PROJ-12[,AB#1234] | --clear
|
|
303
317
|
Sets a block's tickets by hand. Imports won't change them anymore.
|
|
304
318
|
estela ai-cost How your subscription splits across projects.
|
|
319
|
+
estela quota [--json] [--no-refresh]
|
|
320
|
+
Which project is eating your Claude Code or Codex quota? Tokens per
|
|
321
|
+
project in the current 5-hour window and this week (Monday to
|
|
322
|
+
today), and this week against the average of your previous 4 over
|
|
323
|
+
the same stretch. Real usage only: it never says how much you have
|
|
324
|
+
left, because Anthropic and OpenAI don't publish the limits. The
|
|
325
|
+
5-hour window is an estimate: how its start is rounded isn't public,
|
|
326
|
+
and anything you use on claude.ai or another machine also counts and
|
|
327
|
+
Estela can't see it. Copilot is left out: it doesn't record reliable
|
|
328
|
+
tokens. Before calculating it reads transcripts since Monday
|
|
329
|
+
(--no-refresh skips that). Unlike ai-cost, which splits money, this
|
|
330
|
+
splits tokens.
|
|
305
331
|
estela badge [--project <id>]
|
|
306
332
|
Badge for your README: the project's measured hours and AI cost.
|
|
307
333
|
Without --project, the one for the repository you're in.
|
|
@@ -1910,6 +1936,115 @@ async function cmdBadge(args, dbPath) {
|
|
|
1910
1936
|
db.close();
|
|
1911
1937
|
}
|
|
1912
1938
|
}
|
|
1939
|
+
/**
|
|
1940
|
+
* `estela quota` — qué proyecto se come la cuota. El cálculo vive en
|
|
1941
|
+
* `metrics/quota.ts`; aquí solo se presenta.
|
|
1942
|
+
*/
|
|
1943
|
+
async function cmdQuota(args, dbPath) {
|
|
1944
|
+
const db = (0, schema_js_1.openDatabase)(dbPath);
|
|
1945
|
+
try {
|
|
1946
|
+
const now = new Date();
|
|
1947
|
+
// Sobre la base de la demo no se lee nada tuyo: mezclaría tus turnos
|
|
1948
|
+
// reales con los inventados, justo lo que la demo promete no hacer.
|
|
1949
|
+
const esDemo = (0, node_path_1.basename)(dbPath) === "estela-demo.db";
|
|
1950
|
+
if (args.flags["no-refresh"] !== true && !esDemo) {
|
|
1951
|
+
try {
|
|
1952
|
+
await (0, quota_js_2.refreshQuotaTurns)(db, (0, quota_js_1.weekStart)(now));
|
|
1953
|
+
}
|
|
1954
|
+
catch {
|
|
1955
|
+
// Sin refrescar se sigue: lo guardado sigue valiendo, solo más viejo.
|
|
1956
|
+
if (args.flags["json"] !== true)
|
|
1957
|
+
console.log((0, index_js_1.tr) `(No se pudieron leer los transcripts de esta semana; se usa lo ya guardado.)`);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
const report = (0, quota_js_2.loadQuotaReport)(db, now);
|
|
1961
|
+
if (args.flags["json"] === true) {
|
|
1962
|
+
console.log(JSON.stringify((0, quota_js_1.quotaToJson)(report), null, 2));
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
printQuota(report, store.firstTurnByAgent(db).size > 0);
|
|
1966
|
+
}
|
|
1967
|
+
finally {
|
|
1968
|
+
db.close();
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
function printQuota(report, hayTurnos) {
|
|
1972
|
+
console.log((0, index_js_1.tr) `\nConsumo de cuota · lo calcula Estela en tu máquina; no es el límite de tu plan\n`);
|
|
1973
|
+
if (report.agents.length === 0) {
|
|
1974
|
+
console.log(hayTurnos
|
|
1975
|
+
? (0, index_js_1.tr) `Sin consumo de Claude Code ni Codex esta semana.`
|
|
1976
|
+
: (0, index_js_1.tr) `Sin turnos de agente guardados. Empieza con: estela setup`);
|
|
1977
|
+
}
|
|
1978
|
+
for (const a of report.agents) {
|
|
1979
|
+
const nombre = agentName(a.agent);
|
|
1980
|
+
if (a.window) {
|
|
1981
|
+
console.log((0, index_js_1.tr) `${nombre} — ventana de 5 h actual (estimada): desde las ${hhmm(a.window.start)}, termina a las ${hhmm(a.window.end)}`);
|
|
1982
|
+
printQuotaTable(a.window);
|
|
1983
|
+
}
|
|
1984
|
+
else if (a.lastWindowEnded) {
|
|
1985
|
+
console.log((0, index_js_1.tr) `${nombre} — sin ventana de 5 h abierta ahora; la última terminó ${cuando(a.lastWindowEnded, report.generatedAt)}.\n`);
|
|
1986
|
+
}
|
|
1987
|
+
console.log((0, index_js_1.tr) `${nombre} — esta semana (${diaCorto(report.weekStart)} → hoy)`);
|
|
1988
|
+
printQuotaTable(a.week);
|
|
1989
|
+
console.log(` ${comparacion(a, report.generatedAt)}\n`);
|
|
1990
|
+
}
|
|
1991
|
+
if (report.copilotExcluded)
|
|
1992
|
+
console.log((0, index_js_1.tr) `Copilot no guarda tokens fiables y queda fuera de este cálculo.`);
|
|
1993
|
+
console.log((0, index_js_1.tr) `La ventana de 5 h es una estimación: no se publica cómo se redondea su inicio, y lo que`);
|
|
1994
|
+
console.log((0, index_js_1.tr) `uses en claude.ai u otro equipo también consume cuota y Estela no lo ve.`);
|
|
1995
|
+
}
|
|
1996
|
+
function printQuotaTable(b) {
|
|
1997
|
+
const filas = b.rows.map((r) => [nombreDeFila(r.bucket), r.usage, pct(r.share)]);
|
|
1998
|
+
const ancho = Math.min(28, Math.max((0, index_js_1.tr) `Proyecto`.length, (0, index_js_1.tr) `Total`.length, ...filas.map((f) => f[0].length)));
|
|
1999
|
+
const cabeza = [(0, index_js_1.tr) `Entrada`, (0, index_js_1.tr) `Salida`, (0, index_js_1.tr) `Lect. caché`, (0, index_js_1.tr) `Escr. caché`, (0, index_js_1.tr) `Total`];
|
|
2000
|
+
const col = Math.max(10, ...cabeza.map((c) => c.length + 2));
|
|
2001
|
+
const celdas = (u) => [u.input, u.output, u.cacheRead, u.cacheWrite, u.total].map((n) => fmtTokens(n).padStart(col)).join("");
|
|
2002
|
+
const nombre = (t) => (t.length > ancho ? t.slice(0, ancho - 1) + "…" : t).padEnd(ancho);
|
|
2003
|
+
console.log(` ${nombre((0, index_js_1.tr) `Proyecto`)}${cabeza.map((c) => c.padStart(col)).join("")}${"%".padStart(7)}`);
|
|
2004
|
+
for (const [n, u, p] of filas)
|
|
2005
|
+
console.log(` ${nombre(n)}${celdas(u)}${p.padStart(7)}`);
|
|
2006
|
+
console.log(` ${nombre((0, index_js_1.tr) `Total`)}${celdas(b.total)}\n`);
|
|
2007
|
+
}
|
|
2008
|
+
function nombreDeFila(b) {
|
|
2009
|
+
if (b.kind === "project")
|
|
2010
|
+
return b.projectName;
|
|
2011
|
+
if (b.kind === "folder")
|
|
2012
|
+
return (0, index_js_1.tr) `sin proyecto · ${b.folder}`;
|
|
2013
|
+
return (0, index_js_1.tr) `sin carpeta`;
|
|
2014
|
+
}
|
|
2015
|
+
function comparacion(a, now) {
|
|
2016
|
+
const c = a.comparison;
|
|
2017
|
+
if (c.status === "insufficient") {
|
|
2018
|
+
return (0, index_js_1.tr) `Sin historial suficiente para comparar: hacen falta ${quota_js_1.HISTORY_WEEKS} semanas completas y hay ${c.completeWeeks}.`;
|
|
2019
|
+
}
|
|
2020
|
+
const tramo = (0, index_js_1.tr) `lunes → ${dates_js_1.DAYS[(0, index_js_1.getLang)()][now.getDay()]} ${hhmm(now)}`;
|
|
2021
|
+
if (c.ratio === null) {
|
|
2022
|
+
return (0, index_js_1.tr) `Frente a tu historial: ${fmtTokens(c.current)} esta semana; en el mismo tramo (${tramo}) de las ${c.weeks} semanas anteriores no hubo consumo.`;
|
|
2023
|
+
}
|
|
2024
|
+
const diff = Math.round((c.ratio - 1) * 100);
|
|
2025
|
+
const cuanto = diff > 0 ? (0, index_js_1.tr) `un ${diff} % más` : diff < 0 ? (0, index_js_1.tr) `un ${-diff} % menos` : (0, index_js_1.tr) `lo mismo`;
|
|
2026
|
+
return (0, index_js_1.tr) `Frente a tu historial: ${fmtTokens(c.current)} esta semana, y una media de ${fmtTokens(c.average)} en el mismo tramo (${tramo}) de las ${c.weeks} semanas anteriores: ${cuanto}.`;
|
|
2027
|
+
}
|
|
2028
|
+
/** Tokens con el formato corto del idioma: "22,7 M" o "22.7M". */
|
|
2029
|
+
function fmtTokens(n) {
|
|
2030
|
+
return new Intl.NumberFormat((0, index_js_1.getLang)() === "en" ? "en-US" : "es-ES", { notation: "compact", maximumFractionDigits: 1 }).format(n);
|
|
2031
|
+
}
|
|
2032
|
+
function pct(share) {
|
|
2033
|
+
return (0, index_js_1.tr) `${Math.round(share * 100)} %`;
|
|
2034
|
+
}
|
|
2035
|
+
function hhmm(d) {
|
|
2036
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
2037
|
+
}
|
|
2038
|
+
/** "lun 5 oct" o "Mon Oct 5". */
|
|
2039
|
+
function diaCorto(d) {
|
|
2040
|
+
const dia = dates_js_1.DAYS[(0, index_js_1.getLang)()][d.getDay()].slice(0, 3);
|
|
2041
|
+
const mes = dates_js_1.MONTHS[(0, index_js_1.getLang)()][d.getMonth()].slice(0, 3);
|
|
2042
|
+
return (0, index_js_1.getLang)() === "en" ? `${dia} ${mes} ${d.getDate()}` : `${dia} ${d.getDate()} ${mes}`;
|
|
2043
|
+
}
|
|
2044
|
+
/** "a las 14:00" si fue hoy; si no, con su día. */
|
|
2045
|
+
function cuando(d, now) {
|
|
2046
|
+
return (0, shared_1.localDate)(d) === (0, shared_1.localDate)(now) ? (0, index_js_1.tr) `a las ${hhmm(d)}` : (0, index_js_1.tr) `el ${diaCorto(d)} a las ${hhmm(d)}`;
|
|
2047
|
+
}
|
|
1913
2048
|
function cmdReceipt(args, dbPath) {
|
|
1914
2049
|
const db = (0, schema_js_1.openDatabase)(dbPath);
|
|
1915
2050
|
try {
|
|
@@ -2106,6 +2241,9 @@ async function main() {
|
|
|
2106
2241
|
case "ai-cost":
|
|
2107
2242
|
cmdAiCost(dbPath);
|
|
2108
2243
|
break;
|
|
2244
|
+
case "quota":
|
|
2245
|
+
await cmdQuota(args, dbPath);
|
|
2246
|
+
break;
|
|
2109
2247
|
case "badge":
|
|
2110
2248
|
await cmdBadge(args, dbPath);
|
|
2111
2249
|
break;
|