@sunasteriskrnd/takumi 1.0.0-dev.43 → 1.0.0-dev.44

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 (2) hide show
  1. package/dist/index.js +139 -22
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -19815,7 +19815,7 @@ var package_default;
19815
19815
  var init_package = __esm(() => {
19816
19816
  package_default = {
19817
19817
  name: "@sunasteriskrnd/takumi",
19818
- version: "1.0.0-dev.43",
19818
+ version: "1.0.0-dev.44",
19819
19819
  description: "CLI tool for bootstrapping and managing Takumi projects",
19820
19820
  type: "module",
19821
19821
  repository: {
@@ -53485,6 +53485,7 @@ function tryOpenBrowser(target) {
53485
53485
  }
53486
53486
 
53487
53487
  // src/domains/sessions/server.ts
53488
+ init_logger();
53488
53489
  import * as http from "node:http";
53489
53490
 
53490
53491
  // src/domains/sessions/analytics.ts
@@ -53521,16 +53522,26 @@ function accumulateTokens(a3, b3) {
53521
53522
  a3.cacheRead += b3.cacheRead;
53522
53523
  a3.cacheWrite += b3.cacheWrite;
53523
53524
  }
53524
- function buildLast30(byDay, now) {
53525
+ function buildDenseWindow(byDay, startDayMs, endDayMs) {
53525
53526
  const out = [];
53526
- const todayMs = Date.parse(`${dayKey(now)}T00:00:00.000Z`);
53527
- for (let i = LAST_N_DAYS - 1;i >= 0; i -= 1) {
53528
- const date = dayKey(todayMs - i * DAY_MS);
53527
+ for (let ms = startDayMs;ms <= endDayMs; ms += DAY_MS) {
53528
+ const date = dayKey(ms);
53529
53529
  out.push({ date, count: byDay.get(date) ?? 0 });
53530
53530
  }
53531
53531
  return out;
53532
53532
  }
53533
- function foldAnalytics(aggregates, now) {
53533
+ function windowBounds(now, range) {
53534
+ if (!range) {
53535
+ const todayMs = Date.parse(`${dayKey(now)}T00:00:00.000Z`);
53536
+ return { start: todayMs - (LAST_N_DAYS - 1) * DAY_MS, end: todayMs };
53537
+ }
53538
+ const lo = Math.min(range.from, range.to);
53539
+ const hi = Math.max(range.from, range.to);
53540
+ const end = Date.parse(`${dayKey(hi)}T00:00:00.000Z`);
53541
+ const start = Math.max(Date.parse(`${dayKey(lo)}T00:00:00.000Z`), end - (PER_DAY_MAX - 1) * DAY_MS);
53542
+ return { start, end };
53543
+ }
53544
+ function foldAnalytics(aggregates, now, range) {
53534
53545
  const totals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
53535
53546
  const eventsByDay = new Map;
53536
53547
  const tokensByDow = new Map;
@@ -53585,7 +53596,8 @@ function foldAnalytics(aggregates, now) {
53585
53596
  const perDay = [...eventsByDay.entries()].filter(([date]) => Date.parse(`${date}T00:00:00.000Z`) >= oldestAllowedMs).map(([date, count]) => ({ date, count })).sort((a3, b3) => a3.date.localeCompare(b3.date));
53586
53597
  const byProjectAll = [...tokensByProject.entries()].map(([label, tokens]) => ({ label, tokens })).sort((a3, b3) => b3.tokens - a3.tokens || a3.label.localeCompare(b3.label));
53587
53598
  const byProject = byProjectAll.slice(0, TOP_PROJECTS);
53588
- const daily = buildLast30(tokensByDay, now);
53599
+ const { start: windowStart, end: windowEnd } = windowBounds(now, range);
53600
+ const daily = buildDenseWindow(tokensByDay, windowStart, windowEnd);
53589
53601
  const rankedModels = [...tokensByModel.entries()].map(([label, tokens]) => ({ label, tokens, total: sumTokens(tokens) })).filter((m2) => m2.total > 0).sort((a3, b3) => b3.total - a3.total || a3.label.localeCompare(b3.label)).slice(0, TOP_PROJECTS);
53590
53602
  const byModel = rankedModels.map(({ label, total }) => ({ label, tokens: total }));
53591
53603
  const byModelDetailed = rankedModels.map(({ label, tokens }) => ({ label, tokens }));
@@ -53602,7 +53614,7 @@ function foldAnalytics(aggregates, now) {
53602
53614
  events: {
53603
53615
  total: eventTotal,
53604
53616
  perDay,
53605
- last30: buildLast30(eventsByDay, now)
53617
+ last30: buildDenseWindow(eventsByDay, windowStart, windowEnd)
53606
53618
  },
53607
53619
  tokens: {
53608
53620
  byType: { ...totals },
@@ -54912,6 +54924,24 @@ function invalidate() {
54912
54924
  }
54913
54925
  }
54914
54926
 
54927
+ // src/domains/sessions/session-meta-filter.ts
54928
+ function filterSessionMetas(metas, filter) {
54929
+ return metas.filter((m2) => {
54930
+ if (filter.project !== undefined && m2.project !== filter.project)
54931
+ return false;
54932
+ if (filter.from === undefined && filter.to === undefined)
54933
+ return true;
54934
+ const ms = m2.startedAt === null ? Number.NaN : Date.parse(m2.startedAt);
54935
+ if (Number.isNaN(ms))
54936
+ return false;
54937
+ if (filter.from !== undefined && ms < filter.from)
54938
+ return false;
54939
+ if (filter.to !== undefined && ms > filter.to)
54940
+ return false;
54941
+ return true;
54942
+ });
54943
+ }
54944
+
54915
54945
  // src/domains/sessions/store/ingest.ts
54916
54946
  import { createHash as createHash9 } from "node:crypto";
54917
54947
  import { closeSync as closeSync4, openSync as openSync4, readSync as readSync4, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
@@ -64676,6 +64706,32 @@ async function readStoreAggregates(db) {
64676
64706
  get2(r2.session_id).agentCount += Number(r2.n ?? 0);
64677
64707
  return map;
64678
64708
  }
64709
+ async function attachSessionTokenTotals(db, list2) {
64710
+ const totals = await readSessionTokenTotals(db);
64711
+ return list2.map((s) => {
64712
+ const tokens = totals.get(s.id);
64713
+ return tokens ? { ...s, tokens } : s;
64714
+ });
64715
+ }
64716
+ async function readSessionTokenTotals(db) {
64717
+ const rows = await db.selectFrom("event").where("type", "=", "tokens").select((eb) => [
64718
+ "session_id",
64719
+ eb.fn.sum("input").as("input"),
64720
+ eb.fn.sum("output").as("output"),
64721
+ eb.fn.sum("cache_read").as("cache_read"),
64722
+ eb.fn.sum("cache_write").as("cache_write")
64723
+ ]).groupBy("session_id").execute();
64724
+ const map = new Map;
64725
+ for (const r2 of rows) {
64726
+ map.set(r2.session_id, {
64727
+ input: Number(r2.input ?? 0),
64728
+ output: Number(r2.output ?? 0),
64729
+ cacheCreate: Number(r2.cache_write ?? 0),
64730
+ cacheRead: Number(r2.cache_read ?? 0)
64731
+ });
64732
+ }
64733
+ return map;
64734
+ }
64679
64735
  function buildAggregatesFromStore(sessions, store) {
64680
64736
  return sessions.map((s) => {
64681
64737
  const a3 = store.get(s.id) ?? emptyStoreAggregate();
@@ -64692,30 +64748,52 @@ function buildAggregatesFromStore(sessions, store) {
64692
64748
  }
64693
64749
 
64694
64750
  // src/domains/sessions/analytics-source.ts
64695
- var cache2 = null;
64751
+ var DAY_MS2 = 24 * 60 * 60 * 1000;
64752
+ var DEFAULT_WINDOW_DAYS = 30;
64753
+ var cache2 = new Map;
64696
64754
  var storeHandle = null;
64697
64755
  function setStore(handle) {
64698
64756
  storeHandle = handle;
64699
64757
  }
64700
64758
  function invalidate2() {
64701
- cache2 = null;
64759
+ cache2 = new Map;
64702
64760
  }
64703
- async function computeFromStore(handle, refresh2, now) {
64761
+ var cacheKey = (o2) => `${o2.from ?? ""}|${o2.to ?? ""}|${o2.project === undefined ? "" : `p:${o2.project}`}`;
64762
+ function rangeOf(o2, now) {
64763
+ if (o2.from === undefined && o2.to === undefined)
64764
+ return;
64765
+ const to = o2.to ?? now;
64766
+ const from = o2.from ?? to - (DEFAULT_WINDOW_DAYS - 1) * DAY_MS2;
64767
+ return { from, to };
64768
+ }
64769
+ async function computeFromStore(handle, opts, now) {
64770
+ const refresh2 = opts.refresh ?? false;
64704
64771
  if (refresh2)
64705
64772
  await ingestAll(handle, { refresh: true });
64706
64773
  const sessions = await list({ refresh: refresh2 });
64774
+ const metas = filterSessionMetas(sessions.map((s) => ({ id: s.id, startedAt: s.startedAt, project: s.project })), { from: opts.from, to: opts.to, project: opts.project });
64707
64775
  const storeAggs = await readStoreAggregates(handle.db);
64708
- const metas = sessions.map((s) => ({ id: s.id, startedAt: s.startedAt, project: s.project }));
64709
- return foldAnalytics(buildAggregatesFromStore(metas, storeAggs), now);
64776
+ const payload = foldAnalytics(buildAggregatesFromStore(metas, storeAggs), now, rangeOf(opts, now));
64777
+ return {
64778
+ ...payload,
64779
+ filters: {
64780
+ from: opts.from ?? null,
64781
+ to: opts.to ?? null,
64782
+ project: opts.project ?? null
64783
+ }
64784
+ };
64710
64785
  }
64711
64786
  async function getAnalytics(opts = {}) {
64712
- if (cache2 && !opts.refresh)
64713
- return cache2;
64787
+ const key = cacheKey(opts);
64788
+ const hit = cache2.get(key);
64789
+ if (hit && !opts.refresh)
64790
+ return hit;
64714
64791
  if (!storeHandle)
64715
64792
  throw new StoreUnavailableError;
64716
64793
  const now = Date.now();
64717
- cache2 = await computeFromStore(storeHandle, opts.refresh ?? false, now);
64718
- return cache2;
64794
+ const payload = await computeFromStore(storeHandle, opts, now);
64795
+ cache2.set(key, payload);
64796
+ return payload;
64719
64797
  }
64720
64798
 
64721
64799
  // src/domains/sessions/origin-allowlist.ts
@@ -64743,13 +64821,33 @@ function isAllowedOrigin(origin) {
64743
64821
  return LOOPBACK_HOST_PATTERN.test(origin);
64744
64822
  }
64745
64823
 
64824
+ // src/domains/sessions/range-params.ts
64825
+ function intParam(search, key) {
64826
+ const raw = search.get(key);
64827
+ if (raw === null || raw === "")
64828
+ return;
64829
+ const n = Number(raw);
64830
+ if (!Number.isInteger(n) || n < 0)
64831
+ return { error: `invalid ${key}: ${raw}` };
64832
+ return n;
64833
+ }
64834
+ function parseRangeProjectParams(search) {
64835
+ const from = intParam(search, "from");
64836
+ if (typeof from === "object")
64837
+ return from;
64838
+ const to = intParam(search, "to");
64839
+ if (typeof to === "object")
64840
+ return to;
64841
+ return { from, to, project: search.get("project") || undefined };
64842
+ }
64843
+
64746
64844
  // src/domains/sessions/store/bridge-store.ts
64747
64845
  init_logger();
64748
64846
 
64749
64847
  // src/domains/sessions/store/prune.ts
64750
64848
  import { existsSync as existsSync34, statSync as statSync6 } from "node:fs";
64751
64849
  init_logger();
64752
- var DAY_MS2 = 24 * 60 * 60 * 1000;
64850
+ var DAY_MS3 = 24 * 60 * 60 * 1000;
64753
64851
  var DEFAULT_RETENTION_DAYS = 180;
64754
64852
  var SIZE_DELETE_BATCH = 500;
64755
64853
  var SIZE_PRUNE_MAX_ITERS = 200;
@@ -64775,7 +64873,7 @@ async function runPrune(handle, opts = {}) {
64775
64873
  const now = opts.now ?? Date.now();
64776
64874
  const retentionDays = opts.retentionDays ?? DEFAULT_RETENTION_DAYS;
64777
64875
  const dbPath = opts.dbPath ?? getConsoleStorePath();
64778
- const cutoff = now - retentionDays * DAY_MS2;
64876
+ const cutoff = now - retentionDays * DAY_MS3;
64779
64877
  await db.deleteFrom("event").where("ts", "<", cutoff).execute();
64780
64878
  if (opts.maxSizeMb && opts.maxSizeMb > 0) {
64781
64879
  const cap = opts.maxSizeMb * 1024 * 1024;
@@ -64953,6 +65051,17 @@ async function attachStore(deps = {}) {
64953
65051
  }
64954
65052
 
64955
65053
  // src/domains/sessions/server.ts
65054
+ var tokenStore = null;
65055
+ async function withTokenTotals(list2) {
65056
+ if (!tokenStore)
65057
+ return list2;
65058
+ try {
65059
+ return await attachSessionTokenTotals(tokenStore.db, list2);
65060
+ } catch (err) {
65061
+ logger.verbose(`console bridge: token totals enrichment failed (${String(err)})`);
65062
+ return list2;
65063
+ }
65064
+ }
64956
65065
  var PORT_BUMP_MAX = 9;
64957
65066
  function applyCorsHeaders(req, res) {
64958
65067
  const origin = req.headers.origin;
@@ -65010,15 +65119,20 @@ async function route(req, res, version3) {
65010
65119
  if (refresh2)
65011
65120
  invalidate();
65012
65121
  const list2 = await list({ refresh: refresh2 });
65013
- sendJSON(res, 200, list2);
65122
+ sendJSON(res, 200, await withTokenTotals(list2));
65014
65123
  return;
65015
65124
  }
65016
65125
  if (pathname === "/api/analytics") {
65017
65126
  const refresh2 = url.searchParams.get("refresh") === "1";
65127
+ const rp = parseRangeProjectParams(url.searchParams);
65128
+ if ("error" in rp) {
65129
+ sendJSON(res, 400, { error: rp.error });
65130
+ return;
65131
+ }
65018
65132
  if (refresh2)
65019
65133
  invalidate2();
65020
65134
  try {
65021
- const payload = await getAnalytics({ refresh: refresh2 });
65135
+ const payload = await getAnalytics({ refresh: refresh2, ...rp });
65022
65136
  sendJSON(res, 200, payload);
65023
65137
  } catch (err) {
65024
65138
  if (err instanceof StoreUnavailableError) {
@@ -65091,6 +65205,7 @@ function start(options2 = {}) {
65091
65205
  close: async () => {
65092
65206
  if (attached) {
65093
65207
  setStore(null);
65208
+ tokenStore = null;
65094
65209
  await attached.stop().catch(() => {});
65095
65210
  }
65096
65211
  await closeServer();
@@ -65101,8 +65216,10 @@ function start(options2 = {}) {
65101
65216
  };
65102
65217
  const doAttach = options2.attachStore ?? attachStore;
65103
65218
  doAttach({ onInvalidate: () => invalidate2() }).then((attached) => {
65104
- if (attached)
65219
+ if (attached) {
65105
65220
  setStore(attached.handle);
65221
+ tokenStore = attached.handle;
65222
+ }
65106
65223
  finish(attached);
65107
65224
  }).catch(() => finish(null));
65108
65225
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sunasteriskrnd/takumi",
3
- "version": "1.0.0-dev.43",
3
+ "version": "1.0.0-dev.44",
4
4
  "description": "CLI tool for bootstrapping and managing Takumi projects",
5
5
  "type": "module",
6
6
  "repository": {