estela 0.1.20 → 0.1.21
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/dist/billing/amortize.d.ts.map +1 -1
- package/dist/billing/amortize.js +3 -2
- package/dist/billing/amortize.js.map +1 -1
- package/dist/billing/invoice.d.ts.map +1 -1
- package/dist/billing/invoice.js +19 -18
- package/dist/billing/invoice.js.map +1 -1
- package/dist/billing/sessionize.d.ts.map +1 -1
- package/dist/billing/sessionize.js +3 -2
- package/dist/billing/sessionize.js.map +1 -1
- package/dist/billing.d.ts.map +1 -1
- package/dist/billing.js +5 -4
- package/dist/billing.js.map +1 -1
- package/dist/bin.js +6 -2
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +311 -164
- package/dist/cli.js.map +1 -1
- package/dist/cloud/auth.d.ts.map +1 -1
- package/dist/cloud/auth.js +9 -8
- package/dist/cloud/auth.js.map +1 -1
- package/dist/cloud/client.d.ts.map +1 -1
- package/dist/cloud/client.js +11 -6
- package/dist/cloud/client.js.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +4 -3
- package/dist/db/schema.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +50 -45
- package/dist/doctor.js.map +1 -1
- package/dist/i18n/en.d.ts +10 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +326 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/i18n.test.d.ts +2 -0
- package/dist/i18n/i18n.test.d.ts.map +1 -0
- package/dist/i18n/i18n.test.js +201 -0
- package/dist/i18n/i18n.test.js.map +1 -0
- package/dist/i18n/index.d.ts +54 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +106 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/publish.d.ts.map +1 -1
- package/dist/publish.js +6 -5
- package/dist/publish.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +31 -29
- package/dist/server.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +8 -5
- package/dist/setup.js.map +1 -1
- package/dist/sync/team.d.ts.map +1 -1
- package/dist/sync/team.js +7 -6
- package/dist/sync/team.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +7 -6
- package/dist/sync.js.map +1 -1
- package/dist/team.d.ts.map +1 -1
- package/dist/team.js +9 -8
- package/dist/team.js.map +1 -1
- package/dist/updateCheck.d.ts.map +1 -1
- package/dist/updateCheck.js +3 -2
- package/dist/updateCheck.js.map +1 -1
- package/dist/watchers/claude.d.ts.map +1 -1
- package/dist/watchers/claude.js +3 -2
- package/dist/watchers/claude.js.map +1 -1
- package/node_modules/@estela/shared/dist/money.d.ts +1 -0
- package/node_modules/@estela/shared/dist/money.d.ts.map +1 -1
- package/node_modules/@estela/shared/dist/money.js +9 -1
- package/node_modules/@estela/shared/dist/money.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -69,7 +69,8 @@ Object.defineProperty(exports, "breakdownOfTurn", { enumerable: true, get: funct
|
|
|
69
69
|
const claude_js_1 = require("./watchers/claude.js");
|
|
70
70
|
const git_js_1 = require("./watchers/git.js");
|
|
71
71
|
const identity_js_1 = require("./watchers/identity.js");
|
|
72
|
-
const
|
|
72
|
+
const index_js_1 = require("./i18n/index.js");
|
|
73
|
+
const HELP_ES = `
|
|
73
74
|
estela — registro de horas para desarrollo asistido por IA
|
|
74
75
|
|
|
75
76
|
estela setup
|
|
@@ -184,8 +185,132 @@ estela — registro de horas para desarrollo asistido por IA
|
|
|
184
185
|
--from-tax-id y --from-email se ignoran.
|
|
185
186
|
|
|
186
187
|
Opciones globales: --db <ruta> (por defecto ${schema_js_1.DEFAULT_DB_PATH})
|
|
188
|
+
--lang es|en Idioma. Sin ella se usa el del sistema;
|
|
189
|
+
ESTELA_LANG=en lo fija para siempre.
|
|
187
190
|
--version Solo imprime la versión instalada.
|
|
188
191
|
`;
|
|
192
|
+
const HELP_EN = `
|
|
193
|
+
estela — time tracking for AI-assisted development
|
|
194
|
+
|
|
195
|
+
estela setup
|
|
196
|
+
First run: detects your agents and repositories, and rebuilds your
|
|
197
|
+
history. Two minutes, no questions, no account.
|
|
198
|
+
|
|
199
|
+
estela import [--since YYYY-MM-DD] [--repo <path>]
|
|
200
|
+
Rebuilds your history from agent transcripts and git.
|
|
201
|
+
|
|
202
|
+
estela client add --id <id> --name <name> --currency <EUR|USD|...>
|
|
203
|
+
[--tax-id <tax ID>] [--email <a@b.com>]
|
|
204
|
+
--tax-id and --email are the client details printed on the report
|
|
205
|
+
from "estela report". Running the command again with the same --id
|
|
206
|
+
rewrites the whole record: pass everything you want to keep again,
|
|
207
|
+
anything you leave out is cleared.
|
|
208
|
+
estela project add --id <id> --client <id> --name <name> --repo <path>
|
|
209
|
+
[--rounding <min>] [--ai-cost absorbed|passthrough]
|
|
210
|
+
estela rate set --project <id> --rate <amount> [--from YYYY-MM-DD]
|
|
211
|
+
estela budget --project <id> [--amount <dollars>|none]
|
|
212
|
+
Monthly AI budget. Without --amount, shows the current one. Feeds the
|
|
213
|
+
warning on the Team tab.
|
|
214
|
+
|
|
215
|
+
estela subscription add --id <id> --name <name> --fee <fee> [--currency USD]
|
|
216
|
+
Records a flat subscription (Claude Max, Cursor...). With one, your
|
|
217
|
+
real spend is the fee split by usage, not the per-token total.
|
|
218
|
+
|
|
219
|
+
estela log --project <id> --hours <n> --what "<what you did>"
|
|
220
|
+
[--kind development|meeting|research|review|travel|support|other]
|
|
221
|
+
[--date YYYY-MM-DD] [--minutes <n>]
|
|
222
|
+
Hours no import will ever infer: meetings, travel, research, and
|
|
223
|
+
development without an agent that didn't leave commits either.
|
|
224
|
+
|
|
225
|
+
estela author --project <id> [--email <a@b.com>[,other@c.com]]
|
|
226
|
+
Which email you commit with on that project. Without --email, lists
|
|
227
|
+
the repository's authors so you can pick yours.
|
|
228
|
+
|
|
229
|
+
estela web [--port 4319] Opens the dashboard in your browser.
|
|
230
|
+
estela doctor Checks your data and flags anything that
|
|
231
|
+
would ruin a demo. Run it before publishing.
|
|
232
|
+
estela status What's been captured and not yet assigned.
|
|
233
|
+
estela entries --project <id> Blocks assigned to a project.
|
|
234
|
+
estela ai-cost How your subscription splits across projects.
|
|
235
|
+
estela share --project <id> [--from YYYY-MM-DD] [--to YYYY-MM-DD]
|
|
236
|
+
[--author <your name>] [--with-amounts] [--out <file.html>]
|
|
237
|
+
Shareable report in a single HTML file. Hours and commits; no AI
|
|
238
|
+
usage, which is yours as long as you're the one paying for it.
|
|
239
|
+
|
|
240
|
+
estela publish --project <id> [--author <your name>] [--with-amounts]
|
|
241
|
+
[--token <existing>] [--no-team] [--client <email>[,other]]
|
|
242
|
+
Read-only dashboard so your client can USE the product, not just
|
|
243
|
+
receive a document. Hosted on getestela.dev — Free allows one at a
|
|
244
|
+
time, Pro and Teams have no limit. Requires "estela login" first.
|
|
245
|
+
With --client they get an email and it shows up in their account
|
|
246
|
+
when they sign in to getestela.dev/app. Without --client that list
|
|
247
|
+
isn't touched: republishing never unlinks anyone who already had it.
|
|
248
|
+
|
|
249
|
+
estela team ai-cost --project <id> --allow | --deny
|
|
250
|
+
If whoever invited you asks to see the project's AI cost, this is
|
|
251
|
+
your answer. Without --allow it never leaves your machine, and
|
|
252
|
+
silence isn't a yes. It's shared summed for the project, never per person.
|
|
253
|
+
|
|
254
|
+
estela login --email <you@domain.com> [--api <url>]
|
|
255
|
+
Links this machine to your account. Without it, Estela stays 100%
|
|
256
|
+
local: nothing below changes what you already do without an account.
|
|
257
|
+
estela logout
|
|
258
|
+
Unlinks this machine. Your local data isn't touched.
|
|
259
|
+
|
|
260
|
+
estela sync enable --project <id>
|
|
261
|
+
Turns on sync for a project across your machines. A Pro feature;
|
|
262
|
+
without it, that project never leaves this machine.
|
|
263
|
+
estela sync [--project <id>]
|
|
264
|
+
Uploads and downloads what changed since the last sync. Without
|
|
265
|
+
--project, syncs every enabled project (personal and team).
|
|
266
|
+
|
|
267
|
+
estela team invite --project <id> --email <a@b.com>[,other@c.com] [--kind employee|freelancer]
|
|
268
|
+
Invites someone so their hours on that project are actually measured
|
|
269
|
+
on the dashboard, instead of estimated from commits. A Teams feature.
|
|
270
|
+
Use the emails they commit with, not the ones they sign in with.
|
|
271
|
+
estela team accept --token <token> [--as-id <local-id>]
|
|
272
|
+
Accepts an invitation and gets the project ready to sync. Without
|
|
273
|
+
--as-id it keeps the name it has on the team that invited you; you
|
|
274
|
+
only need it if you already have a project of your own with that name.
|
|
275
|
+
estela team repo --project <id> --add <path>
|
|
276
|
+
Links your local clone to a team project you've already accepted.
|
|
277
|
+
estela team list --project <id>
|
|
278
|
+
Who has accepted, their measured hours, and when they last synced.
|
|
279
|
+
estela team revoke --token <token>
|
|
280
|
+
Removes someone from the team. If you pay for Teams, the charge goes down.
|
|
281
|
+
|
|
282
|
+
estela upgrade --plan pro|teams [--annual]
|
|
283
|
+
Starts the plan. Prints a Stripe Checkout link — payment happens
|
|
284
|
+
there, never in the terminal. --annual bills the whole year at once,
|
|
285
|
+
with two months off compared to monthly.
|
|
286
|
+
estela billing portal
|
|
287
|
+
Change your card or cancel. Stripe handles it, not us.
|
|
288
|
+
estela account
|
|
289
|
+
Your email, your plan, and which machines you've signed in from.
|
|
290
|
+
|
|
291
|
+
estela export --project <id> [--out <file.csv>]
|
|
292
|
+
|
|
293
|
+
estela report --project <id> --cutoff YYYY-MM-DD [--dry-run]
|
|
294
|
+
[--pdf <file.pdf>] [--csv <file.csv>] [--fx <rate>]
|
|
295
|
+
[--number <no.>]
|
|
296
|
+
[--from-name <your name>] [--from-email <email>]
|
|
297
|
+
[--from-tax-id <your tax ID>]
|
|
298
|
+
Report of hours and commits to back up your work. It isn't an
|
|
299
|
+
invoice: Estela doesn't issue tax documents.
|
|
300
|
+
--number sets the report number; without it, reports are numbered
|
|
301
|
+
automatically and sequentially per year (INF-${new Date().getFullYear()}-001, -002...).
|
|
302
|
+
The --from-* flags are your details as the issuer, in the PDF header.
|
|
303
|
+
They need --from-name: without a name that block isn't printed, and
|
|
304
|
+
--from-tax-id and --from-email are ignored.
|
|
305
|
+
|
|
306
|
+
Global options: --db <path> (default ${schema_js_1.DEFAULT_DB_PATH})
|
|
307
|
+
--lang es|en Language. Without it, your system's is used;
|
|
308
|
+
ESTELA_LANG=en sets it for good.
|
|
309
|
+
--version Only prints the installed version.
|
|
310
|
+
`;
|
|
311
|
+
function helpText() {
|
|
312
|
+
return (0, index_js_1.getLang)() === "en" ? HELP_EN : HELP_ES;
|
|
313
|
+
}
|
|
189
314
|
function parseArgs(argv) {
|
|
190
315
|
const positional = [];
|
|
191
316
|
const flags = {};
|
|
@@ -213,11 +338,26 @@ function str(args, key) {
|
|
|
213
338
|
function required(args, key) {
|
|
214
339
|
const value = str(args, key);
|
|
215
340
|
if (!value)
|
|
216
|
-
throw new UserError(`Falta --${key}`);
|
|
341
|
+
throw new UserError((0, index_js_1.tr) `Falta --${key}`);
|
|
217
342
|
return value;
|
|
218
343
|
}
|
|
219
344
|
class UserError extends Error {
|
|
220
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Nombre de un tipo de trabajo en el idioma de la terminal. WORK_KIND_LABELS
|
|
348
|
+
* vive en `shared` y está en español; aquí se traduce sin tocar ese paquete.
|
|
349
|
+
*/
|
|
350
|
+
function kindLabel(kind) {
|
|
351
|
+
switch (kind) {
|
|
352
|
+
case "development": return (0, index_js_1.tr) `Desarrollo`;
|
|
353
|
+
case "meeting": return (0, index_js_1.tr) `Reunión`;
|
|
354
|
+
case "research": return (0, index_js_1.tr) `Investigación`;
|
|
355
|
+
case "review": return (0, index_js_1.tr) `Revisión`;
|
|
356
|
+
case "travel": return (0, index_js_1.tr) `Desplazamiento`;
|
|
357
|
+
case "support": return (0, index_js_1.tr) `Soporte`;
|
|
358
|
+
case "other": return (0, index_js_1.tr) `Otro`;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
221
361
|
// ---------------------------------------------------------------------------
|
|
222
362
|
/**
|
|
223
363
|
* `estela web` — abre el panel local.
|
|
@@ -228,9 +368,9 @@ class UserError extends Error {
|
|
|
228
368
|
async function cmdWeb(args, dbPath) {
|
|
229
369
|
const port = Number(str(args, "port") ?? 4319);
|
|
230
370
|
const url = await (0, server_js_1.startServer)({ port, dbPath });
|
|
231
|
-
console.log(`\n Estela ${url}`);
|
|
232
|
-
console.log(` Datos: ${dbPath}`);
|
|
233
|
-
console.log(`\n Ctrl+C para parar.\n`);
|
|
371
|
+
console.log((0, index_js_1.tr) `\n Estela ${url}`);
|
|
372
|
+
console.log((0, index_js_1.tr) ` Datos: ${dbPath}`);
|
|
373
|
+
console.log((0, index_js_1.tr) `\n Ctrl+C para parar.\n`);
|
|
234
374
|
// No devuelve: el servidor se queda escuchando.
|
|
235
375
|
await new Promise(() => { });
|
|
236
376
|
}
|
|
@@ -267,8 +407,8 @@ async function cmdLogout(dbPath) {
|
|
|
267
407
|
async function cmdSetup(args, dbPath) {
|
|
268
408
|
const db = (0, schema_js_1.openDatabase)(dbPath);
|
|
269
409
|
try {
|
|
270
|
-
console.log(
|
|
271
|
-
console.log(
|
|
410
|
+
console.log((0, index_js_1.tr) `\nEstela\n`);
|
|
411
|
+
console.log((0, index_js_1.tr) `Leyendo lo que tus agentes ya guardaron en disco…`);
|
|
272
412
|
const scan = await (0, claude_js_1.scanClaudeCode)({});
|
|
273
413
|
const turns = (0, claude_js_1.resolveScratchpads)(scan.turns);
|
|
274
414
|
// Los repos no pueden salir solo de los transcripts: quien programa sin
|
|
@@ -286,57 +426,57 @@ async function cmdSetup(args, dbPath) {
|
|
|
286
426
|
if (cwdRoot)
|
|
287
427
|
repos.add(cwdRoot);
|
|
288
428
|
if (turns.length === 0 && repos.size === 0) {
|
|
289
|
-
console.log(
|
|
290
|
-
console.log(
|
|
291
|
-
console.log(
|
|
429
|
+
console.log((0, index_js_1.tr) `\n No se han encontrado sesiones de Claude Code en ~/.claude, ni`);
|
|
430
|
+
console.log((0, index_js_1.tr) ` un repositorio de Git en esta carpeta. Corre esto de nuevo desde`);
|
|
431
|
+
console.log((0, index_js_1.tr) ` dentro de tu proyecto, o trabaja un rato con tu agente y vuelve.\n`);
|
|
292
432
|
return;
|
|
293
433
|
}
|
|
294
434
|
if (turns.length > 0) {
|
|
295
435
|
store.saveTurns(db, turns);
|
|
296
436
|
store.logScan(db, "claude-code", scan.report);
|
|
297
|
-
console.log(` ${scan.report.turnsAccepted} turnos · ` +
|
|
298
|
-
`${scan.report.producerVersions.length} versiones de Claude Code`);
|
|
437
|
+
console.log((0, index_js_1.tr) ` ${scan.report.turnsAccepted} turnos · ` +
|
|
438
|
+
(0, index_js_1.tr) `${scan.report.producerVersions.length} versiones de Claude Code`);
|
|
299
439
|
}
|
|
300
440
|
else {
|
|
301
|
-
console.log(
|
|
302
|
-
console.log(
|
|
441
|
+
console.log((0, index_js_1.tr) ` No se han encontrado sesiones de Claude Code en ~/.claude —`);
|
|
442
|
+
console.log((0, index_js_1.tr) ` sin problema, se reconstruye igual desde tus commits de Git.`);
|
|
303
443
|
}
|
|
304
|
-
console.log(`\nBuscando repositorios… (${repos.size})`);
|
|
444
|
+
console.log((0, index_js_1.tr) `\nBuscando repositorios… (${repos.size})`);
|
|
305
445
|
const plans = await (0, setup_js_1.planSetup)(db, [...repos], await (0, git_js_1.gitUserEmail)(process.cwd()));
|
|
306
446
|
const nuevos = (0, setup_js_1.applySetup)(db, plans);
|
|
307
447
|
for (const plan of plans.filter((x) => !x.existing)) {
|
|
308
448
|
console.log(` + ${plan.name}` +
|
|
309
|
-
(plan.emails.length ? ` (commiteas como ${plan.emails[0]})` :
|
|
449
|
+
(plan.emails.length ? (0, index_js_1.tr) ` (commiteas como ${plan.emails[0]})` : (0, index_js_1.tr) ` ⚠ sin autor claro`));
|
|
310
450
|
}
|
|
311
451
|
const yaEstaban = plans.filter((x) => x.existing).length;
|
|
312
452
|
if (yaEstaban)
|
|
313
|
-
console.log(` ${yaEstaban} ya estaban configurados y no se tocan`);
|
|
314
|
-
console.log(
|
|
453
|
+
console.log((0, index_js_1.tr) ` ${yaEstaban} ya estaban configurados y no se tocan`);
|
|
454
|
+
console.log((0, index_js_1.tr) `\nReconstruyendo tu historial…`);
|
|
315
455
|
await cmdImport({ _: [], flags: { ...(args.flags["db"] ? { db: args.flags["db"] } : {}) } }, dbPath);
|
|
316
456
|
const s = (0, setup_js_1.summarize)(db);
|
|
317
457
|
console.log(`\n${"─".repeat(66)}`);
|
|
318
458
|
console.log(` ${(0, setup_js_1.summaryLine)(s)}`);
|
|
319
459
|
if (s.from)
|
|
320
|
-
console.log(` del ${s.from} al ${s.to}`);
|
|
460
|
+
console.log((0, index_js_1.tr) ` del ${s.from} al ${s.to}`);
|
|
321
461
|
if (s.people > 1) {
|
|
322
|
-
console.log(` ${s.people} personas han commiteado en esos repositorios`);
|
|
462
|
+
console.log((0, index_js_1.tr) ` ${s.people} personas han commiteado en esos repositorios`);
|
|
323
463
|
}
|
|
324
464
|
console.log(`${"─".repeat(66)}\n`);
|
|
325
465
|
const sinAutor = plans.filter((p) => !p.existing && p.emails.length === 0);
|
|
326
466
|
if (sinAutor.length) {
|
|
327
|
-
console.log(` ⚠ En ${sinAutor.length} repositorio(s) no se ha podido saber con qué`);
|
|
328
|
-
console.log(` correo commiteas, así que ahí no se ha capturado nada tuyo:`);
|
|
467
|
+
console.log((0, index_js_1.tr) ` ⚠ En ${sinAutor.length} repositorio(s) no se ha podido saber con qué`);
|
|
468
|
+
console.log((0, index_js_1.tr) ` correo commiteas, así que ahí no se ha capturado nada tuyo:`);
|
|
329
469
|
for (const p of sinAutor.slice(0, 3)) {
|
|
330
|
-
console.log(` estela author --project ${p.projectId}`);
|
|
470
|
+
console.log((0, index_js_1.tr) ` estela author --project ${p.projectId}`);
|
|
331
471
|
}
|
|
332
472
|
console.log("");
|
|
333
473
|
}
|
|
334
|
-
console.log(
|
|
335
|
-
console.log(
|
|
474
|
+
console.log((0, index_js_1.tr) ` Ábrelo: estela web`);
|
|
475
|
+
console.log((0, index_js_1.tr) ` Revísalo: estela doctor`);
|
|
336
476
|
if (nuevos > 0) {
|
|
337
|
-
console.log(
|
|
338
|
-
console.log(
|
|
339
|
-
console.log(
|
|
477
|
+
console.log((0, index_js_1.tr) `\n Los proyectos se han creado como internos y sin tarifa. Si alguno`);
|
|
478
|
+
console.log((0, index_js_1.tr) ` es de un cliente al que facturas, ponle la suya y podrás emitir`);
|
|
479
|
+
console.log((0, index_js_1.tr) ` informes: estela rate set --project <id> --rate 50\n`);
|
|
340
480
|
}
|
|
341
481
|
}
|
|
342
482
|
finally {
|
|
@@ -349,10 +489,10 @@ async function cmdImport(args, dbPath) {
|
|
|
349
489
|
const sinceRaw = str(args, "since");
|
|
350
490
|
const since = sinceRaw ? new Date(`${sinceRaw}T00:00:00Z`) : undefined;
|
|
351
491
|
if (since && Number.isNaN(since.getTime()))
|
|
352
|
-
throw new UserError(`Fecha inválida: ${sinceRaw}`);
|
|
492
|
+
throw new UserError((0, index_js_1.tr) `Fecha inválida: ${sinceRaw}`);
|
|
353
493
|
const repoFilter = str(args, "repo");
|
|
354
494
|
const repoPaths = repoFilter ? [(0, node_path_1.resolve)(repoFilter)] : undefined;
|
|
355
|
-
console.log(
|
|
495
|
+
console.log((0, index_js_1.tr) `Leyendo transcripts de agentes…`);
|
|
356
496
|
const scan = await (0, claude_js_1.scanClaudeCode)({
|
|
357
497
|
...(since ? { since } : {}),
|
|
358
498
|
...(repoPaths ? { repoPaths } : {}),
|
|
@@ -364,15 +504,15 @@ async function cmdImport(args, dbPath) {
|
|
|
364
504
|
const rescued = turns.filter((t, i) => t.repoPath !== scan.turns[i].repoPath).length;
|
|
365
505
|
const savedTurns = store.saveTurns(db, turns);
|
|
366
506
|
store.logScan(db, "claude-code", report);
|
|
367
|
-
console.log(` ${report.filesRead} archivos · ${report.recordsSeen} registros`);
|
|
368
|
-
console.log(` ${report.turnsAccepted} turnos aceptados · ${savedTurns} nuevos`);
|
|
369
|
-
console.log(` ${report.duplicatesDropped} duplicados descartados` +
|
|
507
|
+
console.log((0, index_js_1.tr) ` ${report.filesRead} archivos · ${report.recordsSeen} registros`);
|
|
508
|
+
console.log((0, index_js_1.tr) ` ${report.turnsAccepted} turnos aceptados · ${savedTurns} nuevos`);
|
|
509
|
+
console.log((0, index_js_1.tr) ` ${report.duplicatesDropped} duplicados descartados` +
|
|
370
510
|
(report.turnsAccepted > 0
|
|
371
|
-
? ` (${(report.duplicatesDropped / (report.turnsAccepted + report.duplicatesDropped) * 100).toFixed(0)}% de las filas)`
|
|
511
|
+
? (0, index_js_1.tr) ` (${(report.duplicatesDropped / (report.turnsAccepted + report.duplicatesDropped) * 100).toFixed(0)}% de las filas)`
|
|
372
512
|
: ""));
|
|
373
|
-
console.log(` ${report.unknownRecords} registros internos ignorados · ${report.malformedRecords} malformados`);
|
|
513
|
+
console.log((0, index_js_1.tr) ` ${report.unknownRecords} registros internos ignorados · ${report.malformedRecords} malformados`);
|
|
374
514
|
if (report.producerVersions.length) {
|
|
375
|
-
console.log(` versiones de Claude Code: ${report.producerVersions.join(", ")}`);
|
|
515
|
+
console.log((0, index_js_1.tr) ` versiones de Claude Code: ${report.producerVersions.join(", ")}`);
|
|
376
516
|
}
|
|
377
517
|
for (const warning of report.warnings)
|
|
378
518
|
console.warn(` ⚠ ${warning}`);
|
|
@@ -407,9 +547,13 @@ async function cmdImport(args, dbPath) {
|
|
|
407
547
|
}
|
|
408
548
|
const mine = await (0, identity_js_1.myEmailsByRepo)(db, repos);
|
|
409
549
|
if (rescued > 0) {
|
|
410
|
-
console.log(` ${rescued} turnos de scratchpad devueltos a su repositorio`);
|
|
550
|
+
console.log((0, index_js_1.tr) ` ${rescued} turnos de scratchpad devueltos a su repositorio`);
|
|
411
551
|
}
|
|
412
|
-
|
|
552
|
+
// En la primera ejecución lo normal es un solo repositorio: "1 repositorios"
|
|
553
|
+
// es lo primero que lee quien acaba de instalar.
|
|
554
|
+
console.log(repos.size === 1
|
|
555
|
+
? (0, index_js_1.tr) `Git: 1 repositorio · ${savedCommits} commits nuevos`
|
|
556
|
+
: (0, index_js_1.tr) `Git: ${repos.size} repositorios · ${savedCommits} commits nuevos`);
|
|
413
557
|
// Agrupar en bloques, fusionar por rama y día, e imputar a los proyectos.
|
|
414
558
|
// Trabajo deducido de los agentes, y además el de los commits que no
|
|
415
559
|
// solapan con ninguno: quien programa sin IA solo deja ese rastro.
|
|
@@ -425,7 +569,7 @@ async function cmdImport(args, dbPath) {
|
|
|
425
569
|
const porProyecto = (b) => (b.repoPath ? store.projectForRepo(db, b.repoPath) ?? b.repoPath : "");
|
|
426
570
|
const blocks = (0, sessionize_js_1.groupByBranchAndDay)(raw, porProyecto);
|
|
427
571
|
if (fromCommits.length) {
|
|
428
|
-
console.log(` ${fromCommits.length} bloques deducidos solo de commits (sin agente)`);
|
|
572
|
+
console.log((0, index_js_1.tr) ` ${fromCommits.length} bloques deducidos solo de commits (sin agente)`);
|
|
429
573
|
}
|
|
430
574
|
let entries = 0;
|
|
431
575
|
let unassigned = 0;
|
|
@@ -463,11 +607,11 @@ async function cmdImport(args, dbPath) {
|
|
|
463
607
|
store.saveTimeEntry(db, { ...entry, id: `te_${projectId}_${day}_${slug}` });
|
|
464
608
|
entries++;
|
|
465
609
|
}
|
|
466
|
-
console.log(`Bloques: ${raw.length} detectados → ${blocks.length} tras agrupar por rama y día · ` +
|
|
467
|
-
`${entries} imputados · ${unassigned} sin proyecto`);
|
|
610
|
+
console.log((0, index_js_1.tr) `Bloques: ${raw.length} detectados → ${blocks.length} tras agrupar por rama y día · ` +
|
|
611
|
+
(0, index_js_1.tr) `${entries} imputados · ${unassigned} sin proyecto`);
|
|
468
612
|
if (unassigned > 0) {
|
|
469
|
-
console.log(`\nHay ${unassigned} bloques sin proyecto asignado. Regístralos con:`);
|
|
470
|
-
console.log(` estela project add --id <id> --client <id> --name <nombre> --repo <ruta>`);
|
|
613
|
+
console.log((0, index_js_1.tr) `\nHay ${unassigned} bloques sin proyecto asignado. Regístralos con:`);
|
|
614
|
+
console.log((0, index_js_1.tr) ` estela project add --id <id> --client <id> --name <nombre> --repo <ruta>`);
|
|
471
615
|
}
|
|
472
616
|
}
|
|
473
617
|
finally {
|
|
@@ -500,7 +644,7 @@ function cmdClientAdd(args, dbPath) {
|
|
|
500
644
|
...(str(args, "tax-id") ? { taxId: str(args, "tax-id") } : {}),
|
|
501
645
|
...(str(args, "email") ? { email: str(args, "email") } : {}),
|
|
502
646
|
});
|
|
503
|
-
console.log(`Cliente "${required(args, "name")}" guardado. Factura en ${currency}.`);
|
|
647
|
+
console.log((0, index_js_1.tr) `Cliente "${required(args, "name")}" guardado. Factura en ${currency}.`);
|
|
504
648
|
}
|
|
505
649
|
finally {
|
|
506
650
|
db.close();
|
|
@@ -511,7 +655,7 @@ function cmdProjectAdd(args, dbPath) {
|
|
|
511
655
|
try {
|
|
512
656
|
const clientId = required(args, "client");
|
|
513
657
|
if (!store.getClient(db, clientId)) {
|
|
514
|
-
throw new UserError(`No existe el cliente "${clientId}". Créalo primero con: estela client add`);
|
|
658
|
+
throw new UserError((0, index_js_1.tr) `No existe el cliente "${clientId}". Créalo primero con: estela client add`);
|
|
515
659
|
}
|
|
516
660
|
const repo = str(args, "repo");
|
|
517
661
|
store.upsertProject(db, {
|
|
@@ -524,9 +668,9 @@ function cmdProjectAdd(args, dbPath) {
|
|
|
524
668
|
aiCostPolicy: (str(args, "ai-cost") ?? "absorbed"),
|
|
525
669
|
kind: (str(args, "kind") ?? "client"),
|
|
526
670
|
});
|
|
527
|
-
console.log(`Proyecto "${required(args, "name")}" guardado.`);
|
|
671
|
+
console.log((0, index_js_1.tr) `Proyecto "${required(args, "name")}" guardado.`);
|
|
528
672
|
if (repo)
|
|
529
|
-
console.log(` repositorio: ${(0, node_path_1.resolve)(repo)}`);
|
|
673
|
+
console.log((0, index_js_1.tr) ` repositorio: ${(0, node_path_1.resolve)(repo)}`);
|
|
530
674
|
}
|
|
531
675
|
finally {
|
|
532
676
|
db.close();
|
|
@@ -548,27 +692,27 @@ function cmdBudget(args, dbPath) {
|
|
|
548
692
|
const projectId = required(args, "project");
|
|
549
693
|
const project = store.getProject(db, projectId);
|
|
550
694
|
if (!project)
|
|
551
|
-
throw new UserError(`No existe el proyecto "${projectId}".`);
|
|
695
|
+
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
|
|
552
696
|
const raw = str(args, "amount");
|
|
553
697
|
if (raw === undefined) {
|
|
554
698
|
const current = db.prepare("SELECT ai_budget_micro_usd AS b FROM projects WHERE id = ?").get(projectId);
|
|
555
699
|
console.log(current.b === null
|
|
556
|
-
? `"${project.name}" no tiene presupuesto de IA.`
|
|
557
|
-
: `"${project.name}": $${(current.b / 1e6).toFixed(2)} al mes.`);
|
|
700
|
+
? (0, index_js_1.tr) `"${project.name}" no tiene presupuesto de IA.`
|
|
701
|
+
: (0, index_js_1.tr) `"${project.name}": $${(current.b / 1e6).toFixed(2)} al mes.`);
|
|
558
702
|
return;
|
|
559
703
|
}
|
|
560
704
|
if (raw === "none") {
|
|
561
705
|
db.prepare("UPDATE projects SET ai_budget_micro_usd = NULL WHERE id = ?").run(projectId);
|
|
562
|
-
console.log(`Presupuesto de "${project.name}" retirado.`);
|
|
706
|
+
console.log((0, index_js_1.tr) `Presupuesto de "${project.name}" retirado.`);
|
|
563
707
|
return;
|
|
564
708
|
}
|
|
565
709
|
const dollars = Number(raw.replace(",", "."));
|
|
566
710
|
if (!Number.isFinite(dollars) || dollars < 0) {
|
|
567
|
-
throw new UserError(`Importe inválido: ${raw}`);
|
|
711
|
+
throw new UserError((0, index_js_1.tr) `Importe inválido: ${raw}`);
|
|
568
712
|
}
|
|
569
713
|
db.prepare("UPDATE projects SET ai_budget_micro_usd = ? WHERE id = ?")
|
|
570
714
|
.run(Math.round(dollars * 1e6), projectId);
|
|
571
|
-
console.log(`Presupuesto de IA de "${project.name}": $${dollars.toFixed(2)} al mes.`);
|
|
715
|
+
console.log((0, index_js_1.tr) `Presupuesto de IA de "${project.name}": $${dollars.toFixed(2)} al mes.`);
|
|
572
716
|
}
|
|
573
717
|
finally {
|
|
574
718
|
db.close();
|
|
@@ -580,15 +724,15 @@ function cmdRateSet(args, dbPath) {
|
|
|
580
724
|
const projectId = required(args, "project");
|
|
581
725
|
const project = store.getProject(db, projectId);
|
|
582
726
|
if (!project)
|
|
583
|
-
throw new UserError(`No existe el proyecto "${projectId}".`);
|
|
727
|
+
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
|
|
584
728
|
const client = store.getClient(db, project.clientId);
|
|
585
729
|
const currency = str(args, "currency")?.toUpperCase() ?? client.currency;
|
|
586
730
|
const rate = (0, shared_1.parseMoney)(required(args, "rate"), currency);
|
|
587
731
|
const fromRaw = str(args, "from");
|
|
588
732
|
const effectiveFrom = fromRaw ? new Date(`${fromRaw}T00:00:00Z`) : new Date(0);
|
|
589
733
|
store.addRatePeriod(db, { projectId, hourlyRate: rate, effectiveFrom, effectiveTo: null });
|
|
590
|
-
console.log(`Tarifa de "${project.name}": ${(0, shared_1.formatMoney)(rate)}/hora` +
|
|
591
|
-
(fromRaw ? ` desde ${fromRaw}.` :
|
|
734
|
+
console.log((0, index_js_1.tr) `Tarifa de "${project.name}": ${(0, shared_1.formatMoney)(rate)}/hora` +
|
|
735
|
+
(fromRaw ? (0, index_js_1.tr) ` desde ${fromRaw}.` : (0, index_js_1.tr) ` (aplica a todo el histórico).`));
|
|
592
736
|
}
|
|
593
737
|
finally {
|
|
594
738
|
db.close();
|
|
@@ -598,15 +742,15 @@ function cmdStatus(dbPath) {
|
|
|
598
742
|
const db = (0, schema_js_1.openDatabase)(dbPath);
|
|
599
743
|
try {
|
|
600
744
|
const turns = db.prepare("SELECT COUNT(*) n, SUM(cost_micro_usd) c, MIN(at) a, MAX(at) b FROM agent_turns").get();
|
|
601
|
-
console.log(`Base de datos: ${dbPath}\n`);
|
|
602
|
-
console.log(`Turnos de agente: ${turns.n}`);
|
|
745
|
+
console.log((0, index_js_1.tr) `Base de datos: ${dbPath}\n`);
|
|
746
|
+
console.log((0, index_js_1.tr) `Turnos de agente: ${turns.n}`);
|
|
603
747
|
if (turns.a)
|
|
604
|
-
console.log(`Periodo: ${turns.a.slice(0, 10)} → ${turns.b.slice(0, 10)}`);
|
|
605
|
-
console.log(`Coste de IA total: ${(0, shared_1.formatAiCost)({ microUsd: turns.c ?? 0 })}\n`);
|
|
748
|
+
console.log((0, index_js_1.tr) `Periodo: ${turns.a.slice(0, 10)} → ${turns.b.slice(0, 10)}`);
|
|
749
|
+
console.log((0, index_js_1.tr) `Coste de IA total: ${(0, shared_1.formatAiCost)({ microUsd: turns.c ?? 0 })}\n`);
|
|
606
750
|
const clients = store.listClients(db);
|
|
607
751
|
if (clients.length === 0) {
|
|
608
|
-
console.log(
|
|
609
|
-
console.log(
|
|
752
|
+
console.log((0, index_js_1.tr) `Sin clientes configurados. Empieza con:`);
|
|
753
|
+
console.log((0, index_js_1.tr) ` estela client add --id <id> --name <nombre> --currency EUR`);
|
|
610
754
|
return;
|
|
611
755
|
}
|
|
612
756
|
for (const client of clients) {
|
|
@@ -618,13 +762,13 @@ function cmdStatus(dbPath) {
|
|
|
618
762
|
const ai = pending.reduce((s, e) => s + e.aiCost.microUsd, 0);
|
|
619
763
|
const rate = (0, invoice_js_1.rateAt)(store.getRates(db, project.id), project.id, new Date());
|
|
620
764
|
console.log(` ${project.name}`);
|
|
621
|
-
console.log(` tarifa: ${rate ? `${(0, shared_1.formatMoney)(rate)}/h` :
|
|
622
|
-
console.log(` sin facturar: ${(0, shared_1.formatDuration)(seconds)} en ${pending.length} bloques`);
|
|
765
|
+
console.log((0, index_js_1.tr) ` tarifa: ${rate ? `${(0, shared_1.formatMoney)(rate)}/h` : (0, index_js_1.tr) `— sin definir —`}`);
|
|
766
|
+
console.log((0, index_js_1.tr) ` sin facturar: ${(0, shared_1.formatDuration)(seconds)} en ${pending.length} bloques`);
|
|
623
767
|
if (rate && seconds > 0) {
|
|
624
768
|
const amount = { amount: Math.round((rate.amount * seconds) / 3600), currency: rate.currency };
|
|
625
|
-
console.log(` importe: ${(0, shared_1.formatMoney)(amount)}`);
|
|
769
|
+
console.log((0, index_js_1.tr) ` importe: ${(0, shared_1.formatMoney)(amount)}`);
|
|
626
770
|
}
|
|
627
|
-
console.log(` coste de IA: ${(0, shared_1.formatAiCost)({ microUsd: ai })}`);
|
|
771
|
+
console.log((0, index_js_1.tr) ` coste de IA: ${(0, shared_1.formatAiCost)({ microUsd: ai })}`);
|
|
628
772
|
}
|
|
629
773
|
console.log("");
|
|
630
774
|
}
|
|
@@ -644,15 +788,15 @@ function cmdEntries(args, dbPath) {
|
|
|
644
788
|
const projectId = required(args, "project");
|
|
645
789
|
const project = store.getProject(db, projectId);
|
|
646
790
|
if (!project)
|
|
647
|
-
throw new UserError(`No existe el proyecto "${projectId}".`);
|
|
791
|
+
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
|
|
648
792
|
const entries = store.getTimeEntries(db, projectId);
|
|
649
793
|
if (entries.length === 0) {
|
|
650
|
-
console.log(
|
|
794
|
+
console.log((0, index_js_1.tr) `Sin bloques imputados. Ejecuta: estela import`);
|
|
651
795
|
return;
|
|
652
796
|
}
|
|
653
797
|
console.log(`${project.name}\n`);
|
|
654
798
|
for (const e of entries) {
|
|
655
|
-
const flag = e.invoiceId ?
|
|
799
|
+
const flag = e.invoiceId ? (0, index_js_1.tr) `facturado` : e.billable ? (0, index_js_1.tr) `pendiente` : (0, index_js_1.tr) `no facturable`;
|
|
656
800
|
console.log(`${e.startedAt.toISOString().slice(0, 16).replace("T", " ")} ` +
|
|
657
801
|
`${(0, shared_1.formatDuration)(e.seconds).padStart(8)} ` +
|
|
658
802
|
`${(0, shared_1.formatAiCost)(e.aiCost).padStart(9)} ` +
|
|
@@ -675,7 +819,7 @@ function cmdShare(args, dbPath) {
|
|
|
675
819
|
const projectId = required(args, "project");
|
|
676
820
|
const project = store.getProject(db, projectId);
|
|
677
821
|
if (!project)
|
|
678
|
-
throw new UserError(`No existe el proyecto "${projectId}".`);
|
|
822
|
+
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
|
|
679
823
|
const client = store.getClient(db, project.clientId);
|
|
680
824
|
const to = str(args, "to") ?? (0, shared_1.localDate)(new Date());
|
|
681
825
|
const from = str(args, "from") ?? to.slice(0, 8) + "01";
|
|
@@ -696,12 +840,12 @@ function cmdShare(args, dbPath) {
|
|
|
696
840
|
});
|
|
697
841
|
const out = str(args, "out") ?? `informe-${projectId}-${to}.html`;
|
|
698
842
|
(0, node_fs_1.writeFileSync)(out, html, "utf8");
|
|
699
|
-
console.log(`\nInforme: ${out}`);
|
|
700
|
-
console.log(` ${project.name} · ${client.name} · ${from} al ${to}`);
|
|
701
|
-
console.log(` Un solo fichero. Se abre sin instalar nada y sin depender de tu máquina.`);
|
|
843
|
+
console.log((0, index_js_1.tr) `\nInforme: ${out}`);
|
|
844
|
+
console.log((0, index_js_1.tr) ` ${project.name} · ${client.name} · ${from} al ${to}`);
|
|
845
|
+
console.log((0, index_js_1.tr) ` Un solo fichero. Se abre sin instalar nada y sin depender de tu máquina.`);
|
|
702
846
|
if (args.flags["with-amounts"] !== true) {
|
|
703
|
-
console.log(`\n Incluye horas y commits. Sin importes (añade --with-amounts) y sin`);
|
|
704
|
-
console.log(` consumo de IA, que es tuyo mientras la pagues tú.`);
|
|
847
|
+
console.log((0, index_js_1.tr) `\n Incluye horas y commits. Sin importes (añade --with-amounts) y sin`);
|
|
848
|
+
console.log((0, index_js_1.tr) ` consumo de IA, que es tuyo mientras la pagues tú.`);
|
|
705
849
|
}
|
|
706
850
|
}
|
|
707
851
|
finally {
|
|
@@ -722,28 +866,28 @@ function cmdLog(args, dbPath) {
|
|
|
722
866
|
const projectId = required(args, "project");
|
|
723
867
|
const project = store.getProject(db, projectId);
|
|
724
868
|
if (!project) {
|
|
725
|
-
const known = store.listProjects(db).map((p) => p.id).join(", ") ||
|
|
726
|
-
throw new UserError(`No existe el proyecto "${projectId}". Los que hay: ${known}`);
|
|
869
|
+
const known = store.listProjects(db).map((p) => p.id).join(", ") || (0, index_js_1.tr) `(ninguno)`;
|
|
870
|
+
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}". Los que hay: ${known}`);
|
|
727
871
|
}
|
|
728
872
|
const kind = (str(args, "kind") ?? "meeting");
|
|
729
873
|
if (!(kind in shared_1.WORK_KIND_LABELS)) {
|
|
730
|
-
throw new UserError(`Tipo "${kind}" no reconocido. Usa uno de: ${Object.keys(shared_1.WORK_KIND_LABELS).join(", ")}`);
|
|
874
|
+
throw new UserError((0, index_js_1.tr) `Tipo "${kind}" no reconocido. Usa uno de: ${Object.keys(shared_1.WORK_KIND_LABELS).join(", ")}`);
|
|
731
875
|
}
|
|
732
876
|
const hoursRaw = str(args, "hours");
|
|
733
877
|
const minutesRaw = str(args, "minutes");
|
|
734
878
|
if (!hoursRaw && !minutesRaw)
|
|
735
|
-
throw new UserError(
|
|
879
|
+
throw new UserError((0, index_js_1.tr) `Indica --hours o --minutes`);
|
|
736
880
|
const seconds = Math.round((hoursRaw ? Number(hoursRaw.replace(",", ".")) * 3600 : 0) +
|
|
737
881
|
(minutesRaw ? Number(minutesRaw) * 60 : 0));
|
|
738
882
|
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
739
|
-
throw new UserError(`Duración inválida: ${hoursRaw ?? minutesRaw}`);
|
|
883
|
+
throw new UserError((0, index_js_1.tr) `Duración inválida: ${hoursRaw ?? minutesRaw}`);
|
|
740
884
|
}
|
|
741
885
|
const day = str(args, "date") ?? (0, shared_1.localDate)(new Date());
|
|
742
886
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(day))
|
|
743
|
-
throw new UserError(`Fecha inválida: ${day}`);
|
|
887
|
+
throw new UserError((0, index_js_1.tr) `Fecha inválida: ${day}`);
|
|
744
888
|
// A media mañana: no sabemos la hora real y fingir precisión sería peor.
|
|
745
889
|
const startedAt = new Date(`${day}T10:00:00`);
|
|
746
|
-
const what = str(args, "what") ??
|
|
890
|
+
const what = str(args, "what") ?? kindLabel(kind);
|
|
747
891
|
const id = store.saveTimeEntry(db, {
|
|
748
892
|
projectId,
|
|
749
893
|
startedAt,
|
|
@@ -764,10 +908,10 @@ function cmdLog(args, dbPath) {
|
|
|
764
908
|
const amount = rate
|
|
765
909
|
? ` · ${(0, shared_1.formatMoney)({ amount: Math.round((rate.amount * seconds) / 3600), currency: rate.currency })}`
|
|
766
910
|
: "";
|
|
767
|
-
console.log(`\n${
|
|
911
|
+
console.log(`\n${kindLabel(kind)} · ${(0, shared_1.formatDuration)(seconds)}${amount}`);
|
|
768
912
|
console.log(` ${what}`);
|
|
769
913
|
console.log(` ${project.name} · ${day}`);
|
|
770
|
-
console.log(`\n Registro manual: un import no lo va a tocar. (${id})`);
|
|
914
|
+
console.log((0, index_js_1.tr) `\n Registro manual: un import no lo va a tocar. (${id})`);
|
|
771
915
|
}
|
|
772
916
|
finally {
|
|
773
917
|
db.close();
|
|
@@ -816,18 +960,18 @@ async function cmdPublish(args, dbPath) {
|
|
|
816
960
|
throw new UserError(error.message);
|
|
817
961
|
throw error;
|
|
818
962
|
}
|
|
819
|
-
console.log(`\nPanel publicado.\n`);
|
|
820
|
-
console.log(` Enlace: ${result.url}\n`);
|
|
963
|
+
console.log((0, index_js_1.tr) `\nPanel publicado.\n`);
|
|
964
|
+
console.log((0, index_js_1.tr) ` Enlace: ${result.url}\n`);
|
|
821
965
|
if (result.adopted) {
|
|
822
|
-
console.log(` Este panel ya existía y queda vinculado a tu cuenta desde ahora.\n`);
|
|
966
|
+
console.log((0, index_js_1.tr) ` Este panel ya existía y queda vinculado a tu cuenta desde ahora.\n`);
|
|
823
967
|
}
|
|
824
968
|
if (clientes?.length) {
|
|
825
|
-
console.log(` Compartido con: ${clientes.join(", ")}`);
|
|
826
|
-
console.log(` Al entrar en getestela.dev/app con ese correo, lo verán ahí.\n`);
|
|
969
|
+
console.log((0, index_js_1.tr) ` Compartido con: ${clientes.join(", ")}`);
|
|
970
|
+
console.log((0, index_js_1.tr) ` Al entrar en getestela.dev/app con ese correo, lo verán ahí.\n`);
|
|
827
971
|
}
|
|
828
|
-
console.log(` El token es lo único que protege la página: quien tenga el enlace, entra.`);
|
|
829
|
-
console.log(` Al volver a publicar desde esta máquina, este enlace se reusa solo.`);
|
|
830
|
-
console.log(` Desde otra máquina, pasa --token ${result.token} o el cliente perderá su enlace.`);
|
|
972
|
+
console.log((0, index_js_1.tr) ` El token es lo único que protege la página: quien tenga el enlace, entra.`);
|
|
973
|
+
console.log((0, index_js_1.tr) ` Al volver a publicar desde esta máquina, este enlace se reusa solo.`);
|
|
974
|
+
console.log((0, index_js_1.tr) ` Desde otra máquina, pasa --token ${result.token} o el cliente perderá su enlace.`);
|
|
831
975
|
}
|
|
832
976
|
finally {
|
|
833
977
|
db.close();
|
|
@@ -843,16 +987,16 @@ function cmdSyncEnable(args, dbPath) {
|
|
|
843
987
|
try {
|
|
844
988
|
const projectId = required(args, "project");
|
|
845
989
|
if (!store.getProject(db, projectId)) {
|
|
846
|
-
throw new UserError(`No existe el proyecto "${projectId}".`);
|
|
990
|
+
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
|
|
847
991
|
}
|
|
848
992
|
if (!store.listPersonalSyncCandidates(db).includes(projectId)) {
|
|
849
|
-
throw new UserError(`"${projectId}" ya está enlazado a un equipo y no puede sincronizarse también como personal.`);
|
|
993
|
+
throw new UserError((0, index_js_1.tr) `"${projectId}" ya está enlazado a un equipo y no puede sincronizarse también como personal.`);
|
|
850
994
|
}
|
|
851
995
|
store.setProjectSync(db, {
|
|
852
996
|
projectId, scope: "personal", remoteProjectId: projectId,
|
|
853
997
|
remoteOrgId: null, inviteToken: null,
|
|
854
998
|
});
|
|
855
|
-
console.log(`Sync activado para "${projectId}". Ejecuta "estela sync" para subir y bajar.`);
|
|
999
|
+
console.log((0, index_js_1.tr) `Sync activado para "${projectId}". Ejecuta "estela sync" para subir y bajar.`);
|
|
856
1000
|
}
|
|
857
1001
|
finally {
|
|
858
1002
|
db.close();
|
|
@@ -865,7 +1009,7 @@ async function cmdSync(args, dbPath) {
|
|
|
865
1009
|
const only = str(args, "project");
|
|
866
1010
|
const projectIds = only ? [only] : store.listProjectSyncs(db).map((s) => s.projectId);
|
|
867
1011
|
if (projectIds.length === 0) {
|
|
868
|
-
console.log(`Ningún proyecto sincroniza todavía. Actívalo con: estela sync enable --project <id>`);
|
|
1012
|
+
console.log((0, index_js_1.tr) `Ningún proyecto sincroniza todavía. Actívalo con: estela sync enable --project <id>`);
|
|
869
1013
|
return;
|
|
870
1014
|
}
|
|
871
1015
|
for (const projectId of projectIds) {
|
|
@@ -875,11 +1019,11 @@ async function cmdSync(args, dbPath) {
|
|
|
875
1019
|
try {
|
|
876
1020
|
if (scope === "team") {
|
|
877
1021
|
const outcome = await (0, team_js_1.syncTeamProject)(db, projectId);
|
|
878
|
-
console.log(`${label}: ${(0, shared_1.formatDuration)(outcome.seconds)} totales enviadas`);
|
|
1022
|
+
console.log((0, index_js_1.tr) `${label}: ${(0, shared_1.formatDuration)(outcome.seconds)} totales enviadas`);
|
|
879
1023
|
}
|
|
880
1024
|
else {
|
|
881
1025
|
const outcome = await (0, sync_js_1.syncProject)(db, projectId);
|
|
882
|
-
console.log(`${label}: ${outcome.pushed} subidas, ${outcome.pulled} bajadas`);
|
|
1026
|
+
console.log((0, index_js_1.tr) `${label}: ${outcome.pushed} subidas, ${outcome.pulled} bajadas`);
|
|
883
1027
|
}
|
|
884
1028
|
}
|
|
885
1029
|
catch (error) {
|
|
@@ -907,7 +1051,7 @@ async function cmdTeamInvite(args, dbPath) {
|
|
|
907
1051
|
const emails = required(args, "email").split(",").map((e) => e.trim()).filter(Boolean);
|
|
908
1052
|
const kind = (str(args, "kind") ?? "employee");
|
|
909
1053
|
if (kind !== "employee" && kind !== "freelancer") {
|
|
910
|
-
throw new UserError(`--kind debe ser "employee" o "freelancer".`);
|
|
1054
|
+
throw new UserError((0, index_js_1.tr) `--kind debe ser "employee" o "freelancer".`);
|
|
911
1055
|
}
|
|
912
1056
|
let result;
|
|
913
1057
|
try {
|
|
@@ -918,10 +1062,10 @@ async function cmdTeamInvite(args, dbPath) {
|
|
|
918
1062
|
throw new UserError(error.message);
|
|
919
1063
|
throw error;
|
|
920
1064
|
}
|
|
921
|
-
console.log(`\nInvitación creada para: ${emails.join(", ")}\n`);
|
|
922
|
-
console.log(` Pásale esto (por el canal que uses con él, no hace falta correo):\n`);
|
|
923
|
-
console.log(` estela login --email <su-correo>`);
|
|
924
|
-
console.log(` estela team accept --token ${result.token} --as-id <id-que-elija>\n`);
|
|
1065
|
+
console.log((0, index_js_1.tr) `\nInvitación creada para: ${emails.join(", ")}\n`);
|
|
1066
|
+
console.log((0, index_js_1.tr) ` Pásale esto (por el canal que uses con él, no hace falta correo):\n`);
|
|
1067
|
+
console.log((0, index_js_1.tr) ` estela login --email <su-correo>`);
|
|
1068
|
+
console.log((0, index_js_1.tr) ` estela team accept --token ${result.token} --as-id <id-que-elija>\n`);
|
|
925
1069
|
}
|
|
926
1070
|
finally {
|
|
927
1071
|
db.close();
|
|
@@ -949,23 +1093,23 @@ async function cmdTeamAiCost(args, dbPath) {
|
|
|
949
1093
|
const allow = args.flags["allow"] === true;
|
|
950
1094
|
const deny = args.flags["deny"] === true;
|
|
951
1095
|
if (allow === deny) {
|
|
952
|
-
throw new UserError(
|
|
1096
|
+
throw new UserError((0, index_js_1.tr) `Elige una: --allow para compartirlo, --deny para no hacerlo.`);
|
|
953
1097
|
}
|
|
954
1098
|
const sync = store.getProjectSync(db, projectId);
|
|
955
1099
|
if (!sync || sync.scope !== "team" || !sync.inviteToken) {
|
|
956
|
-
throw new UserError(`"${projectId}" no es un proyecto de equipo.`);
|
|
1100
|
+
throw new UserError((0, index_js_1.tr) `"${projectId}" no es un proyecto de equipo.`);
|
|
957
1101
|
}
|
|
958
1102
|
const account = store.getCloudAccount(db);
|
|
959
1103
|
if (!account)
|
|
960
|
-
throw new UserError(
|
|
1104
|
+
throw new UserError((0, index_js_1.tr) `Vincula la máquina con "estela login" antes.`);
|
|
961
1105
|
const decision = allow ? "granted" : "declined";
|
|
962
1106
|
await (0, client_js_1.cloudPost)(account.apiBaseUrl, "/team/ai-cost/decision", { token: sync.inviteToken, decision }, { deviceToken: account.deviceToken });
|
|
963
1107
|
store.setAiConsent(db, projectId, false, decision);
|
|
964
1108
|
console.log(allow
|
|
965
|
-
? `\nHecho. El coste de IA de "${projectId}" se sumará al del proyecto en el ` +
|
|
966
|
-
`siguiente "estela sync".\n\n Nunca se enseña por persona, solo el total del proyecto.\n`
|
|
967
|
-
: `\nHecho. El coste de IA de "${projectId}" NO se comparte, y se avisa a quien lo pidió.\n\n` +
|
|
968
|
-
` Tus horas se siguen midiendo igual: esto solo afecta al gasto de IA.\n`);
|
|
1109
|
+
? (0, index_js_1.tr) `\nHecho. El coste de IA de "${projectId}" se sumará al del proyecto en el ` +
|
|
1110
|
+
(0, index_js_1.tr) `siguiente "estela sync".\n\n Nunca se enseña por persona, solo el total del proyecto.\n`
|
|
1111
|
+
: (0, index_js_1.tr) `\nHecho. El coste de IA de "${projectId}" NO se comparte, y se avisa a quien lo pidió.\n\n` +
|
|
1112
|
+
(0, index_js_1.tr) ` Tus horas se siguen midiendo igual: esto solo afecta al gasto de IA.\n`);
|
|
969
1113
|
}
|
|
970
1114
|
finally {
|
|
971
1115
|
db.close();
|
|
@@ -987,14 +1131,14 @@ async function cmdTeamAccept(args, dbPath) {
|
|
|
987
1131
|
throw new UserError(error.message);
|
|
988
1132
|
throw error;
|
|
989
1133
|
}
|
|
990
|
-
console.log(`\nInvitación aceptada: "${result.projectName}"`);
|
|
991
|
-
console.log(` Declarada para: ${result.emails.join(", ")} (${result.inviteeKind})`);
|
|
992
|
-
console.log(` Si no eres tú, avisa a quien te invitó.\n`);
|
|
993
|
-
console.log(`En esta máquina se llama "${result.localProjectId}".\n`);
|
|
994
|
-
console.log(`Ahora vincula tu repositorio local y sincroniza:\n`);
|
|
995
|
-
console.log(` estela team repo --project ${result.localProjectId} --add <ruta-de-tu-clon>`);
|
|
996
|
-
console.log(` estela import`);
|
|
997
|
-
console.log(` estela sync\n`);
|
|
1134
|
+
console.log((0, index_js_1.tr) `\nInvitación aceptada: "${result.projectName}"`);
|
|
1135
|
+
console.log((0, index_js_1.tr) ` Declarada para: ${result.emails.join(", ")} (${result.inviteeKind})`);
|
|
1136
|
+
console.log((0, index_js_1.tr) ` Si no eres tú, avisa a quien te invitó.\n`);
|
|
1137
|
+
console.log((0, index_js_1.tr) `En esta máquina se llama "${result.localProjectId}".\n`);
|
|
1138
|
+
console.log((0, index_js_1.tr) `Ahora vincula tu repositorio local y sincroniza:\n`);
|
|
1139
|
+
console.log((0, index_js_1.tr) ` estela team repo --project ${result.localProjectId} --add <ruta-de-tu-clon>`);
|
|
1140
|
+
console.log((0, index_js_1.tr) ` estela import`);
|
|
1141
|
+
console.log((0, index_js_1.tr) ` estela sync\n`);
|
|
998
1142
|
}
|
|
999
1143
|
finally {
|
|
1000
1144
|
db.close();
|
|
@@ -1007,9 +1151,9 @@ function cmdTeamRepo(args, dbPath) {
|
|
|
1007
1151
|
const projectId = required(args, "project");
|
|
1008
1152
|
const repo = required(args, "add");
|
|
1009
1153
|
if (!store.getProject(db, projectId))
|
|
1010
|
-
throw new UserError(`No existe el proyecto "${projectId}".`);
|
|
1154
|
+
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
|
|
1011
1155
|
store.addProjectRepo(db, projectId, (0, node_path_1.resolve)(repo));
|
|
1012
|
-
console.log(`Repositorio vinculado a "${projectId}": ${(0, node_path_1.resolve)(repo)}`);
|
|
1156
|
+
console.log((0, index_js_1.tr) `Repositorio vinculado a "${projectId}": ${(0, node_path_1.resolve)(repo)}`);
|
|
1013
1157
|
}
|
|
1014
1158
|
finally {
|
|
1015
1159
|
db.close();
|
|
@@ -1030,11 +1174,11 @@ async function cmdTeamList(args, dbPath) {
|
|
|
1030
1174
|
throw error;
|
|
1031
1175
|
}
|
|
1032
1176
|
if (members.length === 0) {
|
|
1033
|
-
console.log(`Nadie ha aceptado todavía en "${projectId}".`);
|
|
1177
|
+
console.log((0, index_js_1.tr) `Nadie ha aceptado todavía en "${projectId}".`);
|
|
1034
1178
|
return;
|
|
1035
1179
|
}
|
|
1036
1180
|
for (const m of members) {
|
|
1037
|
-
const synced = m.syncedAt ? `sincronizado ${m.syncedAt.slice(0, 10)}` :
|
|
1181
|
+
const synced = m.syncedAt ? (0, index_js_1.tr) `sincronizado ${m.syncedAt.slice(0, 10)}` : (0, index_js_1.tr) `sin sincronizar aún`;
|
|
1038
1182
|
console.log(` ${m.emails.join(", ")} (${m.inviteeKind}) · ${(0, shared_1.formatDuration)(m.seconds)} · ${synced}`);
|
|
1039
1183
|
}
|
|
1040
1184
|
}
|
|
@@ -1055,7 +1199,7 @@ async function cmdTeamRevoke(args, dbPath) {
|
|
|
1055
1199
|
throw new UserError(error.message);
|
|
1056
1200
|
throw error;
|
|
1057
1201
|
}
|
|
1058
|
-
console.log(`Invitación revocada.`);
|
|
1202
|
+
console.log((0, index_js_1.tr) `Invitación revocada.`);
|
|
1059
1203
|
}
|
|
1060
1204
|
finally {
|
|
1061
1205
|
db.close();
|
|
@@ -1067,7 +1211,7 @@ async function cmdUpgrade(args, dbPath) {
|
|
|
1067
1211
|
try {
|
|
1068
1212
|
const plan = required(args, "plan");
|
|
1069
1213
|
if (plan !== "pro" && plan !== "teams")
|
|
1070
|
-
throw new UserError(`--plan debe ser "pro" o "teams".`);
|
|
1214
|
+
throw new UserError((0, index_js_1.tr) `--plan debe ser "pro" o "teams".`);
|
|
1071
1215
|
const interval = args.flags["annual"] ? "yearly" : "monthly";
|
|
1072
1216
|
let result;
|
|
1073
1217
|
try {
|
|
@@ -1078,7 +1222,7 @@ async function cmdUpgrade(args, dbPath) {
|
|
|
1078
1222
|
throw new UserError(error.message);
|
|
1079
1223
|
throw error;
|
|
1080
1224
|
}
|
|
1081
|
-
console.log(`\nAbre esto para completar el alta a ${plan === "pro" ? "Pro" : "Teams"}` +
|
|
1225
|
+
console.log((0, index_js_1.tr) `\nAbre esto para completar el alta a ${plan === "pro" ? "Pro" : "Teams"}` +
|
|
1082
1226
|
`${interval === "yearly" ? " (anual)" : ""}:\n`);
|
|
1083
1227
|
console.log(` ${result.url}\n`);
|
|
1084
1228
|
}
|
|
@@ -1099,7 +1243,7 @@ async function cmdBillingPortal(dbPath) {
|
|
|
1099
1243
|
throw new UserError(error.message);
|
|
1100
1244
|
throw error;
|
|
1101
1245
|
}
|
|
1102
|
-
console.log(`\nGestiona tu pago aquí:\n\n ${result.url}\n`);
|
|
1246
|
+
console.log((0, index_js_1.tr) `\nGestiona tu pago aquí:\n\n ${result.url}\n`);
|
|
1103
1247
|
}
|
|
1104
1248
|
finally {
|
|
1105
1249
|
db.close();
|
|
@@ -1111,8 +1255,8 @@ async function cmdAccount(dbPath) {
|
|
|
1111
1255
|
try {
|
|
1112
1256
|
const account = store.getCloudAccount(db);
|
|
1113
1257
|
if (!account) {
|
|
1114
|
-
throw new UserError(`Esta máquina no está vinculada a ninguna cuenta. Vincúlala con:\n\n` +
|
|
1115
|
-
` estela login --email tu@correo.com\n`);
|
|
1258
|
+
throw new UserError((0, index_js_1.tr) `Esta máquina no está vinculada a ninguna cuenta. Vincúlala con:\n\n` +
|
|
1259
|
+
(0, index_js_1.tr) ` estela login --email tu@correo.com\n`);
|
|
1116
1260
|
}
|
|
1117
1261
|
let info;
|
|
1118
1262
|
try {
|
|
@@ -1120,13 +1264,13 @@ async function cmdAccount(dbPath) {
|
|
|
1120
1264
|
}
|
|
1121
1265
|
catch (error) {
|
|
1122
1266
|
if (error instanceof client_js_1.CloudError && error.status === 401) {
|
|
1123
|
-
throw new UserError(`Tu sesión ya no vale. Vuelve a vincular la máquina:\n\n estela login --email tu@correo.com\n`);
|
|
1267
|
+
throw new UserError((0, index_js_1.tr) `Tu sesión ya no vale. Vuelve a vincular la máquina:\n\n estela login --email tu@correo.com\n`);
|
|
1124
1268
|
}
|
|
1125
1269
|
throw error;
|
|
1126
1270
|
}
|
|
1127
|
-
console.log(`\n${info.email} · plan ${info.plan}\n`);
|
|
1271
|
+
console.log((0, index_js_1.tr) `\n${info.email} · plan ${info.plan}\n`);
|
|
1128
1272
|
for (const d of info.devices) {
|
|
1129
|
-
console.log(` ${d.label ??
|
|
1273
|
+
console.log((0, index_js_1.tr) ` ${d.label ?? (0, index_js_1.tr) `(sin nombre)`} · v${d.clientVersion ?? "?"} · visto ${d.lastSeenAt?.slice(0, 10) ?? (0, index_js_1.tr) `nunca`}`);
|
|
1130
1274
|
}
|
|
1131
1275
|
}
|
|
1132
1276
|
finally {
|
|
@@ -1146,25 +1290,25 @@ async function cmdAuthor(args, dbPath) {
|
|
|
1146
1290
|
const projectId = required(args, "project");
|
|
1147
1291
|
const project = store.getProject(db, projectId);
|
|
1148
1292
|
if (!project)
|
|
1149
|
-
throw new UserError(`No existe el proyecto "${projectId}".`);
|
|
1293
|
+
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
|
|
1150
1294
|
const emails = (str(args, "email") ?? "").split(",").map((e) => e.trim()).filter(Boolean);
|
|
1151
1295
|
if (emails.length === 0) {
|
|
1152
1296
|
const repo = project.repoPaths[0];
|
|
1153
1297
|
if (!repo)
|
|
1154
|
-
throw new UserError(
|
|
1155
|
-
console.log(`\nAutores en ${project.name}:\n`);
|
|
1298
|
+
throw new UserError((0, index_js_1.tr) `El proyecto no tiene repositorio asignado.`);
|
|
1299
|
+
console.log((0, index_js_1.tr) `\nAutores en ${project.name}:\n`);
|
|
1156
1300
|
for (const a of await (0, git_js_1.repoAuthors)(repo)) {
|
|
1157
|
-
console.log(` ${a.email.padEnd(38)} ${String(a.commits).padStart(5)} commits, hasta ${a.lastAt}`);
|
|
1301
|
+
console.log((0, index_js_1.tr) ` ${a.email.padEnd(38)} ${String(a.commits).padStart(5)} commits, hasta ${a.lastAt}`);
|
|
1158
1302
|
}
|
|
1159
1303
|
const current = store.getProjectAuthors(db, projectId);
|
|
1160
|
-
console.log(`\nConfigurado ahora: ${current.length ? current.join(", ") :
|
|
1161
|
-
console.log(`\nElige el tuyo con:`);
|
|
1162
|
-
console.log(` estela author --project ${projectId} --email tu@correo.com`);
|
|
1304
|
+
console.log((0, index_js_1.tr) `\nConfigurado ahora: ${current.length ? current.join(", ") : (0, index_js_1.tr) `— nada —`}`);
|
|
1305
|
+
console.log((0, index_js_1.tr) `\nElige el tuyo con:`);
|
|
1306
|
+
console.log((0, index_js_1.tr) ` estela author --project ${projectId} --email tu@correo.com`);
|
|
1163
1307
|
return;
|
|
1164
1308
|
}
|
|
1165
1309
|
store.setProjectAuthors(db, projectId, emails);
|
|
1166
|
-
console.log(`\n${project.name}: commits filtrados por ${emails.join(", ")}`);
|
|
1167
|
-
console.log(`Ejecuta "estela import" para recoger los que faltaban.`);
|
|
1310
|
+
console.log((0, index_js_1.tr) `\n${project.name}: commits filtrados por ${emails.join(", ")}`);
|
|
1311
|
+
console.log((0, index_js_1.tr) `Ejecuta "estela import" para recoger los que faltaban.`);
|
|
1168
1312
|
}
|
|
1169
1313
|
finally {
|
|
1170
1314
|
db.close();
|
|
@@ -1190,14 +1334,14 @@ function cmdExport(args, dbPath) {
|
|
|
1190
1334
|
const projectId = required(args, "project");
|
|
1191
1335
|
const project = store.getProject(db, projectId);
|
|
1192
1336
|
if (!project)
|
|
1193
|
-
throw new UserError(`No existe el proyecto "${projectId}".`);
|
|
1337
|
+
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
|
|
1194
1338
|
const client = store.getClient(db, project.clientId);
|
|
1195
1339
|
const rates = store.getRates(db, projectId);
|
|
1196
1340
|
const csv = (0, csv_js_1.timeEntriesToCsv)(store.getTimeEntries(db, projectId), project, client, (at) => (0, invoice_js_1.rateAt)(rates, projectId, at));
|
|
1197
1341
|
const out = str(args, "out");
|
|
1198
1342
|
if (out) {
|
|
1199
1343
|
(0, node_fs_1.writeFileSync)(out, csv, "utf8");
|
|
1200
|
-
console.log(`CSV: ${out}`);
|
|
1344
|
+
console.log((0, index_js_1.tr) `CSV: ${out}`);
|
|
1201
1345
|
}
|
|
1202
1346
|
else {
|
|
1203
1347
|
process.stdout.write(csv);
|
|
@@ -1220,8 +1364,8 @@ function cmdSubscriptionAdd(args, dbPath) {
|
|
|
1220
1364
|
effectiveFrom: fromRaw ? new Date(`${fromRaw}T00:00:00Z`) : new Date(0),
|
|
1221
1365
|
effectiveTo: null,
|
|
1222
1366
|
});
|
|
1223
|
-
console.log(`Suscripción "${str(args, "name") ?? required(args, "id")}": ${(0, shared_1.formatMoney)(fee)}/mes.`);
|
|
1224
|
-
console.log(
|
|
1367
|
+
console.log((0, index_js_1.tr) `Suscripción "${str(args, "name") ?? required(args, "id")}": ${(0, shared_1.formatMoney)(fee)}/mes.`);
|
|
1368
|
+
console.log((0, index_js_1.tr) `Se repartirá entre proyectos según lo que consumió cada uno.`);
|
|
1225
1369
|
}
|
|
1226
1370
|
finally {
|
|
1227
1371
|
db.close();
|
|
@@ -1234,13 +1378,13 @@ function cmdAiCost(dbPath) {
|
|
|
1234
1378
|
const subs = store.listSubscriptions(db);
|
|
1235
1379
|
const rows = store.consumptionByProjectMonth(db);
|
|
1236
1380
|
if (rows.length === 0) {
|
|
1237
|
-
console.log(
|
|
1381
|
+
console.log((0, index_js_1.tr) `Sin consumo registrado. Ejecuta: estela import`);
|
|
1238
1382
|
return;
|
|
1239
1383
|
}
|
|
1240
1384
|
if (subs.length === 0) {
|
|
1241
|
-
console.log(
|
|
1242
|
-
console.log(
|
|
1243
|
-
console.log(
|
|
1385
|
+
console.log((0, index_js_1.tr) `Sin suscripciones registradas: el coste se muestra a tarifa API.\n`);
|
|
1386
|
+
console.log((0, index_js_1.tr) `Si pagas cuota fija, regístrala para ver el gasto real:`);
|
|
1387
|
+
console.log((0, index_js_1.tr) ` estela subscription add --id claude-max --name "Claude Max" --fee 200\n`);
|
|
1244
1388
|
}
|
|
1245
1389
|
const shares = (0, amortize_js_1.amortize)(rows, subs, store.totalConsumptionByMonth(db));
|
|
1246
1390
|
const names = new Map(store.listProjects(db).map((p) => [p.id, p.name]));
|
|
@@ -1248,7 +1392,7 @@ function cmdAiCost(dbPath) {
|
|
|
1248
1392
|
for (const s of shares) {
|
|
1249
1393
|
if (s.month !== month) {
|
|
1250
1394
|
month = s.month;
|
|
1251
|
-
console.log(`\n${month} (consumo total: ${(0, shared_1.formatAiCost)(s.monthTotal)} equiv. API)`);
|
|
1395
|
+
console.log((0, index_js_1.tr) `\n${month} (consumo total: ${(0, shared_1.formatAiCost)(s.monthTotal)} equiv. API)`);
|
|
1252
1396
|
}
|
|
1253
1397
|
const label = (names.get(s.projectId) ?? s.projectId).slice(0, 34).padEnd(36);
|
|
1254
1398
|
const pct = `${(s.share * 100).toFixed(1)}%`.padStart(7);
|
|
@@ -1256,8 +1400,8 @@ function cmdAiCost(dbPath) {
|
|
|
1256
1400
|
console.log(` ${label}${(0, shared_1.formatAiCost)(s.consumption).padStart(10)}${pct}${real}`);
|
|
1257
1401
|
}
|
|
1258
1402
|
if (subs.length > 0) {
|
|
1259
|
-
console.log(`\nLa última columna es dinero real: tu cuota repartida por consumo.`);
|
|
1260
|
-
console.log(`La primera es la tarifa API equivalente, útil solo como medida de uso.`);
|
|
1403
|
+
console.log((0, index_js_1.tr) `\nLa última columna es dinero real: tu cuota repartida por consumo.`);
|
|
1404
|
+
console.log((0, index_js_1.tr) `La primera es la tarifa API equivalente, útil solo como medida de uso.`);
|
|
1261
1405
|
}
|
|
1262
1406
|
}
|
|
1263
1407
|
finally {
|
|
@@ -1270,12 +1414,12 @@ function cmdInvoice(args, dbPath) {
|
|
|
1270
1414
|
const projectId = required(args, "project");
|
|
1271
1415
|
const project = store.getProject(db, projectId);
|
|
1272
1416
|
if (!project)
|
|
1273
|
-
throw new UserError(`No existe el proyecto "${projectId}".`);
|
|
1417
|
+
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
|
|
1274
1418
|
const client = store.getClient(db, project.clientId);
|
|
1275
1419
|
const cutoffRaw = required(args, "cutoff");
|
|
1276
1420
|
const cutoffAt = new Date(`${cutoffRaw}T23:59:59Z`);
|
|
1277
1421
|
if (Number.isNaN(cutoffAt.getTime()))
|
|
1278
|
-
throw new UserError(`Fecha inválida: ${cutoffRaw}`);
|
|
1422
|
+
throw new UserError((0, index_js_1.tr) `Fecha inválida: ${cutoffRaw}`);
|
|
1279
1423
|
const dryRun = args.flags["dry-run"] === true;
|
|
1280
1424
|
const number = str(args, "number") ?? store.nextInvoiceNumber(db, "INF");
|
|
1281
1425
|
const fx = str(args, "fx");
|
|
@@ -1303,16 +1447,16 @@ function cmdInvoice(args, dbPath) {
|
|
|
1303
1447
|
// Coste real de IA del periodo: la cuota de tus suscripciones repartida
|
|
1304
1448
|
// por consumo. Es lo que de verdad te costó, frente a la tarifa API.
|
|
1305
1449
|
if (invoice.aiAmortized) {
|
|
1306
|
-
console.log(`Coste real de IA imputado desde tu suscripción: ${(0, shared_1.formatMoney)(invoice.aiAmortized)}`);
|
|
1450
|
+
console.log((0, index_js_1.tr) `Coste real de IA imputado desde tu suscripción: ${(0, shared_1.formatMoney)(invoice.aiAmortized)}`);
|
|
1307
1451
|
}
|
|
1308
1452
|
if (subs.length === 0) {
|
|
1309
|
-
console.log(
|
|
1310
|
-
console.log(
|
|
1311
|
-
console.log(
|
|
1453
|
+
console.log((0, index_js_1.tr) `\nSin suscripciones registradas. Si pagas cuota fija, el importe de arriba`);
|
|
1454
|
+
console.log((0, index_js_1.tr) `es tarifa API equivalente, no lo que gastaste. Regístrala con:`);
|
|
1455
|
+
console.log((0, index_js_1.tr) ` estela subscription add --id claude-max --name "Claude Max" --fee 200`);
|
|
1312
1456
|
}
|
|
1313
1457
|
if (fx) {
|
|
1314
1458
|
const m = (0, invoice_js_1.marginOf)(invoice, Number(fx));
|
|
1315
|
-
console.log(`\nMargen a tarifa API: ${(0, shared_1.formatMoney)(m.margin)} de ${(0, shared_1.formatMoney)(m.revenue)} ` +
|
|
1459
|
+
console.log((0, index_js_1.tr) `\nMargen a tarifa API: ${(0, shared_1.formatMoney)(m.margin)} de ${(0, shared_1.formatMoney)(m.revenue)} ` +
|
|
1316
1460
|
`(${m.marginPct.toFixed(1)}%)`);
|
|
1317
1461
|
}
|
|
1318
1462
|
// --- Exportables ---------------------------------------------------------
|
|
@@ -1330,22 +1474,22 @@ function cmdInvoice(args, dbPath) {
|
|
|
1330
1474
|
...(invoice.aiAmortized ? { amortizedAiCost: invoice.aiAmortized } : {}),
|
|
1331
1475
|
});
|
|
1332
1476
|
(0, node_fs_1.writeFileSync)(pdfPath, pdf);
|
|
1333
|
-
console.log(`\nPDF: ${pdfPath} (${(pdf.length / 1024).toFixed(1)} KB, ${invoice.lines.length} conceptos)`);
|
|
1477
|
+
console.log((0, index_js_1.tr) `\nPDF: ${pdfPath} (${(pdf.length / 1024).toFixed(1)} KB, ${invoice.lines.length} conceptos)`);
|
|
1334
1478
|
}
|
|
1335
1479
|
const csvPath = str(args, "csv");
|
|
1336
1480
|
if (csvPath) {
|
|
1337
1481
|
(0, node_fs_1.writeFileSync)(csvPath, (0, csv_js_1.invoiceToCsv)(invoice, client, project), "utf8");
|
|
1338
|
-
console.log(`CSV: ${csvPath}`);
|
|
1482
|
+
console.log((0, index_js_1.tr) `CSV: ${csvPath}`);
|
|
1339
1483
|
}
|
|
1340
1484
|
if (dryRun) {
|
|
1341
|
-
console.log(
|
|
1485
|
+
console.log((0, index_js_1.tr) `\n[--dry-run] No se guardó nada. Repite sin --dry-run para emitirlo.`);
|
|
1342
1486
|
return;
|
|
1343
1487
|
}
|
|
1344
1488
|
const billed = entries
|
|
1345
1489
|
.filter((e) => e.billable && e.invoiceId === null && e.endedAt <= cutoffAt)
|
|
1346
1490
|
.map((e) => e.id);
|
|
1347
1491
|
store.saveInvoice(db, invoice, billed);
|
|
1348
|
-
console.log(`\nInforme ${invoice.number} emitido. ${billed.length} bloques marcados como informados.`);
|
|
1492
|
+
console.log((0, index_js_1.tr) `\nInforme ${invoice.number} emitido. ${billed.length} bloques marcados como informados.`);
|
|
1349
1493
|
}
|
|
1350
1494
|
finally {
|
|
1351
1495
|
db.close();
|
|
@@ -1362,6 +1506,9 @@ const VERSION = require("../package.json").version;
|
|
|
1362
1506
|
async function main() {
|
|
1363
1507
|
const argv = process.argv.slice(2);
|
|
1364
1508
|
const args = parseArgs(argv);
|
|
1509
|
+
// Lo primero, antes de imprimir nada: hasta la ayuda sale ya en su idioma.
|
|
1510
|
+
(0, index_js_1.setLang)((0, index_js_1.detectLang)({ flag: str(args, "lang"), env: process.env, platform: process.platform }));
|
|
1511
|
+
(0, shared_1.setMoneyLocale)((0, index_js_1.getLang)() === "en" ? "en-US" : "es-EC");
|
|
1365
1512
|
// Global y antes que nada: "estela --version" no debe abrir ninguna base de
|
|
1366
1513
|
// datos ni comprobar el resto de argumentos para responder algo tan simple.
|
|
1367
1514
|
if (args.flags["version"] === true) {
|
|
@@ -1471,10 +1618,10 @@ async function main() {
|
|
|
1471
1618
|
break;
|
|
1472
1619
|
case "":
|
|
1473
1620
|
case "help":
|
|
1474
|
-
console.log(
|
|
1621
|
+
console.log(helpText());
|
|
1475
1622
|
break;
|
|
1476
1623
|
default:
|
|
1477
|
-
console.error(`Comando desconocido: "${command}"\n${
|
|
1624
|
+
console.error((0, index_js_1.tr) `Comando desconocido: "${command}"\n${helpText()}`);
|
|
1478
1625
|
process.exitCode = 1;
|
|
1479
1626
|
}
|
|
1480
1627
|
}
|