estela 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +138 -0
  2. package/dist/billing/amortize.d.ts +44 -0
  3. package/dist/billing/amortize.d.ts.map +1 -0
  4. package/dist/billing/amortize.js +83 -0
  5. package/dist/billing/amortize.js.map +1 -0
  6. package/dist/billing/amortize.test.d.ts +2 -0
  7. package/dist/billing/amortize.test.d.ts.map +1 -0
  8. package/dist/billing/amortize.test.js +121 -0
  9. package/dist/billing/amortize.test.js.map +1 -0
  10. package/dist/billing/format.test.d.ts +2 -0
  11. package/dist/billing/format.test.d.ts.map +1 -0
  12. package/dist/billing/format.test.js +110 -0
  13. package/dist/billing/format.test.js.map +1 -0
  14. package/dist/billing/invoice.d.ts +52 -0
  15. package/dist/billing/invoice.d.ts.map +1 -0
  16. package/dist/billing/invoice.js +148 -0
  17. package/dist/billing/invoice.js.map +1 -0
  18. package/dist/billing/invoice.test.d.ts +2 -0
  19. package/dist/billing/invoice.test.d.ts.map +1 -0
  20. package/dist/billing/invoice.test.js +200 -0
  21. package/dist/billing/invoice.test.js.map +1 -0
  22. package/dist/billing/sessionize.d.ts +104 -0
  23. package/dist/billing/sessionize.d.ts.map +1 -0
  24. package/dist/billing/sessionize.js +0 -0
  25. package/dist/billing/sessionize.js.map +1 -0
  26. package/dist/billing/sessionize.test.d.ts +2 -0
  27. package/dist/billing/sessionize.test.d.ts.map +1 -0
  28. package/dist/billing/sessionize.test.js +67 -0
  29. package/dist/billing/sessionize.test.js.map +1 -0
  30. package/dist/cli.d.ts +4 -0
  31. package/dist/cli.d.ts.map +1 -0
  32. package/dist/cli.js +1091 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/cloud/auth.d.ts +4 -0
  35. package/dist/cloud/auth.d.ts.map +1 -0
  36. package/dist/cloud/auth.js +98 -0
  37. package/dist/cloud/auth.js.map +1 -0
  38. package/dist/cloud/client.d.ts +12 -0
  39. package/dist/cloud/client.d.ts.map +1 -0
  40. package/dist/cloud/client.js +34 -0
  41. package/dist/cloud/client.js.map +1 -0
  42. package/dist/db/schema.d.ts +11 -0
  43. package/dist/db/schema.d.ts.map +1 -0
  44. package/dist/db/schema.js +424 -0
  45. package/dist/db/schema.js.map +1 -0
  46. package/dist/db/schema.test.d.ts +2 -0
  47. package/dist/db/schema.test.d.ts.map +1 -0
  48. package/dist/db/schema.test.js +335 -0
  49. package/dist/db/schema.test.js.map +1 -0
  50. package/dist/db/store.d.ts +113 -0
  51. package/dist/db/store.d.ts.map +1 -0
  52. package/dist/db/store.js +550 -0
  53. package/dist/db/store.js.map +1 -0
  54. package/dist/doctor.d.ts +24 -0
  55. package/dist/doctor.d.ts.map +1 -0
  56. package/dist/doctor.js +229 -0
  57. package/dist/doctor.js.map +1 -0
  58. package/dist/export/csv.d.ts +9 -0
  59. package/dist/export/csv.d.ts.map +1 -0
  60. package/dist/export/csv.js +81 -0
  61. package/dist/export/csv.js.map +1 -0
  62. package/dist/export/invoice-pdf.d.ts +21 -0
  63. package/dist/export/invoice-pdf.d.ts.map +1 -0
  64. package/dist/export/invoice-pdf.js +135 -0
  65. package/dist/export/invoice-pdf.js.map +1 -0
  66. package/dist/export/panel.d.ts +78 -0
  67. package/dist/export/panel.d.ts.map +1 -0
  68. package/dist/export/panel.js +693 -0
  69. package/dist/export/panel.js.map +1 -0
  70. package/dist/export/panel.test.d.ts +2 -0
  71. package/dist/export/panel.test.d.ts.map +1 -0
  72. package/dist/export/panel.test.js +273 -0
  73. package/dist/export/panel.test.js.map +1 -0
  74. package/dist/export/pdf.d.ts +32 -0
  75. package/dist/export/pdf.d.ts.map +1 -0
  76. package/dist/export/pdf.js +229 -0
  77. package/dist/export/pdf.js.map +1 -0
  78. package/dist/export/pdf.test.d.ts +2 -0
  79. package/dist/export/pdf.test.d.ts.map +1 -0
  80. package/dist/export/pdf.test.js +151 -0
  81. package/dist/export/pdf.test.js.map +1 -0
  82. package/dist/export/share.d.ts +42 -0
  83. package/dist/export/share.d.ts.map +1 -0
  84. package/dist/export/share.js +181 -0
  85. package/dist/export/share.js.map +1 -0
  86. package/dist/export/share.test.d.ts +2 -0
  87. package/dist/export/share.test.d.ts.map +1 -0
  88. package/dist/export/share.test.js +116 -0
  89. package/dist/export/share.test.js.map +1 -0
  90. package/dist/metrics/index.d.ts +101 -0
  91. package/dist/metrics/index.d.ts.map +1 -0
  92. package/dist/metrics/index.js +221 -0
  93. package/dist/metrics/index.js.map +1 -0
  94. package/dist/metrics/metrics.test.d.ts +2 -0
  95. package/dist/metrics/metrics.test.d.ts.map +1 -0
  96. package/dist/metrics/metrics.test.js +162 -0
  97. package/dist/metrics/metrics.test.js.map +1 -0
  98. package/dist/metrics/people.d.ts +46 -0
  99. package/dist/metrics/people.d.ts.map +1 -0
  100. package/dist/metrics/people.js +126 -0
  101. package/dist/metrics/people.js.map +1 -0
  102. package/dist/metrics/people.test.d.ts +2 -0
  103. package/dist/metrics/people.test.d.ts.map +1 -0
  104. package/dist/metrics/people.test.js +84 -0
  105. package/dist/metrics/people.test.js.map +1 -0
  106. package/dist/metrics/team.d.ts +41 -0
  107. package/dist/metrics/team.d.ts.map +1 -0
  108. package/dist/metrics/team.js +0 -0
  109. package/dist/metrics/team.js.map +1 -0
  110. package/dist/metrics/team.test.d.ts +2 -0
  111. package/dist/metrics/team.test.d.ts.map +1 -0
  112. package/dist/metrics/team.test.js +90 -0
  113. package/dist/metrics/team.test.js.map +1 -0
  114. package/dist/pricing/catalog.d.ts +39 -0
  115. package/dist/pricing/catalog.d.ts.map +1 -0
  116. package/dist/pricing/catalog.js +59 -0
  117. package/dist/pricing/catalog.js.map +1 -0
  118. package/dist/pricing/cost.d.ts +25 -0
  119. package/dist/pricing/cost.d.ts.map +1 -0
  120. package/dist/pricing/cost.js +50 -0
  121. package/dist/pricing/cost.js.map +1 -0
  122. package/dist/pricing/cost.test.d.ts +2 -0
  123. package/dist/pricing/cost.test.d.ts.map +1 -0
  124. package/dist/pricing/cost.test.js +74 -0
  125. package/dist/pricing/cost.test.js.map +1 -0
  126. package/dist/server.d.ts +15 -0
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +1141 -0
  129. package/dist/server.js.map +1 -0
  130. package/dist/setup.d.ts +70 -0
  131. package/dist/setup.d.ts.map +1 -0
  132. package/dist/setup.js +223 -0
  133. package/dist/setup.js.map +1 -0
  134. package/dist/setup.test.d.ts +2 -0
  135. package/dist/setup.test.d.ts.map +1 -0
  136. package/dist/setup.test.js +131 -0
  137. package/dist/setup.test.js.map +1 -0
  138. package/dist/watchers/claude.d.ts +34 -0
  139. package/dist/watchers/claude.d.ts.map +1 -0
  140. package/dist/watchers/claude.js +235 -0
  141. package/dist/watchers/claude.js.map +1 -0
  142. package/dist/watchers/claude.test.d.ts +2 -0
  143. package/dist/watchers/claude.test.d.ts.map +1 -0
  144. package/dist/watchers/claude.test.js +180 -0
  145. package/dist/watchers/claude.test.js.map +1 -0
  146. package/dist/watchers/git.d.ts +42 -0
  147. package/dist/watchers/git.d.ts.map +1 -0
  148. package/dist/watchers/git.js +218 -0
  149. package/dist/watchers/git.js.map +1 -0
  150. package/dist/watchers/git.test.d.ts +2 -0
  151. package/dist/watchers/git.test.d.ts.map +1 -0
  152. package/dist/watchers/git.test.js +73 -0
  153. package/dist/watchers/git.test.js.map +1 -0
  154. package/dist/watchers/identity.d.ts +34 -0
  155. package/dist/watchers/identity.d.ts.map +1 -0
  156. package/dist/watchers/identity.js +85 -0
  157. package/dist/watchers/identity.js.map +1 -0
  158. package/dist/watchers/identity.test.d.ts +2 -0
  159. package/dist/watchers/identity.test.d.ts.map +1 -0
  160. package/dist/watchers/identity.test.js +48 -0
  161. package/dist/watchers/identity.test.js.map +1 -0
  162. package/dist/web-ui.test.d.ts +2 -0
  163. package/dist/web-ui.test.d.ts.map +1 -0
  164. package/dist/web-ui.test.js +312 -0
  165. package/dist/web-ui.test.js.map +1 -0
  166. package/node_modules/@estela/shared/dist/index.d.ts +3 -0
  167. package/node_modules/@estela/shared/dist/index.d.ts.map +1 -0
  168. package/node_modules/@estela/shared/dist/index.js +19 -0
  169. package/node_modules/@estela/shared/dist/index.js.map +1 -0
  170. package/node_modules/@estela/shared/dist/money.d.ts +71 -0
  171. package/node_modules/@estela/shared/dist/money.d.ts.map +1 -0
  172. package/node_modules/@estela/shared/dist/money.js +171 -0
  173. package/node_modules/@estela/shared/dist/money.js.map +1 -0
  174. package/node_modules/@estela/shared/dist/types.d.ts +365 -0
  175. package/node_modules/@estela/shared/dist/types.d.ts.map +1 -0
  176. package/node_modules/@estela/shared/dist/types.js +24 -0
  177. package/node_modules/@estela/shared/dist/types.js.map +1 -0
  178. package/node_modules/@estela/shared/package.json +5 -0
  179. package/package.json +41 -0
  180. package/web/app.css +749 -0
  181. package/web/app.js +1257 -0
  182. package/web/apple-touch-icon.png +0 -0
  183. package/web/favicon.png +0 -0
  184. package/web/index.html +132 -0
  185. package/web/lib.js +94 -0
  186. package/web/logo-mark.png +0 -0
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InvoiceError = void 0;
4
+ exports.issueInvoice = issueInvoice;
5
+ exports.rateAt = rateAt;
6
+ exports.renderInvoice = renderInvoice;
7
+ exports.marginOf = marginOf;
8
+ const shared_1 = require("@estela/shared");
9
+ class InvoiceError extends Error {
10
+ }
11
+ exports.InvoiceError = InvoiceError;
12
+ function issueInvoice(input) {
13
+ const { client, project, rates, cutoffAt } = input;
14
+ const issuedAt = input.issuedAt ?? new Date();
15
+ const pending = input.entries
16
+ .filter((e) => e.projectId === project.id)
17
+ .filter((e) => e.billable && e.invoiceId === null)
18
+ .filter((e) => e.endedAt <= cutoffAt)
19
+ .sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
20
+ if (pending.length === 0) {
21
+ throw new InvoiceError(`No hay horas pendientes de facturar en "${project.name}" hasta ${cutoffAt.toISOString().slice(0, 10)}.`);
22
+ }
23
+ const lines = [];
24
+ let totalSeconds = 0;
25
+ for (const entry of pending) {
26
+ const rate = rateAt(rates, project.id, entry.startedAt);
27
+ if (!rate) {
28
+ throw new InvoiceError(`Sin tarifa vigente para "${project.name}" el ${entry.startedAt.toISOString().slice(0, 10)}. ` +
29
+ `Define una con: estela rate set --project ${project.id} --rate <importe> --currency ${client.currency}`);
30
+ }
31
+ if (rate.currency !== client.currency) {
32
+ throw new InvoiceError(`La tarifa de "${project.name}" está en ${rate.currency} pero a ${client.name} se le factura en ` +
33
+ `${client.currency}. Corrige la tarifa o la moneda del cliente: no se convierte automáticamente.`);
34
+ }
35
+ const seconds = (0, shared_1.roundSeconds)(entry.seconds, project.roundingMinutes);
36
+ if (seconds === 0)
37
+ continue;
38
+ totalSeconds += seconds;
39
+ lines.push({
40
+ description: entry.description,
41
+ seconds,
42
+ hourlyRate: rate,
43
+ amount: (0, shared_1.billableAmount)(seconds, rate),
44
+ });
45
+ }
46
+ if (lines.length === 0) {
47
+ throw new InvoiceError(`Todas las entradas quedaron en cero tras redondear a ${project.roundingMinutes} min.`);
48
+ }
49
+ const subtotal = (0, shared_1.sumMoney)(lines.map((l) => l.amount), client.currency);
50
+ const aiCost = (0, shared_1.sumAiCost)(pending.map((e) => e.aiCost));
51
+ let aiCostBilled = null;
52
+ let usdFxRate = null;
53
+ if (project.aiCostPolicy === "passthrough" && aiCost.microUsd > 0) {
54
+ if (client.currency === "USD") {
55
+ usdFxRate = 1;
56
+ }
57
+ else if (input.usdFxRate === undefined) {
58
+ throw new InvoiceError(`"${project.name}" repercute el coste de IA (${(0, shared_1.formatAiCost)(aiCost)} USD) y se factura en ` +
59
+ `${client.currency}. Indica el tipo de cambio del día con --fx <USD->${client.currency}>.`);
60
+ }
61
+ else {
62
+ usdFxRate = input.usdFxRate;
63
+ }
64
+ aiCostBilled = (0, shared_1.aiCostToMoney)(aiCost, client.currency, usdFxRate);
65
+ }
66
+ const total = aiCostBilled ? (0, shared_1.addMoney)(subtotal, aiCostBilled) : subtotal;
67
+ return {
68
+ id: `inv_${input.number}`,
69
+ number: input.number,
70
+ clientId: client.id,
71
+ projectId: project.id,
72
+ issuedAt,
73
+ cutoffAt,
74
+ periodStart: pending[0].startedAt,
75
+ currency: client.currency,
76
+ lines,
77
+ subtotal,
78
+ total,
79
+ totalSeconds,
80
+ aiCost,
81
+ usdFxRate,
82
+ aiCostBilled,
83
+ aiAmortized: input.aiAmortized ?? null,
84
+ ...(input.notes !== undefined ? { notes: input.notes } : {}),
85
+ };
86
+ }
87
+ /** Tarifa vigente en una fecha. Las tarifas se apilan, no se sobrescriben. */
88
+ function rateAt(rates, projectId, at) {
89
+ const applicable = rates
90
+ .filter((r) => r.projectId === projectId)
91
+ .filter((r) => r.effectiveFrom <= at && (r.effectiveTo === null || at < r.effectiveTo))
92
+ .sort((a, b) => b.effectiveFrom.getTime() - a.effectiveFrom.getTime());
93
+ return applicable[0]?.hourlyRate ?? null;
94
+ }
95
+ /** Renderiza la factura como texto plano para revisarla antes de enviarla. */
96
+ function renderInvoice(invoice, client, project) {
97
+ const out = [];
98
+ const rule = "─".repeat(78);
99
+ const date = (d) => d.toISOString().slice(0, 10);
100
+ out.push(rule);
101
+ out.push(`INFORME DE HORAS ${invoice.number}`.padEnd(52) + `Emitido: ${date(invoice.issuedAt)}`);
102
+ out.push(rule);
103
+ out.push(`Cliente: ${client.name}${client.taxId ? ` (${client.taxId})` : ""}`);
104
+ out.push(`Proyecto: ${project.name}`);
105
+ out.push(`Periodo: ${date(invoice.periodStart)} -> ${date(invoice.cutoffAt)}`);
106
+ out.push("");
107
+ out.push("CONCEPTO".padEnd(46) + "TIEMPO".padStart(9) + "TARIFA".padStart(11) + "IMPORTE".padStart(12));
108
+ out.push(rule);
109
+ for (const line of invoice.lines) {
110
+ const desc = line.description.length > 45 ? line.description.slice(0, 42) + "..." : line.description;
111
+ out.push(desc.padEnd(46) +
112
+ (0, shared_1.formatDuration)(line.seconds).padStart(9) +
113
+ `${(0, shared_1.formatMoney)(line.hourlyRate)}/h`.padStart(11) +
114
+ (0, shared_1.formatMoney)(line.amount).padStart(12));
115
+ }
116
+ out.push(rule);
117
+ out.push(`${invoice.lines.length} conceptos`.padEnd(46) +
118
+ (0, shared_1.formatDuration)(invoice.totalSeconds).padStart(9) +
119
+ "".padStart(11) +
120
+ (0, shared_1.formatMoney)(invoice.subtotal).padStart(12));
121
+ if (invoice.aiCostBilled) {
122
+ out.push(`Coste de IA repercutido (1 USD = ${invoice.usdFxRate} ${invoice.currency})`.padEnd(66) +
123
+ (0, shared_1.formatMoney)(invoice.aiCostBilled).padStart(12));
124
+ }
125
+ out.push("");
126
+ out.push("VALOR DEL TRABAJO".padEnd(66) + (0, shared_1.formatMoney)(invoice.total).padStart(12));
127
+ out.push(rule);
128
+ if (project.aiCostPolicy === "absorbed" && invoice.aiCost.microUsd > 0) {
129
+ out.push(`Interno (no facturado): IA del periodo ${(0, shared_1.formatAiCost)(invoice.aiCost)} USD ` +
130
+ `en tarifa API equivalente.`);
131
+ }
132
+ if (invoice.notes)
133
+ out.push(invoice.notes);
134
+ return out.join("\n");
135
+ }
136
+ /** Margen real del periodo: lo cobrado menos lo que costó la IA. */
137
+ function marginOf(invoice, usdFxRate) {
138
+ const revenue = invoice.subtotal;
139
+ const cost = (0, shared_1.aiCostToMoney)(invoice.aiCost, invoice.currency, usdFxRate);
140
+ const margin = (0, shared_1.money)(revenue.amount - cost.amount, invoice.currency);
141
+ return {
142
+ revenue,
143
+ aiCost: cost,
144
+ margin,
145
+ marginPct: revenue.amount === 0 ? 0 : (margin.amount / revenue.amount) * 100,
146
+ };
147
+ }
148
+ //# sourceMappingURL=invoice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice.js","sourceRoot":"","sources":["../../src/billing/invoice.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=invoice.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice.test.d.ts","sourceRoot":"","sources":["../../src/billing/invoice.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = require("node:test");
8
+ const shared_1 = require("@estela/shared");
9
+ const invoice_js_1 = require("./invoice.js");
10
+ // El caso real: freelance, proyecto por horas, 13 EUR/hora pactados.
11
+ const EUROSISTEMA = {
12
+ id: "eurosistema", name: "Eurosistema", currency: "EUR", taxId: "B-12345678",
13
+ };
14
+ const PROYECTO = {
15
+ id: "eurosistema-blazor",
16
+ clientId: "eurosistema",
17
+ name: "Eurosistema Blazor",
18
+ repoPaths: ["/Users/nx-jleon/Documents/WORKSPACES/Eurosistema_Blazor"],
19
+ billable: true,
20
+ roundingMinutes: 0,
21
+ aiCostPolicy: "absorbed", kind: "client",
22
+ };
23
+ const TARIFA = {
24
+ projectId: "eurosistema-blazor",
25
+ hourlyRate: (0, shared_1.money)(1300, "EUR"), // 13,00 EUR/hora
26
+ effectiveFrom: new Date("2025-01-01T00:00:00Z"),
27
+ effectiveTo: null,
28
+ };
29
+ function entry(id, startIso, seconds, microUsd = 0) {
30
+ const startedAt = new Date(startIso);
31
+ return {
32
+ id, projectId: "eurosistema-blazor", startedAt,
33
+ endedAt: new Date(startedAt.getTime() + seconds * 1000),
34
+ seconds, description: `Bloque ${id}`, billable: true, invoiceId: null,
35
+ aiCost: { microUsd }, agentSeconds: seconds, commitHashes: [], agents: ["claude-code"],
36
+ source: "agent", kind: "development", branch: "main",
37
+ };
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // Dinero
41
+ // ---------------------------------------------------------------------------
42
+ (0, node_test_1.test)("13 EUR/hora se guarda como 1300 céntimos, no como 13.0", () => {
43
+ const rate = (0, shared_1.parseMoney)("13", "EUR");
44
+ strict_1.default.equal(rate.amount, 1300);
45
+ strict_1.default.equal(rate.currency, "EUR");
46
+ strict_1.default.equal((0, shared_1.formatMoney)(rate), "€13,00");
47
+ });
48
+ (0, node_test_1.test)("acepta decimales y coma decimal", () => {
49
+ strict_1.default.equal((0, shared_1.parseMoney)("13.50", "EUR").amount, 1350);
50
+ strict_1.default.equal((0, shared_1.parseMoney)("13,50", "EUR").amount, 1350);
51
+ strict_1.default.equal((0, shared_1.parseMoney)("0.01", "EUR").amount, 1);
52
+ });
53
+ (0, node_test_1.test)("una hora exacta a 13 EUR son 13 EUR", () => {
54
+ strict_1.default.equal((0, shared_1.billableAmount)(3600, (0, shared_1.money)(1300, "EUR")).amount, 1300);
55
+ });
56
+ (0, node_test_1.test)("media hora a 13 EUR son 6,50 EUR sin error de coma flotante", () => {
57
+ strict_1.default.equal((0, shared_1.billableAmount)(1800, (0, shared_1.money)(1300, "EUR")).amount, 650);
58
+ });
59
+ (0, node_test_1.test)("sumar diez mil bloques no acumula deriva", () => {
60
+ let total = 0;
61
+ for (let i = 0; i < 10_000; i++)
62
+ total += (0, shared_1.billableAmount)(600, (0, shared_1.money)(1300, "EUR")).amount;
63
+ // 10 min a 13 EUR/h = 216,666... céntimos -> 217 tras redondear
64
+ strict_1.default.equal(total, 217 * 10_000);
65
+ strict_1.default.ok(Number.isInteger(total));
66
+ });
67
+ (0, node_test_1.test)("no se pueden sumar monedas distintas sin tipo de cambio", () => {
68
+ const { addMoney } = require("@estela/shared");
69
+ strict_1.default.throws(() => addMoney((0, shared_1.money)(100, "EUR"), (0, shared_1.money)(100, "USD")), /Monedas distintas/);
70
+ });
71
+ (0, node_test_1.test)("el redondeo de tiempo es una decisión separada del de dinero", () => {
72
+ // Half-up sobre el bloque: 50 min está más cerca de 45 que de 60.
73
+ strict_1.default.equal((0, shared_1.roundSeconds)(3000, 15), 2700); // 50 min -> 45 min
74
+ strict_1.default.equal((0, shared_1.roundSeconds)(3300, 15), 3600); // 55 min -> 1 h
75
+ strict_1.default.equal((0, shared_1.roundSeconds)(3000, 0), 3000); // exacto
76
+ strict_1.default.equal((0, shared_1.roundSeconds)(400, 15), 0); // 6,6 min -> 0 con bloques de 15
77
+ });
78
+ // ---------------------------------------------------------------------------
79
+ // Tarifas con vigencia
80
+ // ---------------------------------------------------------------------------
81
+ (0, node_test_1.test)("subir la tarifa no reescribe lo ya trabajado", () => {
82
+ const rates = [
83
+ { ...TARIFA, effectiveTo: new Date("2026-06-01T00:00:00Z") },
84
+ { projectId: "eurosistema-blazor", hourlyRate: (0, shared_1.money)(1600, "EUR"),
85
+ effectiveFrom: new Date("2026-06-01T00:00:00Z"), effectiveTo: null },
86
+ ];
87
+ strict_1.default.equal((0, invoice_js_1.rateAt)(rates, "eurosistema-blazor", new Date("2026-03-15T10:00:00Z")).amount, 1300);
88
+ strict_1.default.equal((0, invoice_js_1.rateAt)(rates, "eurosistema-blazor", new Date("2026-08-15T10:00:00Z")).amount, 1600);
89
+ });
90
+ // ---------------------------------------------------------------------------
91
+ // Emisión
92
+ // ---------------------------------------------------------------------------
93
+ (0, node_test_1.test)("factura al corte: incluye lo anterior y excluye lo posterior", () => {
94
+ const entries = [
95
+ entry("a", "2026-08-10T09:00:00Z", 3600),
96
+ entry("b", "2026-08-15T09:00:00Z", 5400), // 1,5 h
97
+ entry("c", "2026-08-28T09:00:00Z", 3600), // después del corte
98
+ ];
99
+ const invoice = (0, invoice_js_1.issueInvoice)({
100
+ client: EUROSISTEMA, project: PROYECTO, rates: [TARIFA], entries,
101
+ cutoffAt: new Date("2026-08-25T23:59:59Z"), number: "F-2026-001",
102
+ });
103
+ strict_1.default.equal(invoice.lines.length, 2);
104
+ strict_1.default.equal(invoice.totalSeconds, 9000); // 2,5 h
105
+ strict_1.default.equal(invoice.subtotal.amount, 3250); // 2,5 * 13 = 32,50 EUR
106
+ strict_1.default.equal((0, shared_1.formatMoney)(invoice.total), "€32,50");
107
+ strict_1.default.equal(invoice.currency, "EUR");
108
+ });
109
+ (0, node_test_1.test)("el coste de IA se registra aunque no se facture", () => {
110
+ const invoice = (0, invoice_js_1.issueInvoice)({
111
+ client: EUROSISTEMA, project: PROYECTO, rates: [TARIFA],
112
+ entries: [entry("a", "2026-08-10T09:00:00Z", 3600, 8_016_665)],
113
+ cutoffAt: new Date("2026-08-25T23:59:59Z"), number: "F-2026-002",
114
+ });
115
+ strict_1.default.equal(invoice.aiCost.microUsd, 8_016_665);
116
+ strict_1.default.equal(invoice.aiCostBilled, null, "con política 'absorbed' no se repercute");
117
+ strict_1.default.equal(invoice.total.amount, 1300, "el total es solo la hora facturada");
118
+ });
119
+ (0, node_test_1.test)("repercutir IA en EUR exige tipo de cambio explícito", () => {
120
+ const passthrough = { ...PROYECTO, aiCostPolicy: "passthrough" };
121
+ const input = {
122
+ client: EUROSISTEMA, project: passthrough, rates: [TARIFA],
123
+ entries: [entry("a", "2026-08-10T09:00:00Z", 3600, 10_000_000)], // $10
124
+ cutoffAt: new Date("2026-08-25T23:59:59Z"), number: "F-2026-003",
125
+ };
126
+ strict_1.default.throws(() => (0, invoice_js_1.issueInvoice)(input), /tipo de cambio/);
127
+ const invoice = (0, invoice_js_1.issueInvoice)({ ...input, usdFxRate: 0.92 });
128
+ strict_1.default.equal(invoice.usdFxRate, 0.92, "el tipo queda grabado en la factura");
129
+ strict_1.default.equal(invoice.aiCostBilled.amount, 920); // $10 -> 9,20 EUR
130
+ strict_1.default.equal(invoice.total.amount, 1300 + 920);
131
+ });
132
+ (0, node_test_1.test)("una tarifa en otra moneda que la del cliente se rechaza en vez de convertirse", () => {
133
+ const rateUsd = { ...TARIFA, hourlyRate: (0, shared_1.money)(1300, "USD") };
134
+ strict_1.default.throws(() => (0, invoice_js_1.issueInvoice)({
135
+ client: EUROSISTEMA, project: PROYECTO, rates: [rateUsd],
136
+ entries: [entry("a", "2026-08-10T09:00:00Z", 3600)],
137
+ cutoffAt: new Date("2026-08-25T23:59:59Z"), number: "F-2026-004",
138
+ }), /USD.*EUR|EUR.*USD/);
139
+ });
140
+ (0, node_test_1.test)("distintos freelancers, distintas monedas", () => {
141
+ const usa = { id: "acme", name: "Acme", currency: "USD" };
142
+ const proyecto = { ...PROYECTO, id: "acme-app", clientId: "acme" };
143
+ const rate = {
144
+ projectId: "acme-app", hourlyRate: (0, shared_1.parseMoney)("85", "USD"),
145
+ effectiveFrom: new Date("2025-01-01T00:00:00Z"), effectiveTo: null,
146
+ };
147
+ const invoice = (0, invoice_js_1.issueInvoice)({
148
+ client: usa, project: proyecto, rates: [rate],
149
+ entries: [{ ...entry("a", "2026-08-10T09:00:00Z", 7200), projectId: "acme-app" }],
150
+ cutoffAt: new Date("2026-08-25T23:59:59Z"), number: "F-2026-005",
151
+ });
152
+ strict_1.default.equal(invoice.currency, "USD");
153
+ strict_1.default.equal(invoice.subtotal.amount, 17_000); // 2 h * $85
154
+ strict_1.default.equal((0, shared_1.formatMoney)(invoice.subtotal), "$170,00");
155
+ });
156
+ (0, node_test_1.test)("sin tarifa definida, error claro en vez de factura a cero", () => {
157
+ strict_1.default.throws(() => (0, invoice_js_1.issueInvoice)({
158
+ client: EUROSISTEMA, project: PROYECTO, rates: [],
159
+ entries: [entry("a", "2026-08-10T09:00:00Z", 3600)],
160
+ cutoffAt: new Date("2026-08-25T23:59:59Z"), number: "F-2026-006",
161
+ }), /Sin tarifa vigente/);
162
+ });
163
+ (0, node_test_1.test)("lo ya facturado no se vuelve a facturar", () => {
164
+ const entries = [
165
+ { ...entry("a", "2026-08-10T09:00:00Z", 3600), invoiceId: "inv_F-2026-001" },
166
+ entry("b", "2026-08-11T09:00:00Z", 3600),
167
+ ];
168
+ const invoice = (0, invoice_js_1.issueInvoice)({
169
+ client: EUROSISTEMA, project: PROYECTO, rates: [TARIFA], entries,
170
+ cutoffAt: new Date("2026-08-25T23:59:59Z"), number: "F-2026-007",
171
+ });
172
+ strict_1.default.equal(invoice.lines.length, 1);
173
+ strict_1.default.equal(invoice.subtotal.amount, 1300);
174
+ });
175
+ (0, node_test_1.test)("margen real tras descontar la IA", () => {
176
+ const invoice = (0, invoice_js_1.issueInvoice)({
177
+ client: EUROSISTEMA, project: PROYECTO, rates: [TARIFA],
178
+ entries: [entry("a", "2026-08-10T09:00:00Z", 36_000, 8_016_665)], // 10 h, $8,02 de IA
179
+ cutoffAt: new Date("2026-08-25T23:59:59Z"), number: "F-2026-008",
180
+ });
181
+ const m = (0, invoice_js_1.marginOf)(invoice, 0.92);
182
+ strict_1.default.equal(m.revenue.amount, 13_000); // 10 h * 13 EUR = 130 EUR
183
+ strict_1.default.equal(m.aiCost.amount, 738); // $8,02 * 0,92 = 7,38 EUR
184
+ strict_1.default.equal(m.margin.amount, 13_000 - 738);
185
+ strict_1.default.ok(m.marginPct > 94 && m.marginPct < 95);
186
+ });
187
+ (0, node_test_1.test)("un tipo de cambio nunca se recalcula solo", () => {
188
+ const en_marzo = (0, shared_1.convertMoney)((0, shared_1.money)(1000, "USD"), "EUR", 0.92);
189
+ const en_noviembre = (0, shared_1.convertMoney)((0, shared_1.money)(1000, "USD"), "EUR", 0.88);
190
+ strict_1.default.equal(en_marzo.amount, 920);
191
+ strict_1.default.equal(en_noviembre.amount, 880);
192
+ // La factura guarda su tipo, así que la de marzo sigue diciendo 920.
193
+ });
194
+ (0, node_test_1.test)("sin horas pendientes, error explicativo", () => {
195
+ strict_1.default.throws(() => (0, invoice_js_1.issueInvoice)({
196
+ client: EUROSISTEMA, project: PROYECTO, rates: [TARIFA], entries: [],
197
+ cutoffAt: new Date("2026-08-25T23:59:59Z"), number: "F-2026-009",
198
+ }), invoice_js_1.InvoiceError);
199
+ });
200
+ //# sourceMappingURL=invoice.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice.test.js","sourceRoot":"","sources":["../../src/billing/invoice.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ import type { AgentTurn, AiCost, CommitRecord } from "@estela/shared";
2
+ /**
3
+ * Agrupa turnos sueltos en bloques de trabajo continuo.
4
+ *
5
+ * Un turno de agente es un instante, no una duración: el transcript dice cuándo
6
+ * respondió el modelo, no cuánto tiempo estuviste tú trabajando. La duración se
7
+ * infiere del hueco entre turnos consecutivos.
8
+ *
9
+ * Dos parámetros gobiernan la inferencia y ambos son conservadores a propósito,
10
+ * porque estas horas acaban en una factura:
11
+ *
12
+ * - `gapMinutes`: hueco a partir del cual se considera que te fuiste. Por
13
+ * debajo se cuenta como trabajo continuo (estabas leyendo la respuesta o
14
+ * probando la app, que es trabajo real aunque no haya tecleo).
15
+ * - `tailMinutes`: cuánto se imputa después del último turno del bloque. Sin
16
+ * esto, un bloque de un solo turno duraría cero.
17
+ *
18
+ * Redondear a tu favor aquí es facturarle de más a un cliente. Ante la duda,
19
+ * estos valores subestiman.
20
+ */
21
+ export interface SessionizeOptions {
22
+ readonly gapMinutes?: number;
23
+ readonly tailMinutes?: number;
24
+ }
25
+ export interface WorkBlock {
26
+ readonly startedAt: Date;
27
+ readonly endedAt: Date;
28
+ readonly seconds: number;
29
+ readonly repoPath: string | null;
30
+ readonly branch: string | null;
31
+ readonly turnCount: number;
32
+ readonly aiCost: AiCost;
33
+ readonly models: readonly string[];
34
+ readonly sessionIds: readonly string[];
35
+ /** Commits que caen dentro del bloque. Se rellena con `attachCommits`. */
36
+ readonly commits: readonly CommitRecord[];
37
+ /** Modelos sin precio en el catálogo. Si no está vacío, el coste es parcial. */
38
+ readonly unpricedModels: readonly string[];
39
+ }
40
+ export declare function sessionize(turns: readonly AgentTurn[], options?: SessionizeOptions): WorkBlock[];
41
+ /**
42
+ * Fusiona bloques del mismo día y la misma rama en un solo concepto.
43
+ *
44
+ * Sin esto, una jornada normal produce veinte líneas de "3m", "2m", "1h 11m"
45
+ * porque cada pausa para pensar parte el bloque. Eso es ruido para quien recibe
46
+ * la factura y para ti al revisarla: lo que hiciste ese día en esa rama es *una*
47
+ * cosa, aunque la hicieras en tandas.
48
+ *
49
+ * El detalle no se pierde: los bloques originales siguen siendo la fuente y esta
50
+ * agrupación solo cambia cómo se presentan.
51
+ */
52
+ export declare function groupByBranchAndDay(blocks: readonly WorkBlock[]): WorkBlock[];
53
+ /**
54
+ * Título de un bloque para la línea de factura.
55
+ *
56
+ * Los commits mandan: son lo único que describe el trabajo con palabras que
57
+ * escribiste tú. Sin commits se cae a la rama, que al menos nombra la tarea.
58
+ */
59
+ export declare function describeBlock(block: WorkBlock): string;
60
+ /**
61
+ * Asocia commits a bloques por solapamiento temporal.
62
+ *
63
+ * La correlación es por tiempo, autor y repositorio. No se toca el mensaje del
64
+ * commit ni se instala ningún hook: un identificador inyectado en el historial
65
+ * de git es irreversible una vez subido, y el solapamiento temporal basta.
66
+ *
67
+ * Un commit puede caer justo después del último turno (cierras el bloque y
68
+ * commiteas), así que se admite un margen posterior.
69
+ */
70
+ export declare function attachCommits(blocks: readonly WorkBlock[], commits: readonly CommitRecord[], graceMinutes?: number): WorkBlock[];
71
+ /**
72
+ * Bloques de trabajo deducidos solo de los commits.
73
+ *
74
+ * Para quien programa sin agente de IA, los commits son el único rastro que
75
+ * queda. Pero el modelo es distinto al de los turnos: un turno marca un
76
+ * instante *dentro* del trabajo, así que el tiempo se infiere del hueco entre
77
+ * turnos consecutivos. Un commit marca el *final* de un tramo, y lo trabajado
78
+ * está antes de él.
79
+ *
80
+ * De ahí las dos reglas:
81
+ *
82
+ * - Los commits próximos entre sí forman una tanda. Dentro de la tanda, el
83
+ * tiempo entre commits sí es trabajo.
84
+ * - Antes del primer commit de la tanda se imputa `leadInMinutes`. Cuánto se
85
+ * trabajó realmente antes de ese commit es indeducible, así que el valor por
86
+ * defecto es corto a propósito: estas horas acaban en una factura y
87
+ * quedarse corto es preferible a cobrar de más.
88
+ *
89
+ * El resultado es menos preciso que el deducido de un agente, y conviene que
90
+ * quien lo use lo sepa.
91
+ */
92
+ export declare function sessionizeCommits(commits: readonly CommitRecord[], options?: {
93
+ gapMinutes?: number;
94
+ leadInMinutes?: number;
95
+ }): WorkBlock[];
96
+ /**
97
+ * Quita los bloques de commits que se solapan con trabajo ya deducido de un
98
+ * agente.
99
+ *
100
+ * Sin esto se contaría dos veces el mismo rato: el agente ya cubrió esas horas
101
+ * y el commit que las cerró generaría otro bloque encima.
102
+ */
103
+ export declare function withoutOverlap(commitBlocks: readonly WorkBlock[], agentBlocks: readonly WorkBlock[]): WorkBlock[];
104
+ //# sourceMappingURL=sessionize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionize.d.ts","sourceRoot":"","sources":["../../src/billing/sessionize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAItE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,gFAAgF;IAChF,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C;AAKD,wBAAgB,UAAU,CACxB,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,OAAO,GAAE,iBAAsB,GAC9B,SAAS,EAAE,CAkCb;AAqCD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,EAAE,CAiD7E;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAUtD;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,SAAS,EAAE,EAC5B,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,YAAY,SAAK,GAChB,SAAS,EAAE,CAcb;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5D,SAAS,EAAE,CAgDb;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,YAAY,EAAE,SAAS,SAAS,EAAE,EAClC,WAAW,EAAE,SAAS,SAAS,EAAE,GAChC,SAAS,EAAE,CAKb"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionize.js","sourceRoot":"","sources":["../../src/billing/sessionize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sessionize.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionize.test.d.ts","sourceRoot":"","sources":["../../src/billing/sessionize.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = require("node:test");
8
+ const sessionize_js_1 = require("./sessionize.js");
9
+ // --- Trabajo sin agente ------------------------------------------------------
10
+ function commit(hash, iso, repo = "/r", branch = "main") {
11
+ return {
12
+ repoPath: repo, hash, at: new Date(iso), authorEmail: "yo@ejemplo.com", authorName: "Yo",
13
+ branch, subject: `commit ${hash}`, linesAdded: 1, linesDeleted: 0, files: [],
14
+ };
15
+ }
16
+ (0, node_test_1.test)("sin turnos de agente, los commits siguen produciendo trabajo", () => {
17
+ // El caso que motiva todo esto: alguien programa sin IA. sessionize() da cero
18
+ // bloques y sus horas desaparecían del parte.
19
+ strict_1.default.equal((0, sessionize_js_1.sessionize)([]).length, 0);
20
+ const blocks = (0, sessionize_js_1.sessionizeCommits)([
21
+ commit("a", "2026-08-27T10:00:00Z"),
22
+ commit("b", "2026-08-27T10:40:00Z"),
23
+ ], { leadInMinutes: 25 });
24
+ strict_1.default.equal(blocks.length, 1);
25
+ // 25 min antes del primero + los 40 entre ambos.
26
+ strict_1.default.equal(blocks[0].seconds, 65 * 60);
27
+ strict_1.default.equal(blocks[0].turnCount, 0);
28
+ strict_1.default.equal(blocks[0].aiCost.microUsd, 0);
29
+ });
30
+ (0, node_test_1.test)("un hueco largo entre commits parte la tanda", () => {
31
+ const blocks = (0, sessionize_js_1.sessionizeCommits)([
32
+ commit("a", "2026-08-27T09:00:00Z"),
33
+ commit("b", "2026-08-27T16:00:00Z"),
34
+ ], { gapMinutes: 90 });
35
+ strict_1.default.equal(blocks.length, 2);
36
+ });
37
+ (0, node_test_1.test)("un commit suelto dura el lead-in, no cero", () => {
38
+ const blocks = (0, sessionize_js_1.sessionizeCommits)([commit("a", "2026-08-27T09:00:00Z")], { leadInMinutes: 25 });
39
+ strict_1.default.equal(blocks[0].seconds, 25 * 60);
40
+ });
41
+ (0, node_test_1.test)("los repositorios no se mezclan en una misma tanda", () => {
42
+ const blocks = (0, sessionize_js_1.sessionizeCommits)([
43
+ commit("a", "2026-08-27T09:00:00Z", "/uno"),
44
+ commit("b", "2026-08-27T09:10:00Z", "/dos"),
45
+ ]);
46
+ strict_1.default.equal(blocks.length, 2);
47
+ });
48
+ (0, node_test_1.test)("el trabajo ya cubierto por un agente no se cuenta dos veces", () => {
49
+ // Un commit dentro de una sesión de agente: el rato ya está facturado.
50
+ const agent = [{
51
+ startedAt: new Date("2026-08-27T09:00:00Z"),
52
+ endedAt: new Date("2026-08-27T11:00:00Z"),
53
+ seconds: 7200, repoPath: "/r", branch: "main", turnCount: 12,
54
+ aiCost: { microUsd: 0 }, models: [], sessionIds: [], commits: [],
55
+ unpricedModels: [],
56
+ }];
57
+ const solapado = (0, sessionize_js_1.sessionizeCommits)([commit("a", "2026-08-27T10:30:00Z")]);
58
+ strict_1.default.equal((0, sessionize_js_1.withoutOverlap)(solapado, agent).length, 0);
59
+ const aparte = (0, sessionize_js_1.sessionizeCommits)([commit("b", "2026-08-27T18:00:00Z")]);
60
+ strict_1.default.equal((0, sessionize_js_1.withoutOverlap)(aparte, agent).length, 1);
61
+ });
62
+ (0, node_test_1.test)("el lead-in por defecto subestima: nunca imputa una jornada entera", () => {
63
+ // Estas horas se facturan. Ante la duda, quedarse corto.
64
+ const blocks = (0, sessionize_js_1.sessionizeCommits)([commit("a", "2026-08-27T18:00:00Z")]);
65
+ strict_1.default.ok(blocks[0].seconds <= 30 * 60, `un commit suelto imputó ${blocks[0].seconds}s`);
66
+ });
67
+ //# sourceMappingURL=sessionize.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionize.test.js","sourceRoot":"","sources":["../../src/billing/sessionize.test.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { breakdownOfTurn } from "./pricing/cost.js";
3
+ export { breakdownOfTurn };
4
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAiCA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AA2/BpD,OAAO,EAAE,eAAe,EAAE,CAAC"}