estela 0.1.40 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -2
- package/dist/billing/assign.d.ts +45 -0
- package/dist/billing/assign.js +159 -0
- package/dist/bin.js +23 -1
- package/dist/cli.js +220 -57
- package/dist/db/store.d.ts +2 -0
- package/dist/db/store.js +24 -1
- package/dist/demo.js +1 -1
- package/dist/i18n/en.js +39 -0
- package/dist/metrics/quota.d.ts +44 -4
- package/dist/metrics/quota.js +60 -14
- package/dist/project-of.d.ts +52 -0
- package/dist/project-of.js +124 -0
- package/dist/quota.d.ts +10 -1
- package/dist/quota.js +14 -20
- package/dist/server.js +10 -27
- package/dist/statusline-cache.d.ts +35 -0
- package/dist/statusline-cache.js +57 -0
- package/dist/statusline-install.d.ts +52 -0
- package/dist/statusline-install.js +115 -0
- package/dist/statusline-run.d.ts +8 -0
- package/dist/statusline-run.js +256 -0
- package/dist/statusline.d.ts +71 -0
- package/dist/statusline.js +152 -0
- package/dist/watchers/claude.d.ts +11 -1
- package/dist/watchers/claude.js +32 -15
- package/package.json +3 -1
- package/dist/billing/amortize.d.ts.map +0 -1
- package/dist/billing/amortize.js.map +0 -1
- package/dist/billing/amortize.test.d.ts +0 -2
- package/dist/billing/amortize.test.d.ts.map +0 -1
- package/dist/billing/amortize.test.js +0 -121
- package/dist/billing/amortize.test.js.map +0 -1
- package/dist/billing/format.test.d.ts +0 -2
- package/dist/billing/format.test.d.ts.map +0 -1
- package/dist/billing/format.test.js +0 -110
- package/dist/billing/format.test.js.map +0 -1
- package/dist/billing/invoice.d.ts.map +0 -1
- package/dist/billing/invoice.js.map +0 -1
- package/dist/billing/invoice.test.d.ts +0 -2
- package/dist/billing/invoice.test.d.ts.map +0 -1
- package/dist/billing/invoice.test.js +0 -200
- package/dist/billing/invoice.test.js.map +0 -1
- package/dist/billing/localize.d.ts.map +0 -1
- package/dist/billing/localize.js.map +0 -1
- package/dist/billing/localize.test.d.ts +0 -2
- package/dist/billing/localize.test.d.ts.map +0 -1
- package/dist/billing/localize.test.js +0 -144
- package/dist/billing/localize.test.js.map +0 -1
- package/dist/billing/sessionize.d.ts.map +0 -1
- package/dist/billing/sessionize.js.map +0 -1
- package/dist/billing/sessionize.test.d.ts +0 -2
- package/dist/billing/sessionize.test.d.ts.map +0 -1
- package/dist/billing/sessionize.test.js +0 -187
- package/dist/billing/sessionize.test.js.map +0 -1
- package/dist/billing.d.ts.map +0 -1
- package/dist/billing.js.map +0 -1
- package/dist/bin.d.ts.map +0 -1
- package/dist/bin.js.map +0 -1
- package/dist/cli-badge.test.d.ts +0 -2
- package/dist/cli-badge.test.d.ts.map +0 -1
- package/dist/cli-badge.test.js +0 -118
- package/dist/cli-badge.test.js.map +0 -1
- package/dist/cli-idioma.test.d.ts +0 -2
- package/dist/cli-idioma.test.d.ts.map +0 -1
- package/dist/cli-idioma.test.js +0 -190
- package/dist/cli-idioma.test.js.map +0 -1
- package/dist/cli-quota.test.d.ts +0 -2
- package/dist/cli-quota.test.d.ts.map +0 -1
- package/dist/cli-quota.test.js +0 -216
- package/dist/cli-quota.test.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cloud/auth.d.ts.map +0 -1
- package/dist/cloud/auth.js.map +0 -1
- package/dist/cloud/client.d.ts.map +0 -1
- package/dist/cloud/client.js.map +0 -1
- package/dist/cloud/errors.test.d.ts +0 -2
- package/dist/cloud/errors.test.d.ts.map +0 -1
- package/dist/cloud/errors.test.js +0 -34
- package/dist/cloud/errors.test.js.map +0 -1
- package/dist/db/schema.d.ts.map +0 -1
- package/dist/db/schema.js.map +0 -1
- package/dist/db/schema.test.d.ts +0 -2
- package/dist/db/schema.test.d.ts.map +0 -1
- package/dist/db/schema.test.js +0 -825
- package/dist/db/schema.test.js.map +0 -1
- package/dist/db/store.d.ts.map +0 -1
- package/dist/db/store.js.map +0 -1
- package/dist/demo.d.ts.map +0 -1
- package/dist/demo.js.map +0 -1
- package/dist/demo.test.d.ts +0 -2
- package/dist/demo.test.d.ts.map +0 -1
- package/dist/demo.test.js +0 -148
- package/dist/demo.test.js.map +0 -1
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js.map +0 -1
- package/dist/doctor.test.d.ts +0 -2
- package/dist/doctor.test.d.ts.map +0 -1
- package/dist/doctor.test.js +0 -167
- package/dist/doctor.test.js.map +0 -1
- package/dist/export/badge.d.ts.map +0 -1
- package/dist/export/badge.js.map +0 -1
- package/dist/export/badge.test.d.ts +0 -2
- package/dist/export/badge.test.d.ts.map +0 -1
- package/dist/export/badge.test.js +0 -32
- package/dist/export/badge.test.js.map +0 -1
- package/dist/export/clockify.d.ts.map +0 -1
- package/dist/export/clockify.js.map +0 -1
- package/dist/export/clockify.test.d.ts +0 -2
- package/dist/export/clockify.test.d.ts.map +0 -1
- package/dist/export/clockify.test.js +0 -51
- package/dist/export/clockify.test.js.map +0 -1
- package/dist/export/csv.d.ts.map +0 -1
- package/dist/export/csv.js.map +0 -1
- package/dist/export/dates.d.ts.map +0 -1
- package/dist/export/dates.js.map +0 -1
- package/dist/export/idioma.test.d.ts +0 -2
- package/dist/export/idioma.test.d.ts.map +0 -1
- package/dist/export/idioma.test.js +0 -255
- package/dist/export/idioma.test.js.map +0 -1
- package/dist/export/invoice-pdf.d.ts.map +0 -1
- package/dist/export/invoice-pdf.js.map +0 -1
- package/dist/export/panel.d.ts.map +0 -1
- package/dist/export/panel.js.map +0 -1
- package/dist/export/panel.test.d.ts +0 -2
- package/dist/export/panel.test.d.ts.map +0 -1
- package/dist/export/panel.test.js +0 -386
- package/dist/export/panel.test.js.map +0 -1
- package/dist/export/pdf.d.ts.map +0 -1
- package/dist/export/pdf.js.map +0 -1
- package/dist/export/pdf.test.d.ts +0 -2
- package/dist/export/pdf.test.d.ts.map +0 -1
- package/dist/export/pdf.test.js +0 -172
- package/dist/export/pdf.test.js.map +0 -1
- package/dist/export/pixelfont.d.ts.map +0 -1
- package/dist/export/pixelfont.js.map +0 -1
- package/dist/export/png.d.ts.map +0 -1
- package/dist/export/png.js.map +0 -1
- package/dist/export/receipt.d.ts.map +0 -1
- package/dist/export/receipt.js.map +0 -1
- package/dist/export/receipt.test.d.ts +0 -2
- package/dist/export/receipt.test.d.ts.map +0 -1
- package/dist/export/receipt.test.js +0 -207
- package/dist/export/receipt.test.js.map +0 -1
- package/dist/export/share.d.ts.map +0 -1
- package/dist/export/share.js.map +0 -1
- package/dist/export/share.test.d.ts +0 -2
- package/dist/export/share.test.d.ts.map +0 -1
- package/dist/export/share.test.js +0 -116
- package/dist/export/share.test.js.map +0 -1
- package/dist/i18n/en.d.ts.map +0 -1
- package/dist/i18n/en.js.map +0 -1
- package/dist/i18n/i18n.test.d.ts +0 -2
- package/dist/i18n/i18n.test.d.ts.map +0 -1
- package/dist/i18n/i18n.test.js +0 -226
- package/dist/i18n/i18n.test.js.map +0 -1
- package/dist/i18n/index.d.ts.map +0 -1
- package/dist/i18n/index.js.map +0 -1
- package/dist/i18n/labels.d.ts.map +0 -1
- package/dist/i18n/labels.js.map +0 -1
- package/dist/metrics/index.d.ts.map +0 -1
- package/dist/metrics/index.js.map +0 -1
- package/dist/metrics/metrics.test.d.ts +0 -2
- package/dist/metrics/metrics.test.d.ts.map +0 -1
- package/dist/metrics/metrics.test.js +0 -162
- package/dist/metrics/metrics.test.js.map +0 -1
- package/dist/metrics/people.d.ts.map +0 -1
- package/dist/metrics/people.js.map +0 -1
- package/dist/metrics/people.test.d.ts +0 -2
- package/dist/metrics/people.test.d.ts.map +0 -1
- package/dist/metrics/people.test.js +0 -92
- package/dist/metrics/people.test.js.map +0 -1
- package/dist/metrics/quota.d.ts.map +0 -1
- package/dist/metrics/quota.js.map +0 -1
- package/dist/metrics/quota.test.d.ts +0 -2
- package/dist/metrics/quota.test.d.ts.map +0 -1
- package/dist/metrics/quota.test.js +0 -262
- package/dist/metrics/quota.test.js.map +0 -1
- package/dist/metrics/team.d.ts.map +0 -1
- package/dist/metrics/team.js.map +0 -1
- package/dist/metrics/team.test.d.ts +0 -2
- package/dist/metrics/team.test.d.ts.map +0 -1
- package/dist/metrics/team.test.js +0 -90
- package/dist/metrics/team.test.js.map +0 -1
- package/dist/node-version.d.ts.map +0 -1
- package/dist/node-version.js.map +0 -1
- package/dist/node-version.test.d.ts +0 -2
- package/dist/node-version.test.d.ts.map +0 -1
- package/dist/node-version.test.js +0 -53
- package/dist/node-version.test.js.map +0 -1
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js.map +0 -1
- package/dist/paths.test.d.ts +0 -2
- package/dist/paths.test.d.ts.map +0 -1
- package/dist/paths.test.js +0 -32
- package/dist/paths.test.js.map +0 -1
- package/dist/pricing/catalog.d.ts.map +0 -1
- package/dist/pricing/catalog.js.map +0 -1
- package/dist/pricing/cost.d.ts.map +0 -1
- package/dist/pricing/cost.js.map +0 -1
- package/dist/pricing/cost.test.d.ts +0 -2
- package/dist/pricing/cost.test.d.ts.map +0 -1
- package/dist/pricing/cost.test.js +0 -100
- package/dist/pricing/cost.test.js.map +0 -1
- package/dist/publish.d.ts.map +0 -1
- package/dist/publish.js.map +0 -1
- package/dist/publish.test.d.ts +0 -2
- package/dist/publish.test.d.ts.map +0 -1
- package/dist/publish.test.js +0 -113
- package/dist/publish.test.js.map +0 -1
- package/dist/quota.d.ts.map +0 -1
- package/dist/quota.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/server.test.d.ts +0 -2
- package/dist/server.test.d.ts.map +0 -1
- package/dist/server.test.js +0 -402
- package/dist/server.test.js.map +0 -1
- package/dist/setup.d.ts.map +0 -1
- package/dist/setup.js.map +0 -1
- package/dist/setup.test.d.ts +0 -2
- package/dist/setup.test.d.ts.map +0 -1
- package/dist/setup.test.js +0 -144
- package/dist/setup.test.js.map +0 -1
- package/dist/sync/team.d.ts.map +0 -1
- package/dist/sync/team.js.map +0 -1
- package/dist/sync/team.test.d.ts +0 -2
- package/dist/sync/team.test.d.ts.map +0 -1
- package/dist/sync/team.test.js +0 -102
- package/dist/sync/team.test.js.map +0 -1
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js.map +0 -1
- package/dist/sync.test.d.ts +0 -2
- package/dist/sync.test.d.ts.map +0 -1
- package/dist/sync.test.js +0 -151
- package/dist/sync.test.js.map +0 -1
- package/dist/team.d.ts.map +0 -1
- package/dist/team.js.map +0 -1
- package/dist/team.test.d.ts +0 -2
- package/dist/team.test.d.ts.map +0 -1
- package/dist/team.test.js +0 -163
- package/dist/team.test.js.map +0 -1
- package/dist/updateCheck.d.ts.map +0 -1
- package/dist/updateCheck.js.map +0 -1
- package/dist/updateCheck.test.d.ts +0 -2
- package/dist/updateCheck.test.d.ts.map +0 -1
- package/dist/updateCheck.test.js +0 -27
- package/dist/updateCheck.test.js.map +0 -1
- package/dist/watchers/agents.d.ts.map +0 -1
- package/dist/watchers/agents.js.map +0 -1
- package/dist/watchers/agents.test.d.ts +0 -2
- package/dist/watchers/agents.test.d.ts.map +0 -1
- package/dist/watchers/agents.test.js +0 -96
- package/dist/watchers/agents.test.js.map +0 -1
- package/dist/watchers/claude.d.ts.map +0 -1
- package/dist/watchers/claude.js.map +0 -1
- package/dist/watchers/claude.test.d.ts +0 -2
- package/dist/watchers/claude.test.d.ts.map +0 -1
- package/dist/watchers/claude.test.js +0 -180
- package/dist/watchers/claude.test.js.map +0 -1
- package/dist/watchers/codex.d.ts.map +0 -1
- package/dist/watchers/codex.js.map +0 -1
- package/dist/watchers/codex.test.d.ts +0 -2
- package/dist/watchers/codex.test.d.ts.map +0 -1
- package/dist/watchers/codex.test.js +0 -207
- package/dist/watchers/codex.test.js.map +0 -1
- package/dist/watchers/copilot.d.ts.map +0 -1
- package/dist/watchers/copilot.js.map +0 -1
- package/dist/watchers/copilot.test.d.ts +0 -2
- package/dist/watchers/copilot.test.d.ts.map +0 -1
- package/dist/watchers/copilot.test.js +0 -98
- package/dist/watchers/copilot.test.js.map +0 -1
- package/dist/watchers/git.d.ts.map +0 -1
- package/dist/watchers/git.js.map +0 -1
- package/dist/watchers/git.test.d.ts +0 -2
- package/dist/watchers/git.test.d.ts.map +0 -1
- package/dist/watchers/git.test.js +0 -75
- package/dist/watchers/git.test.js.map +0 -1
- package/dist/watchers/identity.d.ts.map +0 -1
- package/dist/watchers/identity.js.map +0 -1
- package/dist/watchers/identity.test.d.ts +0 -2
- package/dist/watchers/identity.test.d.ts.map +0 -1
- package/dist/watchers/identity.test.js +0 -48
- package/dist/watchers/identity.test.js.map +0 -1
- package/dist/web-ui.test.d.ts +0 -2
- package/dist/web-ui.test.d.ts.map +0 -1
- package/dist/web-ui.test.js +0 -414
- package/dist/web-ui.test.js.map +0 -1
- package/dist/workitems.test.d.ts +0 -2
- package/dist/workitems.test.d.ts.map +0 -1
- package/dist/workitems.test.js +0 -59
- package/dist/workitems.test.js.map +0 -1
package/dist/metrics/quota.js
CHANGED
|
@@ -9,8 +9,10 @@ exports.weekStart = weekStart;
|
|
|
9
9
|
exports.sameStretch = sameStretch;
|
|
10
10
|
exports.compareWithHistory = compareWithHistory;
|
|
11
11
|
exports.buildQuota = buildQuota;
|
|
12
|
+
exports.officialWindow = officialWindow;
|
|
12
13
|
exports.quotaRange = quotaRange;
|
|
13
14
|
exports.quotaToJson = quotaToJson;
|
|
15
|
+
const cost_js_1 = require("../pricing/cost.js");
|
|
14
16
|
/**
|
|
15
17
|
* "¿Qué proyecto se come mi cuota?"
|
|
16
18
|
*
|
|
@@ -57,29 +59,44 @@ function add(u, t) {
|
|
|
57
59
|
};
|
|
58
60
|
}
|
|
59
61
|
function bucketKey(b) {
|
|
60
|
-
return b.kind === "project" ? `p:${b.projectId}` : b.kind === "folder" ? `f:${b.
|
|
62
|
+
return b.kind === "project" ? `p:${b.projectId}` : b.kind === "folder" ? `f:${b.path}` : "-";
|
|
63
|
+
}
|
|
64
|
+
/** Precio de lista de un turno, en micro-USD, o null si su modelo no tiene precio. */
|
|
65
|
+
function listCostOf(t) {
|
|
66
|
+
if (!t.model)
|
|
67
|
+
return null;
|
|
68
|
+
return (0, cost_js_1.costOfTurn)(t.tokens, t.model, t.at)?.microUsd ?? null;
|
|
61
69
|
}
|
|
62
70
|
/**
|
|
63
71
|
* Reparto por proyecto de los turnos con `from <= at < to`, de mayor a menor.
|
|
64
72
|
* Un proyecto sin actividad en el periodo no sale: una fila a cero en una
|
|
65
73
|
* tabla de "quién se come la cuota" es ruido.
|
|
66
74
|
*/
|
|
67
|
-
function breakdown(turns, from, to, bucketOf) {
|
|
75
|
+
function breakdown(turns, from, to, bucketOf, options = {}) {
|
|
68
76
|
const acc = new Map();
|
|
69
77
|
let total = ZERO;
|
|
78
|
+
let listCost = 0;
|
|
79
|
+
let unpriced = 0;
|
|
70
80
|
for (const t of turns) {
|
|
71
81
|
if (t.at < from || t.at >= to)
|
|
72
82
|
continue;
|
|
73
83
|
const bucket = bucketOf(t.repoPath);
|
|
74
84
|
const key = bucketKey(bucket);
|
|
75
|
-
const
|
|
76
|
-
|
|
85
|
+
const cost = listCostOf(t);
|
|
86
|
+
if (cost === null)
|
|
87
|
+
unpriced++;
|
|
88
|
+
const prev = acc.get(key) ?? { bucket, usage: ZERO, listCost: 0 };
|
|
89
|
+
acc.set(key, { bucket, usage: add(prev.usage, t.tokens), listCost: prev.listCost + (cost ?? 0) });
|
|
77
90
|
total = add(total, t.tokens);
|
|
91
|
+
listCost += cost ?? 0;
|
|
78
92
|
}
|
|
93
|
+
const weighted = options.weighted === true;
|
|
94
|
+
const base = (r) => (weighted ? r.listCost : r.usage.total);
|
|
95
|
+
const whole = weighted ? listCost : total.total;
|
|
79
96
|
const rows = [...acc.values()]
|
|
80
|
-
.map((
|
|
81
|
-
.sort((a, b) => b
|
|
82
|
-
return { rows, total };
|
|
97
|
+
.map((r) => ({ ...r, share: whole > 0 ? base(r) / whole : 0 }))
|
|
98
|
+
.sort((a, b) => base(b) - base(a));
|
|
99
|
+
return { rows, total, listCost, unpriced, weighted };
|
|
83
100
|
}
|
|
84
101
|
// ---------------------------------------------------------------------------
|
|
85
102
|
// La ventana de 5 horas
|
|
@@ -176,20 +193,26 @@ function compareWithHistory(times, firstTurnAt, now) {
|
|
|
176
193
|
*/
|
|
177
194
|
function buildQuota(input) {
|
|
178
195
|
const { now, bucketOf } = input;
|
|
196
|
+
const opts = { weighted: input.weighted === true };
|
|
179
197
|
const monday = weekStart(now);
|
|
180
198
|
const until = new Date(now.getTime() + 1);
|
|
181
199
|
const turns = input.turns.filter((t) => t.at <= now);
|
|
182
200
|
const agents = [];
|
|
183
201
|
for (const agent of exports.QUOTA_AGENTS) {
|
|
184
202
|
const own = turns.filter((t) => t.agent === agent);
|
|
185
|
-
const
|
|
186
|
-
|
|
203
|
+
const official = agent === "claude-code" && input.claudeWindow &&
|
|
204
|
+
input.claudeWindow.start <= now && now < input.claudeWindow.end ? input.claudeWindow : null;
|
|
205
|
+
const estimate = windowAt(own.map((t) => t.at), now);
|
|
206
|
+
const current = official ?? estimate.current;
|
|
207
|
+
const week = breakdown(own, monday, until, bucketOf, opts);
|
|
187
208
|
if (!current && week.rows.length === 0)
|
|
188
209
|
continue;
|
|
189
210
|
agents.push({
|
|
190
211
|
agent,
|
|
191
|
-
window: current
|
|
192
|
-
|
|
212
|
+
window: current
|
|
213
|
+
? { ...current, ...breakdown(own, current.start, until, bucketOf, opts), estimated: !official }
|
|
214
|
+
: null,
|
|
215
|
+
lastWindowEnded: current ? null : estimate.lastEnded,
|
|
193
216
|
week,
|
|
194
217
|
comparison: compareWithHistory(own.map((t) => ({ at: t.at, total: add(ZERO, t.tokens).total })), input.firstTurnAt.get(agent) ?? null, now),
|
|
195
218
|
});
|
|
@@ -201,6 +224,25 @@ function buildQuota(input) {
|
|
|
201
224
|
copilotExcluded: turns.some((t) => t.agent === "copilot" && t.at >= monday),
|
|
202
225
|
};
|
|
203
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* La ventana de 5 h que dio Claude Code, a partir del último `resets_at` que
|
|
229
|
+
* vio la status line: empieza 5 h antes de su reinicio.
|
|
230
|
+
*
|
|
231
|
+
* Solo vale si el dato tiene menos de 5 h (uno más viejo puede ser de otra
|
|
232
|
+
* ventana) y si el reinicio aún no ha llegado (pasado ese momento, la ventana
|
|
233
|
+
* en curso es otra que nadie ha visto). Si no, null: se estima como siempre.
|
|
234
|
+
*/
|
|
235
|
+
function officialWindow(seen, now) {
|
|
236
|
+
if (!seen)
|
|
237
|
+
return null;
|
|
238
|
+
const age = now.getTime() - seen.seenAt.getTime();
|
|
239
|
+
if (age < 0 || age >= WINDOW_MS)
|
|
240
|
+
return null;
|
|
241
|
+
if (seen.resetsAt <= now)
|
|
242
|
+
return null;
|
|
243
|
+
const start = new Date(seen.resetsAt.getTime() - WINDOW_MS);
|
|
244
|
+
return start <= now ? { start, end: seen.resetsAt } : null;
|
|
245
|
+
}
|
|
204
246
|
/** Desde cuándo hay que leer turnos para montar el informe de `now`. */
|
|
205
247
|
function quotaRange(now) {
|
|
206
248
|
return { start: sameStretch(now, exports.HISTORY_WEEKS).start, end: new Date(now.getTime() + 1) };
|
|
@@ -219,19 +261,23 @@ function quotaToJson(report) {
|
|
|
219
261
|
projectName: r.bucket.kind === "project" ? r.bucket.projectName : null,
|
|
220
262
|
folder: r.bucket.kind === "folder" ? r.bucket.folder : null,
|
|
221
263
|
...r.usage,
|
|
264
|
+
listCostMicroUsd: r.listCost,
|
|
222
265
|
share: Math.round(r.share * 10_000) / 10_000,
|
|
223
266
|
}));
|
|
267
|
+
const weighted = report.agents.some((a) => a.week.weighted);
|
|
224
268
|
return {
|
|
225
269
|
generatedAt: report.generatedAt.toISOString(),
|
|
226
270
|
weekStart: report.weekStart.toISOString(),
|
|
227
|
-
// Para que ningún script lo trate como un dato exacto sin haberlo leído
|
|
228
|
-
|
|
271
|
+
// Para que ningún script lo trate como un dato exacto sin haberlo leído:
|
|
272
|
+
// false solo si todas las ventanas que salen las dio Claude Code.
|
|
273
|
+
windowIsEstimate: report.agents.some((a) => !a.window || a.window.estimated),
|
|
229
274
|
windowHours: exports.WINDOW_HOURS,
|
|
275
|
+
shareBasis: weighted ? "list-price" : "tokens",
|
|
230
276
|
agents: report.agents.map((a) => ({
|
|
231
277
|
agent: a.agent,
|
|
232
278
|
window: a.window
|
|
233
279
|
? { start: a.window.start.toISOString(), end: a.window.end.toISOString(),
|
|
234
|
-
projects: rows(a.window), total: a.window.total }
|
|
280
|
+
estimated: a.window.estimated, projects: rows(a.window), total: a.window.total }
|
|
235
281
|
: null,
|
|
236
282
|
lastWindowEnded: a.lastWindowEnded?.toISOString() ?? null,
|
|
237
283
|
week: { projects: rows(a.week), total: a.week.total },
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
/**
|
|
3
|
+
* A qué proyecto pertenece una carpeta de trabajo: lo comparten `estela quota`
|
|
4
|
+
* y `estela statusline`.
|
|
5
|
+
*
|
|
6
|
+
* El `cwd` de una sesión no siempre está dentro de la carpeta registrada:
|
|
7
|
+
* un worktree vive al lado del repositorio (`../web-feature`), y una sesión
|
|
8
|
+
* abierta en `repo/src` de un repo sin registrar se llamaba "src". Por eso,
|
|
9
|
+
* en este orden:
|
|
10
|
+
*
|
|
11
|
+
* 1. La carpeta tal cual contra los proyectos registrados (el prefijo más
|
|
12
|
+
* largo gana). Cubre `repo/src` y también un subproyecto registrado
|
|
13
|
+
* dentro de un monorepo (`repo/packages/x`), que resolviendo antes a la
|
|
14
|
+
* raíz git se perdería.
|
|
15
|
+
* 2. La raíz git — y si es un worktree, la del repositorio principal —
|
|
16
|
+
* contra los proyectos registrados.
|
|
17
|
+
* 3. Si nada coincide, "sin proyecto" con el nombre de la raíz git, nunca
|
|
18
|
+
* el de una subcarpeta. Fuera de git, la propia carpeta.
|
|
19
|
+
*/
|
|
20
|
+
export type Owner = {
|
|
21
|
+
readonly kind: "project";
|
|
22
|
+
readonly projectId: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `path` agrupa (dos repos pueden llamarse igual); `name` es lo que se
|
|
26
|
+
* enseña; `inGit` dice si es la raíz de un repo o una carpeta suelta.
|
|
27
|
+
*/
|
|
28
|
+
| {
|
|
29
|
+
readonly kind: "folder";
|
|
30
|
+
readonly path: string;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly inGit: boolean;
|
|
33
|
+
} | {
|
|
34
|
+
readonly kind: "none";
|
|
35
|
+
};
|
|
36
|
+
/** Un resolvedor con caché: una semana tiene miles de turnos y pocas carpetas. */
|
|
37
|
+
export declare function ownerResolver(db: DatabaseSync): (dir: string | null) => Owner;
|
|
38
|
+
/** El nombre de una carpeta para enseñarlo: la última parte de la ruta. */
|
|
39
|
+
export declare function folderName(path: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* La raíz del repositorio git que contiene `dir`, o null si no está en git.
|
|
42
|
+
*
|
|
43
|
+
* Se sube hasta encontrar `.git`. Si es una carpeta, esa es la raíz. Si es un
|
|
44
|
+
* fichero (`gitdir: …`), es un worktree o un submódulo: un worktree tiene un
|
|
45
|
+
* `commondir` que apunta al `.git` del repositorio principal, y la raíz es la
|
|
46
|
+
* de ese; un submódulo no lo tiene, y es su propio repositorio.
|
|
47
|
+
*
|
|
48
|
+
* Solo lee el disco, sin ejecutar git: se llama en cada evento de la status
|
|
49
|
+
* line. Si la carpeta ya no existe (un worktree borrado), sube igualmente.
|
|
50
|
+
*/
|
|
51
|
+
export declare function gitRoot(dir: string): string | null;
|
|
52
|
+
//# sourceMappingURL=project-of.d.ts.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ownerResolver = ownerResolver;
|
|
37
|
+
exports.folderName = folderName;
|
|
38
|
+
exports.gitRoot = gitRoot;
|
|
39
|
+
const node_fs_1 = require("node:fs");
|
|
40
|
+
const node_path_1 = require("node:path");
|
|
41
|
+
const store = __importStar(require("./db/store.js"));
|
|
42
|
+
const paths_js_1 = require("./paths.js");
|
|
43
|
+
/** Un resolvedor con caché: una semana tiene miles de turnos y pocas carpetas. */
|
|
44
|
+
function ownerResolver(db) {
|
|
45
|
+
const cache = new Map();
|
|
46
|
+
return (dir) => {
|
|
47
|
+
if (!dir)
|
|
48
|
+
return { kind: "none" };
|
|
49
|
+
let owner = cache.get(dir);
|
|
50
|
+
if (!owner) {
|
|
51
|
+
owner = resolveOwner(db, dir);
|
|
52
|
+
cache.set(dir, owner);
|
|
53
|
+
}
|
|
54
|
+
return owner;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function resolveOwner(db, dir) {
|
|
58
|
+
const direct = store.projectForRepo(db, dir);
|
|
59
|
+
if (direct)
|
|
60
|
+
return { kind: "project", projectId: direct };
|
|
61
|
+
const root = gitRoot(dir);
|
|
62
|
+
if (root && root !== dir) {
|
|
63
|
+
const viaRoot = store.projectForRepo(db, root);
|
|
64
|
+
if (viaRoot)
|
|
65
|
+
return { kind: "project", projectId: viaRoot };
|
|
66
|
+
}
|
|
67
|
+
const path = root ?? (0, paths_js_1.normalizePath)(dir);
|
|
68
|
+
return { kind: "folder", path, name: folderName(path), inGit: root !== null };
|
|
69
|
+
}
|
|
70
|
+
/** El nombre de una carpeta para enseñarlo: la última parte de la ruta. */
|
|
71
|
+
function folderName(path) {
|
|
72
|
+
return (0, node_path_1.basename)(path.replace(/[\\/]+$/, "")) || path;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* La raíz del repositorio git que contiene `dir`, o null si no está en git.
|
|
76
|
+
*
|
|
77
|
+
* Se sube hasta encontrar `.git`. Si es una carpeta, esa es la raíz. Si es un
|
|
78
|
+
* fichero (`gitdir: …`), es un worktree o un submódulo: un worktree tiene un
|
|
79
|
+
* `commondir` que apunta al `.git` del repositorio principal, y la raíz es la
|
|
80
|
+
* de ese; un submódulo no lo tiene, y es su propio repositorio.
|
|
81
|
+
*
|
|
82
|
+
* Solo lee el disco, sin ejecutar git: se llama en cada evento de la status
|
|
83
|
+
* line. Si la carpeta ya no existe (un worktree borrado), sube igualmente.
|
|
84
|
+
*/
|
|
85
|
+
function gitRoot(dir) {
|
|
86
|
+
// Una ruta relativa (o "~/…", como en la demo) se resolvería contra la
|
|
87
|
+
// carpeta desde la que se ejecuta Estela, y acabaría en el repo de quien la
|
|
88
|
+
// ejecuta. Los cwd de verdad son absolutos.
|
|
89
|
+
if (!(0, node_path_1.isAbsolute)(dir))
|
|
90
|
+
return null;
|
|
91
|
+
let d = (0, node_path_1.resolve)(dir);
|
|
92
|
+
for (;;) {
|
|
93
|
+
const dotGit = (0, node_path_1.join)(d, ".git");
|
|
94
|
+
try {
|
|
95
|
+
const st = (0, node_fs_1.statSync)(dotGit);
|
|
96
|
+
if (st.isDirectory())
|
|
97
|
+
return (0, paths_js_1.normalizePath)(d);
|
|
98
|
+
if (st.isFile())
|
|
99
|
+
return (0, paths_js_1.normalizePath)(worktreeMain(dotGit) ?? d);
|
|
100
|
+
}
|
|
101
|
+
catch { /* no hay .git aquí: se sube */ }
|
|
102
|
+
const parent = (0, node_path_1.dirname)(d);
|
|
103
|
+
if (parent === d)
|
|
104
|
+
return null;
|
|
105
|
+
d = parent;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** La raíz del repositorio principal de un worktree, o null si no lo es. */
|
|
109
|
+
function worktreeMain(dotGitFile) {
|
|
110
|
+
try {
|
|
111
|
+
const m = /^gitdir:\s*(.+?)\s*$/m.exec((0, node_fs_1.readFileSync)(dotGitFile, "utf8"));
|
|
112
|
+
if (!m)
|
|
113
|
+
return null;
|
|
114
|
+
const gitdir = (0, node_path_1.isAbsolute)(m[1]) ? m[1] : (0, node_path_1.resolve)((0, node_path_1.dirname)(dotGitFile), m[1]);
|
|
115
|
+
const common = (0, node_fs_1.readFileSync)((0, node_path_1.join)(gitdir, "commondir"), "utf8").trim();
|
|
116
|
+
const commonDir = (0, node_path_1.isAbsolute)(common) ? common : (0, node_path_1.resolve)(gitdir, common);
|
|
117
|
+
// El .git del principal: su carpeta padre es la raíz del repositorio.
|
|
118
|
+
return (0, node_path_1.basename)(commonDir) === ".git" ? (0, node_path_1.dirname)(commonDir) : null;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=project-of.js.map
|
package/dist/quota.d.ts
CHANGED
|
@@ -4,7 +4,16 @@ import { type QuotaReport } from "./metrics/quota.js";
|
|
|
4
4
|
* La cuota sobre la base: lo que comparten `estela quota` y el panel web.
|
|
5
5
|
* El cálculo vive en `metrics/quota.ts`; aquí solo se leen los datos.
|
|
6
6
|
*/
|
|
7
|
-
export
|
|
7
|
+
export interface QuotaLoadOptions {
|
|
8
|
+
/** % por precio de lista en vez de por tokens (`--weighted`). */
|
|
9
|
+
readonly weighted?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* La base de la que se lee, para encontrar al lado lo que guardó la status
|
|
12
|
+
* line (el `resets_at` de Claude Code). Sin ella, la ventana se estima.
|
|
13
|
+
*/
|
|
14
|
+
readonly dbPath?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function loadQuotaReport(db: DatabaseSync, now?: Date, options?: QuotaLoadOptions): QuotaReport;
|
|
8
17
|
/**
|
|
9
18
|
* Trae los turnos de esta semana antes de calcular.
|
|
10
19
|
*
|
package/dist/quota.js
CHANGED
|
@@ -35,38 +35,32 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.loadQuotaReport = loadQuotaReport;
|
|
37
37
|
exports.refreshQuotaTurns = refreshQuotaTurns;
|
|
38
|
-
const node_path_1 = require("node:path");
|
|
39
38
|
const store = __importStar(require("./db/store.js"));
|
|
40
39
|
const quota_js_1 = require("./metrics/quota.js");
|
|
40
|
+
const project_of_js_1 = require("./project-of.js");
|
|
41
|
+
const statusline_cache_js_1 = require("./statusline-cache.js");
|
|
41
42
|
const agents_js_1 = require("./watchers/agents.js");
|
|
42
|
-
|
|
43
|
-
* La cuota sobre la base: lo que comparten `estela quota` y el panel web.
|
|
44
|
-
* El cálculo vive en `metrics/quota.ts`; aquí solo se leen los datos.
|
|
45
|
-
*/
|
|
46
|
-
function loadQuotaReport(db, now = new Date()) {
|
|
43
|
+
function loadQuotaReport(db, now = new Date(), options = {}) {
|
|
47
44
|
const range = (0, quota_js_1.quotaRange)(now);
|
|
48
45
|
const names = new Map(store.listProjects(db).map((p) => [p.id, p.name]));
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
const
|
|
46
|
+
// La carpeta, y si no, su raíz git: ver project-of.ts. Una carpeta sin
|
|
47
|
+
// proyecto sale con el nombre de su raíz git, no el de una subcarpeta.
|
|
48
|
+
const ownerOf = (0, project_of_js_1.ownerResolver)(db);
|
|
52
49
|
const bucketOf = (repoPath) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
? { kind: "project", projectId, projectName: names.get(projectId) ?? projectId }
|
|
60
|
-
: { kind: "folder", folder: (0, node_path_1.basename)(repoPath.replace(/[\\/]+$/, "")) || repoPath };
|
|
61
|
-
cache.set(repoPath, bucket);
|
|
62
|
-
}
|
|
63
|
-
return bucket;
|
|
50
|
+
const o = ownerOf(repoPath);
|
|
51
|
+
if (o.kind === "project")
|
|
52
|
+
return { kind: "project", projectId: o.projectId, projectName: names.get(o.projectId) ?? o.projectId };
|
|
53
|
+
if (o.kind === "folder")
|
|
54
|
+
return { kind: "folder", folder: o.name, path: o.path };
|
|
55
|
+
return { kind: "none" };
|
|
64
56
|
};
|
|
65
57
|
return (0, quota_js_1.buildQuota)({
|
|
66
58
|
turns: store.quotaTurns(db, range.start, range.end),
|
|
67
59
|
firstTurnAt: store.firstTurnByAgent(db),
|
|
68
60
|
now,
|
|
69
61
|
bucketOf,
|
|
62
|
+
weighted: options.weighted === true,
|
|
63
|
+
claudeWindow: options.dbPath ? (0, quota_js_1.officialWindow)((0, statusline_cache_js_1.readFiveHourReset)(options.dbPath), now) : null,
|
|
70
64
|
});
|
|
71
65
|
}
|
|
72
66
|
/**
|
package/dist/server.js
CHANGED
|
@@ -60,6 +60,7 @@ const identity_js_1 = require("./watchers/identity.js");
|
|
|
60
60
|
const index_js_2 = require("./i18n/index.js");
|
|
61
61
|
const paths_js_1 = require("./paths.js");
|
|
62
62
|
const localize_js_1 = require("./billing/localize.js");
|
|
63
|
+
const assign_js_1 = require("./billing/assign.js");
|
|
63
64
|
const quota_js_1 = require("./metrics/quota.js");
|
|
64
65
|
const quota_js_2 = require("./quota.js");
|
|
65
66
|
const labels_js_1 = require("./i18n/labels.js");
|
|
@@ -141,32 +142,14 @@ async function importOnce(dbPath) {
|
|
|
141
142
|
const commits = (0, identity_js_1.onlyMine)(allRepoCommits, mine);
|
|
142
143
|
const fromAgents = (0, sessionize_js_1.attachCommits)((0, sessionize_js_1.sessionize)(turns), commits);
|
|
143
144
|
const fromCommits = (0, sessionize_js_1.withoutOverlap)((0, sessionize_js_1.sessionizeCommits)(commits), fromAgents);
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const project = store.getProject(db, projectId);
|
|
153
|
-
const day = (0, shared_1.localDate)(block.startedAt);
|
|
154
|
-
const slug = (block.branch ?? "sin-rama").replace(/[^a-zA-Z0-9]+/g, "-");
|
|
155
|
-
store.saveTimeEntry(db, {
|
|
156
|
-
id: `te_${projectId}_${day}_${slug}`,
|
|
157
|
-
projectId,
|
|
158
|
-
startedAt: block.startedAt, endedAt: block.endedAt, seconds: block.seconds,
|
|
159
|
-
description: (0, sessionize_js_1.describeBlock)(block),
|
|
160
|
-
billable: project.billable, invoiceId: null,
|
|
161
|
-
aiCost: block.aiCost,
|
|
162
|
-
// Sin agente no hay segundos de agente que atribuir.
|
|
163
|
-
agentSeconds: block.turnCount > 0 ? block.seconds : 0,
|
|
164
|
-
commitHashes: block.commits.map((c) => c.hash),
|
|
165
|
-
agents: block.turnCount > 0 ? [...(block.agents ?? ["claude-code"])] : [],
|
|
166
|
-
source: block.turnCount > 0 ? "agent" : "commit", kind: "development",
|
|
167
|
-
branch: block.branch,
|
|
168
|
-
workItems: (0, shared_1.extractWorkItems)(block.branch, block.commits.map((c) => c.subject), project.tracker),
|
|
169
|
-
});
|
|
145
|
+
// Las mismas reglas que `estela import` (billing/assign.ts). Las horas de
|
|
146
|
+
// subcarpetas y worktrees solo se recuperan si ya se confirmó una vez
|
|
147
|
+
// desde la terminal: el panel no cambia horas importadas en segundo plano
|
|
148
|
+
// sin que nadie haya visto antes qué iba a cambiar.
|
|
149
|
+
const recover = (0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_path_1.dirname)(dbPath), ".recover-git-root"));
|
|
150
|
+
const plan = (0, assign_js_1.planEntries)(db, [...fromAgents, ...fromCommits], { recover });
|
|
151
|
+
for (const { entry } of plan.entries) {
|
|
152
|
+
store.saveTimeEntry(db, entry);
|
|
170
153
|
imputed++;
|
|
171
154
|
}
|
|
172
155
|
return imputed;
|
|
@@ -325,7 +308,7 @@ async function api(req, res, url, dbPath) {
|
|
|
325
308
|
// el panel ya importa solo cada pocos minutos, y una petición web no debe
|
|
326
309
|
// ponerse a leer el disco entero.
|
|
327
310
|
if (path === "/api/quota" && req.method === "GET") {
|
|
328
|
-
return json(res, 200, (0, quota_js_1.quotaToJson)((0, quota_js_2.loadQuotaReport)(db)));
|
|
311
|
+
return json(res, 200, (0, quota_js_1.quotaToJson)((0, quota_js_2.loadQuotaReport)(db, new Date(), { dbPath })));
|
|
329
312
|
}
|
|
330
313
|
if (path === "/api/day" && req.method === "GET") {
|
|
331
314
|
const date = url.searchParams.get("date") ?? todayIso();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `statusline.json`, junto a la base: lo que la status line recuerda entre
|
|
3
|
+
* llamadas.
|
|
4
|
+
*
|
|
5
|
+
* - `files`: hasta qué byte se leyó cada transcript, para leer solo lo nuevo.
|
|
6
|
+
* - `fiveHour`: el último `rate_limits.five_hour.resets_at` que mandó Claude
|
|
7
|
+
* Code, y cuándo se vio. `estela quota` lo usa para saber dónde empieza de
|
|
8
|
+
* verdad la ventana de 5 h en vez de estimarla.
|
|
9
|
+
*
|
|
10
|
+
* Es una caché: si se pierde o se corrompe, todo sigue funcionando, solo que
|
|
11
|
+
* relee más o vuelve a estimar.
|
|
12
|
+
*/
|
|
13
|
+
export interface StatuslineCache {
|
|
14
|
+
files: Record<string, {
|
|
15
|
+
offset: number;
|
|
16
|
+
seenAt: string;
|
|
17
|
+
}>;
|
|
18
|
+
fiveHour?: {
|
|
19
|
+
resetsAt: string;
|
|
20
|
+
seenAt: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare function cachePath(dbPath: string): string;
|
|
24
|
+
export declare function loadCache(dbPath: string): StatuslineCache;
|
|
25
|
+
export declare function saveCache(dbPath: string, cache: StatuslineCache): void;
|
|
26
|
+
/**
|
|
27
|
+
* Apunta el `resets_at` de la ventana de 5 h. Solo si existe ya el directorio
|
|
28
|
+
* de Estela: la status line no es quien empieza a guardar datos.
|
|
29
|
+
*/
|
|
30
|
+
export declare function recordFiveHourReset(dbPath: string, resetsAt: Date, now: Date): void;
|
|
31
|
+
export declare function readFiveHourReset(dbPath: string): {
|
|
32
|
+
resetsAt: Date;
|
|
33
|
+
seenAt: Date;
|
|
34
|
+
} | null;
|
|
35
|
+
//# sourceMappingURL=statusline-cache.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cachePath = cachePath;
|
|
4
|
+
exports.loadCache = loadCache;
|
|
5
|
+
exports.saveCache = saveCache;
|
|
6
|
+
exports.recordFiveHourReset = recordFiveHourReset;
|
|
7
|
+
exports.readFiveHourReset = readFiveHourReset;
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const MAX_FILES = 30;
|
|
11
|
+
/** Con el mismo reinicio, no se reescribe más a menudo que esto. */
|
|
12
|
+
const FIVE_HOUR_REFRESH_MS = 10 * 60_000;
|
|
13
|
+
function cachePath(dbPath) {
|
|
14
|
+
return (0, node_path_1.join)((0, node_path_1.dirname)(dbPath), "statusline.json");
|
|
15
|
+
}
|
|
16
|
+
function loadCache(dbPath) {
|
|
17
|
+
try {
|
|
18
|
+
const c = JSON.parse((0, node_fs_1.readFileSync)(cachePath(dbPath), "utf8"));
|
|
19
|
+
return c && typeof c.files === "object" && c.files ? c : { files: {} };
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return { files: {} };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function saveCache(dbPath, cache) {
|
|
26
|
+
// Solo los transcripts vistos más recientemente: uno por sesión, y las
|
|
27
|
+
// sesiones viejas ya no escriben.
|
|
28
|
+
const files = Object.fromEntries(Object.entries(cache.files)
|
|
29
|
+
.sort((a, b) => b[1].seenAt.localeCompare(a[1].seenAt)).slice(0, MAX_FILES));
|
|
30
|
+
try {
|
|
31
|
+
(0, node_fs_1.writeFileSync)(cachePath(dbPath), JSON.stringify({ ...cache, files }));
|
|
32
|
+
}
|
|
33
|
+
catch { /* sin caché, la próxima vez se relee: más lento, no incorrecto */ }
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Apunta el `resets_at` de la ventana de 5 h. Solo si existe ya el directorio
|
|
37
|
+
* de Estela: la status line no es quien empieza a guardar datos.
|
|
38
|
+
*/
|
|
39
|
+
function recordFiveHourReset(dbPath, resetsAt, now) {
|
|
40
|
+
if (!(0, node_fs_1.existsSync)((0, node_path_1.dirname)(dbPath)))
|
|
41
|
+
return;
|
|
42
|
+
const cache = loadCache(dbPath);
|
|
43
|
+
const prev = cache.fiveHour;
|
|
44
|
+
const same = prev && new Date(prev.resetsAt).getTime() === resetsAt.getTime();
|
|
45
|
+
if (same && now.getTime() - new Date(prev.seenAt).getTime() < FIVE_HOUR_REFRESH_MS)
|
|
46
|
+
return;
|
|
47
|
+
cache.fiveHour = { resetsAt: resetsAt.toISOString(), seenAt: now.toISOString() };
|
|
48
|
+
saveCache(dbPath, cache);
|
|
49
|
+
}
|
|
50
|
+
function readFiveHourReset(dbPath) {
|
|
51
|
+
const f = loadCache(dbPath).fiveHour;
|
|
52
|
+
if (!f)
|
|
53
|
+
return null;
|
|
54
|
+
const resetsAt = new Date(f.resetsAt), seenAt = new Date(f.seenAt);
|
|
55
|
+
return Number.isNaN(resetsAt.getTime()) || Number.isNaN(seenAt.getTime()) ? null : { resetsAt, seenAt };
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=statusline-cache.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `estela statusline install | uninstall`: el bloque `statusLine` de
|
|
3
|
+
* `~/.claude/settings.json`.
|
|
4
|
+
*
|
|
5
|
+
* Ese fichero es de quien usa Claude Code, con sus permisos, sus hooks y sus
|
|
6
|
+
* plugins. Se toca lo justo: se lee, se cambia una clave y se reescribe el
|
|
7
|
+
* resto tal cual; antes, una copia. Si ya hay otra status line, no se pisa.
|
|
8
|
+
*
|
|
9
|
+
* Funciones puras sobre el texto del fichero: quien llama lee y escribe.
|
|
10
|
+
*/
|
|
11
|
+
export declare const OUR_COMMAND = "estela statusline";
|
|
12
|
+
/** Dónde vive la configuración de Claude Code (`CLAUDE_CONFIG_DIR` la mueve). */
|
|
13
|
+
export declare function claudeSettingsPath(env?: NodeJS.ProcessEnv): string;
|
|
14
|
+
export type InstallResult = {
|
|
15
|
+
readonly status: "installed" | "replaced" | "chained";
|
|
16
|
+
readonly text: string;
|
|
17
|
+
readonly previous: string | null;
|
|
18
|
+
} | {
|
|
19
|
+
readonly status: "already";
|
|
20
|
+
} | {
|
|
21
|
+
readonly status: "conflict";
|
|
22
|
+
readonly existing: string;
|
|
23
|
+
} | {
|
|
24
|
+
readonly status: "invalid";
|
|
25
|
+
};
|
|
26
|
+
export type UninstallResult = {
|
|
27
|
+
readonly status: "removed" | "restored";
|
|
28
|
+
readonly text: string;
|
|
29
|
+
readonly restored: string | null;
|
|
30
|
+
} | {
|
|
31
|
+
readonly status: "absent";
|
|
32
|
+
} | {
|
|
33
|
+
readonly status: "foreign";
|
|
34
|
+
readonly existing: string;
|
|
35
|
+
} | {
|
|
36
|
+
readonly status: "invalid";
|
|
37
|
+
};
|
|
38
|
+
export declare function isOurs(command: string | null): boolean;
|
|
39
|
+
/** Comillas simples de shell: lo que va dentro se pasa tal cual. */
|
|
40
|
+
export declare function shellQuote(text: string): string;
|
|
41
|
+
/** El comando encadenado dentro de `--append '…'`, si lo hay. */
|
|
42
|
+
export declare function appendedCommand(command: string): string | null;
|
|
43
|
+
export declare function planInstall(text: string | null, options?: {
|
|
44
|
+
readonly force?: boolean;
|
|
45
|
+
readonly append?: boolean;
|
|
46
|
+
}): InstallResult;
|
|
47
|
+
export declare function planUninstall(text: string | null): UninstallResult;
|
|
48
|
+
/** `estela` en el PATH. Con `npx` cada evento tardaría cientos de milisegundos. */
|
|
49
|
+
export declare function estelaOnPath(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
|
|
50
|
+
/** "settings.json.bak-20261014-161230": una copia por instalación, sin pisar las anteriores. */
|
|
51
|
+
export declare function backupName(path: string, now?: Date): string;
|
|
52
|
+
//# sourceMappingURL=statusline-install.d.ts.map
|