chat-logbook 0.13.0 → 0.14.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/api/dist/index.js CHANGED
@@ -5,9 +5,9 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/index.ts
8
- import fs6 from "fs";
8
+ import fs8 from "fs";
9
9
  import os from "os";
10
- import path4 from "path";
10
+ import path7 from "path";
11
11
  import { exec } from "child_process";
12
12
  import { fileURLToPath as fileURLToPath2 } from "url";
13
13
 
@@ -304,13 +304,13 @@ function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromi
304
304
  }
305
305
  }
306
306
  }
307
- function writeFromReadableStream(stream, writable) {
308
- if (stream.locked) {
307
+ function writeFromReadableStream(stream2, writable) {
308
+ if (stream2.locked) {
309
309
  throw new TypeError("ReadableStream is locked.");
310
310
  } else if (writable.destroyed) {
311
311
  return;
312
312
  }
313
- return writeFromReadableStreamDefaultReader(stream.getReader(), writable);
313
+ return writeFromReadableStreamDefaultReader(stream2.getReader(), writable);
314
314
  }
315
315
  var buildOutgoingHttpHeaders = (headers) => {
316
316
  const res = {};
@@ -705,26 +705,26 @@ var handleParsingNestedValues = (form, key, value) => {
705
705
  };
706
706
 
707
707
  // ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/utils/url.js
708
- var splitPath = (path5) => {
709
- const paths = path5.split("/");
708
+ var splitPath = (path8) => {
709
+ const paths = path8.split("/");
710
710
  if (paths[0] === "") {
711
711
  paths.shift();
712
712
  }
713
713
  return paths;
714
714
  };
715
715
  var splitRoutingPath = (routePath) => {
716
- const { groups, path: path5 } = extractGroupsFromPath(routePath);
717
- const paths = splitPath(path5);
716
+ const { groups, path: path8 } = extractGroupsFromPath(routePath);
717
+ const paths = splitPath(path8);
718
718
  return replaceGroupMarks(paths, groups);
719
719
  };
720
- var extractGroupsFromPath = (path5) => {
720
+ var extractGroupsFromPath = (path8) => {
721
721
  const groups = [];
722
- path5 = path5.replace(/\{[^}]+\}/g, (match2, index2) => {
722
+ path8 = path8.replace(/\{[^}]+\}/g, (match2, index2) => {
723
723
  const mark = `@${index2}`;
724
724
  groups.push([mark, match2]);
725
725
  return mark;
726
726
  });
727
- return { groups, path: path5 };
727
+ return { groups, path: path8 };
728
728
  };
729
729
  var replaceGroupMarks = (paths, groups) => {
730
730
  for (let i = groups.length - 1; i >= 0; i--) {
@@ -781,8 +781,8 @@ var getPath = (request) => {
781
781
  const queryIndex = url.indexOf("?", i);
782
782
  const hashIndex = url.indexOf("#", i);
783
783
  const end = queryIndex === -1 ? hashIndex === -1 ? void 0 : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
784
- const path5 = url.slice(start, end);
785
- return tryDecodeURI(path5.includes("%25") ? path5.replace(/%25/g, "%2525") : path5);
784
+ const path8 = url.slice(start, end);
785
+ return tryDecodeURI(path8.includes("%25") ? path8.replace(/%25/g, "%2525") : path8);
786
786
  } else if (charCode === 63 || charCode === 35) {
787
787
  break;
788
788
  }
@@ -799,11 +799,11 @@ var mergePath = (base, sub, ...rest) => {
799
799
  }
800
800
  return `${base?.[0] === "/" ? "" : "/"}${base}${sub === "/" ? "" : `${base?.at(-1) === "/" ? "" : "/"}${sub?.[0] === "/" ? sub.slice(1) : sub}`}`;
801
801
  };
802
- var checkOptionalParameter = (path5) => {
803
- if (path5.charCodeAt(path5.length - 1) !== 63 || !path5.includes(":")) {
802
+ var checkOptionalParameter = (path8) => {
803
+ if (path8.charCodeAt(path8.length - 1) !== 63 || !path8.includes(":")) {
804
804
  return null;
805
805
  }
806
- const segments = path5.split("/");
806
+ const segments = path8.split("/");
807
807
  const results = [];
808
808
  let basePath = "";
809
809
  segments.forEach((segment) => {
@@ -944,9 +944,9 @@ var HonoRequest = class {
944
944
  */
945
945
  path;
946
946
  bodyCache = {};
947
- constructor(request, path5 = "/", matchResult = [[]]) {
947
+ constructor(request, path8 = "/", matchResult = [[]]) {
948
948
  this.raw = request;
949
- this.path = path5;
949
+ this.path = path8;
950
950
  this.#matchResult = matchResult;
951
951
  this.#validatedData = {};
952
952
  }
@@ -1683,8 +1683,8 @@ var Hono = class _Hono {
1683
1683
  return this;
1684
1684
  };
1685
1685
  });
1686
- this.on = (method, path5, ...handlers) => {
1687
- for (const p of [path5].flat()) {
1686
+ this.on = (method, path8, ...handlers) => {
1687
+ for (const p of [path8].flat()) {
1688
1688
  this.#path = p;
1689
1689
  for (const m of [method].flat()) {
1690
1690
  handlers.map((handler) => {
@@ -1741,8 +1741,8 @@ var Hono = class _Hono {
1741
1741
  * app.route("/api", app2) // GET /api/user
1742
1742
  * ```
1743
1743
  */
1744
- route(path5, app2) {
1745
- const subApp = this.basePath(path5);
1744
+ route(path8, app2) {
1745
+ const subApp = this.basePath(path8);
1746
1746
  app2.routes.map((r) => {
1747
1747
  let handler;
1748
1748
  if (app2.errorHandler === errorHandler) {
@@ -1768,9 +1768,9 @@ var Hono = class _Hono {
1768
1768
  * const api = new Hono().basePath('/api')
1769
1769
  * ```
1770
1770
  */
1771
- basePath(path5) {
1771
+ basePath(path8) {
1772
1772
  const subApp = this.#clone();
1773
- subApp._basePath = mergePath(this._basePath, path5);
1773
+ subApp._basePath = mergePath(this._basePath, path8);
1774
1774
  return subApp;
1775
1775
  }
1776
1776
  /**
@@ -1844,7 +1844,7 @@ var Hono = class _Hono {
1844
1844
  * })
1845
1845
  * ```
1846
1846
  */
1847
- mount(path5, applicationHandler, options) {
1847
+ mount(path8, applicationHandler, options) {
1848
1848
  let replaceRequest;
1849
1849
  let optionHandler;
1850
1850
  if (options) {
@@ -1871,7 +1871,7 @@ var Hono = class _Hono {
1871
1871
  return [c.env, executionContext];
1872
1872
  };
1873
1873
  replaceRequest ||= (() => {
1874
- const mergedPath = mergePath(this._basePath, path5);
1874
+ const mergedPath = mergePath(this._basePath, path8);
1875
1875
  const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
1876
1876
  return (request) => {
1877
1877
  const url = new URL(request.url);
@@ -1886,14 +1886,14 @@ var Hono = class _Hono {
1886
1886
  }
1887
1887
  await next();
1888
1888
  };
1889
- this.#addRoute(METHOD_NAME_ALL, mergePath(path5, "*"), handler);
1889
+ this.#addRoute(METHOD_NAME_ALL, mergePath(path8, "*"), handler);
1890
1890
  return this;
1891
1891
  }
1892
- #addRoute(method, path5, handler) {
1892
+ #addRoute(method, path8, handler) {
1893
1893
  method = method.toUpperCase();
1894
- path5 = mergePath(this._basePath, path5);
1895
- const r = { basePath: this._basePath, path: path5, method, handler };
1896
- this.router.add(method, path5, [handler, r]);
1894
+ path8 = mergePath(this._basePath, path8);
1895
+ const r = { basePath: this._basePath, path: path8, method, handler };
1896
+ this.router.add(method, path8, [handler, r]);
1897
1897
  this.routes.push(r);
1898
1898
  }
1899
1899
  #handleError(err, c) {
@@ -1906,10 +1906,10 @@ var Hono = class _Hono {
1906
1906
  if (method === "HEAD") {
1907
1907
  return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
1908
1908
  }
1909
- const path5 = this.getPath(request, { env });
1910
- const matchResult = this.router.match(method, path5);
1909
+ const path8 = this.getPath(request, { env });
1910
+ const matchResult = this.router.match(method, path8);
1911
1911
  const c = new Context(request, {
1912
- path: path5,
1912
+ path: path8,
1913
1913
  matchResult,
1914
1914
  env,
1915
1915
  executionCtx,
@@ -2009,7 +2009,7 @@ var Hono = class _Hono {
2009
2009
 
2010
2010
  // ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/router/reg-exp-router/matcher.js
2011
2011
  var emptyParam = [];
2012
- function match(method, path5) {
2012
+ function match(method, path8) {
2013
2013
  const matchers = this.buildAllMatchers();
2014
2014
  const match2 = ((method2, path22) => {
2015
2015
  const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
@@ -2025,7 +2025,7 @@ function match(method, path5) {
2025
2025
  return [matcher[1][index2], match3];
2026
2026
  });
2027
2027
  this.match = match2;
2028
- return match2(method, path5);
2028
+ return match2(method, path8);
2029
2029
  }
2030
2030
 
2031
2031
  // ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/router/reg-exp-router/node.js
@@ -2140,12 +2140,12 @@ var Node = class _Node {
2140
2140
  var Trie = class {
2141
2141
  #context = { varIndex: 0 };
2142
2142
  #root = new Node();
2143
- insert(path5, index2, pathErrorCheckOnly) {
2143
+ insert(path8, index2, pathErrorCheckOnly) {
2144
2144
  const paramAssoc = [];
2145
2145
  const groups = [];
2146
2146
  for (let i = 0; ; ) {
2147
2147
  let replaced = false;
2148
- path5 = path5.replace(/\{[^}]+\}/g, (m) => {
2148
+ path8 = path8.replace(/\{[^}]+\}/g, (m) => {
2149
2149
  const mark = `@\\${i}`;
2150
2150
  groups[i] = [mark, m];
2151
2151
  i++;
@@ -2156,7 +2156,7 @@ var Trie = class {
2156
2156
  break;
2157
2157
  }
2158
2158
  }
2159
- const tokens = path5.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
2159
+ const tokens = path8.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
2160
2160
  for (let i = groups.length - 1; i >= 0; i--) {
2161
2161
  const [mark] = groups[i];
2162
2162
  for (let j = tokens.length - 1; j >= 0; j--) {
@@ -2195,9 +2195,9 @@ var Trie = class {
2195
2195
  // ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/router/reg-exp-router/router.js
2196
2196
  var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
2197
2197
  var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
2198
- function buildWildcardRegExp(path5) {
2199
- return wildcardRegExpCache[path5] ??= new RegExp(
2200
- path5 === "*" ? "" : `^${path5.replace(
2198
+ function buildWildcardRegExp(path8) {
2199
+ return wildcardRegExpCache[path8] ??= new RegExp(
2200
+ path8 === "*" ? "" : `^${path8.replace(
2201
2201
  /\/\*$|([.\\+*[^\]$()])/g,
2202
2202
  (_, metaChar) => metaChar ? `\\${metaChar}` : "(?:|/.*)"
2203
2203
  )}$`
@@ -2219,17 +2219,17 @@ function buildMatcherFromPreprocessedRoutes(routes) {
2219
2219
  );
2220
2220
  const staticMap = /* @__PURE__ */ Object.create(null);
2221
2221
  for (let i = 0, j = -1, len = routesWithStaticPathFlag.length; i < len; i++) {
2222
- const [pathErrorCheckOnly, path5, handlers] = routesWithStaticPathFlag[i];
2222
+ const [pathErrorCheckOnly, path8, handlers] = routesWithStaticPathFlag[i];
2223
2223
  if (pathErrorCheckOnly) {
2224
- staticMap[path5] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
2224
+ staticMap[path8] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
2225
2225
  } else {
2226
2226
  j++;
2227
2227
  }
2228
2228
  let paramAssoc;
2229
2229
  try {
2230
- paramAssoc = trie.insert(path5, j, pathErrorCheckOnly);
2230
+ paramAssoc = trie.insert(path8, j, pathErrorCheckOnly);
2231
2231
  } catch (e) {
2232
- throw e === PATH_ERROR ? new UnsupportedPathError(path5) : e;
2232
+ throw e === PATH_ERROR ? new UnsupportedPathError(path8) : e;
2233
2233
  }
2234
2234
  if (pathErrorCheckOnly) {
2235
2235
  continue;
@@ -2263,12 +2263,12 @@ function buildMatcherFromPreprocessedRoutes(routes) {
2263
2263
  }
2264
2264
  return [regexp, handlerMap, staticMap];
2265
2265
  }
2266
- function findMiddleware(middleware, path5) {
2266
+ function findMiddleware(middleware, path8) {
2267
2267
  if (!middleware) {
2268
2268
  return void 0;
2269
2269
  }
2270
2270
  for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) {
2271
- if (buildWildcardRegExp(k).test(path5)) {
2271
+ if (buildWildcardRegExp(k).test(path8)) {
2272
2272
  return [...middleware[k]];
2273
2273
  }
2274
2274
  }
@@ -2282,7 +2282,7 @@ var RegExpRouter = class {
2282
2282
  this.#middleware = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
2283
2283
  this.#routes = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
2284
2284
  }
2285
- add(method, path5, handler) {
2285
+ add(method, path8, handler) {
2286
2286
  const middleware = this.#middleware;
2287
2287
  const routes = this.#routes;
2288
2288
  if (!middleware || !routes) {
@@ -2297,18 +2297,18 @@ var RegExpRouter = class {
2297
2297
  });
2298
2298
  });
2299
2299
  }
2300
- if (path5 === "/*") {
2301
- path5 = "*";
2300
+ if (path8 === "/*") {
2301
+ path8 = "*";
2302
2302
  }
2303
- const paramCount = (path5.match(/\/:/g) || []).length;
2304
- if (/\*$/.test(path5)) {
2305
- const re = buildWildcardRegExp(path5);
2303
+ const paramCount = (path8.match(/\/:/g) || []).length;
2304
+ if (/\*$/.test(path8)) {
2305
+ const re = buildWildcardRegExp(path8);
2306
2306
  if (method === METHOD_NAME_ALL) {
2307
2307
  Object.keys(middleware).forEach((m) => {
2308
- middleware[m][path5] ||= findMiddleware(middleware[m], path5) || findMiddleware(middleware[METHOD_NAME_ALL], path5) || [];
2308
+ middleware[m][path8] ||= findMiddleware(middleware[m], path8) || findMiddleware(middleware[METHOD_NAME_ALL], path8) || [];
2309
2309
  });
2310
2310
  } else {
2311
- middleware[method][path5] ||= findMiddleware(middleware[method], path5) || findMiddleware(middleware[METHOD_NAME_ALL], path5) || [];
2311
+ middleware[method][path8] ||= findMiddleware(middleware[method], path8) || findMiddleware(middleware[METHOD_NAME_ALL], path8) || [];
2312
2312
  }
2313
2313
  Object.keys(middleware).forEach((m) => {
2314
2314
  if (method === METHOD_NAME_ALL || method === m) {
@@ -2326,7 +2326,7 @@ var RegExpRouter = class {
2326
2326
  });
2327
2327
  return;
2328
2328
  }
2329
- const paths = checkOptionalParameter(path5) || [path5];
2329
+ const paths = checkOptionalParameter(path8) || [path8];
2330
2330
  for (let i = 0, len = paths.length; i < len; i++) {
2331
2331
  const path22 = paths[i];
2332
2332
  Object.keys(routes).forEach((m) => {
@@ -2353,13 +2353,13 @@ var RegExpRouter = class {
2353
2353
  const routes = [];
2354
2354
  let hasOwnRoute = method === METHOD_NAME_ALL;
2355
2355
  [this.#middleware, this.#routes].forEach((r) => {
2356
- const ownRoute = r[method] ? Object.keys(r[method]).map((path5) => [path5, r[method][path5]]) : [];
2356
+ const ownRoute = r[method] ? Object.keys(r[method]).map((path8) => [path8, r[method][path8]]) : [];
2357
2357
  if (ownRoute.length !== 0) {
2358
2358
  hasOwnRoute ||= true;
2359
2359
  routes.push(...ownRoute);
2360
2360
  } else if (method !== METHOD_NAME_ALL) {
2361
2361
  routes.push(
2362
- ...Object.keys(r[METHOD_NAME_ALL]).map((path5) => [path5, r[METHOD_NAME_ALL][path5]])
2362
+ ...Object.keys(r[METHOD_NAME_ALL]).map((path8) => [path8, r[METHOD_NAME_ALL][path8]])
2363
2363
  );
2364
2364
  }
2365
2365
  });
@@ -2379,13 +2379,13 @@ var SmartRouter = class {
2379
2379
  constructor(init) {
2380
2380
  this.#routers = init.routers;
2381
2381
  }
2382
- add(method, path5, handler) {
2382
+ add(method, path8, handler) {
2383
2383
  if (!this.#routes) {
2384
2384
  throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
2385
2385
  }
2386
- this.#routes.push([method, path5, handler]);
2386
+ this.#routes.push([method, path8, handler]);
2387
2387
  }
2388
- match(method, path5) {
2388
+ match(method, path8) {
2389
2389
  if (!this.#routes) {
2390
2390
  throw new Error("Fatal error");
2391
2391
  }
@@ -2400,7 +2400,7 @@ var SmartRouter = class {
2400
2400
  for (let i2 = 0, len2 = routes.length; i2 < len2; i2++) {
2401
2401
  router.add(...routes[i2]);
2402
2402
  }
2403
- res = router.match(method, path5);
2403
+ res = router.match(method, path8);
2404
2404
  } catch (e) {
2405
2405
  if (e instanceof UnsupportedPathError) {
2406
2406
  continue;
@@ -2450,10 +2450,10 @@ var Node2 = class _Node2 {
2450
2450
  }
2451
2451
  this.#patterns = [];
2452
2452
  }
2453
- insert(method, path5, handler) {
2453
+ insert(method, path8, handler) {
2454
2454
  this.#order = ++this.#order;
2455
2455
  let curNode = this;
2456
- const parts = splitRoutingPath(path5);
2456
+ const parts = splitRoutingPath(path8);
2457
2457
  const possibleKeys = [];
2458
2458
  for (let i = 0, len = parts.length; i < len; i++) {
2459
2459
  const p = parts[i];
@@ -2502,12 +2502,12 @@ var Node2 = class _Node2 {
2502
2502
  }
2503
2503
  }
2504
2504
  }
2505
- search(method, path5) {
2505
+ search(method, path8) {
2506
2506
  const handlerSets = [];
2507
2507
  this.#params = emptyParams;
2508
2508
  const curNode = this;
2509
2509
  let curNodes = [curNode];
2510
- const parts = splitPath(path5);
2510
+ const parts = splitPath(path8);
2511
2511
  const curNodesQueue = [];
2512
2512
  const len = parts.length;
2513
2513
  let partOffsets = null;
@@ -2549,13 +2549,13 @@ var Node2 = class _Node2 {
2549
2549
  if (matcher instanceof RegExp) {
2550
2550
  if (partOffsets === null) {
2551
2551
  partOffsets = new Array(len);
2552
- let offset = path5[0] === "/" ? 1 : 0;
2552
+ let offset = path8[0] === "/" ? 1 : 0;
2553
2553
  for (let p = 0; p < len; p++) {
2554
2554
  partOffsets[p] = offset;
2555
2555
  offset += parts[p].length + 1;
2556
2556
  }
2557
2557
  }
2558
- const restPathString = path5.substring(partOffsets[i]);
2558
+ const restPathString = path8.substring(partOffsets[i]);
2559
2559
  const m = matcher.exec(restPathString);
2560
2560
  if (m) {
2561
2561
  params[name] = m[0];
@@ -2608,18 +2608,18 @@ var TrieRouter = class {
2608
2608
  constructor() {
2609
2609
  this.#node = new Node2();
2610
2610
  }
2611
- add(method, path5, handler) {
2612
- const results = checkOptionalParameter(path5);
2611
+ add(method, path8, handler) {
2612
+ const results = checkOptionalParameter(path8);
2613
2613
  if (results) {
2614
2614
  for (let i = 0, len = results.length; i < len; i++) {
2615
2615
  this.#node.insert(method, results[i], handler);
2616
2616
  }
2617
2617
  return;
2618
2618
  }
2619
- this.#node.insert(method, path5, handler);
2619
+ this.#node.insert(method, path8, handler);
2620
2620
  }
2621
- match(method, path5) {
2622
- return this.#node.search(method, path5);
2621
+ match(method, path8) {
2622
+ return this.#node.search(method, path8);
2623
2623
  }
2624
2624
  };
2625
2625
 
@@ -2638,6 +2638,155 @@ var Hono2 = class extends Hono {
2638
2638
  }
2639
2639
  };
2640
2640
 
2641
+ // ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/utils/stream.js
2642
+ var StreamingApi = class {
2643
+ writer;
2644
+ encoder;
2645
+ writable;
2646
+ abortSubscribers = [];
2647
+ responseReadable;
2648
+ /**
2649
+ * Whether the stream has been aborted.
2650
+ */
2651
+ aborted = false;
2652
+ /**
2653
+ * Whether the stream has been closed normally.
2654
+ */
2655
+ closed = false;
2656
+ constructor(writable, _readable) {
2657
+ this.writable = writable;
2658
+ this.writer = writable.getWriter();
2659
+ this.encoder = new TextEncoder();
2660
+ const reader = _readable.getReader();
2661
+ this.abortSubscribers.push(async () => {
2662
+ await reader.cancel();
2663
+ });
2664
+ this.responseReadable = new ReadableStream({
2665
+ async pull(controller) {
2666
+ const { done, value } = await reader.read();
2667
+ done ? controller.close() : controller.enqueue(value);
2668
+ },
2669
+ cancel: () => {
2670
+ this.abort();
2671
+ }
2672
+ });
2673
+ }
2674
+ async write(input) {
2675
+ try {
2676
+ if (typeof input === "string") {
2677
+ input = this.encoder.encode(input);
2678
+ }
2679
+ await this.writer.write(input);
2680
+ } catch {
2681
+ }
2682
+ return this;
2683
+ }
2684
+ async writeln(input) {
2685
+ await this.write(input + "\n");
2686
+ return this;
2687
+ }
2688
+ sleep(ms) {
2689
+ return new Promise((res) => setTimeout(res, ms));
2690
+ }
2691
+ async close() {
2692
+ try {
2693
+ await this.writer.close();
2694
+ } catch {
2695
+ }
2696
+ this.closed = true;
2697
+ }
2698
+ async pipe(body) {
2699
+ this.writer.releaseLock();
2700
+ await body.pipeTo(this.writable, { preventClose: true });
2701
+ this.writer = this.writable.getWriter();
2702
+ }
2703
+ onAbort(listener) {
2704
+ this.abortSubscribers.push(listener);
2705
+ }
2706
+ /**
2707
+ * Abort the stream.
2708
+ * You can call this method when stream is aborted by external event.
2709
+ */
2710
+ abort() {
2711
+ if (!this.aborted) {
2712
+ this.aborted = true;
2713
+ this.abortSubscribers.forEach((subscriber) => subscriber());
2714
+ }
2715
+ }
2716
+ };
2717
+
2718
+ // ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/helper/streaming/utils.js
2719
+ var isOldBunVersion = () => {
2720
+ const version2 = typeof Bun !== "undefined" ? Bun.version : void 0;
2721
+ if (version2 === void 0) {
2722
+ return false;
2723
+ }
2724
+ const result = version2.startsWith("1.1") || version2.startsWith("1.0") || version2.startsWith("0.");
2725
+ isOldBunVersion = () => result;
2726
+ return result;
2727
+ };
2728
+
2729
+ // ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/helper/streaming/sse.js
2730
+ var SSEStreamingApi = class extends StreamingApi {
2731
+ constructor(writable, readable) {
2732
+ super(writable, readable);
2733
+ }
2734
+ async writeSSE(message) {
2735
+ const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {});
2736
+ const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
2737
+ return `data: ${line}`;
2738
+ }).join("\n");
2739
+ for (const key of ["event", "id", "retry"]) {
2740
+ if (message[key] && /[\r\n]/.test(message[key])) {
2741
+ throw new Error(`${key} must not contain "\\r" or "\\n"`);
2742
+ }
2743
+ }
2744
+ const sseData = [
2745
+ message.event && `event: ${message.event}`,
2746
+ dataLines,
2747
+ message.id && `id: ${message.id}`,
2748
+ message.retry && `retry: ${message.retry}`
2749
+ ].filter(Boolean).join("\n") + "\n\n";
2750
+ await this.write(sseData);
2751
+ }
2752
+ };
2753
+ var run = async (stream2, cb, onError) => {
2754
+ try {
2755
+ await cb(stream2);
2756
+ } catch (e) {
2757
+ if (e instanceof Error && onError) {
2758
+ await onError(e, stream2);
2759
+ await stream2.writeSSE({
2760
+ event: "error",
2761
+ data: e.message
2762
+ });
2763
+ } else {
2764
+ console.error(e);
2765
+ }
2766
+ } finally {
2767
+ stream2.close();
2768
+ }
2769
+ };
2770
+ var contextStash = /* @__PURE__ */ new WeakMap();
2771
+ var streamSSE = (c, cb, onError) => {
2772
+ const { readable, writable } = new TransformStream();
2773
+ const stream2 = new SSEStreamingApi(writable, readable);
2774
+ if (isOldBunVersion()) {
2775
+ c.req.raw.signal.addEventListener("abort", () => {
2776
+ if (!stream2.closed) {
2777
+ stream2.abort();
2778
+ }
2779
+ });
2780
+ }
2781
+ contextStash.set(stream2.responseReadable, c);
2782
+ c.header("Transfer-Encoding", "chunked");
2783
+ c.header("Content-Type", "text/event-stream");
2784
+ c.header("Cache-Control", "no-cache");
2785
+ c.header("Connection", "keep-alive");
2786
+ run(stream2, cb, onError);
2787
+ return c.newResponse(stream2.responseReadable);
2788
+ };
2789
+
2641
2790
  // ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/utils/mime.js
2642
2791
  var getMimeType = (filename, mimes = baseMimes) => {
2643
2792
  const regexp = /\.([a-zA-Z0-9]+?)$/;
@@ -2728,32 +2877,32 @@ var pr54206Applied = () => {
2728
2877
  return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
2729
2878
  };
2730
2879
  var useReadableToWeb = pr54206Applied();
2731
- var createStreamBody = (stream) => {
2880
+ var createStreamBody = (stream2) => {
2732
2881
  if (useReadableToWeb) {
2733
- return Readable2.toWeb(stream);
2882
+ return Readable2.toWeb(stream2);
2734
2883
  }
2735
2884
  const body = new ReadableStream({
2736
2885
  start(controller) {
2737
- stream.on("data", (chunk) => {
2886
+ stream2.on("data", (chunk) => {
2738
2887
  controller.enqueue(chunk);
2739
2888
  });
2740
- stream.on("error", (err) => {
2889
+ stream2.on("error", (err) => {
2741
2890
  controller.error(err);
2742
2891
  });
2743
- stream.on("end", () => {
2892
+ stream2.on("end", () => {
2744
2893
  controller.close();
2745
2894
  });
2746
2895
  },
2747
2896
  cancel() {
2748
- stream.destroy();
2897
+ stream2.destroy();
2749
2898
  }
2750
2899
  });
2751
2900
  return body;
2752
2901
  };
2753
- var getStats = (path5) => {
2902
+ var getStats = (path8) => {
2754
2903
  let stats;
2755
2904
  try {
2756
- stats = statSync(path5);
2905
+ stats = statSync(path8);
2757
2906
  } catch {
2758
2907
  }
2759
2908
  return stats;
@@ -2796,21 +2945,21 @@ var serveStatic = (options = { root: "" }) => {
2796
2945
  return next();
2797
2946
  }
2798
2947
  }
2799
- let path5 = join(
2948
+ let path8 = join(
2800
2949
  root,
2801
2950
  !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename
2802
2951
  );
2803
- let stats = getStats(path5);
2952
+ let stats = getStats(path8);
2804
2953
  if (stats && stats.isDirectory()) {
2805
2954
  const indexFile = options.index ?? "index.html";
2806
- path5 = join(path5, indexFile);
2807
- stats = getStats(path5);
2955
+ path8 = join(path8, indexFile);
2956
+ stats = getStats(path8);
2808
2957
  }
2809
2958
  if (!stats) {
2810
- await options.onNotFound?.(path5, c);
2959
+ await options.onNotFound?.(path8, c);
2811
2960
  return next();
2812
2961
  }
2813
- const mimeType = getMimeType(path5);
2962
+ const mimeType = getMimeType(path8);
2814
2963
  c.header("Content-Type", mimeType || "application/octet-stream");
2815
2964
  if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
2816
2965
  const acceptEncodingSet = new Set(
@@ -2820,12 +2969,12 @@ var serveStatic = (options = { root: "" }) => {
2820
2969
  if (!acceptEncodingSet.has(encoding)) {
2821
2970
  continue;
2822
2971
  }
2823
- const precompressedStats = getStats(path5 + ENCODINGS[encoding]);
2972
+ const precompressedStats = getStats(path8 + ENCODINGS[encoding]);
2824
2973
  if (precompressedStats) {
2825
2974
  c.header("Content-Encoding", encoding);
2826
2975
  c.header("Vary", "Accept-Encoding", { append: true });
2827
2976
  stats = precompressedStats;
2828
- path5 = path5 + ENCODINGS[encoding];
2977
+ path8 = path8 + ENCODINGS[encoding];
2829
2978
  break;
2830
2979
  }
2831
2980
  }
@@ -2839,7 +2988,7 @@ var serveStatic = (options = { root: "" }) => {
2839
2988
  result = c.body(null);
2840
2989
  } else if (!range) {
2841
2990
  c.header("Content-Length", size.toString());
2842
- result = c.body(createStreamBody(createReadStream(path5)), 200);
2991
+ result = c.body(createStreamBody(createReadStream(path8)), 200);
2843
2992
  } else {
2844
2993
  c.header("Accept-Ranges", "bytes");
2845
2994
  c.header("Date", stats.birthtime.toUTCString());
@@ -2850,12 +2999,12 @@ var serveStatic = (options = { root: "" }) => {
2850
2999
  end = size - 1;
2851
3000
  }
2852
3001
  const chunksize = end - start + 1;
2853
- const stream = createReadStream(path5, { start, end });
3002
+ const stream2 = createReadStream(path8, { start, end });
2854
3003
  c.header("Content-Length", chunksize.toString());
2855
3004
  c.header("Content-Range", `bytes ${start}-${end}/${stats.size}`);
2856
- result = c.body(createStreamBody(stream), 206);
3005
+ result = c.body(createStreamBody(stream2), 206);
2857
3006
  }
2858
- await options.onFound?.(path5, c);
3007
+ await options.onFound?.(path8, c);
2859
3008
  return result;
2860
3009
  };
2861
3010
  };
@@ -2924,6 +3073,154 @@ function loadChatVisibility(metadata2, opts) {
2924
3073
  };
2925
3074
  }
2926
3075
 
3076
+ // src/title.ts
3077
+ function deriveBaseTitle(firstUserText) {
3078
+ const text2 = firstUserText?.trim().split("\n")[0]?.trim();
3079
+ return text2 && text2.length > 0 ? text2 : "Untitled";
3080
+ }
3081
+
3082
+ // src/list-pagination.ts
3083
+ import fs from "fs";
3084
+ import path from "path";
3085
+ import Database from "better-sqlite3";
3086
+
3087
+ // src/list-filter.ts
3088
+ function buildFilterClauses({
3089
+ projects,
3090
+ tags: tags3,
3091
+ hasMetadata
3092
+ }) {
3093
+ const clauses = [];
3094
+ const params = [];
3095
+ if (projects && projects.length > 0) {
3096
+ const placeholders = projects.map(() => "?").join(", ");
3097
+ clauses.push(`coalesce(c.project, '') IN (${placeholders})`);
3098
+ params.push(...projects);
3099
+ }
3100
+ if (tags3 && tags3.length > 0) {
3101
+ const realTagIds = tags3.filter((t) => t !== "");
3102
+ const wantUntagged = tags3.includes("");
3103
+ if (realTagIds.length > 0) {
3104
+ if (!hasMetadata) {
3105
+ clauses.push("0");
3106
+ } else {
3107
+ const placeholders = realTagIds.map(() => "?").join(", ");
3108
+ clauses.push(
3109
+ `c.id IN (SELECT chat_id FROM meta.chat_tags
3110
+ WHERE tag_id IN (${placeholders})
3111
+ GROUP BY chat_id HAVING count(*) = ?)`
3112
+ );
3113
+ params.push(...realTagIds, realTagIds.length);
3114
+ }
3115
+ }
3116
+ if (wantUntagged && hasMetadata) {
3117
+ clauses.push("c.id NOT IN (SELECT chat_id FROM meta.chat_tags)");
3118
+ }
3119
+ }
3120
+ return { clauses, params };
3121
+ }
3122
+
3123
+ // src/list-pagination.ts
3124
+ var ARCHIVE_DB = "archive.db";
3125
+ var METADATA_DB = "metadata.db";
3126
+ var SORT_EXPR = {
3127
+ createdAt: "c.created_at",
3128
+ updatedAt: "c.updated_at",
3129
+ deletedAt: "m.deleted_at",
3130
+ // The Title axis orders by the precomputed collation key on the ATTACHed
3131
+ // `meta.chat_sort_keys` (alias `k`), reached through the INNER JOIN added
3132
+ // below (ADR-0019). BINARY compare over `sort_key` is an index range scan.
3133
+ title: "k.sort_key"
3134
+ };
3135
+ function encodeCursor(cursor) {
3136
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
3137
+ }
3138
+ function decodeCursor(token) {
3139
+ try {
3140
+ const parsed = JSON.parse(
3141
+ Buffer.from(token, "base64url").toString("utf8")
3142
+ );
3143
+ const sortKey = parsed?.sortKey;
3144
+ if (parsed && typeof parsed === "object" && (typeof sortKey === "number" || typeof sortKey === "string") && typeof parsed.id === "string") {
3145
+ return parsed;
3146
+ }
3147
+ return null;
3148
+ } catch {
3149
+ return null;
3150
+ }
3151
+ }
3152
+ function createChatPageQuery({
3153
+ dataDir: dataDir2
3154
+ }) {
3155
+ const archive2 = new Database(path.join(dataDir2, ARCHIVE_DB), {
3156
+ readonly: true
3157
+ });
3158
+ const metadataPath = path.join(dataDir2, METADATA_DB);
3159
+ const hasMetadata = fs.existsSync(metadataPath);
3160
+ if (hasMetadata) {
3161
+ archive2.prepare("ATTACH DATABASE ? AS meta").run(metadataPath);
3162
+ }
3163
+ function queryPage(query) {
3164
+ const sortExpr = SORT_EXPR[query.sort];
3165
+ const needsDeletedJoin = query.sort === "deletedAt";
3166
+ const needsSortKeyJoin = query.sort === "title";
3167
+ if ((needsDeletedJoin || needsSortKeyJoin) && !hasMetadata) {
3168
+ return { items: [], nextCursor: null };
3169
+ }
3170
+ let from = "chats c";
3171
+ if (needsDeletedJoin) from += " JOIN meta.chats_meta m ON m.id = c.id";
3172
+ if (needsSortKeyJoin) from += " JOIN meta.chat_sort_keys k ON k.id = c.id";
3173
+ const idCol = needsDeletedJoin ? "m.id" : needsSortKeyJoin ? "k.id" : "c.id";
3174
+ const clauses = [];
3175
+ const params = [];
3176
+ if (needsDeletedJoin) {
3177
+ clauses.push("m.deleted_at IS NOT NULL");
3178
+ } else if (query.trashedOnly) {
3179
+ if (!hasMetadata) return { items: [], nextCursor: null };
3180
+ clauses.push(
3181
+ "c.id IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)"
3182
+ );
3183
+ } else if (!query.includeTrashed && hasMetadata) {
3184
+ clauses.push(
3185
+ "c.id NOT IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)"
3186
+ );
3187
+ }
3188
+ const filter = buildFilterClauses({
3189
+ projects: query.projects,
3190
+ tags: query.tags,
3191
+ hasMetadata
3192
+ });
3193
+ clauses.push(...filter.clauses);
3194
+ params.push(...filter.params);
3195
+ const direction = query.direction ?? "desc";
3196
+ const cmp = direction === "asc" ? ">" : "<";
3197
+ const order = direction === "asc" ? "ASC" : "DESC";
3198
+ if (query.cursor) {
3199
+ clauses.push(
3200
+ `(${sortExpr} ${cmp} ? OR (${sortExpr} = ? AND ${idCol} ${cmp} ?))`
3201
+ );
3202
+ params.push(query.cursor.sortKey, query.cursor.sortKey, query.cursor.id);
3203
+ }
3204
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
3205
+ const rows = archive2.prepare(
3206
+ `SELECT c.id AS id, ${sortExpr} AS sortKey
3207
+ FROM ${from} ${where}
3208
+ ORDER BY ${sortExpr} ${order}, ${idCol} ${order}
3209
+ LIMIT ?`
3210
+ ).all(...params, query.limit + 1);
3211
+ const items = rows.slice(0, query.limit);
3212
+ const last = items[items.length - 1];
3213
+ const nextCursor = rows.length > query.limit && last ? { sortKey: last.sortKey, id: last.id } : null;
3214
+ return { items, nextCursor };
3215
+ }
3216
+ return {
3217
+ queryPage,
3218
+ close() {
3219
+ archive2.close();
3220
+ }
3221
+ };
3222
+ }
3223
+
2927
3224
  // src/chat-reader.ts
2928
3225
  function toApiBlock(block) {
2929
3226
  if (block.type === "tool_result") {
@@ -2938,7 +3235,9 @@ function toApiBlock(block) {
2938
3235
  function createChatReader({
2939
3236
  archive: archive2,
2940
3237
  metadata: metadata2,
2941
- tags: tags3
3238
+ tags: tags3,
3239
+ pageQuery: pageQuery2,
3240
+ countsQuery: countsQuery2
2942
3241
  }) {
2943
3242
  function findChat(id) {
2944
3243
  const code = parseChatId(id);
@@ -2947,8 +3246,7 @@ function createChatReader({
2947
3246
  }
2948
3247
  function deriveTitle(customTitle, firstUserText) {
2949
3248
  if (customTitle && customTitle.trim()) return customTitle;
2950
- const text2 = firstUserText?.trim().split("\n")[0]?.trim();
2951
- return text2 && text2.length > 0 ? text2 : "Untitled";
3249
+ return deriveBaseTitle(firstUserText);
2952
3250
  }
2953
3251
  function key(agent, sourceId) {
2954
3252
  return [agent, sourceId].join("\0");
@@ -2972,22 +3270,28 @@ function createChatReader({
2972
3270
  return true;
2973
3271
  };
2974
3272
  }
3273
+ const toResponse = loadHydration();
3274
+ const chats2 = [];
3275
+ for (const row of rows) {
3276
+ if (!visibility.isVisible(row.id)) continue;
3277
+ if (passesTagFilter && !passesTagFilter(row.id)) continue;
3278
+ chats2.push(toResponse(row, visibility));
3279
+ }
3280
+ return chats2;
3281
+ }
3282
+ function loadHydration(scope) {
2975
3283
  const tsRangeByKey = new Map(
2976
- archive2.read.listChatTsRanges().map((r) => [key(r.agent, r.sourceId), r])
3284
+ archive2.read.listChatTsRanges(scope).map((r) => [key(r.agent, r.sourceId), r])
2977
3285
  );
2978
3286
  const sourcePathByKey = new Map(
2979
- archive2.read.listLatestRawSourcePaths().map((r) => [key(r.agent, r.sourceId), r.sourcePath])
3287
+ archive2.read.listLatestRawSourcePaths(scope).map((r) => [key(r.agent, r.sourceId), r.sourcePath])
2980
3288
  );
2981
3289
  const firstUserTextByKey = new Map(
2982
- archive2.read.listFirstUserTexts().map((r) => [key(r.agent, r.sourceId), r.text])
3290
+ archive2.read.listFirstUserTexts(scope).map((r) => [key(r.agent, r.sourceId), r.text])
2983
3291
  );
2984
3292
  const customTitleById = metadata2.listCustomTitles();
2985
3293
  const tagsByChatId = tags3.listTagsByChat();
2986
- const chats2 = [];
2987
- for (const row of rows) {
2988
- if (!visibility.isVisible(row.id)) continue;
2989
- if (passesTagFilter && !passesTagFilter(row.id)) continue;
2990
- const isDeleted = visibility.isTrashed(row.id);
3294
+ return (row, visibility) => {
2991
3295
  const rowKey = key(row.agent, row.sourceId);
2992
3296
  const tsRange = tsRangeByKey.get(rowKey);
2993
3297
  const firstSeenAtMs = row.firstSeenAt.getTime();
@@ -3007,10 +3311,70 @@ function createChatReader({
3007
3311
  deletedAt: visibility.deletedAt(row.id),
3008
3312
  tags: tagsByChatId.get(row.id) ?? []
3009
3313
  };
3010
- if (isDeleted) chat.isDeleted = true;
3011
- chats2.push(chat);
3314
+ if (visibility.isTrashed(row.id)) chat.isDeleted = true;
3315
+ return chat;
3316
+ };
3317
+ }
3318
+ function listChatsPage({
3319
+ sort,
3320
+ direction,
3321
+ limit,
3322
+ cursor,
3323
+ includeTrashed = false,
3324
+ trashedOnly = false,
3325
+ projects,
3326
+ tags: tagSelection
3327
+ }) {
3328
+ if (!pageQuery2) {
3329
+ throw new Error("listChatsPage requires a pageQuery dependency");
3330
+ }
3331
+ const decoded = cursor ? decodeCursor(cursor) ?? void 0 : void 0;
3332
+ const page = pageQuery2.queryPage({
3333
+ sort,
3334
+ direction,
3335
+ limit,
3336
+ cursor: decoded,
3337
+ includeTrashed,
3338
+ trashedOnly,
3339
+ projects,
3340
+ tags: tagSelection
3341
+ });
3342
+ const visibility = loadChatVisibility(metadata2, {
3343
+ includeTrashed: includeTrashed || trashedOnly
3344
+ });
3345
+ const pageIds = page.items.map((item) => item.id);
3346
+ const rows = archive2.read.listChatRowsByIds(pageIds);
3347
+ const rowById = new Map(rows.map((r) => [r.id, r]));
3348
+ const toResponse = loadHydration({
3349
+ sourceIds: rows.map((r) => r.sourceId)
3350
+ });
3351
+ const chats2 = [];
3352
+ for (const item of page.items) {
3353
+ const row = rowById.get(item.id);
3354
+ if (row) chats2.push(toResponse(row, visibility));
3012
3355
  }
3013
- return chats2;
3356
+ return {
3357
+ chats: chats2,
3358
+ nextCursor: page.nextCursor ? encodeCursor(page.nextCursor) : null
3359
+ };
3360
+ }
3361
+ function listCounts({
3362
+ includeTrashed = false
3363
+ }) {
3364
+ if (!countsQuery2) {
3365
+ throw new Error("listCounts requires a countsQuery dependency");
3366
+ }
3367
+ return countsQuery2.queryCounts({ includeTrashed });
3368
+ }
3369
+ function listFilteredTotal({
3370
+ includeTrashed = false,
3371
+ projects,
3372
+ tags: tags4
3373
+ }) {
3374
+ if (!countsQuery2) {
3375
+ throw new Error("listFilteredTotal requires a countsQuery dependency");
3376
+ }
3377
+ return countsQuery2.queryFilteredTotal({ includeTrashed, projects, tags: tags4 });
3014
3378
  }
3015
3379
  function getMessages(id, { includeTrashed }) {
3016
3380
  const row = findChat(id);
@@ -3024,24 +3388,122 @@ function createChatReader({
3024
3388
  timestamp: m.ts.toISOString()
3025
3389
  }));
3026
3390
  }
3027
- return { listChats, getMessages, findChat };
3391
+ return {
3392
+ listChats,
3393
+ listChatsPage,
3394
+ listCounts,
3395
+ listFilteredTotal,
3396
+ getMessages,
3397
+ findChat
3398
+ };
3028
3399
  }
3029
3400
 
3401
+ // src/list-contract.ts
3402
+ var MAX_PAGE_LIMIT = 200;
3403
+
3030
3404
  // src/app.ts
3031
- function createApp({ archive: archive2, metadata: metadata2, tags: tags3, webDistDir: webDistDir2 }) {
3405
+ var STREAM_HEARTBEAT_MS = 25e3;
3406
+ function createApp({
3407
+ archive: archive2,
3408
+ metadata: metadata2,
3409
+ tags: tags3,
3410
+ pageQuery: pageQuery2,
3411
+ countsQuery: countsQuery2,
3412
+ listEvents: listEvents2,
3413
+ webDistDir: webDistDir2
3414
+ }) {
3032
3415
  const app2 = new Hono2();
3033
- const reader = createChatReader({ archive: archive2, metadata: metadata2, tags: tags3 });
3416
+ const reader = createChatReader({
3417
+ archive: archive2,
3418
+ metadata: metadata2,
3419
+ tags: tags3,
3420
+ pageQuery: pageQuery2,
3421
+ countsQuery: countsQuery2
3422
+ });
3034
3423
  app2.get("/api/chats", (c) => {
3424
+ const includeTrashed = c.req.query("includeTrashed") === "true";
3425
+ const limitParam = c.req.query("limit");
3426
+ if (limitParam !== void 0) {
3427
+ if (!pageQuery2) {
3428
+ return c.json({ error: "Pagination is not available" }, 501);
3429
+ }
3430
+ const limit = Number.parseInt(limitParam, 10);
3431
+ if (!Number.isInteger(limit) || limit <= 0 || limit > MAX_PAGE_LIMIT) {
3432
+ return c.json({ error: "Invalid limit" }, 400);
3433
+ }
3434
+ const sort = c.req.query("sort") ?? "updatedAt";
3435
+ if (sort !== "createdAt" && sort !== "updatedAt" && sort !== "deletedAt" && sort !== "title") {
3436
+ return c.json({ error: "Invalid sort" }, 400);
3437
+ }
3438
+ const direction = c.req.query("direction") ?? "desc";
3439
+ if (direction !== "asc" && direction !== "desc") {
3440
+ return c.json({ error: "Invalid direction" }, 400);
3441
+ }
3442
+ const trashedOnly = c.req.query("trashedOnly") === "true";
3443
+ const projects2 = c.req.queries("project");
3444
+ const tagsParam2 = c.req.query("tags");
3445
+ const tagSelection = tagsParam2 === void 0 ? void 0 : tagsParam2.split(",");
3446
+ const { chats: chats3, nextCursor } = reader.listChatsPage({
3447
+ sort,
3448
+ direction,
3449
+ limit,
3450
+ cursor: c.req.query("cursor"),
3451
+ includeTrashed,
3452
+ trashedOnly,
3453
+ projects: projects2,
3454
+ tags: tagSelection
3455
+ });
3456
+ return c.json({ chats: chats3, nextCursor });
3457
+ }
3035
3458
  const projects = c.req.queries("project");
3036
3459
  const tagsParam = c.req.query("tags");
3037
3460
  const tags4 = tagsParam === void 0 ? void 0 : tagsParam.split(",");
3038
3461
  const chats2 = reader.listChats({
3039
- includeTrashed: c.req.query("includeTrashed") === "true",
3462
+ includeTrashed,
3040
3463
  projects,
3041
3464
  tags: tags4
3042
3465
  });
3043
3466
  return c.json({ chats: chats2 });
3044
3467
  });
3468
+ app2.get("/api/chats/stream", (c) => {
3469
+ if (!listEvents2) {
3470
+ return c.json({ error: "Live updates are not available" }, 501);
3471
+ }
3472
+ const hub = listEvents2;
3473
+ return streamSSE(c, async (stream2) => {
3474
+ const unsubscribe = hub.subscribe((event) => {
3475
+ void stream2.writeSSE({ event: event.type, data: "" });
3476
+ });
3477
+ await new Promise((resolve3) => {
3478
+ const heartbeat = setInterval(() => {
3479
+ void stream2.writeSSE({ event: "ping", data: "" });
3480
+ }, STREAM_HEARTBEAT_MS);
3481
+ stream2.onAbort(() => {
3482
+ clearInterval(heartbeat);
3483
+ unsubscribe();
3484
+ resolve3();
3485
+ });
3486
+ });
3487
+ });
3488
+ });
3489
+ app2.get("/api/chats/counts", (c) => {
3490
+ if (!countsQuery2) {
3491
+ return c.json({ error: "Counts are not available" }, 501);
3492
+ }
3493
+ const includeTrashed = c.req.query("includeTrashed") === "true";
3494
+ return c.json(reader.listCounts({ includeTrashed }));
3495
+ });
3496
+ app2.get("/api/chats/list-total", (c) => {
3497
+ if (!countsQuery2) {
3498
+ return c.json({ error: "Counts are not available" }, 501);
3499
+ }
3500
+ const includeTrashed = c.req.query("includeTrashed") === "true";
3501
+ const projects = c.req.queries("project");
3502
+ const tagsParam = c.req.query("tags");
3503
+ const tags4 = tagsParam === void 0 ? void 0 : tagsParam.split(",");
3504
+ const total = reader.listFilteredTotal({ includeTrashed, projects, tags: tags4 });
3505
+ return c.json({ total });
3506
+ });
3045
3507
  app2.delete("/api/chats/:id", (c) => {
3046
3508
  const row = reader.findChat(c.req.param("id"));
3047
3509
  if (!row) {
@@ -4491,7 +4953,7 @@ var QueryPromise = class {
4491
4953
  function mapResultRow(columns, row, joinsNotNullableMap) {
4492
4954
  const nullifyMap = {};
4493
4955
  const result = columns.reduce(
4494
- (result2, { path: path5, field }, columnIndex) => {
4956
+ (result2, { path: path8, field }, columnIndex) => {
4495
4957
  let decoder;
4496
4958
  if (is(field, Column)) {
4497
4959
  decoder = field;
@@ -4503,8 +4965,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
4503
4965
  decoder = field.sql.decoder;
4504
4966
  }
4505
4967
  let node = result2;
4506
- for (const [pathChunkIndex, pathChunk] of path5.entries()) {
4507
- if (pathChunkIndex < path5.length - 1) {
4968
+ for (const [pathChunkIndex, pathChunk] of path8.entries()) {
4969
+ if (pathChunkIndex < path8.length - 1) {
4508
4970
  if (!(pathChunk in node)) {
4509
4971
  node[pathChunk] = {};
4510
4972
  }
@@ -4512,8 +4974,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
4512
4974
  } else {
4513
4975
  const rawValue = row[columnIndex];
4514
4976
  const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
4515
- if (joinsNotNullableMap && is(field, Column) && path5.length === 2) {
4516
- const objectName = path5[0];
4977
+ if (joinsNotNullableMap && is(field, Column) && path8.length === 2) {
4978
+ const objectName = path8[0];
4517
4979
  if (!(objectName in nullifyMap)) {
4518
4980
  nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
4519
4981
  } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
@@ -5073,10 +5535,10 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
5073
5535
  }
5074
5536
 
5075
5537
  // src/storage/openStore.ts
5076
- import fs2 from "fs";
5077
- import path from "path";
5538
+ import fs3 from "fs";
5539
+ import path2 from "path";
5078
5540
  import { fileURLToPath } from "url";
5079
- import Database from "better-sqlite3";
5541
+ import Database2 from "better-sqlite3";
5080
5542
 
5081
5543
  // ../node_modules/.pnpm/drizzle-orm@0.45.2_@types+better-sqlite3@7.6.13_better-sqlite3@12.9.0/node_modules/drizzle-orm/better-sqlite3/driver.js
5082
5544
  import Client from "better-sqlite3";
@@ -8683,20 +9145,20 @@ function drizzle(...params) {
8683
9145
 
8684
9146
  // ../node_modules/.pnpm/drizzle-orm@0.45.2_@types+better-sqlite3@7.6.13_better-sqlite3@12.9.0/node_modules/drizzle-orm/migrator.js
8685
9147
  import crypto4 from "crypto";
8686
- import fs from "fs";
9148
+ import fs2 from "fs";
8687
9149
  function readMigrationFiles(config) {
8688
9150
  const migrationFolderTo = config.migrationsFolder;
8689
9151
  const migrationQueries = [];
8690
9152
  const journalPath = `${migrationFolderTo}/meta/_journal.json`;
8691
- if (!fs.existsSync(journalPath)) {
9153
+ if (!fs2.existsSync(journalPath)) {
8692
9154
  throw new Error(`Can't find meta/_journal.json file`);
8693
9155
  }
8694
- const journalAsString = fs.readFileSync(`${migrationFolderTo}/meta/_journal.json`).toString();
9156
+ const journalAsString = fs2.readFileSync(`${migrationFolderTo}/meta/_journal.json`).toString();
8695
9157
  const journal = JSON.parse(journalAsString);
8696
9158
  for (const journalEntry of journal.entries) {
8697
9159
  const migrationPath = `${migrationFolderTo}/${journalEntry.tag}.sql`;
8698
9160
  try {
8699
- const query = fs.readFileSync(`${migrationFolderTo}/${journalEntry.tag}.sql`).toString();
9161
+ const query = fs2.readFileSync(`${migrationFolderTo}/${journalEntry.tag}.sql`).toString();
8700
9162
  const result = query.split("--> statement-breakpoint").map((it) => {
8701
9163
  return it;
8702
9164
  });
@@ -8721,12 +9183,12 @@ function migrate(db, config) {
8721
9183
 
8722
9184
  // src/storage/openStore.ts
8723
9185
  function resolveMigrationsFolder(callerUrl, migrationsSubdir) {
8724
- const here = path.dirname(fileURLToPath(callerUrl));
9186
+ const here = path2.dirname(fileURLToPath(callerUrl));
8725
9187
  const candidates = [
8726
- path.join(here, "../..", migrationsSubdir),
8727
- path.join(here, ".", migrationsSubdir)
9188
+ path2.join(here, "../..", migrationsSubdir),
9189
+ path2.join(here, ".", migrationsSubdir)
8728
9190
  ];
8729
- const found = candidates.find((p) => fs2.existsSync(p));
9191
+ const found = candidates.find((p) => fs3.existsSync(p));
8730
9192
  if (!found) {
8731
9193
  throw new Error(`Could not locate migrations folder "${migrationsSubdir}"`);
8732
9194
  }
@@ -8740,8 +9202,8 @@ function openStore({
8740
9202
  schema
8741
9203
  }) {
8742
9204
  const migrationsFolder = resolveMigrationsFolder(callerUrl, migrationsSubdir);
8743
- fs2.mkdirSync(dataDir2, { recursive: true });
8744
- const sqlite = new Database(path.join(dataDir2, dbFile));
9205
+ fs3.mkdirSync(dataDir2, { recursive: true });
9206
+ const sqlite = new Database2(path2.join(dataDir2, dbFile));
8745
9207
  const db = drizzle(sqlite, { schema });
8746
9208
  migrate(db, { migrationsFolder });
8747
9209
  return { db, sqlite };
@@ -8774,13 +9236,32 @@ var chats = sqliteTable(
8774
9236
  agent: text("agent").notNull(),
8775
9237
  sourceId: text("source_id").notNull(),
8776
9238
  firstSeenAt: integer("first_seen_at", { mode: "timestamp_ms" }).notNull(),
9239
+ // Denormalized "conversation start" = min(messages.ts), kept current at
9240
+ // ingest and backfilled in migration 0010. The createdAt sort pages by this
9241
+ // (not first_seen_at, the ingest time) so the paged order matches the
9242
+ // reader's displayed createdAt (issue #143, reconciling ADR-0017's caveat).
9243
+ // Initialized to first_seen_at for a chat with no messages; since
9244
+ // first_seen_at is the ingest time and so >= every message ts, the running
9245
+ // min over (first_seen_at, message ts) equals min(messages.ts) once any
9246
+ // message exists, and first_seen_at when none — never NULL.
9247
+ createdAt: integer("created_at", { mode: "timestamp_ms" }),
9248
+ // Denormalized "most recent activity" = max(messages.ts), kept current at
9249
+ // ingest and backfilled in migration 0009. Lets the activity sort run as a
9250
+ // keyset index range scan instead of an aggregate that defeats the index
9251
+ // (ADR-0017). Initialized to first_seen_at for a chat with no messages, so
9252
+ // it is never NULL and always agrees with the reader's derived `updatedAt`.
9253
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" }),
8777
9254
  project: text("project"),
8778
9255
  projectPath: text("project_path")
8779
9256
  },
8780
9257
  (t) => [
8781
9258
  uniqueIndex("chats_agent_source_idx").on(t.agent, t.sourceId),
8782
9259
  // Backs the server-side Project filter (`WHERE coalesce(project,'') IN …`).
8783
- index("chats_project_idx").on(t.project)
9260
+ index("chats_project_idx").on(t.project),
9261
+ // Covering keyset indexes for the two server-side list sorts (ADR-0017):
9262
+ // (sortKey, id) so the ORDER BY + LIMIT is an index range scan either way.
9263
+ index("chats_created_keyset_idx").on(t.createdAt, t.id),
9264
+ index("chats_updated_keyset_idx").on(t.updatedAt, t.id)
8784
9265
  ]
8785
9266
  );
8786
9267
  var rawMessages = sqliteTable(
@@ -8836,6 +9317,10 @@ function createArchiveReadSeam(db) {
8836
9317
  }
8837
9318
  return db.select().from(chats).all();
8838
9319
  },
9320
+ listChatRowsByIds(ids) {
9321
+ if (ids.length === 0) return [];
9322
+ return db.select().from(chats).where(inArray(chats.id, ids)).all();
9323
+ },
8839
9324
  findChatBySourceId(sourceId) {
8840
9325
  return db.select().from(chats).where(eq(chats.sourceId, sourceId)).get() ?? null;
8841
9326
  },
@@ -8845,15 +9330,17 @@ function createArchiveReadSeam(db) {
8845
9330
  listMessagesByChat(agent, sourceId) {
8846
9331
  return db.select().from(messages).where(and(eq(messages.agent, agent), eq(messages.sourceId, sourceId))).orderBy(asc(messages.ts)).all();
8847
9332
  },
8848
- listChatTsRanges() {
9333
+ listChatTsRanges(opts) {
9334
+ const scope = opts?.sourceIds !== void 0 ? inArray(messages.sourceId, opts.sourceIds) : sql`1 = 1`;
8849
9335
  return db.select({
8850
9336
  agent: messages.agent,
8851
9337
  sourceId: messages.sourceId,
8852
9338
  minTs: sql`min(${messages.ts})`,
8853
9339
  maxTs: sql`max(${messages.ts})`
8854
- }).from(messages).groupBy(messages.agent, messages.sourceId).all();
9340
+ }).from(messages).where(scope).groupBy(messages.agent, messages.sourceId).all();
8855
9341
  },
8856
- listLatestRawSourcePaths() {
9342
+ listLatestRawSourcePaths(opts) {
9343
+ const scope = opts?.sourceIds !== void 0 ? inArray(rawMessages.sourceId, opts.sourceIds) : sql`1 = 1`;
8857
9344
  const ranked = db.select({
8858
9345
  agent: rawMessages.agent,
8859
9346
  sourceId: rawMessages.sourceId,
@@ -8861,14 +9348,18 @@ function createArchiveReadSeam(db) {
8861
9348
  rn: sql`row_number() over (partition by ${rawMessages.agent}, ${rawMessages.sourceId} order by ${rawMessages.ingestedAt} desc)`.as(
8862
9349
  "rn"
8863
9350
  )
8864
- }).from(rawMessages).as("ranked_raw");
9351
+ }).from(rawMessages).where(scope).as("ranked_raw");
8865
9352
  return db.select({
8866
9353
  agent: ranked.agent,
8867
9354
  sourceId: ranked.sourceId,
8868
9355
  sourcePath: ranked.sourcePath
8869
9356
  }).from(ranked).where(eq(ranked.rn, 1)).all();
8870
9357
  },
8871
- listFirstUserTexts() {
9358
+ listFirstUserTexts(opts) {
9359
+ const scope = opts?.sourceIds !== void 0 ? and(
9360
+ eq(messages.role, "user"),
9361
+ inArray(messages.sourceId, opts.sourceIds)
9362
+ ) : eq(messages.role, "user");
8872
9363
  const ranked = db.select({
8873
9364
  agent: messages.agent,
8874
9365
  sourceId: messages.sourceId,
@@ -8876,7 +9367,7 @@ function createArchiveReadSeam(db) {
8876
9367
  rn: sql`row_number() over (partition by ${messages.agent}, ${messages.sourceId} order by ${messages.ts} asc)`.as(
8877
9368
  "rn"
8878
9369
  )
8879
- }).from(messages).where(eq(messages.role, "user")).as("ranked_user");
9370
+ }).from(messages).where(scope).as("ranked_user");
8880
9371
  return db.select({
8881
9372
  agent: ranked.agent,
8882
9373
  sourceId: ranked.sourceId,
@@ -8959,6 +9450,12 @@ function createArchiveRepository({
8959
9450
  agent,
8960
9451
  sourceId,
8961
9452
  firstSeenAt,
9453
+ // Seed both denormalized sort keys to first_seen_at; message upserts
9454
+ // pull createdAt down (min) and push updatedAt up (max) as ts arrive.
9455
+ // first_seen_at is the ingest time and so >= every message ts, making
9456
+ // it a safe upper bound for the running min.
9457
+ createdAt: firstSeenAt,
9458
+ updatedAt: firstSeenAt,
8962
9459
  project: project ?? null,
8963
9460
  projectPath: projectPath ?? null
8964
9461
  }).run();
@@ -8995,6 +9492,10 @@ function createArchiveRepository({
8995
9492
  )
8996
9493
  ).get();
8997
9494
  const ts = parseTs(message.ts);
9495
+ db.update(chats).set({
9496
+ createdAt: sql`min(coalesce(${chats.createdAt}, ${ts.getTime()}), ${ts.getTime()})`,
9497
+ updatedAt: sql`max(coalesce(${chats.updatedAt}, 0), ${ts.getTime()})`
9498
+ }).where(and(eq(chats.agent, agent), eq(chats.sourceId, sourceId))).run();
8998
9499
  if (!existing2) {
8999
9500
  db.insert(messages).values({
9000
9501
  agent,
@@ -9115,26 +9616,53 @@ function createCheckpointRepository({
9115
9616
  }
9116
9617
 
9117
9618
  // src/metadata/repository.ts
9118
- import fs3 from "fs";
9119
- import path2 from "path";
9619
+ import fs4 from "fs";
9620
+ import path3 from "path";
9120
9621
 
9121
9622
  // src/metadata/schema.ts
9122
9623
  var schema_exports3 = {};
9123
9624
  __export(schema_exports3, {
9625
+ chatSortKeys: () => chatSortKeys,
9124
9626
  chatTags: () => chatTags,
9125
9627
  chatsMeta: () => chatsMeta,
9126
9628
  tags: () => tags
9127
9629
  });
9128
- var chatsMeta = sqliteTable("chats_meta", {
9129
- id: text("id").primaryKey(),
9130
- isDeleted: integer("is_deleted", { mode: "boolean" }).notNull().default(false),
9131
- customTitle: text("custom_title"),
9132
- createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
9133
- updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
9134
- // Set when a chat is moved to Trash; null while active. Drives the Trash
9135
- // view's independent "Deleted time" sort axis.
9136
- deletedAt: integer("deleted_at", { mode: "timestamp_ms" })
9137
- });
9630
+ var chatsMeta = sqliteTable(
9631
+ "chats_meta",
9632
+ {
9633
+ id: text("id").primaryKey(),
9634
+ isDeleted: integer("is_deleted", { mode: "boolean" }).notNull().default(false),
9635
+ customTitle: text("custom_title"),
9636
+ createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
9637
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
9638
+ // Set when a chat is moved to Trash; null while active. Drives the Trash
9639
+ // view's independent "Deleted time" sort axis.
9640
+ deletedAt: integer("deleted_at", { mode: "timestamp_ms" })
9641
+ },
9642
+ (table) => [
9643
+ // Covering keyset index for the Trash view's deleted-time axis (#145,
9644
+ // ADR-0017): `(deleted_at, id)` so the page's ORDER BY + LIMIT is an index
9645
+ // range scan, not a full-trash sort. Partial on `deleted_at IS NOT NULL`,
9646
+ // which holds exactly for trashed rows (restore nulls it), so the index
9647
+ // carries only the Trash set and the page query's matching predicate lets
9648
+ // the planner use it.
9649
+ index("chats_meta_deleted_at_idx").on(table.deletedAt, table.id).where(sql`${table.deletedAt} is not null`)
9650
+ ]
9651
+ );
9652
+ var chatSortKeys = sqliteTable(
9653
+ "chat_sort_keys",
9654
+ {
9655
+ id: text("id").primaryKey(),
9656
+ textKey: text("text_key"),
9657
+ sortKey: text("sort_key")
9658
+ },
9659
+ (table) => [
9660
+ // Covering keyset index for the Title axis (ADR-0017/0019): `(sort_key, id)`
9661
+ // so `ORDER BY sort_key, id` + LIMIT is an index range scan either
9662
+ // direction, exactly like the time axes — never a full-table sort.
9663
+ index("chat_sort_keys_sort_key_idx").on(table.sortKey, table.id)
9664
+ ]
9665
+ );
9138
9666
  var tags = sqliteTable("tags", {
9139
9667
  id: text("id").primaryKey(),
9140
9668
  name: text("name").notNull(),
@@ -9154,15 +9682,26 @@ var chatTags = sqliteTable(
9154
9682
  ]
9155
9683
  );
9156
9684
 
9685
+ // src/metadata/title-sort-key.ts
9686
+ function encodeNumericRun(run2) {
9687
+ const core = run2.replace(/^0+(?=\d)/, "");
9688
+ const len = String(core.length);
9689
+ return String(len.length) + len + core;
9690
+ }
9691
+ function computeSortKey(input) {
9692
+ const folded = input.normalize("NFKD").toLowerCase().replace(/\p{M}+/gu, "");
9693
+ return folded.replace(/\d+/g, encodeNumericRun);
9694
+ }
9695
+
9157
9696
  // src/metadata/repository.ts
9158
9697
  var DB_FILE = "metadata.db";
9159
9698
  var LEGACY_DB_FILE = "data.db";
9160
9699
  function migrateLegacyDbFile(dataDir2) {
9161
- const legacy = path2.join(dataDir2, LEGACY_DB_FILE);
9162
- const current = path2.join(dataDir2, DB_FILE);
9163
- if (!fs3.existsSync(legacy)) return;
9164
- if (fs3.existsSync(current)) return;
9165
- fs3.renameSync(legacy, current);
9700
+ const legacy = path3.join(dataDir2, LEGACY_DB_FILE);
9701
+ const current = path3.join(dataDir2, DB_FILE);
9702
+ if (!fs4.existsSync(legacy)) return;
9703
+ if (fs4.existsSync(current)) return;
9704
+ fs4.renameSync(legacy, current);
9166
9705
  }
9167
9706
  var CLAUDE_CODE_AGENT = "claude-code";
9168
9707
  function createMetadataRepository({
@@ -9234,6 +9773,27 @@ function createMetadataRepository({
9234
9773
  target: chatsMeta.id,
9235
9774
  set: { customTitle: title, updatedAt: now }
9236
9775
  }).run();
9776
+ if (title !== null) {
9777
+ const sortKey = computeSortKey(title);
9778
+ db.insert(chatSortKeys).values({ id: internalId, sortKey }).onConflictDoUpdate({ target: chatSortKeys.id, set: { sortKey } }).run();
9779
+ } else {
9780
+ db.update(chatSortKeys).set({ sortKey: sql`${chatSortKeys.textKey}` }).where(eq(chatSortKeys.id, internalId)).run();
9781
+ }
9782
+ },
9783
+ getTitleSortKey(internalId) {
9784
+ const row = db.select({
9785
+ textKey: chatSortKeys.textKey,
9786
+ sortKey: chatSortKeys.sortKey
9787
+ }).from(chatSortKeys).where(eq(chatSortKeys.id, internalId)).get();
9788
+ return row ?? null;
9789
+ },
9790
+ setTitleTextKey(internalId, textKey) {
9791
+ const customTitle = db.select({ customTitle: chatsMeta.customTitle }).from(chatsMeta).where(eq(chatsMeta.id, internalId)).get()?.customTitle;
9792
+ const sortKey = customTitle && customTitle.trim() ? computeSortKey(customTitle) : textKey;
9793
+ db.insert(chatSortKeys).values({ id: internalId, textKey, sortKey }).onConflictDoUpdate({
9794
+ target: chatSortKeys.id,
9795
+ set: { textKey, sortKey }
9796
+ }).run();
9237
9797
  }
9238
9798
  };
9239
9799
  }
@@ -9345,7 +9905,7 @@ function createTagRepository({
9345
9905
  }
9346
9906
 
9347
9907
  // src/ingestion/ingest.ts
9348
- import fs4 from "fs";
9908
+ import fs5 from "fs";
9349
9909
  async function runIngestion(opts) {
9350
9910
  const now = opts.now ?? (() => /* @__PURE__ */ new Date());
9351
9911
  const result = {
@@ -9405,7 +9965,7 @@ async function runIngestion(opts) {
9405
9965
  }
9406
9966
  function safeStat(p) {
9407
9967
  try {
9408
- return fs4.statSync(p);
9968
+ return fs5.statSync(p);
9409
9969
  } catch {
9410
9970
  return null;
9411
9971
  }
@@ -9514,7 +10074,7 @@ var ReaddirpStream = class extends Readable3 {
9514
10074
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
9515
10075
  const statMethod = opts.lstat ? lstat : stat;
9516
10076
  if (wantBigintFsStats) {
9517
- this._stat = (path5) => statMethod(path5, { bigint: true });
10077
+ this._stat = (path8) => statMethod(path8, { bigint: true });
9518
10078
  } else {
9519
10079
  this._stat = statMethod;
9520
10080
  }
@@ -9539,8 +10099,8 @@ var ReaddirpStream = class extends Readable3 {
9539
10099
  const par = this.parent;
9540
10100
  const fil = par && par.files;
9541
10101
  if (fil && fil.length > 0) {
9542
- const { path: path5, depth } = par;
9543
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path5));
10102
+ const { path: path8, depth } = par;
10103
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path8));
9544
10104
  const awaited = await Promise.all(slice);
9545
10105
  for (const entry of awaited) {
9546
10106
  if (!entry)
@@ -9580,20 +10140,20 @@ var ReaddirpStream = class extends Readable3 {
9580
10140
  this.reading = false;
9581
10141
  }
9582
10142
  }
9583
- async _exploreDir(path5, depth) {
10143
+ async _exploreDir(path8, depth) {
9584
10144
  let files;
9585
10145
  try {
9586
- files = await readdir(path5, this._rdOptions);
10146
+ files = await readdir(path8, this._rdOptions);
9587
10147
  } catch (error) {
9588
10148
  this._onError(error);
9589
10149
  }
9590
- return { files, depth, path: path5 };
10150
+ return { files, depth, path: path8 };
9591
10151
  }
9592
- async _formatEntry(dirent, path5) {
10152
+ async _formatEntry(dirent, path8) {
9593
10153
  let entry;
9594
10154
  const basename3 = this._isDirent ? dirent.name : dirent;
9595
10155
  try {
9596
- const fullPath = presolve(pjoin(path5, basename3));
10156
+ const fullPath = presolve(pjoin(path8, basename3));
9597
10157
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
9598
10158
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
9599
10159
  } catch (err) {
@@ -9993,16 +10553,16 @@ var delFromSet = (main, prop, item) => {
9993
10553
  };
9994
10554
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
9995
10555
  var FsWatchInstances = /* @__PURE__ */ new Map();
9996
- function createFsWatchInstance(path5, options, listener, errHandler, emitRaw) {
10556
+ function createFsWatchInstance(path8, options, listener, errHandler, emitRaw) {
9997
10557
  const handleEvent = (rawEvent, evPath) => {
9998
- listener(path5);
9999
- emitRaw(rawEvent, evPath, { watchedPath: path5 });
10000
- if (evPath && path5 !== evPath) {
10001
- fsWatchBroadcast(sp.resolve(path5, evPath), KEY_LISTENERS, sp.join(path5, evPath));
10558
+ listener(path8);
10559
+ emitRaw(rawEvent, evPath, { watchedPath: path8 });
10560
+ if (evPath && path8 !== evPath) {
10561
+ fsWatchBroadcast(sp.resolve(path8, evPath), KEY_LISTENERS, sp.join(path8, evPath));
10002
10562
  }
10003
10563
  };
10004
10564
  try {
10005
- return fs_watch(path5, {
10565
+ return fs_watch(path8, {
10006
10566
  persistent: options.persistent
10007
10567
  }, handleEvent);
10008
10568
  } catch (error) {
@@ -10018,12 +10578,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
10018
10578
  listener(val1, val2, val3);
10019
10579
  });
10020
10580
  };
10021
- var setFsWatchListener = (path5, fullPath, options, handlers) => {
10581
+ var setFsWatchListener = (path8, fullPath, options, handlers) => {
10022
10582
  const { listener, errHandler, rawEmitter } = handlers;
10023
10583
  let cont = FsWatchInstances.get(fullPath);
10024
10584
  let watcher2;
10025
10585
  if (!options.persistent) {
10026
- watcher2 = createFsWatchInstance(path5, options, listener, errHandler, rawEmitter);
10586
+ watcher2 = createFsWatchInstance(path8, options, listener, errHandler, rawEmitter);
10027
10587
  if (!watcher2)
10028
10588
  return;
10029
10589
  return watcher2.close.bind(watcher2);
@@ -10034,7 +10594,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
10034
10594
  addAndConvert(cont, KEY_RAW, rawEmitter);
10035
10595
  } else {
10036
10596
  watcher2 = createFsWatchInstance(
10037
- path5,
10597
+ path8,
10038
10598
  options,
10039
10599
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
10040
10600
  errHandler,
@@ -10049,7 +10609,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
10049
10609
  cont.watcherUnusable = true;
10050
10610
  if (isWindows && error.code === "EPERM") {
10051
10611
  try {
10052
- const fd = await open(path5, "r");
10612
+ const fd = await open(path8, "r");
10053
10613
  await fd.close();
10054
10614
  broadcastErr(error);
10055
10615
  } catch (err) {
@@ -10080,7 +10640,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
10080
10640
  };
10081
10641
  };
10082
10642
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
10083
- var setFsWatchFileListener = (path5, fullPath, options, handlers) => {
10643
+ var setFsWatchFileListener = (path8, fullPath, options, handlers) => {
10084
10644
  const { listener, rawEmitter } = handlers;
10085
10645
  let cont = FsWatchFileInstances.get(fullPath);
10086
10646
  const copts = cont && cont.options;
@@ -10102,7 +10662,7 @@ var setFsWatchFileListener = (path5, fullPath, options, handlers) => {
10102
10662
  });
10103
10663
  const currmtime = curr.mtimeMs;
10104
10664
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
10105
- foreach(cont.listeners, (listener2) => listener2(path5, curr));
10665
+ foreach(cont.listeners, (listener2) => listener2(path8, curr));
10106
10666
  }
10107
10667
  })
10108
10668
  };
@@ -10132,13 +10692,13 @@ var NodeFsHandler = class {
10132
10692
  * @param listener on fs change
10133
10693
  * @returns closer for the watcher instance
10134
10694
  */
10135
- _watchWithNodeFs(path5, listener) {
10695
+ _watchWithNodeFs(path8, listener) {
10136
10696
  const opts = this.fsw.options;
10137
- const directory = sp.dirname(path5);
10138
- const basename3 = sp.basename(path5);
10697
+ const directory = sp.dirname(path8);
10698
+ const basename3 = sp.basename(path8);
10139
10699
  const parent = this.fsw._getWatchedDir(directory);
10140
10700
  parent.add(basename3);
10141
- const absolutePath = sp.resolve(path5);
10701
+ const absolutePath = sp.resolve(path8);
10142
10702
  const options = {
10143
10703
  persistent: opts.persistent
10144
10704
  };
@@ -10148,12 +10708,12 @@ var NodeFsHandler = class {
10148
10708
  if (opts.usePolling) {
10149
10709
  const enableBin = opts.interval !== opts.binaryInterval;
10150
10710
  options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
10151
- closer = setFsWatchFileListener(path5, absolutePath, options, {
10711
+ closer = setFsWatchFileListener(path8, absolutePath, options, {
10152
10712
  listener,
10153
10713
  rawEmitter: this.fsw._emitRaw
10154
10714
  });
10155
10715
  } else {
10156
- closer = setFsWatchListener(path5, absolutePath, options, {
10716
+ closer = setFsWatchListener(path8, absolutePath, options, {
10157
10717
  listener,
10158
10718
  errHandler: this._boundHandleError,
10159
10719
  rawEmitter: this.fsw._emitRaw
@@ -10175,7 +10735,7 @@ var NodeFsHandler = class {
10175
10735
  let prevStats = stats;
10176
10736
  if (parent.has(basename3))
10177
10737
  return;
10178
- const listener = async (path5, newStats) => {
10738
+ const listener = async (path8, newStats) => {
10179
10739
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
10180
10740
  return;
10181
10741
  if (!newStats || newStats.mtimeMs === 0) {
@@ -10189,11 +10749,11 @@ var NodeFsHandler = class {
10189
10749
  this.fsw._emit(EV.CHANGE, file, newStats2);
10190
10750
  }
10191
10751
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
10192
- this.fsw._closeFile(path5);
10752
+ this.fsw._closeFile(path8);
10193
10753
  prevStats = newStats2;
10194
10754
  const closer2 = this._watchWithNodeFs(file, listener);
10195
10755
  if (closer2)
10196
- this.fsw._addPathCloser(path5, closer2);
10756
+ this.fsw._addPathCloser(path8, closer2);
10197
10757
  } else {
10198
10758
  prevStats = newStats2;
10199
10759
  }
@@ -10225,7 +10785,7 @@ var NodeFsHandler = class {
10225
10785
  * @param item basename of this item
10226
10786
  * @returns true if no more processing is needed for this entry.
10227
10787
  */
10228
- async _handleSymlink(entry, directory, path5, item) {
10788
+ async _handleSymlink(entry, directory, path8, item) {
10229
10789
  if (this.fsw.closed) {
10230
10790
  return;
10231
10791
  }
@@ -10235,7 +10795,7 @@ var NodeFsHandler = class {
10235
10795
  this.fsw._incrReadyCount();
10236
10796
  let linkPath;
10237
10797
  try {
10238
- linkPath = await fsrealpath(path5);
10798
+ linkPath = await fsrealpath(path8);
10239
10799
  } catch (e) {
10240
10800
  this.fsw._emitReady();
10241
10801
  return true;
@@ -10245,12 +10805,12 @@ var NodeFsHandler = class {
10245
10805
  if (dir.has(item)) {
10246
10806
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
10247
10807
  this.fsw._symlinkPaths.set(full, linkPath);
10248
- this.fsw._emit(EV.CHANGE, path5, entry.stats);
10808
+ this.fsw._emit(EV.CHANGE, path8, entry.stats);
10249
10809
  }
10250
10810
  } else {
10251
10811
  dir.add(item);
10252
10812
  this.fsw._symlinkPaths.set(full, linkPath);
10253
- this.fsw._emit(EV.ADD, path5, entry.stats);
10813
+ this.fsw._emit(EV.ADD, path8, entry.stats);
10254
10814
  }
10255
10815
  this.fsw._emitReady();
10256
10816
  return true;
@@ -10268,39 +10828,39 @@ var NodeFsHandler = class {
10268
10828
  return;
10269
10829
  const previous = this.fsw._getWatchedDir(wh.path);
10270
10830
  const current = /* @__PURE__ */ new Set();
10271
- let stream = this.fsw._readdirp(directory, {
10831
+ let stream2 = this.fsw._readdirp(directory, {
10272
10832
  fileFilter: (entry) => wh.filterPath(entry),
10273
10833
  directoryFilter: (entry) => wh.filterDir(entry)
10274
10834
  });
10275
- if (!stream)
10835
+ if (!stream2)
10276
10836
  return;
10277
- stream.on(STR_DATA, async (entry) => {
10837
+ stream2.on(STR_DATA, async (entry) => {
10278
10838
  if (this.fsw.closed) {
10279
- stream = void 0;
10839
+ stream2 = void 0;
10280
10840
  return;
10281
10841
  }
10282
10842
  const item = entry.path;
10283
- let path5 = sp.join(directory, item);
10843
+ let path8 = sp.join(directory, item);
10284
10844
  current.add(item);
10285
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path5, item)) {
10845
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path8, item)) {
10286
10846
  return;
10287
10847
  }
10288
10848
  if (this.fsw.closed) {
10289
- stream = void 0;
10849
+ stream2 = void 0;
10290
10850
  return;
10291
10851
  }
10292
10852
  if (item === target || !target && !previous.has(item)) {
10293
10853
  this.fsw._incrReadyCount();
10294
- path5 = sp.join(dir, sp.relative(dir, path5));
10295
- this._addToNodeFs(path5, initialAdd, wh, depth + 1);
10854
+ path8 = sp.join(dir, sp.relative(dir, path8));
10855
+ this._addToNodeFs(path8, initialAdd, wh, depth + 1);
10296
10856
  }
10297
10857
  }).on(EV.ERROR, this._boundHandleError);
10298
10858
  return new Promise((resolve3, reject) => {
10299
- if (!stream)
10859
+ if (!stream2)
10300
10860
  return reject();
10301
- stream.once(STR_END, () => {
10861
+ stream2.once(STR_END, () => {
10302
10862
  if (this.fsw.closed) {
10303
- stream = void 0;
10863
+ stream2 = void 0;
10304
10864
  return;
10305
10865
  }
10306
10866
  const wasThrottled = throttler ? throttler.clear() : false;
@@ -10310,7 +10870,7 @@ var NodeFsHandler = class {
10310
10870
  }).forEach((item) => {
10311
10871
  this.fsw._remove(directory, item);
10312
10872
  });
10313
- stream = void 0;
10873
+ stream2 = void 0;
10314
10874
  if (wasThrottled)
10315
10875
  this._handleRead(directory, false, wh, target, dir, depth, throttler);
10316
10876
  });
@@ -10361,13 +10921,13 @@ var NodeFsHandler = class {
10361
10921
  * @param depth Child path actually targeted for watch
10362
10922
  * @param target Child path actually targeted for watch
10363
10923
  */
10364
- async _addToNodeFs(path5, initialAdd, priorWh, depth, target) {
10924
+ async _addToNodeFs(path8, initialAdd, priorWh, depth, target) {
10365
10925
  const ready = this.fsw._emitReady;
10366
- if (this.fsw._isIgnored(path5) || this.fsw.closed) {
10926
+ if (this.fsw._isIgnored(path8) || this.fsw.closed) {
10367
10927
  ready();
10368
10928
  return false;
10369
10929
  }
10370
- const wh = this.fsw._getWatchHelpers(path5);
10930
+ const wh = this.fsw._getWatchHelpers(path8);
10371
10931
  if (priorWh) {
10372
10932
  wh.filterPath = (entry) => priorWh.filterPath(entry);
10373
10933
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -10383,8 +10943,8 @@ var NodeFsHandler = class {
10383
10943
  const follow = this.fsw.options.followSymlinks;
10384
10944
  let closer;
10385
10945
  if (stats.isDirectory()) {
10386
- const absPath = sp.resolve(path5);
10387
- const targetPath = follow ? await fsrealpath(path5) : path5;
10946
+ const absPath = sp.resolve(path8);
10947
+ const targetPath = follow ? await fsrealpath(path8) : path8;
10388
10948
  if (this.fsw.closed)
10389
10949
  return;
10390
10950
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -10394,29 +10954,29 @@ var NodeFsHandler = class {
10394
10954
  this.fsw._symlinkPaths.set(absPath, targetPath);
10395
10955
  }
10396
10956
  } else if (stats.isSymbolicLink()) {
10397
- const targetPath = follow ? await fsrealpath(path5) : path5;
10957
+ const targetPath = follow ? await fsrealpath(path8) : path8;
10398
10958
  if (this.fsw.closed)
10399
10959
  return;
10400
10960
  const parent = sp.dirname(wh.watchPath);
10401
10961
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
10402
10962
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
10403
- closer = await this._handleDir(parent, stats, initialAdd, depth, path5, wh, targetPath);
10963
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path8, wh, targetPath);
10404
10964
  if (this.fsw.closed)
10405
10965
  return;
10406
10966
  if (targetPath !== void 0) {
10407
- this.fsw._symlinkPaths.set(sp.resolve(path5), targetPath);
10967
+ this.fsw._symlinkPaths.set(sp.resolve(path8), targetPath);
10408
10968
  }
10409
10969
  } else {
10410
10970
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
10411
10971
  }
10412
10972
  ready();
10413
10973
  if (closer)
10414
- this.fsw._addPathCloser(path5, closer);
10974
+ this.fsw._addPathCloser(path8, closer);
10415
10975
  return false;
10416
10976
  } catch (error) {
10417
10977
  if (this.fsw._handleError(error)) {
10418
10978
  ready();
10419
- return path5;
10979
+ return path8;
10420
10980
  }
10421
10981
  }
10422
10982
  }
@@ -10459,24 +11019,24 @@ function createPattern(matcher) {
10459
11019
  }
10460
11020
  return () => false;
10461
11021
  }
10462
- function normalizePath(path5) {
10463
- if (typeof path5 !== "string")
11022
+ function normalizePath(path8) {
11023
+ if (typeof path8 !== "string")
10464
11024
  throw new Error("string expected");
10465
- path5 = sp2.normalize(path5);
10466
- path5 = path5.replace(/\\/g, "/");
11025
+ path8 = sp2.normalize(path8);
11026
+ path8 = path8.replace(/\\/g, "/");
10467
11027
  let prepend = false;
10468
- if (path5.startsWith("//"))
11028
+ if (path8.startsWith("//"))
10469
11029
  prepend = true;
10470
- path5 = path5.replace(DOUBLE_SLASH_RE, "/");
11030
+ path8 = path8.replace(DOUBLE_SLASH_RE, "/");
10471
11031
  if (prepend)
10472
- path5 = "/" + path5;
10473
- return path5;
11032
+ path8 = "/" + path8;
11033
+ return path8;
10474
11034
  }
10475
11035
  function matchPatterns(patterns, testString, stats) {
10476
- const path5 = normalizePath(testString);
11036
+ const path8 = normalizePath(testString);
10477
11037
  for (let index2 = 0; index2 < patterns.length; index2++) {
10478
11038
  const pattern = patterns[index2];
10479
- if (pattern(path5, stats)) {
11039
+ if (pattern(path8, stats)) {
10480
11040
  return true;
10481
11041
  }
10482
11042
  }
@@ -10514,19 +11074,19 @@ var toUnix = (string) => {
10514
11074
  }
10515
11075
  return str;
10516
11076
  };
10517
- var normalizePathToUnix = (path5) => toUnix(sp2.normalize(toUnix(path5)));
10518
- var normalizeIgnored = (cwd = "") => (path5) => {
10519
- if (typeof path5 === "string") {
10520
- return normalizePathToUnix(sp2.isAbsolute(path5) ? path5 : sp2.join(cwd, path5));
11077
+ var normalizePathToUnix = (path8) => toUnix(sp2.normalize(toUnix(path8)));
11078
+ var normalizeIgnored = (cwd = "") => (path8) => {
11079
+ if (typeof path8 === "string") {
11080
+ return normalizePathToUnix(sp2.isAbsolute(path8) ? path8 : sp2.join(cwd, path8));
10521
11081
  } else {
10522
- return path5;
11082
+ return path8;
10523
11083
  }
10524
11084
  };
10525
- var getAbsolutePath = (path5, cwd) => {
10526
- if (sp2.isAbsolute(path5)) {
10527
- return path5;
11085
+ var getAbsolutePath = (path8, cwd) => {
11086
+ if (sp2.isAbsolute(path8)) {
11087
+ return path8;
10528
11088
  }
10529
- return sp2.join(cwd, path5);
11089
+ return sp2.join(cwd, path8);
10530
11090
  };
10531
11091
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
10532
11092
  var DirEntry = class {
@@ -10591,10 +11151,10 @@ var WatchHelper = class {
10591
11151
  dirParts;
10592
11152
  followSymlinks;
10593
11153
  statMethod;
10594
- constructor(path5, follow, fsw) {
11154
+ constructor(path8, follow, fsw) {
10595
11155
  this.fsw = fsw;
10596
- const watchPath = path5;
10597
- this.path = path5 = path5.replace(REPLACER_RE, "");
11156
+ const watchPath = path8;
11157
+ this.path = path8 = path8.replace(REPLACER_RE, "");
10598
11158
  this.watchPath = watchPath;
10599
11159
  this.fullWatchPath = sp2.resolve(watchPath);
10600
11160
  this.dirParts = [];
@@ -10734,20 +11294,20 @@ var FSWatcher = class extends EventEmitter {
10734
11294
  this._closePromise = void 0;
10735
11295
  let paths = unifyPaths(paths_);
10736
11296
  if (cwd) {
10737
- paths = paths.map((path5) => {
10738
- const absPath = getAbsolutePath(path5, cwd);
11297
+ paths = paths.map((path8) => {
11298
+ const absPath = getAbsolutePath(path8, cwd);
10739
11299
  return absPath;
10740
11300
  });
10741
11301
  }
10742
- paths.forEach((path5) => {
10743
- this._removeIgnoredPath(path5);
11302
+ paths.forEach((path8) => {
11303
+ this._removeIgnoredPath(path8);
10744
11304
  });
10745
11305
  this._userIgnored = void 0;
10746
11306
  if (!this._readyCount)
10747
11307
  this._readyCount = 0;
10748
11308
  this._readyCount += paths.length;
10749
- Promise.all(paths.map(async (path5) => {
10750
- const res = await this._nodeFsHandler._addToNodeFs(path5, !_internal, void 0, 0, _origAdd);
11309
+ Promise.all(paths.map(async (path8) => {
11310
+ const res = await this._nodeFsHandler._addToNodeFs(path8, !_internal, void 0, 0, _origAdd);
10751
11311
  if (res)
10752
11312
  this._emitReady();
10753
11313
  return res;
@@ -10769,17 +11329,17 @@ var FSWatcher = class extends EventEmitter {
10769
11329
  return this;
10770
11330
  const paths = unifyPaths(paths_);
10771
11331
  const { cwd } = this.options;
10772
- paths.forEach((path5) => {
10773
- if (!sp2.isAbsolute(path5) && !this._closers.has(path5)) {
11332
+ paths.forEach((path8) => {
11333
+ if (!sp2.isAbsolute(path8) && !this._closers.has(path8)) {
10774
11334
  if (cwd)
10775
- path5 = sp2.join(cwd, path5);
10776
- path5 = sp2.resolve(path5);
11335
+ path8 = sp2.join(cwd, path8);
11336
+ path8 = sp2.resolve(path8);
10777
11337
  }
10778
- this._closePath(path5);
10779
- this._addIgnoredPath(path5);
10780
- if (this._watched.has(path5)) {
11338
+ this._closePath(path8);
11339
+ this._addIgnoredPath(path8);
11340
+ if (this._watched.has(path8)) {
10781
11341
  this._addIgnoredPath({
10782
- path: path5,
11342
+ path: path8,
10783
11343
  recursive: true
10784
11344
  });
10785
11345
  }
@@ -10802,7 +11362,7 @@ var FSWatcher = class extends EventEmitter {
10802
11362
  if (promise instanceof Promise)
10803
11363
  closers.push(promise);
10804
11364
  }));
10805
- this._streams.forEach((stream) => stream.destroy());
11365
+ this._streams.forEach((stream2) => stream2.destroy());
10806
11366
  this._userIgnored = void 0;
10807
11367
  this._readyCount = 0;
10808
11368
  this._readyEmitted = false;
@@ -10843,38 +11403,38 @@ var FSWatcher = class extends EventEmitter {
10843
11403
  * @param stats arguments to be passed with event
10844
11404
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
10845
11405
  */
10846
- async _emit(event, path5, stats) {
11406
+ async _emit(event, path8, stats) {
10847
11407
  if (this.closed)
10848
11408
  return;
10849
11409
  const opts = this.options;
10850
11410
  if (isWindows)
10851
- path5 = sp2.normalize(path5);
11411
+ path8 = sp2.normalize(path8);
10852
11412
  if (opts.cwd)
10853
- path5 = sp2.relative(opts.cwd, path5);
10854
- const args = [path5];
11413
+ path8 = sp2.relative(opts.cwd, path8);
11414
+ const args = [path8];
10855
11415
  if (stats != null)
10856
11416
  args.push(stats);
10857
11417
  const awf = opts.awaitWriteFinish;
10858
11418
  let pw;
10859
- if (awf && (pw = this._pendingWrites.get(path5))) {
11419
+ if (awf && (pw = this._pendingWrites.get(path8))) {
10860
11420
  pw.lastChange = /* @__PURE__ */ new Date();
10861
11421
  return this;
10862
11422
  }
10863
11423
  if (opts.atomic) {
10864
11424
  if (event === EVENTS.UNLINK) {
10865
- this._pendingUnlinks.set(path5, [event, ...args]);
11425
+ this._pendingUnlinks.set(path8, [event, ...args]);
10866
11426
  setTimeout(() => {
10867
- this._pendingUnlinks.forEach((entry, path6) => {
11427
+ this._pendingUnlinks.forEach((entry, path9) => {
10868
11428
  this.emit(...entry);
10869
11429
  this.emit(EVENTS.ALL, ...entry);
10870
- this._pendingUnlinks.delete(path6);
11430
+ this._pendingUnlinks.delete(path9);
10871
11431
  });
10872
11432
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
10873
11433
  return this;
10874
11434
  }
10875
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path5)) {
11435
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path8)) {
10876
11436
  event = EVENTS.CHANGE;
10877
- this._pendingUnlinks.delete(path5);
11437
+ this._pendingUnlinks.delete(path8);
10878
11438
  }
10879
11439
  }
10880
11440
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -10892,16 +11452,16 @@ var FSWatcher = class extends EventEmitter {
10892
11452
  this.emitWithAll(event, args);
10893
11453
  }
10894
11454
  };
10895
- this._awaitWriteFinish(path5, awf.stabilityThreshold, event, awfEmit);
11455
+ this._awaitWriteFinish(path8, awf.stabilityThreshold, event, awfEmit);
10896
11456
  return this;
10897
11457
  }
10898
11458
  if (event === EVENTS.CHANGE) {
10899
- const isThrottled = !this._throttle(EVENTS.CHANGE, path5, 50);
11459
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path8, 50);
10900
11460
  if (isThrottled)
10901
11461
  return this;
10902
11462
  }
10903
11463
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
10904
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path5) : path5;
11464
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path8) : path8;
10905
11465
  let stats2;
10906
11466
  try {
10907
11467
  stats2 = await stat3(fullPath);
@@ -10932,23 +11492,23 @@ var FSWatcher = class extends EventEmitter {
10932
11492
  * @param timeout duration of time to suppress duplicate actions
10933
11493
  * @returns tracking object or false if action should be suppressed
10934
11494
  */
10935
- _throttle(actionType, path5, timeout) {
11495
+ _throttle(actionType, path8, timeout) {
10936
11496
  if (!this._throttled.has(actionType)) {
10937
11497
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
10938
11498
  }
10939
11499
  const action2 = this._throttled.get(actionType);
10940
11500
  if (!action2)
10941
11501
  throw new Error("invalid throttle");
10942
- const actionPath = action2.get(path5);
11502
+ const actionPath = action2.get(path8);
10943
11503
  if (actionPath) {
10944
11504
  actionPath.count++;
10945
11505
  return false;
10946
11506
  }
10947
11507
  let timeoutObject;
10948
11508
  const clear = () => {
10949
- const item = action2.get(path5);
11509
+ const item = action2.get(path8);
10950
11510
  const count = item ? item.count : 0;
10951
- action2.delete(path5);
11511
+ action2.delete(path8);
10952
11512
  clearTimeout(timeoutObject);
10953
11513
  if (item)
10954
11514
  clearTimeout(item.timeoutObject);
@@ -10956,7 +11516,7 @@ var FSWatcher = class extends EventEmitter {
10956
11516
  };
10957
11517
  timeoutObject = setTimeout(clear, timeout);
10958
11518
  const thr = { timeoutObject, clear, count: 0 };
10959
- action2.set(path5, thr);
11519
+ action2.set(path8, thr);
10960
11520
  return thr;
10961
11521
  }
10962
11522
  _incrReadyCount() {
@@ -10970,44 +11530,44 @@ var FSWatcher = class extends EventEmitter {
10970
11530
  * @param event
10971
11531
  * @param awfEmit Callback to be called when ready for event to be emitted.
10972
11532
  */
10973
- _awaitWriteFinish(path5, threshold, event, awfEmit) {
11533
+ _awaitWriteFinish(path8, threshold, event, awfEmit) {
10974
11534
  const awf = this.options.awaitWriteFinish;
10975
11535
  if (typeof awf !== "object")
10976
11536
  return;
10977
11537
  const pollInterval = awf.pollInterval;
10978
11538
  let timeoutHandler;
10979
- let fullPath = path5;
10980
- if (this.options.cwd && !sp2.isAbsolute(path5)) {
10981
- fullPath = sp2.join(this.options.cwd, path5);
11539
+ let fullPath = path8;
11540
+ if (this.options.cwd && !sp2.isAbsolute(path8)) {
11541
+ fullPath = sp2.join(this.options.cwd, path8);
10982
11542
  }
10983
11543
  const now = /* @__PURE__ */ new Date();
10984
11544
  const writes = this._pendingWrites;
10985
11545
  function awaitWriteFinishFn(prevStat) {
10986
11546
  statcb(fullPath, (err, curStat) => {
10987
- if (err || !writes.has(path5)) {
11547
+ if (err || !writes.has(path8)) {
10988
11548
  if (err && err.code !== "ENOENT")
10989
11549
  awfEmit(err);
10990
11550
  return;
10991
11551
  }
10992
11552
  const now2 = Number(/* @__PURE__ */ new Date());
10993
11553
  if (prevStat && curStat.size !== prevStat.size) {
10994
- writes.get(path5).lastChange = now2;
11554
+ writes.get(path8).lastChange = now2;
10995
11555
  }
10996
- const pw = writes.get(path5);
11556
+ const pw = writes.get(path8);
10997
11557
  const df = now2 - pw.lastChange;
10998
11558
  if (df >= threshold) {
10999
- writes.delete(path5);
11559
+ writes.delete(path8);
11000
11560
  awfEmit(void 0, curStat);
11001
11561
  } else {
11002
11562
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
11003
11563
  }
11004
11564
  });
11005
11565
  }
11006
- if (!writes.has(path5)) {
11007
- writes.set(path5, {
11566
+ if (!writes.has(path8)) {
11567
+ writes.set(path8, {
11008
11568
  lastChange: now,
11009
11569
  cancelWait: () => {
11010
- writes.delete(path5);
11570
+ writes.delete(path8);
11011
11571
  clearTimeout(timeoutHandler);
11012
11572
  return event;
11013
11573
  }
@@ -11018,8 +11578,8 @@ var FSWatcher = class extends EventEmitter {
11018
11578
  /**
11019
11579
  * Determines whether user has asked to ignore this path.
11020
11580
  */
11021
- _isIgnored(path5, stats) {
11022
- if (this.options.atomic && DOT_RE.test(path5))
11581
+ _isIgnored(path8, stats) {
11582
+ if (this.options.atomic && DOT_RE.test(path8))
11023
11583
  return true;
11024
11584
  if (!this._userIgnored) {
11025
11585
  const { cwd } = this.options;
@@ -11029,17 +11589,17 @@ var FSWatcher = class extends EventEmitter {
11029
11589
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
11030
11590
  this._userIgnored = anymatch(list, void 0);
11031
11591
  }
11032
- return this._userIgnored(path5, stats);
11592
+ return this._userIgnored(path8, stats);
11033
11593
  }
11034
- _isntIgnored(path5, stat4) {
11035
- return !this._isIgnored(path5, stat4);
11594
+ _isntIgnored(path8, stat4) {
11595
+ return !this._isIgnored(path8, stat4);
11036
11596
  }
11037
11597
  /**
11038
11598
  * Provides a set of common helpers and properties relating to symlink handling.
11039
11599
  * @param path file or directory pattern being watched
11040
11600
  */
11041
- _getWatchHelpers(path5) {
11042
- return new WatchHelper(path5, this.options.followSymlinks, this);
11601
+ _getWatchHelpers(path8) {
11602
+ return new WatchHelper(path8, this.options.followSymlinks, this);
11043
11603
  }
11044
11604
  // Directory helpers
11045
11605
  // -----------------
@@ -11071,63 +11631,63 @@ var FSWatcher = class extends EventEmitter {
11071
11631
  * @param item base path of item/directory
11072
11632
  */
11073
11633
  _remove(directory, item, isDirectory) {
11074
- const path5 = sp2.join(directory, item);
11075
- const fullPath = sp2.resolve(path5);
11076
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path5) || this._watched.has(fullPath);
11077
- if (!this._throttle("remove", path5, 100))
11634
+ const path8 = sp2.join(directory, item);
11635
+ const fullPath = sp2.resolve(path8);
11636
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path8) || this._watched.has(fullPath);
11637
+ if (!this._throttle("remove", path8, 100))
11078
11638
  return;
11079
11639
  if (!isDirectory && this._watched.size === 1) {
11080
11640
  this.add(directory, item, true);
11081
11641
  }
11082
- const wp = this._getWatchedDir(path5);
11642
+ const wp = this._getWatchedDir(path8);
11083
11643
  const nestedDirectoryChildren = wp.getChildren();
11084
- nestedDirectoryChildren.forEach((nested) => this._remove(path5, nested));
11644
+ nestedDirectoryChildren.forEach((nested) => this._remove(path8, nested));
11085
11645
  const parent = this._getWatchedDir(directory);
11086
11646
  const wasTracked = parent.has(item);
11087
11647
  parent.remove(item);
11088
11648
  if (this._symlinkPaths.has(fullPath)) {
11089
11649
  this._symlinkPaths.delete(fullPath);
11090
11650
  }
11091
- let relPath = path5;
11651
+ let relPath = path8;
11092
11652
  if (this.options.cwd)
11093
- relPath = sp2.relative(this.options.cwd, path5);
11653
+ relPath = sp2.relative(this.options.cwd, path8);
11094
11654
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
11095
11655
  const event = this._pendingWrites.get(relPath).cancelWait();
11096
11656
  if (event === EVENTS.ADD)
11097
11657
  return;
11098
11658
  }
11099
- this._watched.delete(path5);
11659
+ this._watched.delete(path8);
11100
11660
  this._watched.delete(fullPath);
11101
11661
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
11102
- if (wasTracked && !this._isIgnored(path5))
11103
- this._emit(eventName, path5);
11104
- this._closePath(path5);
11662
+ if (wasTracked && !this._isIgnored(path8))
11663
+ this._emit(eventName, path8);
11664
+ this._closePath(path8);
11105
11665
  }
11106
11666
  /**
11107
11667
  * Closes all watchers for a path
11108
11668
  */
11109
- _closePath(path5) {
11110
- this._closeFile(path5);
11111
- const dir = sp2.dirname(path5);
11112
- this._getWatchedDir(dir).remove(sp2.basename(path5));
11669
+ _closePath(path8) {
11670
+ this._closeFile(path8);
11671
+ const dir = sp2.dirname(path8);
11672
+ this._getWatchedDir(dir).remove(sp2.basename(path8));
11113
11673
  }
11114
11674
  /**
11115
11675
  * Closes only file-specific watchers
11116
11676
  */
11117
- _closeFile(path5) {
11118
- const closers = this._closers.get(path5);
11677
+ _closeFile(path8) {
11678
+ const closers = this._closers.get(path8);
11119
11679
  if (!closers)
11120
11680
  return;
11121
11681
  closers.forEach((closer) => closer());
11122
- this._closers.delete(path5);
11682
+ this._closers.delete(path8);
11123
11683
  }
11124
- _addPathCloser(path5, closer) {
11684
+ _addPathCloser(path8, closer) {
11125
11685
  if (!closer)
11126
11686
  return;
11127
- let list = this._closers.get(path5);
11687
+ let list = this._closers.get(path8);
11128
11688
  if (!list) {
11129
11689
  list = [];
11130
- this._closers.set(path5, list);
11690
+ this._closers.set(path8, list);
11131
11691
  }
11132
11692
  list.push(closer);
11133
11693
  }
@@ -11135,18 +11695,18 @@ var FSWatcher = class extends EventEmitter {
11135
11695
  if (this.closed)
11136
11696
  return;
11137
11697
  const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
11138
- let stream = readdirp(root, options);
11139
- this._streams.add(stream);
11140
- stream.once(STR_CLOSE, () => {
11141
- stream = void 0;
11698
+ let stream2 = readdirp(root, options);
11699
+ this._streams.add(stream2);
11700
+ stream2.once(STR_CLOSE, () => {
11701
+ stream2 = void 0;
11142
11702
  });
11143
- stream.once(STR_END, () => {
11144
- if (stream) {
11145
- this._streams.delete(stream);
11146
- stream = void 0;
11703
+ stream2.once(STR_END, () => {
11704
+ if (stream2) {
11705
+ this._streams.delete(stream2);
11706
+ stream2 = void 0;
11147
11707
  }
11148
11708
  });
11149
- return stream;
11709
+ return stream2;
11150
11710
  }
11151
11711
  };
11152
11712
  function watch(paths, options = {}) {
@@ -11225,13 +11785,13 @@ function startWatcher(opts) {
11225
11785
  }
11226
11786
  return {
11227
11787
  ready,
11228
- notifyChange(path5) {
11229
- if (watcher2) watcher2.emit("change", path5);
11230
- else scheduleIngest(path5);
11788
+ notifyChange(path8) {
11789
+ if (watcher2) watcher2.emit("change", path8);
11790
+ else scheduleIngest(path8);
11231
11791
  },
11232
- notifyUnlink(path5) {
11233
- if (watcher2) watcher2.emit("unlink", path5);
11234
- else recordUnlink(path5);
11792
+ notifyUnlink(path8) {
11793
+ if (watcher2) watcher2.emit("unlink", path8);
11794
+ else recordUnlink(path8);
11235
11795
  },
11236
11796
  async close() {
11237
11797
  closed = true;
@@ -11243,39 +11803,39 @@ function startWatcher(opts) {
11243
11803
  }
11244
11804
 
11245
11805
  // src/plugins/claude-code/plugin.ts
11246
- import fs5 from "fs";
11247
- import path3 from "path";
11806
+ import fs6 from "fs";
11807
+ import path4 from "path";
11248
11808
  import readline from "readline";
11249
11809
  var ClaudeCodePlugin = class {
11250
11810
  id = "claude-code";
11251
11811
  displayName = "Claude Code";
11252
11812
  async *discover(env) {
11253
- const projectsDir = path3.join(env.homeDir, ".claude", "projects");
11254
- if (!fs5.existsSync(projectsDir)) return;
11255
- const projects = fs5.readdirSync(projectsDir, { withFileTypes: true });
11813
+ const projectsDir = path4.join(env.homeDir, ".claude", "projects");
11814
+ if (!fs6.existsSync(projectsDir)) return;
11815
+ const projects = fs6.readdirSync(projectsDir, { withFileTypes: true });
11256
11816
  for (const project of projects) {
11257
11817
  if (!project.isDirectory()) continue;
11258
- const projectPath = path3.join(projectsDir, project.name);
11259
- const files = fs5.readdirSync(projectPath, { withFileTypes: true });
11818
+ const projectPath = path4.join(projectsDir, project.name);
11819
+ const files = fs6.readdirSync(projectPath, { withFileTypes: true });
11260
11820
  for (const file of files) {
11261
11821
  if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
11262
- const sourcePath = path3.join(projectPath, file.name);
11822
+ const sourcePath = path4.join(projectPath, file.name);
11263
11823
  const sourceId = file.name.replace(/\.jsonl$/, "");
11264
11824
  const cwd = await readCwdFromJsonl(sourcePath);
11265
11825
  yield {
11266
11826
  sourceId,
11267
11827
  sourcePath,
11268
11828
  watchPaths: [sourcePath],
11269
- project: cwd ? path3.basename(cwd) : void 0,
11829
+ project: cwd ? path4.basename(cwd) : void 0,
11270
11830
  projectPath: cwd ?? void 0
11271
11831
  };
11272
11832
  }
11273
11833
  }
11274
11834
  }
11275
11835
  async *extractRaw(ref) {
11276
- if (!fs5.existsSync(ref.sourcePath)) return;
11277
- const stream = fs5.createReadStream(ref.sourcePath, { encoding: "utf-8" });
11278
- const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
11836
+ if (!fs6.existsSync(ref.sourcePath)) return;
11837
+ const stream2 = fs6.createReadStream(ref.sourcePath, { encoding: "utf-8" });
11838
+ const rl = readline.createInterface({ input: stream2, crlfDelay: Infinity });
11279
11839
  let lineNo = 0;
11280
11840
  for await (const line of rl) {
11281
11841
  lineNo += 1;
@@ -11323,8 +11883,8 @@ var ClaudeCodePlugin = class {
11323
11883
  };
11324
11884
  async function readCwdFromJsonl(sourcePath) {
11325
11885
  try {
11326
- const stream = fs5.createReadStream(sourcePath, { encoding: "utf-8" });
11327
- const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
11886
+ const stream2 = fs6.createReadStream(sourcePath, { encoding: "utf-8" });
11887
+ const rl = readline.createInterface({ input: stream2, crlfDelay: Infinity });
11328
11888
  try {
11329
11889
  for await (const line of rl) {
11330
11890
  if (!line) continue;
@@ -11340,7 +11900,7 @@ async function readCwdFromJsonl(sourcePath) {
11340
11900
  }
11341
11901
  } finally {
11342
11902
  rl.close();
11343
- stream.destroy();
11903
+ stream2.destroy();
11344
11904
  }
11345
11905
  } catch {
11346
11906
  }
@@ -11429,10 +11989,131 @@ Environment:
11429
11989
  PORT=<n> Set the HTTP port (overridden by --port)
11430
11990
  `;
11431
11991
 
11992
+ // src/config/data-dir.ts
11993
+ import path5 from "path";
11994
+ function resolveDataDir(env, homeDir) {
11995
+ const override = env.CHAT_LOGBOOK_DATA_DIR?.trim();
11996
+ if (override) {
11997
+ return path5.resolve(override);
11998
+ }
11999
+ return path5.join(homeDir, ".chat-logbook");
12000
+ }
12001
+
12002
+ // src/list-counts.ts
12003
+ import fs7 from "fs";
12004
+ import path6 from "path";
12005
+ import Database3 from "better-sqlite3";
12006
+ var ARCHIVE_DB2 = "archive.db";
12007
+ var METADATA_DB2 = "metadata.db";
12008
+ function createChatCountsQuery({
12009
+ dataDir: dataDir2
12010
+ }) {
12011
+ const archive2 = new Database3(path6.join(dataDir2, ARCHIVE_DB2), {
12012
+ readonly: true
12013
+ });
12014
+ const metadataPath = path6.join(dataDir2, METADATA_DB2);
12015
+ const hasMetadata = fs7.existsSync(metadataPath);
12016
+ if (hasMetadata) {
12017
+ archive2.prepare("ATTACH DATABASE ? AS meta").run(metadataPath);
12018
+ }
12019
+ function queryCounts({
12020
+ includeTrashed = false
12021
+ }) {
12022
+ const viewClause = viewPredicate(includeTrashed, hasMetadata);
12023
+ const total = archive2.prepare(`SELECT count(*) AS n FROM chats c ${viewClause}`).get().n;
12024
+ const projects = archive2.prepare(
12025
+ `SELECT coalesce(c.project, '') AS project,
12026
+ count(*) AS count,
12027
+ max(c.updated_at) AS lastActiveAt
12028
+ FROM chats c ${viewClause}
12029
+ GROUP BY coalesce(c.project, '')`
12030
+ ).all();
12031
+ const tags3 = hasMetadata ? archive2.prepare(
12032
+ `SELECT ct.tag_id AS tagId, count(*) AS count
12033
+ FROM meta.chat_tags ct
12034
+ JOIN chats c ON c.id = ct.chat_id
12035
+ ${viewClause}
12036
+ GROUP BY ct.tag_id`
12037
+ ).all() : [];
12038
+ const untagged = hasMetadata ? archive2.prepare(
12039
+ `SELECT count(*) AS n
12040
+ FROM chats c ${viewClause}
12041
+ AND c.id NOT IN (SELECT chat_id FROM meta.chat_tags)`
12042
+ ).get().n : total;
12043
+ return { total, projects, tags: tags3, untagged };
12044
+ }
12045
+ function queryFilteredTotal({
12046
+ includeTrashed = false,
12047
+ projects,
12048
+ tags: tags3
12049
+ }) {
12050
+ const clauses = [];
12051
+ const params = [];
12052
+ if (hasMetadata) {
12053
+ clauses.push(
12054
+ includeTrashed ? "c.id IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)" : "c.id NOT IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)"
12055
+ );
12056
+ } else if (includeTrashed) {
12057
+ return 0;
12058
+ }
12059
+ const filter = buildFilterClauses({ projects, tags: tags3, hasMetadata });
12060
+ clauses.push(...filter.clauses);
12061
+ params.push(...filter.params);
12062
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
12063
+ return archive2.prepare(`SELECT count(*) AS n FROM chats c ${where}`).get(...params).n;
12064
+ }
12065
+ return {
12066
+ queryCounts,
12067
+ queryFilteredTotal,
12068
+ close() {
12069
+ archive2.close();
12070
+ }
12071
+ };
12072
+ }
12073
+ function viewPredicate(includeTrashed, hasMetadata) {
12074
+ if (!hasMetadata) {
12075
+ return includeTrashed ? "WHERE 0" : "";
12076
+ }
12077
+ return includeTrashed ? "WHERE c.id IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)" : "WHERE c.id NOT IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)";
12078
+ }
12079
+
12080
+ // src/list-events.ts
12081
+ function createListEventHub() {
12082
+ const listeners = /* @__PURE__ */ new Set();
12083
+ return {
12084
+ publish(event) {
12085
+ for (const listener of listeners) listener(event);
12086
+ },
12087
+ subscribe(listener) {
12088
+ listeners.add(listener);
12089
+ return () => {
12090
+ listeners.delete(listener);
12091
+ };
12092
+ }
12093
+ };
12094
+ }
12095
+
12096
+ // src/metadata/reconcile-title-sort-keys.ts
12097
+ function reconcileTitleSortKeys({
12098
+ archive: archive2,
12099
+ metadata: metadata2
12100
+ }) {
12101
+ const key = (agent, sourceId) => [agent, sourceId].join("\0");
12102
+ const firstTextByKey = new Map(
12103
+ archive2.read.listFirstUserTexts().map((r) => [key(r.agent, r.sourceId), r.text])
12104
+ );
12105
+ for (const row of archive2.read.listChatRows()) {
12106
+ const baseTitle = deriveBaseTitle(
12107
+ firstTextByKey.get(key(row.agent, row.sourceId))
12108
+ );
12109
+ metadata2.setTitleTextKey(row.id, computeSortKey(baseTitle));
12110
+ }
12111
+ }
12112
+
11432
12113
  // src/index.ts
11433
- var __dirname = path4.dirname(fileURLToPath2(import.meta.url));
11434
- var pkgPath = path4.join(__dirname, "../../package.json");
11435
- var pkg = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
12114
+ var __dirname = path7.dirname(fileURLToPath2(import.meta.url));
12115
+ var pkgPath = path7.join(__dirname, "../../package.json");
12116
+ var pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
11436
12117
  var action = parseCliArgs(process.argv.slice(2), {
11437
12118
  PORT: process.env.PORT
11438
12119
  });
@@ -11449,27 +12130,58 @@ if (action.kind === "error") {
11449
12130
  process.exit(1);
11450
12131
  }
11451
12132
  updateNotifier({ pkg }).notify({ defer: false, isGlobal: true });
11452
- var dataDir = path4.join(os.homedir(), ".chat-logbook");
11453
- var webDistDir = path4.join(__dirname, "../../web/dist");
12133
+ var dataDir = resolveDataDir(
12134
+ { CHAT_LOGBOOK_DATA_DIR: process.env.CHAT_LOGBOOK_DATA_DIR },
12135
+ os.homedir()
12136
+ );
12137
+ var webDistDir = path7.join(__dirname, "../../web/dist");
11454
12138
  var port = action.port;
11455
12139
  var archive = createArchiveRepository({ dataDir });
11456
12140
  var checkpoint = createCheckpointRepository({ dataDir });
11457
12141
  var metadata = createMetadataRepository({ dataDir });
11458
12142
  var tags2 = createTagRepository({ dataDir });
11459
- var app = createApp({ archive, metadata, tags: tags2, webDistDir });
12143
+ var pageQuery = createChatPageQuery({ dataDir });
12144
+ var countsQuery = createChatCountsQuery({ dataDir });
12145
+ var listEvents = createListEventHub();
12146
+ var app = createApp({
12147
+ archive,
12148
+ metadata,
12149
+ tags: tags2,
12150
+ pageQuery,
12151
+ countsQuery,
12152
+ listEvents,
12153
+ webDistDir
12154
+ });
11460
12155
  var initialIngest = startIngestionInBackground({
11461
12156
  plugins,
11462
12157
  archive,
11463
12158
  checkpoint,
11464
12159
  env: { homeDir: os.homedir() }
11465
12160
  });
12161
+ function reconcileTitles() {
12162
+ try {
12163
+ reconcileTitleSortKeys({ archive, metadata });
12164
+ } catch (err) {
12165
+ console.error("[title-sort-key] reconcile failed:", err);
12166
+ }
12167
+ }
11466
12168
  var watcher = startWatcher({
11467
12169
  plugins,
11468
12170
  archive,
11469
12171
  checkpoint,
11470
- env: { homeDir: os.homedir() }
12172
+ env: { homeDir: os.homedir() },
12173
+ // Each watcher-driven ingest can add a chat or change a first user message;
12174
+ // refresh the Title keys so the Title axis stays current, then push a
12175
+ // `changed` event so connected clients reconcile their loaded window (#132).
12176
+ onIngest: () => {
12177
+ reconcileTitles();
12178
+ listEvents.publish({ type: "changed" });
12179
+ }
11471
12180
  });
11472
- void initialIngest.done.then(() => watcher.ready).catch(() => {
12181
+ void initialIngest.done.then(() => {
12182
+ reconcileTitles();
12183
+ return watcher.ready;
12184
+ }).catch(() => {
11473
12185
  });
11474
12186
  function shutdown() {
11475
12187
  void watcher.close().finally(() => process.exit(0));