estela 0.1.40 → 0.2.1
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 +46 -2
- package/dist/billing/assign.d.ts +45 -0
- package/dist/billing/assign.js +159 -0
- package/dist/bin.js +23 -1
- package/dist/cli.js +220 -57
- package/dist/db/store.d.ts +2 -0
- package/dist/db/store.js +24 -1
- package/dist/demo.js +1 -1
- package/dist/i18n/en.js +39 -0
- package/dist/metrics/quota.d.ts +44 -4
- package/dist/metrics/quota.js +60 -14
- package/dist/project-of.d.ts +52 -0
- package/dist/project-of.js +124 -0
- package/dist/quota.d.ts +10 -1
- package/dist/quota.js +14 -20
- package/dist/server.js +10 -27
- package/dist/statusline-cache.d.ts +35 -0
- package/dist/statusline-cache.js +57 -0
- package/dist/statusline-install.d.ts +52 -0
- package/dist/statusline-install.js +115 -0
- package/dist/statusline-run.d.ts +8 -0
- package/dist/statusline-run.js +256 -0
- package/dist/statusline.d.ts +71 -0
- package/dist/statusline.js +152 -0
- package/dist/watchers/claude.d.ts +11 -1
- package/dist/watchers/claude.js +32 -15
- package/package.json +3 -1
- package/dist/billing/amortize.d.ts.map +0 -1
- package/dist/billing/amortize.js.map +0 -1
- package/dist/billing/amortize.test.d.ts +0 -2
- package/dist/billing/amortize.test.d.ts.map +0 -1
- package/dist/billing/amortize.test.js +0 -121
- package/dist/billing/amortize.test.js.map +0 -1
- package/dist/billing/format.test.d.ts +0 -2
- package/dist/billing/format.test.d.ts.map +0 -1
- package/dist/billing/format.test.js +0 -110
- package/dist/billing/format.test.js.map +0 -1
- package/dist/billing/invoice.d.ts.map +0 -1
- package/dist/billing/invoice.js.map +0 -1
- package/dist/billing/invoice.test.d.ts +0 -2
- package/dist/billing/invoice.test.d.ts.map +0 -1
- package/dist/billing/invoice.test.js +0 -200
- package/dist/billing/invoice.test.js.map +0 -1
- package/dist/billing/localize.d.ts.map +0 -1
- package/dist/billing/localize.js.map +0 -1
- package/dist/billing/localize.test.d.ts +0 -2
- package/dist/billing/localize.test.d.ts.map +0 -1
- package/dist/billing/localize.test.js +0 -144
- package/dist/billing/localize.test.js.map +0 -1
- package/dist/billing/sessionize.d.ts.map +0 -1
- package/dist/billing/sessionize.js.map +0 -1
- package/dist/billing/sessionize.test.d.ts +0 -2
- package/dist/billing/sessionize.test.d.ts.map +0 -1
- package/dist/billing/sessionize.test.js +0 -187
- package/dist/billing/sessionize.test.js.map +0 -1
- package/dist/billing.d.ts.map +0 -1
- package/dist/billing.js.map +0 -1
- package/dist/bin.d.ts.map +0 -1
- package/dist/bin.js.map +0 -1
- package/dist/cli-badge.test.d.ts +0 -2
- package/dist/cli-badge.test.d.ts.map +0 -1
- package/dist/cli-badge.test.js +0 -118
- package/dist/cli-badge.test.js.map +0 -1
- package/dist/cli-idioma.test.d.ts +0 -2
- package/dist/cli-idioma.test.d.ts.map +0 -1
- package/dist/cli-idioma.test.js +0 -190
- package/dist/cli-idioma.test.js.map +0 -1
- package/dist/cli-quota.test.d.ts +0 -2
- package/dist/cli-quota.test.d.ts.map +0 -1
- package/dist/cli-quota.test.js +0 -216
- package/dist/cli-quota.test.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cloud/auth.d.ts.map +0 -1
- package/dist/cloud/auth.js.map +0 -1
- package/dist/cloud/client.d.ts.map +0 -1
- package/dist/cloud/client.js.map +0 -1
- package/dist/cloud/errors.test.d.ts +0 -2
- package/dist/cloud/errors.test.d.ts.map +0 -1
- package/dist/cloud/errors.test.js +0 -34
- package/dist/cloud/errors.test.js.map +0 -1
- package/dist/db/schema.d.ts.map +0 -1
- package/dist/db/schema.js.map +0 -1
- package/dist/db/schema.test.d.ts +0 -2
- package/dist/db/schema.test.d.ts.map +0 -1
- package/dist/db/schema.test.js +0 -825
- package/dist/db/schema.test.js.map +0 -1
- package/dist/db/store.d.ts.map +0 -1
- package/dist/db/store.js.map +0 -1
- package/dist/demo.d.ts.map +0 -1
- package/dist/demo.js.map +0 -1
- package/dist/demo.test.d.ts +0 -2
- package/dist/demo.test.d.ts.map +0 -1
- package/dist/demo.test.js +0 -148
- package/dist/demo.test.js.map +0 -1
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js.map +0 -1
- package/dist/doctor.test.d.ts +0 -2
- package/dist/doctor.test.d.ts.map +0 -1
- package/dist/doctor.test.js +0 -167
- package/dist/doctor.test.js.map +0 -1
- package/dist/export/badge.d.ts.map +0 -1
- package/dist/export/badge.js.map +0 -1
- package/dist/export/badge.test.d.ts +0 -2
- package/dist/export/badge.test.d.ts.map +0 -1
- package/dist/export/badge.test.js +0 -32
- package/dist/export/badge.test.js.map +0 -1
- package/dist/export/clockify.d.ts.map +0 -1
- package/dist/export/clockify.js.map +0 -1
- package/dist/export/clockify.test.d.ts +0 -2
- package/dist/export/clockify.test.d.ts.map +0 -1
- package/dist/export/clockify.test.js +0 -51
- package/dist/export/clockify.test.js.map +0 -1
- package/dist/export/csv.d.ts.map +0 -1
- package/dist/export/csv.js.map +0 -1
- package/dist/export/dates.d.ts.map +0 -1
- package/dist/export/dates.js.map +0 -1
- package/dist/export/idioma.test.d.ts +0 -2
- package/dist/export/idioma.test.d.ts.map +0 -1
- package/dist/export/idioma.test.js +0 -255
- package/dist/export/idioma.test.js.map +0 -1
- package/dist/export/invoice-pdf.d.ts.map +0 -1
- package/dist/export/invoice-pdf.js.map +0 -1
- package/dist/export/panel.d.ts.map +0 -1
- package/dist/export/panel.js.map +0 -1
- package/dist/export/panel.test.d.ts +0 -2
- package/dist/export/panel.test.d.ts.map +0 -1
- package/dist/export/panel.test.js +0 -386
- package/dist/export/panel.test.js.map +0 -1
- package/dist/export/pdf.d.ts.map +0 -1
- package/dist/export/pdf.js.map +0 -1
- package/dist/export/pdf.test.d.ts +0 -2
- package/dist/export/pdf.test.d.ts.map +0 -1
- package/dist/export/pdf.test.js +0 -172
- package/dist/export/pdf.test.js.map +0 -1
- package/dist/export/pixelfont.d.ts.map +0 -1
- package/dist/export/pixelfont.js.map +0 -1
- package/dist/export/png.d.ts.map +0 -1
- package/dist/export/png.js.map +0 -1
- package/dist/export/receipt.d.ts.map +0 -1
- package/dist/export/receipt.js.map +0 -1
- package/dist/export/receipt.test.d.ts +0 -2
- package/dist/export/receipt.test.d.ts.map +0 -1
- package/dist/export/receipt.test.js +0 -207
- package/dist/export/receipt.test.js.map +0 -1
- package/dist/export/share.d.ts.map +0 -1
- package/dist/export/share.js.map +0 -1
- package/dist/export/share.test.d.ts +0 -2
- package/dist/export/share.test.d.ts.map +0 -1
- package/dist/export/share.test.js +0 -116
- package/dist/export/share.test.js.map +0 -1
- package/dist/i18n/en.d.ts.map +0 -1
- package/dist/i18n/en.js.map +0 -1
- package/dist/i18n/i18n.test.d.ts +0 -2
- package/dist/i18n/i18n.test.d.ts.map +0 -1
- package/dist/i18n/i18n.test.js +0 -226
- package/dist/i18n/i18n.test.js.map +0 -1
- package/dist/i18n/index.d.ts.map +0 -1
- package/dist/i18n/index.js.map +0 -1
- package/dist/i18n/labels.d.ts.map +0 -1
- package/dist/i18n/labels.js.map +0 -1
- package/dist/metrics/index.d.ts.map +0 -1
- package/dist/metrics/index.js.map +0 -1
- package/dist/metrics/metrics.test.d.ts +0 -2
- package/dist/metrics/metrics.test.d.ts.map +0 -1
- package/dist/metrics/metrics.test.js +0 -162
- package/dist/metrics/metrics.test.js.map +0 -1
- package/dist/metrics/people.d.ts.map +0 -1
- package/dist/metrics/people.js.map +0 -1
- package/dist/metrics/people.test.d.ts +0 -2
- package/dist/metrics/people.test.d.ts.map +0 -1
- package/dist/metrics/people.test.js +0 -92
- package/dist/metrics/people.test.js.map +0 -1
- package/dist/metrics/quota.d.ts.map +0 -1
- package/dist/metrics/quota.js.map +0 -1
- package/dist/metrics/quota.test.d.ts +0 -2
- package/dist/metrics/quota.test.d.ts.map +0 -1
- package/dist/metrics/quota.test.js +0 -262
- package/dist/metrics/quota.test.js.map +0 -1
- package/dist/metrics/team.d.ts.map +0 -1
- package/dist/metrics/team.js.map +0 -1
- package/dist/metrics/team.test.d.ts +0 -2
- package/dist/metrics/team.test.d.ts.map +0 -1
- package/dist/metrics/team.test.js +0 -90
- package/dist/metrics/team.test.js.map +0 -1
- package/dist/node-version.d.ts.map +0 -1
- package/dist/node-version.js.map +0 -1
- package/dist/node-version.test.d.ts +0 -2
- package/dist/node-version.test.d.ts.map +0 -1
- package/dist/node-version.test.js +0 -53
- package/dist/node-version.test.js.map +0 -1
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js.map +0 -1
- package/dist/paths.test.d.ts +0 -2
- package/dist/paths.test.d.ts.map +0 -1
- package/dist/paths.test.js +0 -32
- package/dist/paths.test.js.map +0 -1
- package/dist/pricing/catalog.d.ts.map +0 -1
- package/dist/pricing/catalog.js.map +0 -1
- package/dist/pricing/cost.d.ts.map +0 -1
- package/dist/pricing/cost.js.map +0 -1
- package/dist/pricing/cost.test.d.ts +0 -2
- package/dist/pricing/cost.test.d.ts.map +0 -1
- package/dist/pricing/cost.test.js +0 -100
- package/dist/pricing/cost.test.js.map +0 -1
- package/dist/publish.d.ts.map +0 -1
- package/dist/publish.js.map +0 -1
- package/dist/publish.test.d.ts +0 -2
- package/dist/publish.test.d.ts.map +0 -1
- package/dist/publish.test.js +0 -113
- package/dist/publish.test.js.map +0 -1
- package/dist/quota.d.ts.map +0 -1
- package/dist/quota.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/server.test.d.ts +0 -2
- package/dist/server.test.d.ts.map +0 -1
- package/dist/server.test.js +0 -402
- package/dist/server.test.js.map +0 -1
- package/dist/setup.d.ts.map +0 -1
- package/dist/setup.js.map +0 -1
- package/dist/setup.test.d.ts +0 -2
- package/dist/setup.test.d.ts.map +0 -1
- package/dist/setup.test.js +0 -144
- package/dist/setup.test.js.map +0 -1
- package/dist/sync/team.d.ts.map +0 -1
- package/dist/sync/team.js.map +0 -1
- package/dist/sync/team.test.d.ts +0 -2
- package/dist/sync/team.test.d.ts.map +0 -1
- package/dist/sync/team.test.js +0 -102
- package/dist/sync/team.test.js.map +0 -1
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js.map +0 -1
- package/dist/sync.test.d.ts +0 -2
- package/dist/sync.test.d.ts.map +0 -1
- package/dist/sync.test.js +0 -151
- package/dist/sync.test.js.map +0 -1
- package/dist/team.d.ts.map +0 -1
- package/dist/team.js.map +0 -1
- package/dist/team.test.d.ts +0 -2
- package/dist/team.test.d.ts.map +0 -1
- package/dist/team.test.js +0 -163
- package/dist/team.test.js.map +0 -1
- package/dist/updateCheck.d.ts.map +0 -1
- package/dist/updateCheck.js.map +0 -1
- package/dist/updateCheck.test.d.ts +0 -2
- package/dist/updateCheck.test.d.ts.map +0 -1
- package/dist/updateCheck.test.js +0 -27
- package/dist/updateCheck.test.js.map +0 -1
- package/dist/watchers/agents.d.ts.map +0 -1
- package/dist/watchers/agents.js.map +0 -1
- package/dist/watchers/agents.test.d.ts +0 -2
- package/dist/watchers/agents.test.d.ts.map +0 -1
- package/dist/watchers/agents.test.js +0 -96
- package/dist/watchers/agents.test.js.map +0 -1
- package/dist/watchers/claude.d.ts.map +0 -1
- package/dist/watchers/claude.js.map +0 -1
- package/dist/watchers/claude.test.d.ts +0 -2
- package/dist/watchers/claude.test.d.ts.map +0 -1
- package/dist/watchers/claude.test.js +0 -180
- package/dist/watchers/claude.test.js.map +0 -1
- package/dist/watchers/codex.d.ts.map +0 -1
- package/dist/watchers/codex.js.map +0 -1
- package/dist/watchers/codex.test.d.ts +0 -2
- package/dist/watchers/codex.test.d.ts.map +0 -1
- package/dist/watchers/codex.test.js +0 -207
- package/dist/watchers/codex.test.js.map +0 -1
- package/dist/watchers/copilot.d.ts.map +0 -1
- package/dist/watchers/copilot.js.map +0 -1
- package/dist/watchers/copilot.test.d.ts +0 -2
- package/dist/watchers/copilot.test.d.ts.map +0 -1
- package/dist/watchers/copilot.test.js +0 -98
- package/dist/watchers/copilot.test.js.map +0 -1
- package/dist/watchers/git.d.ts.map +0 -1
- package/dist/watchers/git.js.map +0 -1
- package/dist/watchers/git.test.d.ts +0 -2
- package/dist/watchers/git.test.d.ts.map +0 -1
- package/dist/watchers/git.test.js +0 -75
- package/dist/watchers/git.test.js.map +0 -1
- package/dist/watchers/identity.d.ts.map +0 -1
- package/dist/watchers/identity.js.map +0 -1
- package/dist/watchers/identity.test.d.ts +0 -2
- package/dist/watchers/identity.test.d.ts.map +0 -1
- package/dist/watchers/identity.test.js +0 -48
- package/dist/watchers/identity.test.js.map +0 -1
- package/dist/web-ui.test.d.ts +0 -2
- package/dist/web-ui.test.d.ts.map +0 -1
- package/dist/web-ui.test.js +0 -414
- package/dist/web-ui.test.js.map +0 -1
- package/dist/workitems.test.d.ts +0 -2
- package/dist/workitems.test.d.ts.map +0 -1
- package/dist/workitems.test.js +0 -59
- package/dist/workitems.test.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -36,6 +36,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
exports.breakdownOfTurn = void 0;
|
|
38
38
|
const node_path_1 = require("node:path");
|
|
39
|
+
const node_readline_1 = require("node:readline");
|
|
39
40
|
// `node:sqlite` avisa de que es experimental en cada arranque. Es ruido para
|
|
40
41
|
// quien solo quiere su factura, y el aviso no le dice nada accionable.
|
|
41
42
|
process.removeAllListeners("warning");
|
|
@@ -78,6 +79,8 @@ const index_js_1 = require("./i18n/index.js");
|
|
|
78
79
|
const dates_js_1 = require("./export/dates.js");
|
|
79
80
|
const quota_js_1 = require("./metrics/quota.js");
|
|
80
81
|
const quota_js_2 = require("./quota.js");
|
|
82
|
+
const assign_js_1 = require("./billing/assign.js");
|
|
83
|
+
const statusline_install_js_1 = require("./statusline-install.js");
|
|
81
84
|
const labels_js_1 = require("./i18n/labels.js");
|
|
82
85
|
const HELP_ES = `
|
|
83
86
|
estela — registro de horas para desarrollo asistido por IA
|
|
@@ -86,8 +89,12 @@ estela — registro de horas para desarrollo asistido por IA
|
|
|
86
89
|
Primera ejecución: detecta tus agentes y repositorios, y reconstruye
|
|
87
90
|
tu historial. Dos minutos, sin preguntas y sin cuenta.
|
|
88
91
|
|
|
89
|
-
estela import [--since YYYY-MM-DD] [--repo <ruta>]
|
|
92
|
+
estela import [--since YYYY-MM-DD] [--repo <ruta>] [--yes]
|
|
90
93
|
Reconstruye tu historial desde los transcripts de los agentes y git.
|
|
94
|
+
Una sesión en una subcarpeta o en un worktree cuenta para el
|
|
95
|
+
proyecto de su repo. La primera vez que eso suma horas a lo ya
|
|
96
|
+
importado, te enseña cuántas y pregunta (--yes no pregunta). Nunca
|
|
97
|
+
mueve horas de un proyecto a otro ni toca un día aprobado.
|
|
91
98
|
|
|
92
99
|
estela client add --id <id> --name <nombre> --currency <EUR|USD|...>
|
|
93
100
|
[--tax-id <NIF/CIF>] [--email <a@b.com>]
|
|
@@ -146,7 +153,7 @@ estela — registro de horas para desarrollo asistido por IA
|
|
|
146
153
|
estela entry link --entry <id> --items PROJ-12[,AB#1234] | --clear
|
|
147
154
|
Pone a mano los tickets de un bloque. El import ya no los cambia.
|
|
148
155
|
estela ai-cost Reparto de tu cuota entre proyectos.
|
|
149
|
-
estela quota [--json] [--no-refresh]
|
|
156
|
+
estela quota [--json] [--no-refresh] [--weighted]
|
|
150
157
|
¿Qué proyecto se come tu cuota de Claude Code o Codex? Tokens por
|
|
151
158
|
proyecto en la ventana de 5 h actual y en la semana (de lunes a
|
|
152
159
|
hoy), y esta semana frente a la media de tus 4 anteriores en el
|
|
@@ -154,9 +161,24 @@ estela — registro de horas para desarrollo asistido por IA
|
|
|
154
161
|
Anthropic y OpenAI no publican los límites. La ventana de 5 h es
|
|
155
162
|
una estimación: no se sabe cómo redondean su inicio, y lo que uses
|
|
156
163
|
en claude.ai u otro equipo también cuenta y Estela no lo ve.
|
|
164
|
+
Si la status line de Estela está instalada, la ventana de Claude
|
|
165
|
+
Code es la que da Claude Code, no una estimación. Una sesión en un
|
|
166
|
+
worktree o en una subcarpeta cuenta para el proyecto de su repo.
|
|
167
|
+
--weighted reparte el % por precio de lista del modelo en vez de
|
|
168
|
+
por tokens (no es cómo el proveedor cuenta tu límite).
|
|
157
169
|
Copilot queda fuera: no guarda tokens fiables. Antes de calcular
|
|
158
170
|
lee los transcripts desde el lunes (--no-refresh lo evita).
|
|
159
171
|
A diferencia de ai-cost, que reparte dinero, esto reparte tokens.
|
|
172
|
+
estela statusline install [--force | --append]
|
|
173
|
+
Una línea bajo cada sesión de Claude Code: proyecto, horas medidas
|
|
174
|
+
hoy, la parte de tu consumo de la semana que es de este proyecto y,
|
|
175
|
+
si Claude Code los manda, sus porcentajes de límite de la semana y
|
|
176
|
+
de 5 h (tal cual, sin estimar nada). Se calcula en tu máquina en
|
|
177
|
+
unos 50 ms. Añade el bloque a ~/.claude/settings.json sin tocar lo
|
|
178
|
+
demás, y antes deja una copia. Si ya tienes otra status line no la
|
|
179
|
+
pisa: --force la sustituye y --append la encadena detrás.
|
|
180
|
+
estela statusline uninstall
|
|
181
|
+
La quita. Si la instalaste con --append, deja la que tenías.
|
|
160
182
|
estela badge [--project <id>]
|
|
161
183
|
Badge para el README: horas medidas y coste de IA del proyecto.
|
|
162
184
|
Sin --project, el del repositorio en el que estás.
|
|
@@ -256,8 +278,12 @@ estela — time tracking for AI-assisted development
|
|
|
256
278
|
First run: detects your agents and repositories, and rebuilds your
|
|
257
279
|
history. Two minutes, no questions, no account.
|
|
258
280
|
|
|
259
|
-
estela import [--since YYYY-MM-DD] [--repo <path>]
|
|
260
|
-
Rebuilds your history from agent transcripts and git.
|
|
281
|
+
estela import [--since YYYY-MM-DD] [--repo <path>] [--yes]
|
|
282
|
+
Rebuilds your history from agent transcripts and git. A session in a
|
|
283
|
+
subfolder or a worktree counts for its repo's project. The first
|
|
284
|
+
time that adds hours to what was already imported, it shows how many
|
|
285
|
+
and asks (--yes skips the question). It never moves hours from one
|
|
286
|
+
project to another or touches an approved day.
|
|
261
287
|
|
|
262
288
|
estela client add --id <id> --name <name> --currency <EUR|USD|...>
|
|
263
289
|
[--tax-id <tax ID>] [--email <a@b.com>]
|
|
@@ -316,7 +342,7 @@ estela — time tracking for AI-assisted development
|
|
|
316
342
|
estela entry link --entry <id> --items PROJ-12[,AB#1234] | --clear
|
|
317
343
|
Sets a block's tickets by hand. Imports won't change them anymore.
|
|
318
344
|
estela ai-cost How your subscription splits across projects.
|
|
319
|
-
estela quota [--json] [--no-refresh]
|
|
345
|
+
estela quota [--json] [--no-refresh] [--weighted]
|
|
320
346
|
Which project is eating your Claude Code or Codex quota? Tokens per
|
|
321
347
|
project in the current 5-hour window and this week (Monday to
|
|
322
348
|
today), and this week against the average of your previous 4 over
|
|
@@ -324,10 +350,26 @@ estela — time tracking for AI-assisted development
|
|
|
324
350
|
left, because Anthropic and OpenAI don't publish the limits. The
|
|
325
351
|
5-hour window is an estimate: how its start is rounded isn't public,
|
|
326
352
|
and anything you use on claude.ai or another machine also counts and
|
|
327
|
-
Estela can't see it.
|
|
328
|
-
|
|
353
|
+
Estela can't see it. With Estela's status line installed, the
|
|
354
|
+
Claude Code window is the one Claude Code reports, not an estimate.
|
|
355
|
+
A session in a worktree or a subfolder counts for its repo's
|
|
356
|
+
project. --weighted splits the % by the model's list price instead
|
|
357
|
+
of by tokens (not how the provider counts your limit). Copilot is
|
|
358
|
+
left out: it doesn't record reliable tokens. Before calculating it reads transcripts since Monday
|
|
329
359
|
(--no-refresh skips that). Unlike ai-cost, which splits money, this
|
|
330
360
|
splits tokens.
|
|
361
|
+
estela statusline install [--force | --append]
|
|
362
|
+
A line under every Claude Code session: project, hours measured
|
|
363
|
+
today, this project's share of your usage this week and, if Claude
|
|
364
|
+
Code sends them, its weekly and 5-hour limit percentages (as they
|
|
365
|
+
come, nothing estimated). Calculated on your machine in about
|
|
366
|
+
50 ms. It adds the block to ~/.claude/settings.json without touching
|
|
367
|
+
anything else, after making a backup. If you already have another
|
|
368
|
+
status line it won't overwrite it: --force replaces it and --append
|
|
369
|
+
chains it after Estela's.
|
|
370
|
+
estela statusline uninstall
|
|
371
|
+
Removes it. If you installed it with --append, your previous one
|
|
372
|
+
is put back.
|
|
331
373
|
estela badge [--project <id>]
|
|
332
374
|
Badge for your README: the project's measured hours and AI cost.
|
|
333
375
|
Without --project, the one for the repository you're in.
|
|
@@ -687,6 +729,41 @@ async function cmdSetup(args, dbPath) {
|
|
|
687
729
|
db.close();
|
|
688
730
|
}
|
|
689
731
|
}
|
|
732
|
+
/** La vista previa de la primera recuperación: horas por proyecto. */
|
|
733
|
+
function printRecoveryPreview(db, plan) {
|
|
734
|
+
console.log((0, index_js_1.tr) `\nEstela ahora reconoce las sesiones en subcarpetas y worktrees de un repositorio.`);
|
|
735
|
+
console.log((0, index_js_1.tr) `Estas horas no estaban imputadas y se sumarían a su proyecto:\n`);
|
|
736
|
+
for (const [projectId, seconds] of [...plan.recovered].sort((a, b) => b[1] - a[1])) {
|
|
737
|
+
const name = store.getProject(db, projectId)?.name ?? projectId;
|
|
738
|
+
console.log(` ${name.padEnd(30)} +${(0, shared_1.formatDuration)(seconds)}`);
|
|
739
|
+
}
|
|
740
|
+
console.log((0, index_js_1.tr) `\n Nada de lo ya imputado cambia de proyecto, y los días aprobados no se tocan.`);
|
|
741
|
+
printRecoveryBlocked(db, plan.blocked);
|
|
742
|
+
console.log("");
|
|
743
|
+
}
|
|
744
|
+
/** Lo que se recuperaría en días aprobados o facturados: se avisa, no se aplica. */
|
|
745
|
+
function printRecoveryBlocked(db, blocked) {
|
|
746
|
+
if (blocked.length === 0)
|
|
747
|
+
return;
|
|
748
|
+
console.log((0, index_js_1.tr) `\n Sin tocar, porque ese día ya está aprobado o facturado:`);
|
|
749
|
+
for (const b of [...blocked].sort((x, y) => x.day.localeCompare(y.day))) {
|
|
750
|
+
const name = store.getProject(db, b.projectId)?.name ?? b.projectId;
|
|
751
|
+
console.log((0, index_js_1.tr) ` ${b.day} · ${name}: ${(0, shared_1.formatDuration)(b.seconds)} recuperables. Revísalo con: estela entries --project ${b.projectId}`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
/** Una pregunta de sí o no. Sin terminal (un script, CI) la respuesta es no. */
|
|
755
|
+
async function confirm(question) {
|
|
756
|
+
if (!process.stdin.isTTY)
|
|
757
|
+
return false;
|
|
758
|
+
const rl = (0, node_readline_1.createInterface)({ input: process.stdin, output: process.stdout });
|
|
759
|
+
try {
|
|
760
|
+
const answer = (await new Promise((res) => rl.question(question, res))).trim().toLowerCase();
|
|
761
|
+
return ["s", "si", "sí", "y", "yes"].includes(answer);
|
|
762
|
+
}
|
|
763
|
+
finally {
|
|
764
|
+
rl.close();
|
|
765
|
+
}
|
|
766
|
+
}
|
|
690
767
|
/**
|
|
691
768
|
* La estrella se pide una vez, al terminar bien el primer setup: es cuando se
|
|
692
769
|
* acaba de ver funcionar. Casi todo el que instala lo hace por npm y nunca
|
|
@@ -780,57 +857,54 @@ async function cmdImport(args, dbPath) {
|
|
|
780
857
|
const fromAgents = (0, sessionize_js_1.attachCommits)((0, sessionize_js_1.sessionize)(turns), commitsAll);
|
|
781
858
|
const fromCommits = (0, sessionize_js_1.withoutOverlap)((0, sessionize_js_1.sessionizeCommits)(commitsAll), fromAgents);
|
|
782
859
|
const raw = [...fromAgents, ...fromCommits];
|
|
783
|
-
// Se agrupa por PROYECTO, no por ruta: el agente abierto en `repo/` y en
|
|
784
|
-
// `repo/src/UI` son dos rutas del mismo proyecto, y agrupar por ruta
|
|
785
|
-
// producía dos bloques que luego se pisaban al guardarse con el mismo id.
|
|
786
|
-
const porProyecto = (b) => (b.repoPath ? store.projectForRepo(db, b.repoPath) ?? b.repoPath : "");
|
|
787
|
-
const blocks = (0, sessionize_js_1.groupByBranchAndDay)(raw, porProyecto);
|
|
788
860
|
if (fromCommits.length) {
|
|
789
861
|
console.log((0, index_js_1.tr) ` ${fromCommits.length} bloques deducidos solo de commits (sin agente)`);
|
|
790
862
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
863
|
+
// Horas de sesiones en subcarpetas y worktrees, que antes se quedaban sin
|
|
864
|
+
// proyecto (ver billing/assign.ts). La primera vez que cambian horas ya
|
|
865
|
+
// importadas se enseñan antes y se pide confirmación; en una base nueva no
|
|
866
|
+
// hay nada que cambiar, así que se aplica sin preguntar.
|
|
867
|
+
const marca = (0, node_path_1.join)((0, node_path_1.dirname)(dbPath), ".recover-git-root");
|
|
868
|
+
const habiaHoras = db.prepare("SELECT COUNT(*) AS n FROM time_entries").get().n > 0;
|
|
869
|
+
let plan = (0, assign_js_1.planEntries)(db, raw, { recover: true });
|
|
870
|
+
const conRaiz = plan;
|
|
871
|
+
const recuperable = [...plan.recovered.values()].reduce((a, b) => a + b, 0);
|
|
872
|
+
if (!(0, node_fs_1.existsSync)(marca) && habiaHoras && recuperable > 0) {
|
|
873
|
+
printRecoveryPreview(db, plan);
|
|
874
|
+
const ok = args.flags["yes"] === true || await confirm((0, index_js_1.tr) `¿Imputar estas horas? [s/N] `);
|
|
875
|
+
if (!ok) {
|
|
876
|
+
plan = (0, assign_js_1.planEntries)(db, raw, { recover: false });
|
|
877
|
+
console.log((0, index_js_1.tr) ` No se ha tocado nada. Cuando quieras: estela import --yes\n`);
|
|
798
878
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
source: block.turnCount > 0 ? "agent" : "commit",
|
|
815
|
-
kind: "development",
|
|
816
|
-
branch: block.branch,
|
|
817
|
-
// El ticket se saca aquí y no más tarde: en la base solo quedan los
|
|
818
|
-
// hashes de los commits, y es en sus mensajes donde va el `AB#1234`.
|
|
819
|
-
workItems: (0, shared_1.extractWorkItems)(block.branch, block.commits.map((c) => c.subject), project.tracker),
|
|
820
|
-
};
|
|
821
|
-
// El id es determinista (proyecto + día + rama) para que reimportar
|
|
822
|
-
// actualice la imputación en vez de duplicarla. La fecha tiene que ser la
|
|
823
|
-
// LOCAL y la misma que usa el servidor: con toISOString() se generaba un
|
|
824
|
-
// id distinto para el mismo bloque y el día se contaba dos veces.
|
|
825
|
-
const day = (0, shared_1.localDate)(block.startedAt);
|
|
826
|
-
const slug = (block.branch ?? "sin-rama").replace(/[^a-zA-Z0-9]+/g, "-");
|
|
827
|
-
store.saveTimeEntry(db, { ...entry, id: `te_${projectId}_${day}_${slug}` });
|
|
828
|
-
entries++;
|
|
829
|
-
}
|
|
830
|
-
console.log((0, index_js_1.tr) `Bloques: ${raw.length} detectados → ${blocks.length} tras agrupar por rama y día · ` +
|
|
879
|
+
else {
|
|
880
|
+
(0, node_fs_1.writeFileSync)(marca, new Date().toISOString());
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
try {
|
|
885
|
+
(0, node_fs_1.writeFileSync)(marca, new Date().toISOString());
|
|
886
|
+
}
|
|
887
|
+
catch { /* sin marca, se volverá a preguntar */ }
|
|
888
|
+
}
|
|
889
|
+
for (const { entry } of plan.entries)
|
|
890
|
+
store.saveTimeEntry(db, entry);
|
|
891
|
+
const entries = plan.entries.length;
|
|
892
|
+
const unassigned = plan.unassigned;
|
|
893
|
+
console.log((0, index_js_1.tr) `Bloques: ${raw.length} detectados → ${plan.grouped} tras agrupar por rama y día · ` +
|
|
831
894
|
(0, index_js_1.tr) `${entries} imputados · ${unassigned} sin proyecto`);
|
|
832
|
-
|
|
833
|
-
|
|
895
|
+
const aplicadas = [...plan.recovered.values()].reduce((a, b) => a + b, 0);
|
|
896
|
+
if (aplicadas > 0 && (0, node_fs_1.existsSync)(marca)) {
|
|
897
|
+
console.log((0, index_js_1.tr) ` ${(0, shared_1.formatDuration)(aplicadas)} recuperadas de sesiones en subcarpetas y worktrees`);
|
|
898
|
+
}
|
|
899
|
+
printRecoveryBlocked(db, plan.blocked);
|
|
900
|
+
// Si no se confirmó, parte de "sin proyecto" son sesiones de subcarpetas y
|
|
901
|
+
// worktrees: a esas no les falta registrarse, les falta el --yes.
|
|
902
|
+
const porRaiz = unassigned - conRaiz.unassigned;
|
|
903
|
+
if (porRaiz > 0) {
|
|
904
|
+
console.log((0, index_js_1.tr) `\n${porRaiz} de esos bloques son de subcarpetas o worktrees de un proyecto: estela import --yes los imputa.`);
|
|
905
|
+
}
|
|
906
|
+
if (unassigned - porRaiz > 0) {
|
|
907
|
+
console.log((0, index_js_1.tr) `\nHay ${unassigned - porRaiz} bloques sin proyecto asignado. Regístralos con:`);
|
|
834
908
|
console.log((0, index_js_1.tr) ` estela project add --id <id> --client <id> --name <nombre> --repo <ruta>`);
|
|
835
909
|
}
|
|
836
910
|
}
|
|
@@ -1936,6 +2010,74 @@ async function cmdBadge(args, dbPath) {
|
|
|
1936
2010
|
db.close();
|
|
1937
2011
|
}
|
|
1938
2012
|
}
|
|
2013
|
+
/**
|
|
2014
|
+
* `estela statusline install` — la línea de Estela en Claude Code. La lógica
|
|
2015
|
+
* del fichero está en `statusline-install.ts`; aquí, leer, escribir y contar.
|
|
2016
|
+
*/
|
|
2017
|
+
function cmdStatuslineInstall(args) {
|
|
2018
|
+
const path = (0, statusline_install_js_1.claudeSettingsPath)();
|
|
2019
|
+
const before = (0, node_fs_1.existsSync)(path) ? (0, node_fs_1.readFileSync)(path, "utf8") : null;
|
|
2020
|
+
const plan = (0, statusline_install_js_1.planInstall)(before, { force: args.flags["force"] === true, append: args.flags["append"] === true });
|
|
2021
|
+
if (plan.status === "invalid") {
|
|
2022
|
+
throw new UserError((0, index_js_1.tr) `${path} no es un JSON válido. No lo toco: arréglalo y vuelve a intentarlo.`);
|
|
2023
|
+
}
|
|
2024
|
+
if (plan.status === "already") {
|
|
2025
|
+
console.log((0, index_js_1.tr) `La status line de Estela ya está instalada en ${path}.`);
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
if (plan.status === "conflict") {
|
|
2029
|
+
console.log((0, index_js_1.tr) `Ya tienes otra status line en ${path}:\n`);
|
|
2030
|
+
console.log(` ${plan.existing}\n`);
|
|
2031
|
+
console.log((0, index_js_1.tr) `No la toco. Elige:`);
|
|
2032
|
+
console.log((0, index_js_1.tr) ` estela statusline install --append enseña las dos, la de Estela primero`);
|
|
2033
|
+
console.log((0, index_js_1.tr) ` estela statusline install --force sustituye la tuya por la de Estela`);
|
|
2034
|
+
process.exitCode = 1;
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
|
|
2038
|
+
if (before !== null) {
|
|
2039
|
+
const copia = (0, statusline_install_js_1.backupName)(path);
|
|
2040
|
+
(0, node_fs_1.copyFileSync)(path, copia);
|
|
2041
|
+
console.log((0, index_js_1.tr) `Copia de seguridad: ${copia}`);
|
|
2042
|
+
}
|
|
2043
|
+
(0, node_fs_1.writeFileSync)(path, plan.text);
|
|
2044
|
+
console.log(plan.status === "chained"
|
|
2045
|
+
? (0, index_js_1.tr) `Status line de Estela instalada, con la tuya detrás: ${plan.previous ?? ""}`
|
|
2046
|
+
: plan.status === "replaced"
|
|
2047
|
+
? (0, index_js_1.tr) `Status line de Estela instalada en lugar de: ${plan.previous ?? ""}`
|
|
2048
|
+
: (0, index_js_1.tr) `Status line de Estela instalada en ${path}.`);
|
|
2049
|
+
console.log((0, index_js_1.tr) `Se verá en la próxima sesión de Claude Code (o al reiniciar la actual).`);
|
|
2050
|
+
if (!(0, statusline_install_js_1.estelaOnPath)()) {
|
|
2051
|
+
console.log((0, index_js_1.tr) `\nAviso: "estela" no está en tu PATH, así que Claude Code no podrá ejecutarla.`);
|
|
2052
|
+
console.log((0, index_js_1.tr) `Instálala de forma global: npm install -g estela`);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
function cmdStatuslineUninstall() {
|
|
2056
|
+
const path = (0, statusline_install_js_1.claudeSettingsPath)();
|
|
2057
|
+
if (!(0, node_fs_1.existsSync)(path)) {
|
|
2058
|
+
console.log((0, index_js_1.tr) `No hay status line de Estela que quitar.`);
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
const plan = (0, statusline_install_js_1.planUninstall)((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
2062
|
+
if (plan.status === "invalid") {
|
|
2063
|
+
throw new UserError((0, index_js_1.tr) `${path} no es un JSON válido. No lo toco: arréglalo y vuelve a intentarlo.`);
|
|
2064
|
+
}
|
|
2065
|
+
if (plan.status === "absent") {
|
|
2066
|
+
console.log((0, index_js_1.tr) `No hay status line de Estela que quitar.`);
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
if (plan.status === "foreign") {
|
|
2070
|
+
console.log((0, index_js_1.tr) `La status line de ${path} no es la de Estela, así que no la toco:\n\n ${plan.existing}`);
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
const copia = (0, statusline_install_js_1.backupName)(path);
|
|
2074
|
+
(0, node_fs_1.copyFileSync)(path, copia);
|
|
2075
|
+
(0, node_fs_1.writeFileSync)(path, plan.text);
|
|
2076
|
+
console.log((0, index_js_1.tr) `Copia de seguridad: ${copia}`);
|
|
2077
|
+
console.log(plan.status === "restored"
|
|
2078
|
+
? (0, index_js_1.tr) `Status line de Estela quitada; vuelve a estar la que tenías: ${plan.restored ?? ""}`
|
|
2079
|
+
: (0, index_js_1.tr) `Status line de Estela quitada.`);
|
|
2080
|
+
}
|
|
1939
2081
|
/**
|
|
1940
2082
|
* `estela quota` — qué proyecto se come la cuota. El cálculo vive en
|
|
1941
2083
|
* `metrics/quota.ts`; aquí solo se presenta.
|
|
@@ -1957,7 +2099,7 @@ async function cmdQuota(args, dbPath) {
|
|
|
1957
2099
|
console.log((0, index_js_1.tr) `(No se pudieron leer los transcripts de esta semana; se usa lo ya guardado.)`);
|
|
1958
2100
|
}
|
|
1959
2101
|
}
|
|
1960
|
-
const report = (0, quota_js_2.loadQuotaReport)(db, now);
|
|
2102
|
+
const report = (0, quota_js_2.loadQuotaReport)(db, now, { dbPath, weighted: args.flags["weighted"] === true });
|
|
1961
2103
|
if (args.flags["json"] === true) {
|
|
1962
2104
|
console.log(JSON.stringify((0, quota_js_1.quotaToJson)(report), null, 2));
|
|
1963
2105
|
return;
|
|
@@ -1978,7 +2120,9 @@ function printQuota(report, hayTurnos) {
|
|
|
1978
2120
|
for (const a of report.agents) {
|
|
1979
2121
|
const nombre = agentName(a.agent);
|
|
1980
2122
|
if (a.window) {
|
|
1981
|
-
console.log(
|
|
2123
|
+
console.log(a.window.estimated
|
|
2124
|
+
? (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)}`
|
|
2125
|
+
: (0, index_js_1.tr) `${nombre} — ventana de 5 h actual: desde las ${hhmm(a.window.start)}, termina a las ${hhmm(a.window.end)}`);
|
|
1982
2126
|
printQuotaTable(a.window);
|
|
1983
2127
|
}
|
|
1984
2128
|
else if (a.lastWindowEnded) {
|
|
@@ -1990,8 +2134,21 @@ function printQuota(report, hayTurnos) {
|
|
|
1990
2134
|
}
|
|
1991
2135
|
if (report.copilotExcluded)
|
|
1992
2136
|
console.log((0, index_js_1.tr) `Copilot no guarda tokens fiables y queda fuera de este cálculo.`);
|
|
1993
|
-
|
|
1994
|
-
|
|
2137
|
+
if (report.agents.some((a) => a.week.weighted)) {
|
|
2138
|
+
console.log((0, index_js_1.tr) `Ponderado por precio de lista; no es cómo el proveedor cuenta tu límite.`);
|
|
2139
|
+
const sinPrecio = report.agents.reduce((n, a) => n + a.week.unpriced, 0);
|
|
2140
|
+
if (sinPrecio > 0)
|
|
2141
|
+
console.log((0, index_js_1.tr) `${sinPrecio} turnos de modelos sin precio de lista cuentan como 0 en ese %.`);
|
|
2142
|
+
}
|
|
2143
|
+
// La ventana estimada lo dice; la que dio Claude Code no lo necesita, pero
|
|
2144
|
+
// el reparto sigue siendo solo de esta máquina.
|
|
2145
|
+
if (report.agents.some((a) => a.window?.estimated)) {
|
|
2146
|
+
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`);
|
|
2147
|
+
console.log((0, index_js_1.tr) `uses en claude.ai u otro equipo también consume cuota y Estela no lo ve.`);
|
|
2148
|
+
}
|
|
2149
|
+
else if (report.agents.some((a) => a.window)) {
|
|
2150
|
+
console.log((0, index_js_1.tr) `La ventana de 5 h es la que da Claude Code; el reparto solo incluye lo usado en esta máquina.`);
|
|
2151
|
+
}
|
|
1995
2152
|
}
|
|
1996
2153
|
function printQuotaTable(b) {
|
|
1997
2154
|
const filas = b.rows.map((r) => [nombreDeFila(r.bucket), r.usage, pct(r.share)]);
|
|
@@ -2244,6 +2401,12 @@ async function main() {
|
|
|
2244
2401
|
case "quota":
|
|
2245
2402
|
await cmdQuota(args, dbPath);
|
|
2246
2403
|
break;
|
|
2404
|
+
case "statusline install":
|
|
2405
|
+
cmdStatuslineInstall(args);
|
|
2406
|
+
break;
|
|
2407
|
+
case "statusline uninstall":
|
|
2408
|
+
cmdStatuslineUninstall();
|
|
2409
|
+
break;
|
|
2247
2410
|
case "badge":
|
|
2248
2411
|
await cmdBadge(args, dbPath);
|
|
2249
2412
|
break;
|
package/dist/db/store.d.ts
CHANGED
|
@@ -92,6 +92,8 @@ export declare function saveTurns(db: DatabaseSync, turns: readonly AgentTurn[])
|
|
|
92
92
|
* `saveTurns` los guarda con el `turn_id` del origen como clave.
|
|
93
93
|
*/
|
|
94
94
|
export declare function quotaTurns(db: DatabaseSync, from: Date, to: Date): QuotaTurn[];
|
|
95
|
+
/** Turnos completos con `from <= at < to`: lo que necesita `sessionize`. */
|
|
96
|
+
export declare function turnsBetween(db: DatabaseSync, from: Date, to: Date): AgentTurn[];
|
|
95
97
|
/** El primer turno guardado de cada agente: hasta dónde llega su historial. */
|
|
96
98
|
export declare function firstTurnByAgent(db: DatabaseSync): Map<AgentKind, Date>;
|
|
97
99
|
/**
|
package/dist/db/store.js
CHANGED
|
@@ -23,6 +23,7 @@ exports.projectForRepo = projectForRepo;
|
|
|
23
23
|
exports.allProjectRepoPaths = allProjectRepoPaths;
|
|
24
24
|
exports.saveTurns = saveTurns;
|
|
25
25
|
exports.quotaTurns = quotaTurns;
|
|
26
|
+
exports.turnsBetween = turnsBetween;
|
|
26
27
|
exports.firstTurnByAgent = firstTurnByAgent;
|
|
27
28
|
exports.saveCommits = saveCommits;
|
|
28
29
|
exports.logScan = logScan;
|
|
@@ -347,12 +348,13 @@ function saveTurns(db, turns) {
|
|
|
347
348
|
*/
|
|
348
349
|
function quotaTurns(db, from, to) {
|
|
349
350
|
const rows = db.prepare(`
|
|
350
|
-
SELECT agent, at, repo_path, tok_input, tok_output, tok_cache_read, tok_cache_w5m, tok_cache_w1h
|
|
351
|
+
SELECT agent, at, model, repo_path, tok_input, tok_output, tok_cache_read, tok_cache_w5m, tok_cache_w1h
|
|
351
352
|
FROM agent_turns WHERE at >= ? AND at < ?
|
|
352
353
|
`).all(iso(from), iso(to));
|
|
353
354
|
return rows.map((r) => ({
|
|
354
355
|
agent: r["agent"],
|
|
355
356
|
at: new Date(r["at"]),
|
|
357
|
+
model: r["model"],
|
|
356
358
|
repoPath: r["repo_path"] ?? null,
|
|
357
359
|
tokens: {
|
|
358
360
|
input: r["tok_input"], output: r["tok_output"],
|
|
@@ -361,6 +363,27 @@ function quotaTurns(db, from, to) {
|
|
|
361
363
|
},
|
|
362
364
|
}));
|
|
363
365
|
}
|
|
366
|
+
/** Turnos completos con `from <= at < to`: lo que necesita `sessionize`. */
|
|
367
|
+
function turnsBetween(db, from, to) {
|
|
368
|
+
const rows = db.prepare(`
|
|
369
|
+
SELECT * FROM agent_turns WHERE at >= ? AND at < ? ORDER BY at
|
|
370
|
+
`).all(iso(from), iso(to));
|
|
371
|
+
return rows.map((r) => ({
|
|
372
|
+
agent: r["agent"],
|
|
373
|
+
turnId: r["turn_id"],
|
|
374
|
+
sessionId: r["session_id"],
|
|
375
|
+
at: new Date(r["at"]),
|
|
376
|
+
model: r["model"],
|
|
377
|
+
repoPath: r["repo_path"] ?? null,
|
|
378
|
+
branch: r["branch"] ?? null,
|
|
379
|
+
tokens: {
|
|
380
|
+
input: r["tok_input"], output: r["tok_output"],
|
|
381
|
+
cacheRead: r["tok_cache_read"],
|
|
382
|
+
cacheWrite5m: r["tok_cache_w5m"], cacheWrite1h: r["tok_cache_w1h"],
|
|
383
|
+
},
|
|
384
|
+
producerVersion: r["producer_version"] ?? null,
|
|
385
|
+
}));
|
|
386
|
+
}
|
|
364
387
|
/** El primer turno guardado de cada agente: hasta dónde llega su historial. */
|
|
365
388
|
function firstTurnByAgent(db) {
|
|
366
389
|
const rows = db.prepare("SELECT agent, MIN(at) AS first FROM agent_turns GROUP BY agent")
|
package/dist/demo.js
CHANGED
|
@@ -289,7 +289,7 @@ function turnosDeCuota(ahora, hoy) {
|
|
|
289
289
|
break;
|
|
290
290
|
out.push({
|
|
291
291
|
agent: "codex", turnId: `demo_quota_codex_${n++}`, sessionId: `demo-codex-${d}`,
|
|
292
|
-
at, model: "gpt-5-
|
|
292
|
+
at, model: "gpt-5.6-sol", repoPath: RUTA(repo), branch: "main",
|
|
293
293
|
tokens: { input: entre(800, 6000), output: entre(200, 2400), cacheRead: entre(8_000, 60_000),
|
|
294
294
|
cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
295
295
|
producerVersion: "0.46.0",
|
package/dist/i18n/en.js
CHANGED
|
@@ -511,5 +511,44 @@ exports.EN = {
|
|
|
511
511
|
"{0} %": "{0}%",
|
|
512
512
|
"a las {0}": "at {0}",
|
|
513
513
|
"el {0} a las {1}": "on {0} at {1}",
|
|
514
|
+
// ── estela statusline ──
|
|
515
|
+
"{0} no es un JSON válido. No lo toco: arréglalo y vuelve a intentarlo.": "{0} isn't valid JSON. I'm not touching it: fix it and try again.",
|
|
516
|
+
"La status line de Estela ya está instalada en {0}.": "Estela's status line is already installed in {0}.",
|
|
517
|
+
"Ya tienes otra status line en {0}:\n": "You already have another status line in {0}:\n",
|
|
518
|
+
"No la toco. Elige:": "I won't touch it. Choose:",
|
|
519
|
+
" estela statusline install --append enseña las dos, la de Estela primero": " estela statusline install --append show both, Estela's first",
|
|
520
|
+
" estela statusline install --force sustituye la tuya por la de Estela": " estela statusline install --force replace yours with Estela's",
|
|
521
|
+
"Copia de seguridad: {0}": "Backup: {0}",
|
|
522
|
+
"Status line de Estela instalada, con la tuya detrás: {0}": "Estela's status line installed, with yours after it: {0}",
|
|
523
|
+
"Status line de Estela instalada en lugar de: {0}": "Estela's status line installed instead of: {0}",
|
|
524
|
+
"Status line de Estela instalada en {0}.": "Estela's status line installed in {0}.",
|
|
525
|
+
"Se verá en la próxima sesión de Claude Code (o al reiniciar la actual).": "You'll see it in your next Claude Code session (or after restarting the current one).",
|
|
526
|
+
"\nAviso: \"estela\" no está en tu PATH, así que Claude Code no podrá ejecutarla.": "\nWarning: \"estela\" isn't on your PATH, so Claude Code won't be able to run it.",
|
|
527
|
+
"Instálala de forma global: npm install -g estela": "Install it globally: npm install -g estela",
|
|
528
|
+
"No hay status line de Estela que quitar.": "There's no Estela status line to remove.",
|
|
529
|
+
"La status line de {0} no es la de Estela, así que no la toco:\n\n {1}": "The status line in {0} isn't Estela's, so I won't touch it:\n\n {1}",
|
|
530
|
+
"Status line de Estela quitada; vuelve a estar la que tenías: {0}": "Estela's status line removed; your previous one is back: {0}",
|
|
531
|
+
"Status line de Estela quitada.": "Estela's status line removed.",
|
|
532
|
+
"sin proyecto": "no project",
|
|
533
|
+
"hoy {0}": "today {0}",
|
|
534
|
+
"este proyecto {0} %": "this project {0}%",
|
|
535
|
+
"semana {0} %": "week {0}%",
|
|
536
|
+
"5 h {0} %": "5h {0}%",
|
|
537
|
+
// ── quota: ventana oficial y --weighted ──
|
|
538
|
+
"{0} — ventana de 5 h actual: desde las {1}, termina a las {2}": "{0} — current 5-hour window: from {1}, ends at {2}",
|
|
539
|
+
"Ponderado por precio de lista; no es cómo el proveedor cuenta tu límite.": "Weighted by list price; this is not how the provider counts your limit.",
|
|
540
|
+
"{0} turnos de modelos sin precio de lista cuentan como 0 en ese %.": "{0} turns from models without a list price count as 0 in that %.",
|
|
541
|
+
"La ventana de 5 h es la que da Claude Code; el reparto solo incluye lo usado en esta máquina.": "The 5-hour window is the one Claude Code reports; the split only includes what was used on this machine.",
|
|
542
|
+
// ── import: horas de subcarpetas y worktrees ──
|
|
543
|
+
"\nEstela ahora reconoce las sesiones en subcarpetas y worktrees de un repositorio.": "\nEstela now recognizes sessions in a repository's subfolders and worktrees.",
|
|
544
|
+
"Estas horas no estaban imputadas y se sumarían a su proyecto:\n": "These hours weren't assigned and would be added to their project:\n",
|
|
545
|
+
"\n Nada de lo ya imputado cambia de proyecto, y los días aprobados no se tocan.": "\n Nothing already assigned changes project, and approved days aren't touched.",
|
|
546
|
+
"\n Sin tocar, porque ese día ya está aprobado o facturado:": "\n Left alone, because that day is already approved or invoiced:",
|
|
547
|
+
" {0} · {1}: {2} recuperables. Revísalo con: estela entries --project {3}": " {0} · {1}: {2} recoverable. Review it with: estela entries --project {3}",
|
|
548
|
+
"¿Imputar estas horas? [s/N] ": "Assign these hours? [y/N] ",
|
|
549
|
+
" No se ha tocado nada. Cuando quieras: estela import --yes\n": " Nothing was changed. Whenever you want: estela import --yes\n",
|
|
550
|
+
" {0} recuperadas de sesiones en subcarpetas y worktrees": " {0} recovered from sessions in subfolders and worktrees",
|
|
551
|
+
"{0} (sin proyecto)": "{0} (no project)",
|
|
552
|
+
"\n{0} de esos bloques son de subcarpetas o worktrees de un proyecto: estela import --yes los imputa.": "\n{0} of those blocks are from a project's subfolders or worktrees: estela import --yes assigns them.",
|
|
514
553
|
};
|
|
515
554
|
//# sourceMappingURL=en.js.map
|
package/dist/metrics/quota.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface QuotaTurn {
|
|
|
38
38
|
readonly at: Date;
|
|
39
39
|
readonly repoPath: string | null;
|
|
40
40
|
readonly tokens: TokenUsage;
|
|
41
|
+
/** Solo para ponderar por precio (`--weighted`). Sin él, el turno no tiene precio. */
|
|
42
|
+
readonly model?: string;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
45
|
* Consumo en tokens. La escritura de caché va junta (5 min + 1 h): para la
|
|
@@ -59,28 +61,42 @@ export type QuotaBucket = {
|
|
|
59
61
|
readonly kind: "project";
|
|
60
62
|
readonly projectId: string;
|
|
61
63
|
readonly projectName: string;
|
|
62
|
-
}
|
|
64
|
+
}
|
|
65
|
+
/** `folder` es el nombre que se enseña; `path`, la raíz git que agrupa. */
|
|
66
|
+
| {
|
|
63
67
|
readonly kind: "folder";
|
|
64
68
|
readonly folder: string;
|
|
69
|
+
readonly path: string;
|
|
65
70
|
} | {
|
|
66
71
|
readonly kind: "none";
|
|
67
72
|
};
|
|
68
73
|
export interface QuotaRow {
|
|
69
74
|
readonly bucket: QuotaBucket;
|
|
70
75
|
readonly usage: QuotaUsage;
|
|
71
|
-
/**
|
|
76
|
+
/** Lo que valdrían sus tokens a precio de lista, en micro-USD. */
|
|
77
|
+
readonly listCost: number;
|
|
78
|
+
/**
|
|
79
|
+
* Parte del total del periodo, entre 0 y 1: por tokens, o por precio de
|
|
80
|
+
* lista si se pidió ponderar.
|
|
81
|
+
*/
|
|
72
82
|
readonly share: number;
|
|
73
83
|
}
|
|
74
84
|
export interface QuotaBreakdown {
|
|
75
85
|
readonly rows: readonly QuotaRow[];
|
|
76
86
|
readonly total: QuotaUsage;
|
|
87
|
+
readonly listCost: number;
|
|
88
|
+
/** Turnos de un modelo sin precio en el catálogo: al ponderar cuentan como 0. */
|
|
89
|
+
readonly unpriced: number;
|
|
90
|
+
readonly weighted: boolean;
|
|
77
91
|
}
|
|
78
92
|
/**
|
|
79
93
|
* Reparto por proyecto de los turnos con `from <= at < to`, de mayor a menor.
|
|
80
94
|
* Un proyecto sin actividad en el periodo no sale: una fila a cero en una
|
|
81
95
|
* tabla de "quién se come la cuota" es ruido.
|
|
82
96
|
*/
|
|
83
|
-
export declare function breakdown(turns: readonly QuotaTurn[], from: Date, to: Date, bucketOf: (repoPath: string | null) => QuotaBucket
|
|
97
|
+
export declare function breakdown(turns: readonly QuotaTurn[], from: Date, to: Date, bucketOf: (repoPath: string | null) => QuotaBucket, options?: {
|
|
98
|
+
readonly weighted?: boolean;
|
|
99
|
+
}): QuotaBreakdown;
|
|
84
100
|
export declare const WINDOW_HOURS = 5;
|
|
85
101
|
export interface QuotaWindow {
|
|
86
102
|
readonly start: Date;
|
|
@@ -150,7 +166,13 @@ export declare function compareWithHistory(times: readonly {
|
|
|
150
166
|
}[], firstTurnAt: Date | null, now: Date): QuotaComparison;
|
|
151
167
|
export interface AgentQuota {
|
|
152
168
|
readonly agent: QuotaAgent;
|
|
153
|
-
|
|
169
|
+
/**
|
|
170
|
+
* `estimated: false` cuando la ventana la dio Claude Code (`resets_at`, que
|
|
171
|
+
* guarda la status line); si no, es la estimación de `fiveHourWindows`.
|
|
172
|
+
*/
|
|
173
|
+
readonly window: (QuotaWindow & QuotaBreakdown & {
|
|
174
|
+
readonly estimated: boolean;
|
|
175
|
+
}) | null;
|
|
154
176
|
/** Si no hay ventana abierta, cuándo acabó la última de los datos. */
|
|
155
177
|
readonly lastWindowEnded: Date | null;
|
|
156
178
|
readonly week: QuotaBreakdown;
|
|
@@ -174,7 +196,25 @@ export declare function buildQuota(input: {
|
|
|
174
196
|
readonly firstTurnAt: ReadonlyMap<AgentKind, Date>;
|
|
175
197
|
readonly now: Date;
|
|
176
198
|
readonly bucketOf: (repoPath: string | null) => QuotaBucket;
|
|
199
|
+
/**
|
|
200
|
+
* La ventana de 5 h de Claude Code tal como la dio Claude Code (ver
|
|
201
|
+
* `officialWindow`). Solo vale para Claude Code: Codex tiene la suya.
|
|
202
|
+
*/
|
|
203
|
+
readonly claudeWindow?: QuotaWindow | null;
|
|
204
|
+
readonly weighted?: boolean;
|
|
177
205
|
}): QuotaReport;
|
|
206
|
+
/**
|
|
207
|
+
* La ventana de 5 h que dio Claude Code, a partir del último `resets_at` que
|
|
208
|
+
* vio la status line: empieza 5 h antes de su reinicio.
|
|
209
|
+
*
|
|
210
|
+
* Solo vale si el dato tiene menos de 5 h (uno más viejo puede ser de otra
|
|
211
|
+
* ventana) y si el reinicio aún no ha llegado (pasado ese momento, la ventana
|
|
212
|
+
* en curso es otra que nadie ha visto). Si no, null: se estima como siempre.
|
|
213
|
+
*/
|
|
214
|
+
export declare function officialWindow(seen: {
|
|
215
|
+
readonly resetsAt: Date;
|
|
216
|
+
readonly seenAt: Date;
|
|
217
|
+
} | null, now: Date): QuotaWindow | null;
|
|
178
218
|
/** Desde cuándo hay que leer turnos para montar el informe de `now`. */
|
|
179
219
|
export declare function quotaRange(now: Date): QuotaWindow;
|
|
180
220
|
/**
|