bertrand 0.24.0 → 0.25.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/dist/bertrand.js CHANGED
@@ -919,8 +919,8 @@ function createSession(opts) {
919
919
  const id = createId();
920
920
  return db.insert(sessions).values({ id, ...opts }).returning().get();
921
921
  }
922
- function getSession(id) {
923
- return getDb().select().from(sessions).where(eq2(sessions.id, id)).get();
922
+ function getSession(id, db = getDb()) {
923
+ return db.select().from(sessions).where(eq2(sessions.id, id)).get();
924
924
  }
925
925
  function getSessionByCategorySlug(categoryId, slug) {
926
926
  return getDb().select().from(sessions).where(and(eq2(sessions.categoryId, categoryId), eq2(sessions.slug, slug))).get();
@@ -931,8 +931,11 @@ function getSessionsByCategory(categoryId) {
931
931
  function getActiveSessions() {
932
932
  return getDb().select({ session: sessions, categoryPath: categories.path }).from(sessions).innerJoin(categories, eq2(sessions.categoryId, categories.id)).where(inArray(sessions.status, ["active", "waiting"])).all();
933
933
  }
934
- function getAllSessions(opts) {
935
- const db = getDb();
934
+ function countLiveSessions(db = getDb()) {
935
+ const row = db.select({ n: sql2`count(*)` }).from(sessions).where(inArray(sessions.status, ["active", "waiting"])).get();
936
+ return row?.n ?? 0;
937
+ }
938
+ function selectSessions(db, opts) {
936
939
  const query = db.select({ session: sessions, categoryPath: categories.path }).from(sessions).innerJoin(categories, eq2(sessions.categoryId, categories.id));
937
940
  if (opts?.excludeArchived) {
938
941
  return query.where(inArray(sessions.status, [
@@ -943,6 +946,15 @@ function getAllSessions(opts) {
943
946
  }
944
947
  return query.all();
945
948
  }
949
+ function getAllSessions(opts) {
950
+ return selectSessions(getDb(), opts);
951
+ }
952
+ function getAllSessionsForProject(project, opts) {
953
+ return selectSessions(getDbForProject(project.slug), opts).map((s) => ({
954
+ ...s,
955
+ project
956
+ }));
957
+ }
946
958
  function updateSessionStatus(id, status) {
947
959
  return getDb().update(sessions).set({ status, updatedAt: sql2`(datetime('now'))` }).where(eq2(sessions.id, id)).returning().get();
948
960
  }
@@ -1036,11 +1048,11 @@ function insertEvent(opts) {
1036
1048
  meta: normalizeEventMeta(opts.event, opts.meta)
1037
1049
  }).returning().get();
1038
1050
  }
1039
- function getEventsBySession(sessionId) {
1040
- return getDb().select().from(events).where(eq4(events.sessionId, sessionId)).orderBy(events.createdAt, events.id).all();
1051
+ function getEventsBySession(sessionId, db = getDb()) {
1052
+ return db.select().from(events).where(eq4(events.sessionId, sessionId)).orderBy(events.createdAt, events.id).all();
1041
1053
  }
1042
- function getEventsByType(sessionId, eventType) {
1043
- return getDb().select().from(events).where(and3(eq4(events.sessionId, sessionId), eq4(events.event, eventType))).orderBy(events.createdAt).all();
1054
+ function getEventsByType(sessionId, eventType, db = getDb()) {
1055
+ return db.select().from(events).where(and3(eq4(events.sessionId, sessionId), eq4(events.event, eventType))).orderBy(events.createdAt).all();
1044
1056
  }
1045
1057
  function getLatestEventOfType(sessionId, eventType, conversationId) {
1046
1058
  const conditions = [
@@ -1745,8 +1757,8 @@ var init_notify = __esm(() => {
1745
1757
 
1746
1758
  // src/db/queries/stats.ts
1747
1759
  import { eq as eq5, sql as sql4 } from "drizzle-orm";
1748
- function getSessionStats(sessionId) {
1749
- return getDb().select().from(sessionStats).where(eq5(sessionStats.sessionId, sessionId)).get();
1760
+ function getSessionStats(sessionId, db = getDb()) {
1761
+ return db.select().from(sessionStats).where(eq5(sessionStats.sessionId, sessionId)).get();
1750
1762
  }
1751
1763
  function upsertSessionStats(sessionId, data) {
1752
1764
  return getDb().insert(sessionStats).values({
@@ -1770,8 +1782,8 @@ function lineCount(s) {
1770
1782
  return s.split(`
1771
1783
  `).length;
1772
1784
  }
1773
- function computeDiffStats(sessionId) {
1774
- const applied = getEventsByType(sessionId, "tool.applied");
1785
+ function computeDiffStats(sessionId, db = getDb()) {
1786
+ const applied = getEventsByType(sessionId, "tool.applied", db);
1775
1787
  const files = new Set;
1776
1788
  let linesAdded = 0;
1777
1789
  let linesRemoved = 0;
@@ -1796,6 +1808,7 @@ function computeDiffStats(sessionId) {
1796
1808
  }
1797
1809
  var init_diff_stats = __esm(() => {
1798
1810
  init_events();
1811
+ init_client();
1799
1812
  });
1800
1813
 
1801
1814
  // src/lib/timing.ts
@@ -1885,12 +1898,12 @@ function computeTimings(events2) {
1885
1898
  }
1886
1899
  return { totalClaudeWorkMs, totalUserWaitMs, activePct, durationS, segments };
1887
1900
  }
1888
- function computeSessionStats(sessionId) {
1889
- const events2 = getEventsBySession(sessionId);
1901
+ function computeSessionStats(sessionId, db = getDb()) {
1902
+ const events2 = getEventsBySession(sessionId, db);
1890
1903
  const summary = computeTimings(events2);
1891
1904
  const conversationIds = new Set(events2.filter((e) => e.conversationId).map((e) => e.conversationId));
1892
1905
  const interactionCount = events2.filter((e) => e.event === "session.waiting" || e.event === "session.answered").length;
1893
- const diff = computeDiffStats(sessionId);
1906
+ const diff = computeDiffStats(sessionId, db);
1894
1907
  return {
1895
1908
  eventCount: events2.length,
1896
1909
  conversationCount: conversationIds.size,
@@ -1916,13 +1929,14 @@ var init_timing = __esm(() => {
1916
1929
  init_events();
1917
1930
  init_stats();
1918
1931
  init_diff_stats();
1932
+ init_client();
1919
1933
  });
1920
1934
 
1921
1935
  // src/lib/engagement_stats.ts
1922
1936
  import { eq as eq6, sql as sql5 } from "drizzle-orm";
1923
- function aggregateToolUsage(sessionId) {
1937
+ function aggregateToolUsage(sessionId, db) {
1924
1938
  const counts = {};
1925
- const applied = getEventsByType(sessionId, "tool.applied");
1939
+ const applied = getEventsByType(sessionId, "tool.applied", db);
1926
1940
  for (const ev of applied) {
1927
1941
  const meta = ev.meta;
1928
1942
  const permissions = meta?.permissions ?? [];
@@ -1932,7 +1946,7 @@ function aggregateToolUsage(sessionId) {
1932
1946
  counts[p.tool] = (counts[p.tool] ?? 0) + (p.count ?? 1);
1933
1947
  }
1934
1948
  }
1935
- const used = getEventsByType(sessionId, "tool.used");
1949
+ const used = getEventsByType(sessionId, "tool.used", db);
1936
1950
  for (const ev of used) {
1937
1951
  const meta = ev.meta;
1938
1952
  const tool = meta?.tool;
@@ -1942,8 +1956,8 @@ function aggregateToolUsage(sessionId) {
1942
1956
  }
1943
1957
  return counts;
1944
1958
  }
1945
- function discardRate(sessionId) {
1946
- const row = getDb().select({
1959
+ function discardRate(sessionId, db) {
1960
+ const row = db.select({
1947
1961
  total: sql5`count(*)`,
1948
1962
  discarded: sql5`sum(case when ${conversations.discarded} then 1 else 0 end)`
1949
1963
  }).from(conversations).where(eq6(conversations.sessionId, sessionId)).get();
@@ -1952,10 +1966,10 @@ function discardRate(sessionId) {
1952
1966
  discarded: row?.discarded ?? 0
1953
1967
  };
1954
1968
  }
1955
- function computeEngagementStats(sessionId) {
1969
+ function computeEngagementStats(sessionId, db = getDb()) {
1956
1970
  return {
1957
- toolUsage: aggregateToolUsage(sessionId),
1958
- discardRate: discardRate(sessionId)
1971
+ toolUsage: aggregateToolUsage(sessionId, db),
1972
+ discardRate: discardRate(sessionId, db)
1959
1973
  };
1960
1974
  }
1961
1975
  var init_engagement_stats = __esm(() => {
@@ -2008,13 +2022,28 @@ var init_session_archive = __esm(() => {
2008
2022
  import { execFile } from "child_process";
2009
2023
  import { existsSync as existsSync6 } from "fs";
2010
2024
  import { join as join8 } from "path";
2011
- function liveStats(sessionId) {
2025
+ function liveStats(sessionId, db) {
2012
2026
  return {
2013
2027
  sessionId,
2014
- ...computeSessionStats(sessionId),
2028
+ ...computeSessionStats(sessionId, db),
2015
2029
  updatedAt: new Date().toISOString()
2016
2030
  };
2017
2031
  }
2032
+ function resolveProjectScope(url) {
2033
+ const nameBySlug = new Map(listProjects().map((p) => [p.slug, p.name]));
2034
+ const param = url.searchParams.get("projects");
2035
+ if (param === null) {
2036
+ const active = resolveActiveProject();
2037
+ return [{ slug: active.slug, name: active.name }];
2038
+ }
2039
+ return param.split(",").map((s) => s.trim()).filter((slug) => nameBySlug.has(slug)).map((slug) => ({ slug, name: nameBySlug.get(slug) }));
2040
+ }
2041
+ function resolveDb(url) {
2042
+ const slug = url.searchParams.get("project");
2043
+ if (slug && projectExists(slug))
2044
+ return getDbForProject(slug);
2045
+ return;
2046
+ }
2018
2047
  function archiveResponse(result) {
2019
2048
  if (result.ok)
2020
2049
  return Response.json(result.session);
@@ -2158,42 +2187,44 @@ function startServer(port = PORT) {
2158
2187
  }
2159
2188
  var PORT, listSessions = (_params, url) => {
2160
2189
  const excludeArchived = url.searchParams.get("excludeArchived") !== "false";
2161
- return getAllSessions({ excludeArchived });
2190
+ return resolveProjectScope(url).flatMap((project) => getAllSessionsForProject(project, { excludeArchived }));
2162
2191
  }, getSessionById = ({ id }) => getSession(id), listEvents = ({ sessionId }, url) => {
2192
+ const db = resolveDb(url);
2163
2193
  const eventType = url.searchParams.get("type");
2164
2194
  if (eventType)
2165
- return getEventsByType(sessionId, eventType);
2166
- return getEventsBySession(sessionId);
2167
- }, listAllStats = () => {
2195
+ return getEventsByType(sessionId, eventType, db);
2196
+ return getEventsBySession(sessionId, db);
2197
+ }, listAllStats = (_params, url) => {
2168
2198
  const result = {};
2169
- for (const { session } of getAllSessions()) {
2170
- const isLive = session.status === "active" || session.status === "waiting";
2171
- if (isLive) {
2172
- result[session.id] = liveStats(session.id);
2173
- continue;
2199
+ for (const project of resolveProjectScope(url)) {
2200
+ const db = getDbForProject(project.slug);
2201
+ for (const { session } of getAllSessionsForProject(project)) {
2202
+ const isLive = session.status === "active" || session.status === "waiting";
2203
+ if (isLive) {
2204
+ result[session.id] = liveStats(session.id, db);
2205
+ continue;
2206
+ }
2207
+ result[session.id] = getSessionStats(session.id, db) ?? liveStats(session.id, db);
2174
2208
  }
2175
- result[session.id] = getSessionStats(session.id) ?? liveStats(session.id);
2176
2209
  }
2177
2210
  return result;
2178
- }, getStatsBySession = ({
2179
- sessionId
2180
- }) => {
2181
- const session = getSession(sessionId);
2211
+ }, getStatsBySession = ({ sessionId }, url) => {
2212
+ const db = resolveDb(url);
2213
+ const session = getSession(sessionId, db);
2182
2214
  if (!session)
2183
2215
  return null;
2184
2216
  const isLive = session.status === "active" || session.status === "waiting";
2185
2217
  if (isLive)
2186
- return liveStats(sessionId);
2187
- return getSessionStats(sessionId) ?? liveStats(sessionId);
2188
- }, getEngagement = ({
2189
- sessionId
2190
- }) => computeEngagementStats(sessionId), listAllProjects = () => {
2218
+ return liveStats(sessionId, db);
2219
+ return getSessionStats(sessionId, db) ?? liveStats(sessionId, db);
2220
+ }, getEngagement = ({ sessionId }, url) => computeEngagementStats(sessionId, resolveDb(url)), listAllProjects = () => {
2191
2221
  const active = resolveActiveProject();
2192
2222
  return listProjects().map((p) => ({
2193
2223
  slug: p.slug,
2194
2224
  name: p.name,
2195
2225
  active: p.slug === active.slug,
2196
- lastUsedAt: p.lastUsedAt
2226
+ lastUsedAt: p.lastUsedAt,
2227
+ liveCount: countLiveSessions(getDbForProject(p.slug))
2197
2228
  }));
2198
2229
  }, getActiveProjectMeta = () => {
2199
2230
  const active = resolveActiveProject();