estela 0.1.40 → 0.2.0
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 +45 -2
- package/dist/billing/assign.d.ts +45 -0
- package/dist/billing/assign.d.ts.map +1 -0
- package/dist/billing/assign.js +159 -0
- package/dist/billing/assign.js.map +1 -0
- package/dist/billing/assign.test.d.ts +2 -0
- package/dist/billing/assign.test.d.ts.map +1 -0
- package/dist/billing/assign.test.js +153 -0
- package/dist/billing/assign.test.js.map +1 -0
- package/dist/bin.js +23 -1
- package/dist/bin.js.map +1 -1
- package/dist/cli-import-recover.test.d.ts +2 -0
- package/dist/cli-import-recover.test.d.ts.map +1 -0
- package/dist/cli-import-recover.test.js +191 -0
- package/dist/cli-import-recover.test.js.map +1 -0
- package/dist/cli-quota.test.js +66 -1
- package/dist/cli-quota.test.js.map +1 -1
- package/dist/cli-statusline.test.d.ts +2 -0
- package/dist/cli-statusline.test.d.ts.map +1 -0
- package/dist/cli-statusline.test.js +333 -0
- package/dist/cli-statusline.test.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +220 -57
- package/dist/cli.js.map +1 -1
- package/dist/db/store.d.ts +2 -0
- package/dist/db/store.d.ts.map +1 -1
- package/dist/db/store.js +24 -1
- package/dist/db/store.js.map +1 -1
- package/dist/demo.js +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +39 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/metrics/quota.d.ts +44 -4
- package/dist/metrics/quota.d.ts.map +1 -1
- package/dist/metrics/quota.js +60 -14
- package/dist/metrics/quota.js.map +1 -1
- package/dist/metrics/quota.test.js +65 -2
- package/dist/metrics/quota.test.js.map +1 -1
- package/dist/project-of.d.ts +52 -0
- package/dist/project-of.d.ts.map +1 -0
- package/dist/project-of.js +124 -0
- package/dist/project-of.js.map +1 -0
- package/dist/project-of.test.d.ts +2 -0
- package/dist/project-of.test.d.ts.map +1 -0
- package/dist/project-of.test.js +203 -0
- package/dist/project-of.test.js.map +1 -0
- package/dist/quota.d.ts +10 -1
- package/dist/quota.d.ts.map +1 -1
- package/dist/quota.js +14 -20
- package/dist/quota.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +10 -27
- package/dist/server.js.map +1 -1
- package/dist/statusline-cache.d.ts +35 -0
- package/dist/statusline-cache.d.ts.map +1 -0
- package/dist/statusline-cache.js +57 -0
- package/dist/statusline-cache.js.map +1 -0
- package/dist/statusline-install.d.ts +52 -0
- package/dist/statusline-install.d.ts.map +1 -0
- package/dist/statusline-install.js +115 -0
- package/dist/statusline-install.js.map +1 -0
- package/dist/statusline-run.d.ts +8 -0
- package/dist/statusline-run.d.ts.map +1 -0
- package/dist/statusline-run.js +256 -0
- package/dist/statusline-run.js.map +1 -0
- package/dist/statusline.d.ts +71 -0
- package/dist/statusline.d.ts.map +1 -0
- package/dist/statusline.js +139 -0
- package/dist/statusline.js.map +1 -0
- package/dist/statusline.test.d.ts +2 -0
- package/dist/statusline.test.d.ts.map +1 -0
- package/dist/statusline.test.js +197 -0
- package/dist/statusline.test.js.map +1 -0
- package/dist/watchers/claude.d.ts +11 -1
- package/dist/watchers/claude.d.ts.map +1 -1
- package/dist/watchers/claude.js +32 -15
- package/dist/watchers/claude.js.map +1 -1
- package/package.json +1 -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;
|