@threadbase-sh/streamer 1.45.0 → 1.46.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.
package/dist/index.d.cts CHANGED
@@ -824,6 +824,22 @@ declare class ConversationCache {
824
824
  name: string;
825
825
  sessionCount: number;
826
826
  }>;
827
+ /** Every project with at least one cached conversation, most recently active
828
+ * first. Paths are the raw `project_path` values, which is what
829
+ * /api/conversations?project= matches on exactly — so a summary row is
830
+ * always joinable against the page it describes. */
831
+ listProjectSummaries(opts: {
832
+ limit: number;
833
+ offset: number;
834
+ }): {
835
+ projects: Array<{
836
+ path: string;
837
+ name: string;
838
+ conversationCount: number;
839
+ lastActivity: string;
840
+ }>;
841
+ total: number;
842
+ };
827
843
  private ensureFileIndex;
828
844
  updateFromLine(filePath: string, rawLine: string): void;
829
845
  /**
@@ -1689,6 +1705,7 @@ type ApiDeps = {
1689
1705
  handleSearchTarget: (id: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
1690
1706
  handleListProjects: (url: URL, res: ServerResponse) => void;
1691
1707
  handleGetPopularProjects: (url: URL, res: ServerResponse) => void;
1708
+ handleGetProjectSummaries: (url: URL, res: ServerResponse) => void;
1692
1709
  handlePairStart: (res: ServerResponse) => void;
1693
1710
  handlePairExchange: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
1694
1711
  handleBrowse: (url: URL, res: ServerResponse) => Promise<void>;
@@ -2119,6 +2136,7 @@ declare class StreamerServer {
2119
2136
  private handleSessionsCount;
2120
2137
  private handleGetRecentSessions;
2121
2138
  private handleGetPopularProjects;
2139
+ private handleGetProjectSummaries;
2122
2140
  private buildStatCache;
2123
2141
  private codexScanOpts;
2124
2142
  private newScanner;
package/dist/index.d.ts CHANGED
@@ -824,6 +824,22 @@ declare class ConversationCache {
824
824
  name: string;
825
825
  sessionCount: number;
826
826
  }>;
827
+ /** Every project with at least one cached conversation, most recently active
828
+ * first. Paths are the raw `project_path` values, which is what
829
+ * /api/conversations?project= matches on exactly — so a summary row is
830
+ * always joinable against the page it describes. */
831
+ listProjectSummaries(opts: {
832
+ limit: number;
833
+ offset: number;
834
+ }): {
835
+ projects: Array<{
836
+ path: string;
837
+ name: string;
838
+ conversationCount: number;
839
+ lastActivity: string;
840
+ }>;
841
+ total: number;
842
+ };
827
843
  private ensureFileIndex;
828
844
  updateFromLine(filePath: string, rawLine: string): void;
829
845
  /**
@@ -1689,6 +1705,7 @@ type ApiDeps = {
1689
1705
  handleSearchTarget: (id: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
1690
1706
  handleListProjects: (url: URL, res: ServerResponse) => void;
1691
1707
  handleGetPopularProjects: (url: URL, res: ServerResponse) => void;
1708
+ handleGetProjectSummaries: (url: URL, res: ServerResponse) => void;
1692
1709
  handlePairStart: (res: ServerResponse) => void;
1693
1710
  handlePairExchange: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
1694
1711
  handleBrowse: (url: URL, res: ServerResponse) => Promise<void>;
@@ -2119,6 +2136,7 @@ declare class StreamerServer {
2119
2136
  private handleSessionsCount;
2120
2137
  private handleGetRecentSessions;
2121
2138
  private handleGetPopularProjects;
2139
+ private handleGetProjectSummaries;
2122
2140
  private buildStatCache;
2123
2141
  private codexScanOpts;
2124
2142
  private newScanner;
package/dist/index.js CHANGED
@@ -5306,7 +5306,10 @@ var createMiscRoutes = (deps) => {
5306
5306
  // Same contract: this server serves GET /api/config/feature-flags. Lives
5307
5307
  // here rather than behind /api/config (admin-only) so a read-only client
5308
5308
  // still learns the server supports flags even if it can't read values.
5309
- featureFlags: true
5309
+ featureFlags: true,
5310
+ // Same contract: this server serves GET /api/projects/summary, which the
5311
+ // Hub's grouped views need before they can draw a tree.
5312
+ projectSummary: true
5310
5313
  });
5311
5314
  });
5312
5315
  app.get("/api/profiles", (c) => c.json([]));
@@ -5453,6 +5456,11 @@ var createProjectRoutes = (deps) => {
5453
5456
  deps.handleGetPopularProjects(url, c.env.outgoing);
5454
5457
  return alreadyHandled4();
5455
5458
  });
5459
+ app.get("/summary", (c) => {
5460
+ const url = new URL(c.req.url);
5461
+ deps.handleGetProjectSummaries(url, c.env.outgoing);
5462
+ return alreadyHandled4();
5463
+ });
5456
5464
  return app;
5457
5465
  };
5458
5466
 
@@ -6320,6 +6328,21 @@ var ConversationCache = class _ConversationCache {
6320
6328
  ORDER BY cnt DESC
6321
6329
  LIMIT ?`
6322
6330
  ),
6331
+ // Same table, same rows and same NULL filter /api/conversations lists
6332
+ // from, so a group's count/last-activity can never disagree with the
6333
+ // page it opens. Bare project_name is the one from the MAX(last_activity)
6334
+ // row (SQLite's documented min/max-aggregate bare-column rule).
6335
+ projectSummaries: db.prepare(
6336
+ `SELECT project_path, project_name, COUNT(*) as cnt, MAX(last_activity) as latest
6337
+ FROM conversation_meta
6338
+ WHERE project_path IS NOT NULL
6339
+ GROUP BY project_path
6340
+ ORDER BY latest DESC, project_path ASC
6341
+ LIMIT ? OFFSET ?`
6342
+ ),
6343
+ projectSummaryCount: db.prepare(
6344
+ "SELECT COUNT(DISTINCT project_path) as n FROM conversation_meta WHERE project_path IS NOT NULL"
6345
+ ),
6323
6346
  getFileState: db.prepare("SELECT * FROM conversation_file_state WHERE path = ?"),
6324
6347
  upsertFileState: db.prepare(
6325
6348
  `INSERT INTO conversation_file_state
@@ -6695,6 +6718,23 @@ var ConversationCache = class _ConversationCache {
6695
6718
  sessionCount: r.cnt
6696
6719
  }));
6697
6720
  }
6721
+ /** Every project with at least one cached conversation, most recently active
6722
+ * first. Paths are the raw `project_path` values, which is what
6723
+ * /api/conversations?project= matches on exactly — so a summary row is
6724
+ * always joinable against the page it describes. */
6725
+ listProjectSummaries(opts) {
6726
+ const total = this.stmts.projectSummaryCount.get().n;
6727
+ const rows = opts.limit === 0 ? [] : this.stmts.projectSummaries.all(opts.limit, opts.offset);
6728
+ return {
6729
+ total,
6730
+ projects: rows.map((r) => ({
6731
+ path: r.project_path,
6732
+ name: r.project_name ?? r.project_path.split(/[/\\]/).pop() ?? r.project_path,
6733
+ conversationCount: r.cnt,
6734
+ lastActivity: new Date(r.latest ?? 0).toISOString()
6735
+ }))
6736
+ };
6737
+ }
6698
6738
  ensureFileIndex() {
6699
6739
  if (this.fileIndexLoaded) return;
6700
6740
  const rows = this.stmts.allFilePaths.all();
@@ -10836,6 +10876,7 @@ var StreamerServer = class {
10836
10876
  handleSearchTarget: (id, req, res) => this.handleSearchTarget(id, req, res),
10837
10877
  handleListProjects: (url, res) => handleListProjects(url, res),
10838
10878
  handleGetPopularProjects: (url, res) => this.handleGetPopularProjects(url, res),
10879
+ handleGetProjectSummaries: (url, res) => this.handleGetProjectSummaries(url, res),
10839
10880
  handlePairStart: (res) => this.handlePairStart(res),
10840
10881
  handlePairExchange: (req, res) => this.handlePairExchange(req, res),
10841
10882
  handleBrowse: (url, res) => this.handleBrowse(url, res),
@@ -12401,6 +12442,20 @@ var StreamerServer = class {
12401
12442
  const projects = this.cache.getPopularProjects(limit);
12402
12443
  json(res, 200, { projects, total: projects.length });
12403
12444
  }
12445
+ handleGetProjectSummaries(url, res) {
12446
+ if (this.rejectIfWarmingUp(res)) return;
12447
+ const limit = intParam(url, "limit", 200);
12448
+ const offset = intParam(url, "offset", 0);
12449
+ if (!this.cache) {
12450
+ json(res, 503, {
12451
+ error: "Conversation cache unavailable",
12452
+ code: "CACHE_UNAVAILABLE"
12453
+ });
12454
+ return;
12455
+ }
12456
+ const { projects, total } = this.cache.listProjectSummaries({ limit, offset });
12457
+ json(res, 200, { projects, total, offset, hasMore: offset + projects.length < total });
12458
+ }
12404
12459
  buildStatCache(previousScanner) {
12405
12460
  if (!this.cache) return void 0;
12406
12461
  if (!previousScanner) {