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.
Files changed (293) hide show
  1. package/README.md +46 -2
  2. package/dist/billing/assign.d.ts +45 -0
  3. package/dist/billing/assign.js +159 -0
  4. package/dist/bin.js +23 -1
  5. package/dist/cli.js +220 -57
  6. package/dist/db/store.d.ts +2 -0
  7. package/dist/db/store.js +24 -1
  8. package/dist/demo.js +1 -1
  9. package/dist/i18n/en.js +39 -0
  10. package/dist/metrics/quota.d.ts +44 -4
  11. package/dist/metrics/quota.js +60 -14
  12. package/dist/project-of.d.ts +52 -0
  13. package/dist/project-of.js +124 -0
  14. package/dist/quota.d.ts +10 -1
  15. package/dist/quota.js +14 -20
  16. package/dist/server.js +10 -27
  17. package/dist/statusline-cache.d.ts +35 -0
  18. package/dist/statusline-cache.js +57 -0
  19. package/dist/statusline-install.d.ts +52 -0
  20. package/dist/statusline-install.js +115 -0
  21. package/dist/statusline-run.d.ts +8 -0
  22. package/dist/statusline-run.js +256 -0
  23. package/dist/statusline.d.ts +71 -0
  24. package/dist/statusline.js +152 -0
  25. package/dist/watchers/claude.d.ts +11 -1
  26. package/dist/watchers/claude.js +32 -15
  27. package/package.json +3 -1
  28. package/dist/billing/amortize.d.ts.map +0 -1
  29. package/dist/billing/amortize.js.map +0 -1
  30. package/dist/billing/amortize.test.d.ts +0 -2
  31. package/dist/billing/amortize.test.d.ts.map +0 -1
  32. package/dist/billing/amortize.test.js +0 -121
  33. package/dist/billing/amortize.test.js.map +0 -1
  34. package/dist/billing/format.test.d.ts +0 -2
  35. package/dist/billing/format.test.d.ts.map +0 -1
  36. package/dist/billing/format.test.js +0 -110
  37. package/dist/billing/format.test.js.map +0 -1
  38. package/dist/billing/invoice.d.ts.map +0 -1
  39. package/dist/billing/invoice.js.map +0 -1
  40. package/dist/billing/invoice.test.d.ts +0 -2
  41. package/dist/billing/invoice.test.d.ts.map +0 -1
  42. package/dist/billing/invoice.test.js +0 -200
  43. package/dist/billing/invoice.test.js.map +0 -1
  44. package/dist/billing/localize.d.ts.map +0 -1
  45. package/dist/billing/localize.js.map +0 -1
  46. package/dist/billing/localize.test.d.ts +0 -2
  47. package/dist/billing/localize.test.d.ts.map +0 -1
  48. package/dist/billing/localize.test.js +0 -144
  49. package/dist/billing/localize.test.js.map +0 -1
  50. package/dist/billing/sessionize.d.ts.map +0 -1
  51. package/dist/billing/sessionize.js.map +0 -1
  52. package/dist/billing/sessionize.test.d.ts +0 -2
  53. package/dist/billing/sessionize.test.d.ts.map +0 -1
  54. package/dist/billing/sessionize.test.js +0 -187
  55. package/dist/billing/sessionize.test.js.map +0 -1
  56. package/dist/billing.d.ts.map +0 -1
  57. package/dist/billing.js.map +0 -1
  58. package/dist/bin.d.ts.map +0 -1
  59. package/dist/bin.js.map +0 -1
  60. package/dist/cli-badge.test.d.ts +0 -2
  61. package/dist/cli-badge.test.d.ts.map +0 -1
  62. package/dist/cli-badge.test.js +0 -118
  63. package/dist/cli-badge.test.js.map +0 -1
  64. package/dist/cli-idioma.test.d.ts +0 -2
  65. package/dist/cli-idioma.test.d.ts.map +0 -1
  66. package/dist/cli-idioma.test.js +0 -190
  67. package/dist/cli-idioma.test.js.map +0 -1
  68. package/dist/cli-quota.test.d.ts +0 -2
  69. package/dist/cli-quota.test.d.ts.map +0 -1
  70. package/dist/cli-quota.test.js +0 -216
  71. package/dist/cli-quota.test.js.map +0 -1
  72. package/dist/cli.d.ts.map +0 -1
  73. package/dist/cli.js.map +0 -1
  74. package/dist/cloud/auth.d.ts.map +0 -1
  75. package/dist/cloud/auth.js.map +0 -1
  76. package/dist/cloud/client.d.ts.map +0 -1
  77. package/dist/cloud/client.js.map +0 -1
  78. package/dist/cloud/errors.test.d.ts +0 -2
  79. package/dist/cloud/errors.test.d.ts.map +0 -1
  80. package/dist/cloud/errors.test.js +0 -34
  81. package/dist/cloud/errors.test.js.map +0 -1
  82. package/dist/db/schema.d.ts.map +0 -1
  83. package/dist/db/schema.js.map +0 -1
  84. package/dist/db/schema.test.d.ts +0 -2
  85. package/dist/db/schema.test.d.ts.map +0 -1
  86. package/dist/db/schema.test.js +0 -825
  87. package/dist/db/schema.test.js.map +0 -1
  88. package/dist/db/store.d.ts.map +0 -1
  89. package/dist/db/store.js.map +0 -1
  90. package/dist/demo.d.ts.map +0 -1
  91. package/dist/demo.js.map +0 -1
  92. package/dist/demo.test.d.ts +0 -2
  93. package/dist/demo.test.d.ts.map +0 -1
  94. package/dist/demo.test.js +0 -148
  95. package/dist/demo.test.js.map +0 -1
  96. package/dist/doctor.d.ts.map +0 -1
  97. package/dist/doctor.js.map +0 -1
  98. package/dist/doctor.test.d.ts +0 -2
  99. package/dist/doctor.test.d.ts.map +0 -1
  100. package/dist/doctor.test.js +0 -167
  101. package/dist/doctor.test.js.map +0 -1
  102. package/dist/export/badge.d.ts.map +0 -1
  103. package/dist/export/badge.js.map +0 -1
  104. package/dist/export/badge.test.d.ts +0 -2
  105. package/dist/export/badge.test.d.ts.map +0 -1
  106. package/dist/export/badge.test.js +0 -32
  107. package/dist/export/badge.test.js.map +0 -1
  108. package/dist/export/clockify.d.ts.map +0 -1
  109. package/dist/export/clockify.js.map +0 -1
  110. package/dist/export/clockify.test.d.ts +0 -2
  111. package/dist/export/clockify.test.d.ts.map +0 -1
  112. package/dist/export/clockify.test.js +0 -51
  113. package/dist/export/clockify.test.js.map +0 -1
  114. package/dist/export/csv.d.ts.map +0 -1
  115. package/dist/export/csv.js.map +0 -1
  116. package/dist/export/dates.d.ts.map +0 -1
  117. package/dist/export/dates.js.map +0 -1
  118. package/dist/export/idioma.test.d.ts +0 -2
  119. package/dist/export/idioma.test.d.ts.map +0 -1
  120. package/dist/export/idioma.test.js +0 -255
  121. package/dist/export/idioma.test.js.map +0 -1
  122. package/dist/export/invoice-pdf.d.ts.map +0 -1
  123. package/dist/export/invoice-pdf.js.map +0 -1
  124. package/dist/export/panel.d.ts.map +0 -1
  125. package/dist/export/panel.js.map +0 -1
  126. package/dist/export/panel.test.d.ts +0 -2
  127. package/dist/export/panel.test.d.ts.map +0 -1
  128. package/dist/export/panel.test.js +0 -386
  129. package/dist/export/panel.test.js.map +0 -1
  130. package/dist/export/pdf.d.ts.map +0 -1
  131. package/dist/export/pdf.js.map +0 -1
  132. package/dist/export/pdf.test.d.ts +0 -2
  133. package/dist/export/pdf.test.d.ts.map +0 -1
  134. package/dist/export/pdf.test.js +0 -172
  135. package/dist/export/pdf.test.js.map +0 -1
  136. package/dist/export/pixelfont.d.ts.map +0 -1
  137. package/dist/export/pixelfont.js.map +0 -1
  138. package/dist/export/png.d.ts.map +0 -1
  139. package/dist/export/png.js.map +0 -1
  140. package/dist/export/receipt.d.ts.map +0 -1
  141. package/dist/export/receipt.js.map +0 -1
  142. package/dist/export/receipt.test.d.ts +0 -2
  143. package/dist/export/receipt.test.d.ts.map +0 -1
  144. package/dist/export/receipt.test.js +0 -207
  145. package/dist/export/receipt.test.js.map +0 -1
  146. package/dist/export/share.d.ts.map +0 -1
  147. package/dist/export/share.js.map +0 -1
  148. package/dist/export/share.test.d.ts +0 -2
  149. package/dist/export/share.test.d.ts.map +0 -1
  150. package/dist/export/share.test.js +0 -116
  151. package/dist/export/share.test.js.map +0 -1
  152. package/dist/i18n/en.d.ts.map +0 -1
  153. package/dist/i18n/en.js.map +0 -1
  154. package/dist/i18n/i18n.test.d.ts +0 -2
  155. package/dist/i18n/i18n.test.d.ts.map +0 -1
  156. package/dist/i18n/i18n.test.js +0 -226
  157. package/dist/i18n/i18n.test.js.map +0 -1
  158. package/dist/i18n/index.d.ts.map +0 -1
  159. package/dist/i18n/index.js.map +0 -1
  160. package/dist/i18n/labels.d.ts.map +0 -1
  161. package/dist/i18n/labels.js.map +0 -1
  162. package/dist/metrics/index.d.ts.map +0 -1
  163. package/dist/metrics/index.js.map +0 -1
  164. package/dist/metrics/metrics.test.d.ts +0 -2
  165. package/dist/metrics/metrics.test.d.ts.map +0 -1
  166. package/dist/metrics/metrics.test.js +0 -162
  167. package/dist/metrics/metrics.test.js.map +0 -1
  168. package/dist/metrics/people.d.ts.map +0 -1
  169. package/dist/metrics/people.js.map +0 -1
  170. package/dist/metrics/people.test.d.ts +0 -2
  171. package/dist/metrics/people.test.d.ts.map +0 -1
  172. package/dist/metrics/people.test.js +0 -92
  173. package/dist/metrics/people.test.js.map +0 -1
  174. package/dist/metrics/quota.d.ts.map +0 -1
  175. package/dist/metrics/quota.js.map +0 -1
  176. package/dist/metrics/quota.test.d.ts +0 -2
  177. package/dist/metrics/quota.test.d.ts.map +0 -1
  178. package/dist/metrics/quota.test.js +0 -262
  179. package/dist/metrics/quota.test.js.map +0 -1
  180. package/dist/metrics/team.d.ts.map +0 -1
  181. package/dist/metrics/team.js.map +0 -1
  182. package/dist/metrics/team.test.d.ts +0 -2
  183. package/dist/metrics/team.test.d.ts.map +0 -1
  184. package/dist/metrics/team.test.js +0 -90
  185. package/dist/metrics/team.test.js.map +0 -1
  186. package/dist/node-version.d.ts.map +0 -1
  187. package/dist/node-version.js.map +0 -1
  188. package/dist/node-version.test.d.ts +0 -2
  189. package/dist/node-version.test.d.ts.map +0 -1
  190. package/dist/node-version.test.js +0 -53
  191. package/dist/node-version.test.js.map +0 -1
  192. package/dist/paths.d.ts.map +0 -1
  193. package/dist/paths.js.map +0 -1
  194. package/dist/paths.test.d.ts +0 -2
  195. package/dist/paths.test.d.ts.map +0 -1
  196. package/dist/paths.test.js +0 -32
  197. package/dist/paths.test.js.map +0 -1
  198. package/dist/pricing/catalog.d.ts.map +0 -1
  199. package/dist/pricing/catalog.js.map +0 -1
  200. package/dist/pricing/cost.d.ts.map +0 -1
  201. package/dist/pricing/cost.js.map +0 -1
  202. package/dist/pricing/cost.test.d.ts +0 -2
  203. package/dist/pricing/cost.test.d.ts.map +0 -1
  204. package/dist/pricing/cost.test.js +0 -100
  205. package/dist/pricing/cost.test.js.map +0 -1
  206. package/dist/publish.d.ts.map +0 -1
  207. package/dist/publish.js.map +0 -1
  208. package/dist/publish.test.d.ts +0 -2
  209. package/dist/publish.test.d.ts.map +0 -1
  210. package/dist/publish.test.js +0 -113
  211. package/dist/publish.test.js.map +0 -1
  212. package/dist/quota.d.ts.map +0 -1
  213. package/dist/quota.js.map +0 -1
  214. package/dist/server.d.ts.map +0 -1
  215. package/dist/server.js.map +0 -1
  216. package/dist/server.test.d.ts +0 -2
  217. package/dist/server.test.d.ts.map +0 -1
  218. package/dist/server.test.js +0 -402
  219. package/dist/server.test.js.map +0 -1
  220. package/dist/setup.d.ts.map +0 -1
  221. package/dist/setup.js.map +0 -1
  222. package/dist/setup.test.d.ts +0 -2
  223. package/dist/setup.test.d.ts.map +0 -1
  224. package/dist/setup.test.js +0 -144
  225. package/dist/setup.test.js.map +0 -1
  226. package/dist/sync/team.d.ts.map +0 -1
  227. package/dist/sync/team.js.map +0 -1
  228. package/dist/sync/team.test.d.ts +0 -2
  229. package/dist/sync/team.test.d.ts.map +0 -1
  230. package/dist/sync/team.test.js +0 -102
  231. package/dist/sync/team.test.js.map +0 -1
  232. package/dist/sync.d.ts.map +0 -1
  233. package/dist/sync.js.map +0 -1
  234. package/dist/sync.test.d.ts +0 -2
  235. package/dist/sync.test.d.ts.map +0 -1
  236. package/dist/sync.test.js +0 -151
  237. package/dist/sync.test.js.map +0 -1
  238. package/dist/team.d.ts.map +0 -1
  239. package/dist/team.js.map +0 -1
  240. package/dist/team.test.d.ts +0 -2
  241. package/dist/team.test.d.ts.map +0 -1
  242. package/dist/team.test.js +0 -163
  243. package/dist/team.test.js.map +0 -1
  244. package/dist/updateCheck.d.ts.map +0 -1
  245. package/dist/updateCheck.js.map +0 -1
  246. package/dist/updateCheck.test.d.ts +0 -2
  247. package/dist/updateCheck.test.d.ts.map +0 -1
  248. package/dist/updateCheck.test.js +0 -27
  249. package/dist/updateCheck.test.js.map +0 -1
  250. package/dist/watchers/agents.d.ts.map +0 -1
  251. package/dist/watchers/agents.js.map +0 -1
  252. package/dist/watchers/agents.test.d.ts +0 -2
  253. package/dist/watchers/agents.test.d.ts.map +0 -1
  254. package/dist/watchers/agents.test.js +0 -96
  255. package/dist/watchers/agents.test.js.map +0 -1
  256. package/dist/watchers/claude.d.ts.map +0 -1
  257. package/dist/watchers/claude.js.map +0 -1
  258. package/dist/watchers/claude.test.d.ts +0 -2
  259. package/dist/watchers/claude.test.d.ts.map +0 -1
  260. package/dist/watchers/claude.test.js +0 -180
  261. package/dist/watchers/claude.test.js.map +0 -1
  262. package/dist/watchers/codex.d.ts.map +0 -1
  263. package/dist/watchers/codex.js.map +0 -1
  264. package/dist/watchers/codex.test.d.ts +0 -2
  265. package/dist/watchers/codex.test.d.ts.map +0 -1
  266. package/dist/watchers/codex.test.js +0 -207
  267. package/dist/watchers/codex.test.js.map +0 -1
  268. package/dist/watchers/copilot.d.ts.map +0 -1
  269. package/dist/watchers/copilot.js.map +0 -1
  270. package/dist/watchers/copilot.test.d.ts +0 -2
  271. package/dist/watchers/copilot.test.d.ts.map +0 -1
  272. package/dist/watchers/copilot.test.js +0 -98
  273. package/dist/watchers/copilot.test.js.map +0 -1
  274. package/dist/watchers/git.d.ts.map +0 -1
  275. package/dist/watchers/git.js.map +0 -1
  276. package/dist/watchers/git.test.d.ts +0 -2
  277. package/dist/watchers/git.test.d.ts.map +0 -1
  278. package/dist/watchers/git.test.js +0 -75
  279. package/dist/watchers/git.test.js.map +0 -1
  280. package/dist/watchers/identity.d.ts.map +0 -1
  281. package/dist/watchers/identity.js.map +0 -1
  282. package/dist/watchers/identity.test.d.ts +0 -2
  283. package/dist/watchers/identity.test.d.ts.map +0 -1
  284. package/dist/watchers/identity.test.js +0 -48
  285. package/dist/watchers/identity.test.js.map +0 -1
  286. package/dist/web-ui.test.d.ts +0 -2
  287. package/dist/web-ui.test.d.ts.map +0 -1
  288. package/dist/web-ui.test.js +0 -414
  289. package/dist/web-ui.test.js.map +0 -1
  290. package/dist/workitems.test.d.ts +0 -2
  291. package/dist/workitems.test.d.ts.map +0 -1
  292. package/dist/workitems.test.js +0 -59
  293. 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. Copilot is left out: it doesn't record reliable
328
- tokens. Before calculating it reads transcripts since Monday
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
- let entries = 0;
792
- let unassigned = 0;
793
- for (const block of blocks) {
794
- const projectId = block.repoPath ? store.projectForRepo(db, block.repoPath) : null;
795
- if (!projectId) {
796
- unassigned++;
797
- continue;
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
- const project = store.getProject(db, projectId);
800
- const entry = {
801
- projectId,
802
- startedAt: block.startedAt,
803
- endedAt: block.endedAt,
804
- seconds: block.seconds,
805
- description: (0, sessionize_js_1.describeBlock)(block),
806
- billable: project.billable,
807
- invoiceId: null,
808
- aiCost: block.aiCost,
809
- // Un bloque deducido solo de commits no tuvo agente: ni segundos de
810
- // agente ni agente al que atribuirlos.
811
- agentSeconds: block.turnCount > 0 ? block.seconds : 0,
812
- commitHashes: block.commits.map((c) => c.hash),
813
- agents: block.turnCount > 0 ? [...(block.agents ?? ["claude-code"])] : [],
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
- if (unassigned > 0) {
833
- console.log((0, index_js_1.tr) `\nHay ${unassigned} bloques sin proyecto asignado. Regístralos con:`);
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((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)}`);
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
- 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.`);
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;
@@ -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-codex", repoPath: RUTA(repo), branch: "main",
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
@@ -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
- /** Parte del total del periodo, entre 0 y 1. */
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): QuotaBreakdown;
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
- readonly window: (QuotaWindow & QuotaBreakdown) | null;
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
  /**