estela 0.1.34 → 0.1.35
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/cli.d.ts.map +1 -1
- package/dist/cli.js +205 -9
- package/dist/cli.js.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +21 -1
- package/dist/db/schema.js.map +1 -1
- package/dist/db/schema.test.js +53 -0
- package/dist/db/schema.test.js.map +1 -1
- package/dist/db/store.d.ts +12 -1
- package/dist/db/store.d.ts.map +1 -1
- package/dist/db/store.js +35 -3
- package/dist/db/store.js.map +1 -1
- package/dist/export/clockify.d.ts +32 -0
- package/dist/export/clockify.d.ts.map +1 -0
- package/dist/export/clockify.js +57 -0
- package/dist/export/clockify.js.map +1 -0
- package/dist/export/clockify.test.d.ts +2 -0
- package/dist/export/clockify.test.d.ts.map +1 -0
- package/dist/export/clockify.test.js +51 -0
- package/dist/export/clockify.test.js.map +1 -0
- package/dist/export/csv.d.ts.map +1 -1
- package/dist/export/csv.js +6 -2
- package/dist/export/csv.js.map +1 -1
- package/dist/export/idioma.test.js +2 -2
- package/dist/export/idioma.test.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +22 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +4 -0
- package/dist/server.js.map +1 -1
- package/dist/workitems.test.d.ts +2 -0
- package/dist/workitems.test.d.ts.map +1 -0
- package/dist/workitems.test.js +59 -0
- package/dist/workitems.test.js.map +1 -0
- package/node_modules/@estela/shared/dist/index.d.ts +1 -0
- package/node_modules/@estela/shared/dist/index.d.ts.map +1 -1
- package/node_modules/@estela/shared/dist/index.js +1 -0
- package/node_modules/@estela/shared/dist/index.js.map +1 -1
- package/node_modules/@estela/shared/dist/types.d.ts +13 -0
- package/node_modules/@estela/shared/dist/types.d.ts.map +1 -1
- package/node_modules/@estela/shared/dist/types.js.map +1 -1
- package/node_modules/@estela/shared/dist/workitems.d.ts +44 -0
- package/node_modules/@estela/shared/dist/workitems.d.ts.map +1 -0
- package/node_modules/@estela/shared/dist/workitems.js +137 -0
- package/node_modules/@estela/shared/dist/workitems.js.map +1 -0
- package/package.json +1 -1
- package/web/app.css +7 -0
- package/web/app.js +8 -1
- package/web/i18n.js +4 -0
- package/web/index.html +2 -0
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAwCA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAu2DpD,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -54,6 +54,7 @@ const demo_js_1 = require("./demo.js");
|
|
|
54
54
|
const doctor_js_1 = require("./doctor.js");
|
|
55
55
|
const setup_js_1 = require("./setup.js");
|
|
56
56
|
const csv_js_1 = require("./export/csv.js");
|
|
57
|
+
const clockify_js_1 = require("./export/clockify.js");
|
|
57
58
|
const invoice_pdf_js_1 = require("./export/invoice-pdf.js");
|
|
58
59
|
const share_js_1 = require("./export/share.js");
|
|
59
60
|
const server_js_1 = require("./server.js");
|
|
@@ -102,6 +103,11 @@ estela — registro de horas para desarrollo asistido por IA
|
|
|
102
103
|
vuelve a haber actividad ahí — "estela doctor" avisa en ese caso, en
|
|
103
104
|
vez de perder el bloque o facturarlo por sorpresa.
|
|
104
105
|
estela project reopen --project <id>
|
|
106
|
+
estela project tracker --project <id> [--system jira|azure|github|gitlab|none]
|
|
107
|
+
[--prefix PROJ,OPS]
|
|
108
|
+
El gestor de tareas del proyecto, para reconocer sus tickets en la
|
|
109
|
+
rama y los commits (feature/1234-login, AB#1234, PROJ-12). Luego
|
|
110
|
+
"estela import" lo aplica a lo ya capturado.
|
|
105
111
|
estela rate set --project <id> --rate <importe> [--from YYYY-MM-DD]
|
|
106
112
|
estela budget --project <id> [--amount <dólares>|none]
|
|
107
113
|
Presupuesto mensual de IA. Sin --amount, consulta el actual. Alimenta
|
|
@@ -128,7 +134,11 @@ estela — registro de horas para desarrollo asistido por IA
|
|
|
128
134
|
estela doctor Revisa los datos y avisa de lo que
|
|
129
135
|
rompería una demo. Úsalo antes de publicar.
|
|
130
136
|
estela status Qué hay capturado y sin imputar.
|
|
131
|
-
estela entries --project <id>
|
|
137
|
+
estela entries --project <id> [--ids]
|
|
138
|
+
Bloques imputados a un proyecto, con sus tickets. --ids enseña el id
|
|
139
|
+
de cada uno, para "estela entry link".
|
|
140
|
+
estela entry link --entry <id> --items PROJ-12[,AB#1234] | --clear
|
|
141
|
+
Pone a mano los tickets de un bloque. El import ya no los cambia.
|
|
132
142
|
estela ai-cost Reparto de tu cuota entre proyectos.
|
|
133
143
|
estela share --project <id> [--from YYYY-MM-DD] [--to YYYY-MM-DD]
|
|
134
144
|
[--author <tu nombre>] [--with-amounts] [--out <fichero.html>]
|
|
@@ -186,7 +196,12 @@ estela — registro de horas para desarrollo asistido por IA
|
|
|
186
196
|
estela account
|
|
187
197
|
Tu correo, tu plan, y desde qué máquinas te has conectado.
|
|
188
198
|
|
|
189
|
-
estela export --project <id> [--out <fichero.csv>]
|
|
199
|
+
estela export --project <id> [--out <fichero.csv>] [--month YYYY-MM]
|
|
200
|
+
[--from YYYY-MM-DD] [--to YYYY-MM-DD] [--format csv|clockify]
|
|
201
|
+
Tus bloques en CSV. --format clockify (Pro) sale listo para importar en
|
|
202
|
+
Clockify; --email es tu correo allí (por defecto el de tu cuenta de
|
|
203
|
+
Estela), y --date-format/--time-format tienen que coincidir con tu
|
|
204
|
+
perfil de Clockify (MM/DD/YYYY y 12h por defecto).
|
|
190
205
|
|
|
191
206
|
estela report --project <id> --cutoff YYYY-MM-DD [--dry-run]
|
|
192
207
|
[--pdf <fichero.pdf>] [--csv <fichero.csv>] [--fx <tipo>]
|
|
@@ -236,6 +251,11 @@ estela — time tracking for AI-assisted development
|
|
|
236
251
|
there's activity there again — "estela doctor" flags that instead of
|
|
237
252
|
silently dropping the block or billing it by surprise.
|
|
238
253
|
estela project reopen --project <id>
|
|
254
|
+
estela project tracker --project <id> [--system jira|azure|github|gitlab|none]
|
|
255
|
+
[--prefix PROJ,OPS]
|
|
256
|
+
The project's issue tracker, to recognise its tickets in the branch
|
|
257
|
+
and commits (feature/1234-login, AB#1234, PROJ-12). Then
|
|
258
|
+
"estela import" applies it to what's already captured.
|
|
239
259
|
estela rate set --project <id> --rate <amount> [--from YYYY-MM-DD]
|
|
240
260
|
estela budget --project <id> [--amount <dollars>|none]
|
|
241
261
|
Monthly AI budget. Without --amount, shows the current one. Feeds the
|
|
@@ -262,7 +282,11 @@ estela — time tracking for AI-assisted development
|
|
|
262
282
|
estela doctor Checks your data and flags anything that
|
|
263
283
|
would ruin a demo. Run it before publishing.
|
|
264
284
|
estela status What's been captured and not yet assigned.
|
|
265
|
-
estela entries --project <id>
|
|
285
|
+
estela entries --project <id> [--ids]
|
|
286
|
+
Blocks assigned to a project, with their tickets. --ids shows each
|
|
287
|
+
one's id, for "estela entry link".
|
|
288
|
+
estela entry link --entry <id> --items PROJ-12[,AB#1234] | --clear
|
|
289
|
+
Sets a block's tickets by hand. Imports won't change them anymore.
|
|
266
290
|
estela ai-cost How your subscription splits across projects.
|
|
267
291
|
estela share --project <id> [--from YYYY-MM-DD] [--to YYYY-MM-DD]
|
|
268
292
|
[--author <your name>] [--with-amounts] [--out <file.html>]
|
|
@@ -320,7 +344,12 @@ estela — time tracking for AI-assisted development
|
|
|
320
344
|
estela account
|
|
321
345
|
Your email, your plan, and which machines you've signed in from.
|
|
322
346
|
|
|
323
|
-
estela export --project <id> [--out <file.csv>]
|
|
347
|
+
estela export --project <id> [--out <file.csv>] [--month YYYY-MM]
|
|
348
|
+
[--from YYYY-MM-DD] [--to YYYY-MM-DD] [--format csv|clockify]
|
|
349
|
+
Your blocks as CSV. --format clockify (Pro) comes out ready to import
|
|
350
|
+
into Clockify; --email is your email there (your Estela account's by
|
|
351
|
+
default), and --date-format/--time-format have to match your Clockify
|
|
352
|
+
profile (MM/DD/YYYY and 12h by default).
|
|
324
353
|
|
|
325
354
|
estela report --project <id> --cutoff YYYY-MM-DD [--dry-run]
|
|
326
355
|
[--pdf <file.pdf>] [--csv <file.csv>] [--fx <rate>]
|
|
@@ -715,6 +744,9 @@ async function cmdImport(args, dbPath) {
|
|
|
715
744
|
source: block.turnCount > 0 ? "agent" : "commit",
|
|
716
745
|
kind: "development",
|
|
717
746
|
branch: block.branch,
|
|
747
|
+
// El ticket se saca aquí y no más tarde: en la base solo quedan los
|
|
748
|
+
// hashes de los commits, y es en sus mensajes donde va el `AB#1234`.
|
|
749
|
+
workItems: (0, shared_1.extractWorkItems)(block.branch, block.commits.map((c) => c.subject), project.tracker),
|
|
718
750
|
};
|
|
719
751
|
// El id es determinista (proyecto + día + rama) para que reimportar
|
|
720
752
|
// actualice la imputación en vez de duplicarla. La fecha tiene que ser la
|
|
@@ -869,6 +901,83 @@ function cmdProjectReopen(args, dbPath) {
|
|
|
869
901
|
db.close();
|
|
870
902
|
}
|
|
871
903
|
}
|
|
904
|
+
/**
|
|
905
|
+
* `estela project tracker` — qué gestor de tareas usa un proyecto, para
|
|
906
|
+
* reconocer sus tickets en ramas y commits. Sin --system enseña el actual.
|
|
907
|
+
*
|
|
908
|
+
* No recalcula nada por sí mismo: `estela import` ya rehace todos los bloques
|
|
909
|
+
* no facturados, y así solo hay un camino que decide los tickets.
|
|
910
|
+
*/
|
|
911
|
+
function cmdProjectTracker(args, dbPath) {
|
|
912
|
+
const db = (0, schema_js_1.openDatabase)(dbPath);
|
|
913
|
+
try {
|
|
914
|
+
const projectId = required(args, "project");
|
|
915
|
+
const project = store.getProject(db, projectId);
|
|
916
|
+
if (!project)
|
|
917
|
+
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
|
|
918
|
+
const system = str(args, "system");
|
|
919
|
+
if (!system) {
|
|
920
|
+
console.log(project.tracker
|
|
921
|
+
? (0, index_js_1.tr) `"${project.name}" usa ${project.tracker.system}${project.tracker.prefixes.length ? " (" + project.tracker.prefixes.join(", ") + ")" : ""}.`
|
|
922
|
+
: (0, index_js_1.tr) `"${project.name}" no tiene gestor de tareas. Solo se reconocen AB#1234 y claves de Jira en mayúscula en la rama.`);
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
if (system === "none") {
|
|
926
|
+
store.setProjectTracker(db, projectId, null);
|
|
927
|
+
console.log((0, index_js_1.tr) `"${project.name}" ya no tiene gestor de tareas.`);
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
if (!shared_1.TRACKER_SYSTEMS.includes(system)) {
|
|
931
|
+
throw new UserError((0, index_js_1.tr) `Gestor desconocido: ${system}. Usa jira, azure, github, gitlab o none.`);
|
|
932
|
+
}
|
|
933
|
+
const prefixes = (str(args, "prefix") ?? "").split(",").map((p) => p.trim().toUpperCase()).filter(Boolean);
|
|
934
|
+
if (prefixes.some((p) => !/^[A-Z][A-Z0-9]*$/.test(p))) {
|
|
935
|
+
throw new UserError((0, index_js_1.tr) `Prefijo inválido: tienen que ser letras y números, como PROJ u OPS.`);
|
|
936
|
+
}
|
|
937
|
+
store.setProjectTracker(db, projectId, { system: system, prefixes });
|
|
938
|
+
console.log((0, index_js_1.tr) `"${project.name}" usa ${system}${prefixes.length ? " (" + prefixes.join(", ") + ")" : ""}.`);
|
|
939
|
+
}
|
|
940
|
+
console.log((0, index_js_1.tr) `Para aplicarlo a lo ya capturado: estela import`);
|
|
941
|
+
}
|
|
942
|
+
finally {
|
|
943
|
+
db.close();
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* `estela entry link` — pone a mano los tickets de un bloque, cuando la rama
|
|
948
|
+
* no lo decía o lo decía mal. Desde entonces el import no los toca;
|
|
949
|
+
* `--clear` los devuelve a lo que se detecte solo.
|
|
950
|
+
*/
|
|
951
|
+
function cmdEntryLink(args, dbPath) {
|
|
952
|
+
const db = (0, schema_js_1.openDatabase)(dbPath);
|
|
953
|
+
try {
|
|
954
|
+
const entryId = required(args, "entry");
|
|
955
|
+
const row = db.prepare("SELECT project_id FROM time_entries WHERE id = ?").get(entryId);
|
|
956
|
+
if (!row)
|
|
957
|
+
throw new UserError((0, index_js_1.tr) `No existe el bloque "${entryId}". Los ids salen con: estela entries --project <id> --ids`);
|
|
958
|
+
if (args.flags["clear"] === true) {
|
|
959
|
+
store.setEntryWorkItems(db, entryId, null);
|
|
960
|
+
console.log((0, index_js_1.tr) `Tickets de "${entryId}" devueltos a lo que detecte el import.`);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
const tracker = store.getProject(db, row.project_id)?.tracker ?? null;
|
|
964
|
+
const raw = required(args, "items").split(",").map((t) => t.trim()).filter(Boolean);
|
|
965
|
+
const keys = [];
|
|
966
|
+
for (const text of raw) {
|
|
967
|
+
const key = (0, shared_1.parseWorkItem)(text, tracker);
|
|
968
|
+
if (!key) {
|
|
969
|
+
throw new UserError((0, index_js_1.tr) `No sé de qué gestor es "${text}". Escríbelo como PROJ-12, AB#1234, o configura el del proyecto con: estela project tracker`);
|
|
970
|
+
}
|
|
971
|
+
if (!keys.includes(key))
|
|
972
|
+
keys.push(key);
|
|
973
|
+
}
|
|
974
|
+
store.setEntryWorkItems(db, entryId, keys);
|
|
975
|
+
console.log(`"${entryId}" → ${keys.map(shared_1.formatWorkItem).join(", ")}`);
|
|
976
|
+
}
|
|
977
|
+
finally {
|
|
978
|
+
db.close();
|
|
979
|
+
}
|
|
980
|
+
}
|
|
872
981
|
/**
|
|
873
982
|
* Presupuesto mensual de IA de un proyecto.
|
|
874
983
|
*
|
|
@@ -987,13 +1096,17 @@ function cmdEntries(args, dbPath) {
|
|
|
987
1096
|
console.log((0, index_js_1.tr) `Sin bloques imputados. Ejecuta: estela import`);
|
|
988
1097
|
return;
|
|
989
1098
|
}
|
|
1099
|
+
const ids = args.flags["ids"] === true;
|
|
990
1100
|
console.log(`${project.name}\n`);
|
|
991
1101
|
for (const e of entries) {
|
|
992
1102
|
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`;
|
|
1103
|
+
const tickets = (e.workItems ?? []).map(shared_1.formatWorkItem).join(",");
|
|
993
1104
|
console.log(`${e.startedAt.toISOString().slice(0, 16).replace("T", " ")} ` +
|
|
994
1105
|
`${(0, shared_1.formatDuration)(e.seconds).padStart(8)} ` +
|
|
995
1106
|
`${(0, shared_1.formatAiCost)(e.aiCost).padStart(9)} ` +
|
|
996
|
-
`${flag.padEnd(14)} ${e.description.slice(0, 44)}`);
|
|
1107
|
+
`${flag.padEnd(14)} ${tickets ? tickets.padEnd(10) + " " : ""}${e.description.slice(0, 44)}`);
|
|
1108
|
+
if (ids)
|
|
1109
|
+
console.log(` ${e.id}`);
|
|
997
1110
|
}
|
|
998
1111
|
}
|
|
999
1112
|
finally {
|
|
@@ -1526,7 +1639,7 @@ function cmdDoctor(dbPath) {
|
|
|
1526
1639
|
db.close();
|
|
1527
1640
|
}
|
|
1528
1641
|
}
|
|
1529
|
-
function cmdExport(args, dbPath) {
|
|
1642
|
+
async function cmdExport(args, dbPath) {
|
|
1530
1643
|
const db = (0, schema_js_1.openDatabase)(dbPath);
|
|
1531
1644
|
try {
|
|
1532
1645
|
const projectId = required(args, "project");
|
|
@@ -1535,15 +1648,44 @@ function cmdExport(args, dbPath) {
|
|
|
1535
1648
|
throw new UserError((0, index_js_1.tr) `No existe el proyecto "${projectId}".`);
|
|
1536
1649
|
const client = store.getClient(db, project.clientId);
|
|
1537
1650
|
const rates = store.getRates(db, projectId);
|
|
1651
|
+
const format = str(args, "format") ?? "csv";
|
|
1652
|
+
if (format !== "csv" && format !== "clockify") {
|
|
1653
|
+
throw new UserError((0, index_js_1.tr) `Formato desconocido: ${format}. Usa csv o clockify.`);
|
|
1654
|
+
}
|
|
1655
|
+
const entries = entriesInRange(store.getTimeEntries(db, projectId), args);
|
|
1538
1656
|
const lang = docLangOf(args, client);
|
|
1539
|
-
|
|
1657
|
+
let csv;
|
|
1658
|
+
if (format === "clockify") {
|
|
1659
|
+
const account = await requirePaidPlan(db, (0, index_js_1.tr) `Exportar a Clockify`);
|
|
1660
|
+
const email = str(args, "email") ?? account?.email;
|
|
1661
|
+
if (!email)
|
|
1662
|
+
throw new UserError((0, index_js_1.tr) `Falta --email: el correo de tu cuenta de Clockify.`);
|
|
1663
|
+
const dateFormat = (str(args, "date-format") ?? "MM/DD/YYYY").toUpperCase();
|
|
1664
|
+
if (!clockify_js_1.CLOCKIFY_DATE_FORMATS.includes(dateFormat)) {
|
|
1665
|
+
throw new UserError((0, index_js_1.tr) `--date-format admite ${clockify_js_1.CLOCKIFY_DATE_FORMATS.join(", ")}.`);
|
|
1666
|
+
}
|
|
1667
|
+
const timeFormat = str(args, "time-format") ?? "12h";
|
|
1668
|
+
if (timeFormat !== "12h" && timeFormat !== "24h")
|
|
1669
|
+
throw new UserError((0, index_js_1.tr) `--time-format admite 12h o 24h.`);
|
|
1670
|
+
csv = (0, index_js_1.withLang)(lang, () => (0, clockify_js_1.timeEntriesToClockifyCsv)(entries, project, client, { email, dateFormat, timeFormat }));
|
|
1671
|
+
}
|
|
1672
|
+
else {
|
|
1673
|
+
csv = (0, index_js_1.withLang)(lang, () => (0, csv_js_1.timeEntriesToCsv)(entries, project, client, (at) => (0, invoice_js_1.rateAt)(rates, projectId, at)));
|
|
1674
|
+
}
|
|
1540
1675
|
const out = str(args, "out");
|
|
1541
1676
|
if (out) {
|
|
1542
1677
|
(0, node_fs_1.writeFileSync)(out, csv, "utf8");
|
|
1543
1678
|
console.log((0, index_js_1.tr) `CSV: ${out}`);
|
|
1544
1679
|
// Solo si va a un fichero: por la salida estándar el aviso se colaría
|
|
1545
1680
|
// dentro del propio CSV.
|
|
1546
|
-
|
|
1681
|
+
if (format === "clockify") {
|
|
1682
|
+
console.log((0, index_js_1.tr) ` ${entries.length} bloques. En Clockify: Settings → Import → Timesheets.`);
|
|
1683
|
+
console.log((0, index_js_1.tr) ` La fecha y la hora tienen que coincidir con el formato de tu perfil de Clockify;`);
|
|
1684
|
+
console.log((0, index_js_1.tr) ` si no, repítelo con --date-format DD/MM/YYYY o --time-format 24h.`);
|
|
1685
|
+
}
|
|
1686
|
+
else {
|
|
1687
|
+
hintDocLang(args, client, lang);
|
|
1688
|
+
}
|
|
1547
1689
|
}
|
|
1548
1690
|
else {
|
|
1549
1691
|
process.stdout.write(csv);
|
|
@@ -1553,6 +1695,54 @@ function cmdExport(args, dbPath) {
|
|
|
1553
1695
|
db.close();
|
|
1554
1696
|
}
|
|
1555
1697
|
}
|
|
1698
|
+
/**
|
|
1699
|
+
* `--month YYYY-MM`, o `--from`/`--to` en fechas locales. Sin ninguno, todo:
|
|
1700
|
+
* es lo que hacía `estela export` antes de tener rango.
|
|
1701
|
+
*/
|
|
1702
|
+
function entriesInRange(entries, args) {
|
|
1703
|
+
const month = str(args, "month");
|
|
1704
|
+
if (month && !/^\d{4}-\d{2}$/.test(month))
|
|
1705
|
+
throw new UserError((0, index_js_1.tr) `--month va como YYYY-MM.`);
|
|
1706
|
+
const from = month ? `${month}-01` : str(args, "from");
|
|
1707
|
+
const to = month ? `${month}-31` : str(args, "to");
|
|
1708
|
+
for (const d of [from, to]) {
|
|
1709
|
+
if (d && !/^\d{4}-\d{2}-\d{2}$/.test(d))
|
|
1710
|
+
throw new UserError((0, index_js_1.tr) `Fecha inválida: ${d}`);
|
|
1711
|
+
}
|
|
1712
|
+
return entries.filter((e) => {
|
|
1713
|
+
const day = (0, shared_1.localDate)(e.startedAt);
|
|
1714
|
+
return (!from || day >= from) && (!to || day <= to);
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Lo de Pro, comprobado contra la cuenta y no contra lo guardado aquí: el plan
|
|
1719
|
+
* local se anota "free" al hacer login y nadie lo actualiza después de pagar.
|
|
1720
|
+
* Sin conexión se deja pasar — mejor regalar un export que bloquear a quien
|
|
1721
|
+
* paga porque está en un avión — y se aprovecha la respuesta para dejar el
|
|
1722
|
+
* plan local al día.
|
|
1723
|
+
*/
|
|
1724
|
+
async function requirePaidPlan(db, what) {
|
|
1725
|
+
const account = store.getCloudAccount(db);
|
|
1726
|
+
if (!account) {
|
|
1727
|
+
throw new UserError((0, index_js_1.tr) `${what} es de Pro. Vincula tu cuenta con: estela login --email tu@correo.com`);
|
|
1728
|
+
}
|
|
1729
|
+
try {
|
|
1730
|
+
const info = await (0, client_js_1.cloudGet)(account.apiBaseUrl, "/account", { deviceToken: account.deviceToken });
|
|
1731
|
+
store.setCloudAccount(db, { ...account, plan: info.plan });
|
|
1732
|
+
if (info.plan === "free") {
|
|
1733
|
+
throw new UserError((0, index_js_1.tr) `${what} es de Pro. Actualiza con: estela upgrade --plan pro`);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
catch (error) {
|
|
1737
|
+
if (error instanceof client_js_1.CloudError && error.status === 401) {
|
|
1738
|
+
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`);
|
|
1739
|
+
}
|
|
1740
|
+
if (!(error instanceof client_js_1.CloudError) || error.status !== undefined)
|
|
1741
|
+
throw error;
|
|
1742
|
+
// Sin conexión: se deja pasar.
|
|
1743
|
+
}
|
|
1744
|
+
return account;
|
|
1745
|
+
}
|
|
1556
1746
|
function cmdSubscriptionAdd(args, dbPath) {
|
|
1557
1747
|
const db = (0, schema_js_1.openDatabase)(dbPath);
|
|
1558
1748
|
try {
|
|
@@ -1754,6 +1944,12 @@ async function main() {
|
|
|
1754
1944
|
case "project reopen":
|
|
1755
1945
|
cmdProjectReopen(args, dbPath);
|
|
1756
1946
|
break;
|
|
1947
|
+
case "project tracker":
|
|
1948
|
+
cmdProjectTracker(args, dbPath);
|
|
1949
|
+
break;
|
|
1950
|
+
case "entry link":
|
|
1951
|
+
cmdEntryLink(args, dbPath);
|
|
1952
|
+
break;
|
|
1757
1953
|
case "rate set":
|
|
1758
1954
|
cmdRateSet(args, dbPath);
|
|
1759
1955
|
break;
|
|
@@ -1788,7 +1984,7 @@ async function main() {
|
|
|
1788
1984
|
await cmdPublish(args, dbPath);
|
|
1789
1985
|
break;
|
|
1790
1986
|
case "export":
|
|
1791
|
-
cmdExport(args, dbPath);
|
|
1987
|
+
await cmdExport(args, dbPath);
|
|
1792
1988
|
break;
|
|
1793
1989
|
case "report":
|
|
1794
1990
|
cmdInvoice(args, dbPath);
|