@threadbase-sh/streamer 1.20.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -26078,7 +26078,7 @@ var require_thread_stream = __commonJS({
26078
26078
  var { version: version2 } = require_package();
26079
26079
  var { EventEmitter: EventEmitter6 } = require("events");
26080
26080
  var { Worker: Worker2 } = require("worker_threads");
26081
- var { join: join24 } = require("path");
26081
+ var { join: join25 } = require("path");
26082
26082
  var { pathToFileURL } = require("url");
26083
26083
  var { wait } = require_wait();
26084
26084
  var {
@@ -26129,7 +26129,7 @@ var require_thread_stream = __commonJS({
26129
26129
  function createWorker(stream, opts) {
26130
26130
  const { filename, workerData } = opts;
26131
26131
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
26132
- const toExecute = bundlerOverrides["thread-stream-worker"] || join24(__dirname, "lib", "worker.js");
26132
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join25(__dirname, "lib", "worker.js");
26133
26133
  const worker = new Worker2(toExecute, {
26134
26134
  ...opts.workerOpts,
26135
26135
  name: opts.workerOpts?.name || "thread-stream",
@@ -26597,7 +26597,7 @@ var require_transport = __commonJS({
26597
26597
  var { createRequire } = require("module");
26598
26598
  var { existsSync: existsSync11 } = require("fs");
26599
26599
  var getCallers = require_caller();
26600
- var { join: join24, isAbsolute: isAbsolute3, sep: sep2 } = require("path");
26600
+ var { join: join25, isAbsolute: isAbsolute3, sep: sep2 } = require("path");
26601
26601
  var { fileURLToPath: fileURLToPath3 } = require("url");
26602
26602
  var sleep = require_atomic_sleep();
26603
26603
  var onExit = require_on_exit_leak_free();
@@ -26750,7 +26750,7 @@ var require_transport = __commonJS({
26750
26750
  throw new Error("only one of target or targets can be specified");
26751
26751
  }
26752
26752
  if (targets) {
26753
- target = bundlerOverrides["pino-worker"] || join24(__dirname, "worker.js");
26753
+ target = bundlerOverrides["pino-worker"] || join25(__dirname, "worker.js");
26754
26754
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
26755
26755
  return {
26756
26756
  ...dest,
@@ -26768,7 +26768,7 @@ var require_transport = __commonJS({
26768
26768
  });
26769
26769
  });
26770
26770
  } else if (pipeline2) {
26771
- target = bundlerOverrides["pino-worker"] || join24(__dirname, "worker.js");
26771
+ target = bundlerOverrides["pino-worker"] || join25(__dirname, "worker.js");
26772
26772
  options.pipelines = [pipeline2.map((dest) => {
26773
26773
  return {
26774
26774
  ...dest,
@@ -26791,7 +26791,7 @@ var require_transport = __commonJS({
26791
26791
  return origin;
26792
26792
  }
26793
26793
  if (origin === "pino/file") {
26794
- return join24(__dirname, "..", "file.js");
26794
+ return join25(__dirname, "..", "file.js");
26795
26795
  }
26796
26796
  let fixTarget2;
26797
26797
  for (const filePath of callers) {
@@ -27772,7 +27772,7 @@ var require_safe_stable_stringify = __commonJS({
27772
27772
  return circularValue;
27773
27773
  }
27774
27774
  let res = "";
27775
- let join24 = ",";
27775
+ let join25 = ",";
27776
27776
  const originalIndentation = indentation;
27777
27777
  if (Array.isArray(value)) {
27778
27778
  if (value.length === 0) {
@@ -27786,7 +27786,7 @@ var require_safe_stable_stringify = __commonJS({
27786
27786
  indentation += spacer;
27787
27787
  res += `
27788
27788
  ${indentation}`;
27789
- join24 = `,
27789
+ join25 = `,
27790
27790
  ${indentation}`;
27791
27791
  }
27792
27792
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -27794,13 +27794,13 @@ ${indentation}`;
27794
27794
  for (; i < maximumValuesToStringify - 1; i++) {
27795
27795
  const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
27796
27796
  res += tmp2 !== void 0 ? tmp2 : "null";
27797
- res += join24;
27797
+ res += join25;
27798
27798
  }
27799
27799
  const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
27800
27800
  res += tmp !== void 0 ? tmp : "null";
27801
27801
  if (value.length - 1 > maximumBreadth) {
27802
27802
  const removedKeys = value.length - maximumBreadth - 1;
27803
- res += `${join24}"... ${getItemCount(removedKeys)} not stringified"`;
27803
+ res += `${join25}"... ${getItemCount(removedKeys)} not stringified"`;
27804
27804
  }
27805
27805
  if (spacer !== "") {
27806
27806
  res += `
@@ -27821,7 +27821,7 @@ ${originalIndentation}`;
27821
27821
  let separator = "";
27822
27822
  if (spacer !== "") {
27823
27823
  indentation += spacer;
27824
- join24 = `,
27824
+ join25 = `,
27825
27825
  ${indentation}`;
27826
27826
  whitespace = " ";
27827
27827
  }
@@ -27835,13 +27835,13 @@ ${indentation}`;
27835
27835
  const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
27836
27836
  if (tmp !== void 0) {
27837
27837
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
27838
- separator = join24;
27838
+ separator = join25;
27839
27839
  }
27840
27840
  }
27841
27841
  if (keyLength > maximumBreadth) {
27842
27842
  const removedKeys = keyLength - maximumBreadth;
27843
27843
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
27844
- separator = join24;
27844
+ separator = join25;
27845
27845
  }
27846
27846
  if (spacer !== "" && separator.length > 1) {
27847
27847
  res = `
@@ -27882,7 +27882,7 @@ ${originalIndentation}`;
27882
27882
  }
27883
27883
  const originalIndentation = indentation;
27884
27884
  let res = "";
27885
- let join24 = ",";
27885
+ let join25 = ",";
27886
27886
  if (Array.isArray(value)) {
27887
27887
  if (value.length === 0) {
27888
27888
  return "[]";
@@ -27895,7 +27895,7 @@ ${originalIndentation}`;
27895
27895
  indentation += spacer;
27896
27896
  res += `
27897
27897
  ${indentation}`;
27898
- join24 = `,
27898
+ join25 = `,
27899
27899
  ${indentation}`;
27900
27900
  }
27901
27901
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -27903,13 +27903,13 @@ ${indentation}`;
27903
27903
  for (; i < maximumValuesToStringify - 1; i++) {
27904
27904
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
27905
27905
  res += tmp2 !== void 0 ? tmp2 : "null";
27906
- res += join24;
27906
+ res += join25;
27907
27907
  }
27908
27908
  const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
27909
27909
  res += tmp !== void 0 ? tmp : "null";
27910
27910
  if (value.length - 1 > maximumBreadth) {
27911
27911
  const removedKeys = value.length - maximumBreadth - 1;
27912
- res += `${join24}"... ${getItemCount(removedKeys)} not stringified"`;
27912
+ res += `${join25}"... ${getItemCount(removedKeys)} not stringified"`;
27913
27913
  }
27914
27914
  if (spacer !== "") {
27915
27915
  res += `
@@ -27922,7 +27922,7 @@ ${originalIndentation}`;
27922
27922
  let whitespace = "";
27923
27923
  if (spacer !== "") {
27924
27924
  indentation += spacer;
27925
- join24 = `,
27925
+ join25 = `,
27926
27926
  ${indentation}`;
27927
27927
  whitespace = " ";
27928
27928
  }
@@ -27931,7 +27931,7 @@ ${indentation}`;
27931
27931
  const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
27932
27932
  if (tmp !== void 0) {
27933
27933
  res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
27934
- separator = join24;
27934
+ separator = join25;
27935
27935
  }
27936
27936
  }
27937
27937
  if (spacer !== "" && separator.length > 1) {
@@ -27989,20 +27989,20 @@ ${originalIndentation}`;
27989
27989
  indentation += spacer;
27990
27990
  let res2 = `
27991
27991
  ${indentation}`;
27992
- const join25 = `,
27992
+ const join26 = `,
27993
27993
  ${indentation}`;
27994
27994
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
27995
27995
  let i = 0;
27996
27996
  for (; i < maximumValuesToStringify - 1; i++) {
27997
27997
  const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
27998
27998
  res2 += tmp2 !== void 0 ? tmp2 : "null";
27999
- res2 += join25;
27999
+ res2 += join26;
28000
28000
  }
28001
28001
  const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
28002
28002
  res2 += tmp !== void 0 ? tmp : "null";
28003
28003
  if (value.length - 1 > maximumBreadth) {
28004
28004
  const removedKeys = value.length - maximumBreadth - 1;
28005
- res2 += `${join25}"... ${getItemCount(removedKeys)} not stringified"`;
28005
+ res2 += `${join26}"... ${getItemCount(removedKeys)} not stringified"`;
28006
28006
  }
28007
28007
  res2 += `
28008
28008
  ${originalIndentation}`;
@@ -28018,16 +28018,16 @@ ${originalIndentation}`;
28018
28018
  return '"[Object]"';
28019
28019
  }
28020
28020
  indentation += spacer;
28021
- const join24 = `,
28021
+ const join25 = `,
28022
28022
  ${indentation}`;
28023
28023
  let res = "";
28024
28024
  let separator = "";
28025
28025
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
28026
28026
  if (isTypedArrayWithEntries(value)) {
28027
- res += stringifyTypedArray(value, join24, maximumBreadth);
28027
+ res += stringifyTypedArray(value, join25, maximumBreadth);
28028
28028
  keys = keys.slice(value.length);
28029
28029
  maximumPropertiesToStringify -= value.length;
28030
- separator = join24;
28030
+ separator = join25;
28031
28031
  }
28032
28032
  if (deterministic) {
28033
28033
  keys = sort(keys, comparator);
@@ -28038,13 +28038,13 @@ ${indentation}`;
28038
28038
  const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
28039
28039
  if (tmp !== void 0) {
28040
28040
  res += `${separator}${strEscape(key2)}: ${tmp}`;
28041
- separator = join24;
28041
+ separator = join25;
28042
28042
  }
28043
28043
  }
28044
28044
  if (keyLength > maximumBreadth) {
28045
28045
  const removedKeys = keyLength - maximumBreadth;
28046
28046
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
28047
- separator = join24;
28047
+ separator = join25;
28048
28048
  }
28049
28049
  if (separator !== "") {
28050
28050
  res = `
@@ -124399,20 +124399,20 @@ var require_semver2 = __commonJS({
124399
124399
 
124400
124400
  // src/lifecycle/constants.ts
124401
124401
  function installDir() {
124402
- return process.env.THREADBASE_INSTALL_DIR ?? (0, import_node_path21.join)((0, import_node_os4.homedir)(), ".threadbase");
124402
+ return process.env.THREADBASE_INSTALL_DIR ?? (0, import_node_path22.join)((0, import_node_os4.homedir)(), ".threadbase");
124403
124403
  }
124404
124404
  function markerPath() {
124405
- return (0, import_node_path21.join)(installDir(), "prod-suspended.json");
124405
+ return (0, import_node_path22.join)(installDir(), "prod-suspended.json");
124406
124406
  }
124407
124407
  function prefsPath() {
124408
- return (0, import_node_path21.join)(installDir(), "dev-prefs.json");
124408
+ return (0, import_node_path22.join)(installDir(), "dev-prefs.json");
124409
124409
  }
124410
- var import_node_os4, import_node_path21, LAUNCHD_LABEL, TASK_NAME;
124410
+ var import_node_os4, import_node_path22, LAUNCHD_LABEL, TASK_NAME;
124411
124411
  var init_constants = __esm({
124412
124412
  "src/lifecycle/constants.ts"() {
124413
124413
  "use strict";
124414
124414
  import_node_os4 = require("os");
124415
- import_node_path21 = require("path");
124415
+ import_node_path22 = require("path");
124416
124416
  LAUNCHD_LABEL = "com.ronen.threadbase";
124417
124417
  TASK_NAME = process.env.THREADBASE_TASK_NAME ?? "Threadbase";
124418
124418
  }
@@ -124487,8 +124487,8 @@ function darwinPlistPath() {
124487
124487
  return `${process.env.HOME}/Library/LaunchAgents/${resolveLoadedLabel()}.plist`;
124488
124488
  }
124489
124489
  function getLogPaths() {
124490
- const dir = (0, import_node_path22.join)(installDir(), "logs");
124491
- return { stdout: (0, import_node_path22.join)(dir, "stdout.log"), stderr: (0, import_node_path22.join)(dir, "stderr.log") };
124490
+ const dir = (0, import_node_path23.join)(installDir(), "logs");
124491
+ return { stdout: (0, import_node_path23.join)(dir, "stdout.log"), stderr: (0, import_node_path23.join)(dir, "stderr.log") };
124492
124492
  }
124493
124493
  function getAgentPid() {
124494
124494
  try {
@@ -124508,12 +124508,12 @@ function getAgentPid() {
124508
124508
  return null;
124509
124509
  }
124510
124510
  }
124511
- var import_node_child_process4, import_node_path22, BREW_LAUNCHD_LABEL2;
124511
+ var import_node_child_process4, import_node_path23, BREW_LAUNCHD_LABEL2;
124512
124512
  var init_launchd = __esm({
124513
124513
  "src/lifecycle/launchd.ts"() {
124514
124514
  "use strict";
124515
124515
  import_node_child_process4 = require("child_process");
124516
- import_node_path22 = require("path");
124516
+ import_node_path23 = require("path");
124517
124517
  init_constants();
124518
124518
  BREW_LAUNCHD_LABEL2 = "homebrew.mxcl.tb-streamer";
124519
124519
  }
@@ -124538,9 +124538,9 @@ var init_marker_schema = __esm({
124538
124538
 
124539
124539
  // src/lifecycle/marker.ts
124540
124540
  function readMarker() {
124541
- if (!(0, import_node_fs18.existsSync)(markerPath())) return null;
124541
+ if (!(0, import_node_fs19.existsSync)(markerPath())) return null;
124542
124542
  try {
124543
- const raw2 = (0, import_node_fs18.readFileSync)(markerPath(), "utf8");
124543
+ const raw2 = (0, import_node_fs19.readFileSync)(markerPath(), "utf8");
124544
124544
  const parsed = JSON.parse(raw2);
124545
124545
  return MarkerSchema.parse(parsed);
124546
124546
  } catch (err) {
@@ -124552,21 +124552,21 @@ function readMarker() {
124552
124552
  }
124553
124553
  function writeMarker(marker) {
124554
124554
  MarkerSchema.parse(marker);
124555
- (0, import_node_fs18.mkdirSync)((0, import_node_path23.dirname)(markerPath()), { recursive: true });
124555
+ (0, import_node_fs19.mkdirSync)((0, import_node_path24.dirname)(markerPath()), { recursive: true });
124556
124556
  const tmp = `${markerPath()}.tmp`;
124557
- (0, import_node_fs18.writeFileSync)(tmp, `${JSON.stringify(marker, null, 2)}
124557
+ (0, import_node_fs19.writeFileSync)(tmp, `${JSON.stringify(marker, null, 2)}
124558
124558
  `, { mode: 384 });
124559
- (0, import_node_fs18.renameSync)(tmp, markerPath());
124559
+ (0, import_node_fs19.renameSync)(tmp, markerPath());
124560
124560
  }
124561
124561
  function clearMarker() {
124562
- if ((0, import_node_fs18.existsSync)(markerPath())) (0, import_node_fs18.rmSync)(markerPath());
124562
+ if ((0, import_node_fs19.existsSync)(markerPath())) (0, import_node_fs19.rmSync)(markerPath());
124563
124563
  }
124564
- var import_node_fs18, import_node_path23, log3;
124564
+ var import_node_fs19, import_node_path24, log3;
124565
124565
  var init_marker = __esm({
124566
124566
  "src/lifecycle/marker.ts"() {
124567
124567
  "use strict";
124568
- import_node_fs18 = require("fs");
124569
- import_node_path23 = require("path");
124568
+ import_node_fs19 = require("fs");
124569
+ import_node_path24 = require("path");
124570
124570
  init_logger();
124571
124571
  init_constants();
124572
124572
  init_marker_schema();
@@ -124661,9 +124661,9 @@ var init_platform = __esm({
124661
124661
  // src/lifecycle/prefs.ts
124662
124662
  function readPrefs() {
124663
124663
  const path2 = prefsPath();
124664
- if (!(0, import_node_fs20.existsSync)(path2)) return { repos: {} };
124664
+ if (!(0, import_node_fs21.existsSync)(path2)) return { repos: {} };
124665
124665
  try {
124666
- return PrefsSchema.parse(JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8")));
124666
+ return PrefsSchema.parse(JSON.parse((0, import_node_fs21.readFileSync)(path2, "utf8")));
124667
124667
  } catch (err) {
124668
124668
  log5.warn(`prefs at ${path2} are malformed; treating as empty`, {
124669
124669
  error: err instanceof Error ? err.message : String(err)
@@ -124673,11 +124673,11 @@ function readPrefs() {
124673
124673
  }
124674
124674
  function savePrefs(p2) {
124675
124675
  const path2 = prefsPath();
124676
- (0, import_node_fs20.mkdirSync)((0, import_node_path24.dirname)(path2), { recursive: true });
124676
+ (0, import_node_fs21.mkdirSync)((0, import_node_path25.dirname)(path2), { recursive: true });
124677
124677
  const tmp = `${path2}.tmp`;
124678
- (0, import_node_fs20.writeFileSync)(tmp, `${JSON.stringify(p2, null, 2)}
124678
+ (0, import_node_fs21.writeFileSync)(tmp, `${JSON.stringify(p2, null, 2)}
124679
124679
  `, { mode: 384 });
124680
- (0, import_node_fs20.renameSync)(tmp, path2);
124680
+ (0, import_node_fs21.renameSync)(tmp, path2);
124681
124681
  }
124682
124682
  function writePrefForRepo(repoToplevel, choice) {
124683
124683
  const p2 = readPrefs();
@@ -124698,14 +124698,14 @@ function forgetRepo(repoToplevel) {
124698
124698
  }
124699
124699
  function forgetAll() {
124700
124700
  const path2 = prefsPath();
124701
- if ((0, import_node_fs20.existsSync)(path2)) (0, import_node_fs20.rmSync)(path2);
124701
+ if ((0, import_node_fs21.existsSync)(path2)) (0, import_node_fs21.rmSync)(path2);
124702
124702
  }
124703
- var import_node_fs20, import_node_path24, log5, PrefsSchema;
124703
+ var import_node_fs21, import_node_path25, log5, PrefsSchema;
124704
124704
  var init_prefs = __esm({
124705
124705
  "src/lifecycle/prefs.ts"() {
124706
124706
  "use strict";
124707
- import_node_fs20 = require("fs");
124708
- import_node_path24 = require("path");
124707
+ import_node_fs21 = require("fs");
124708
+ import_node_path25 = require("path");
124709
124709
  init_zod();
124710
124710
  init_logger();
124711
124711
  init_constants();
@@ -124927,7 +124927,7 @@ __export(setKey_exports, {
124927
124927
  async function defaultReadStdin() {
124928
124928
  return new Promise((resolve6, reject) => {
124929
124929
  const chunks = [];
124930
- process.stdin.on("data", (chunk) => chunks.push(chunk));
124930
+ process.stdin.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
124931
124931
  process.stdin.on("end", () => resolve6(Buffer.concat(chunks).toString("utf-8")));
124932
124932
  process.stdin.on("error", reject);
124933
124933
  });
@@ -136445,13 +136445,23 @@ var authMiddleware = (deps) => async (c, next) => {
136445
136445
  };
136446
136446
 
136447
136447
  // src/api/middleware/cors.middleware.ts
136448
+ var ALLOWED_ORIGINS = /* @__PURE__ */ new Set([
136449
+ "http://localhost:8081",
136450
+ "http://localhost:19006",
136451
+ "http://localhost:3000"
136452
+ ]);
136448
136453
  var corsMiddleware = () => async (c, next) => {
136449
- c.res.headers.set("Access-Control-Allow-Origin", "*");
136450
- c.res.headers.set("Access-Control-Allow-Methods", "GET, POST, PATCH, OPTIONS");
136451
- c.res.headers.set("Access-Control-Allow-Headers", "Authorization, Content-Type, If-None-Match");
136452
- c.res.headers.set("Access-Control-Expose-Headers", "ETag");
136454
+ const origin = c.req.header("origin");
136455
+ const allowedOrigin = origin && ALLOWED_ORIGINS.has(origin) ? origin : null;
136456
+ if (allowedOrigin) {
136457
+ c.res.headers.set("Access-Control-Allow-Origin", allowedOrigin);
136458
+ c.res.headers.set("Vary", "Origin");
136459
+ c.res.headers.set("Access-Control-Allow-Methods", "GET, POST, PATCH, OPTIONS");
136460
+ c.res.headers.set("Access-Control-Allow-Headers", "Authorization, Content-Type, If-None-Match");
136461
+ c.res.headers.set("Access-Control-Expose-Headers", "ETag");
136462
+ }
136453
136463
  if (c.req.method === "OPTIONS") {
136454
- return c.newResponse(null, 204);
136464
+ return c.newResponse(null, allowedOrigin ? 204 : 403);
136455
136465
  }
136456
136466
  await next();
136457
136467
  };
@@ -136595,6 +136605,19 @@ var createMiscRoutes = (deps) => {
136595
136605
  });
136596
136606
  });
136597
136607
  app.get("/api/profiles", (c) => c.json([]));
136608
+ app.post("/api/auth/rotate", (c) => {
136609
+ if (deps.localNoAuth) {
136610
+ return c.json({ error: "key rotation is disabled while localNoAuth is active" }, 403);
136611
+ }
136612
+ const { newKey, persisted } = deps.rotateApiKey();
136613
+ return c.json({
136614
+ apiKey: newKey,
136615
+ persisted,
136616
+ ...persisted ? {} : {
136617
+ warning: "Key rotated in memory only. The server was started with --api-key, so the old key will be restored on restart. Remove --api-key and let the server manage the key via ~/.threadbase/server.yaml for rotation to survive restarts."
136618
+ }
136619
+ });
136620
+ });
136598
136621
  app.post("/api/push/register", (c) => c.json({ ok: true }));
136599
136622
  app.post("/api/__update", async (c) => {
136600
136623
  const cfg = loadUpdateConfig();
@@ -136796,6 +136819,11 @@ var ALREADY_HANDLED4 = 597;
136796
136819
  var alreadyHandled4 = () => new Response(null, { status: ALREADY_HANDLED4 });
136797
136820
  var createProjectRoutes = (deps) => {
136798
136821
  const app = new Hono2();
136822
+ app.get("/", (c) => {
136823
+ const url2 = new URL(c.req.url);
136824
+ deps.handleListProjects(url2, c.env.outgoing);
136825
+ return alreadyHandled4();
136826
+ });
136799
136827
  app.get("/popular", (c) => {
136800
136828
  const url2 = new URL(c.req.url);
136801
136829
  deps.handleGetPopularProjects(url2, c.env.outgoing);
@@ -136814,11 +136842,6 @@ var createScannerRoutes = (deps) => {
136814
136842
  await deps.handleSearch(url2, c.env.outgoing);
136815
136843
  return alreadyHandled5();
136816
136844
  });
136817
- app.get("/project-chats", async (c) => {
136818
- const url2 = new URL(c.req.url);
136819
- await deps.handleListProjectChats(url2, c.env.outgoing);
136820
- return alreadyHandled5();
136821
- });
136822
136845
  return app;
136823
136846
  };
136824
136847
 
@@ -138082,517 +138105,39 @@ async function recordUpload(pool2, instanceId, row) {
138082
138105
  );
138083
138106
  }
138084
138107
 
138085
- // src/schemas/queryParams.schema.ts
138086
- init_zod();
138087
- var ListProjectChatsQuerySchema = external_exports.object({
138088
- refresh: external_exports.enum(["1"]).optional(),
138089
- refreshConversations: external_exports.enum(["1"]).optional()
138090
- });
138091
-
138092
- // src/services/cache/cacheMetadata.ts
138093
- function getCacheMetadata(repo, key) {
138094
- return repo.getCacheMetadata(key);
138095
- }
138096
- function setCacheMetadata(repo, key, value) {
138097
- repo.setCacheMetadata(key, value);
138098
- }
138099
-
138100
- // node_modules/date-fns/constants.js
138101
- var daysInYear = 365.2425;
138102
- var maxTime = Math.pow(10, 8) * 24 * 60 * 60 * 1e3;
138103
- var minTime = -maxTime;
138104
- var millisecondsInMinute = 6e4;
138105
- var millisecondsInHour = 36e5;
138106
- var secondsInHour = 3600;
138107
- var secondsInDay = secondsInHour * 24;
138108
- var secondsInWeek = secondsInDay * 7;
138109
- var secondsInYear = secondsInDay * daysInYear;
138110
- var secondsInMonth = secondsInYear / 12;
138111
- var secondsInQuarter = secondsInMonth * 3;
138112
- var constructFromSymbol = /* @__PURE__ */ Symbol.for("constructDateFrom");
138113
-
138114
- // node_modules/date-fns/constructFrom.js
138115
- function constructFrom(date5, value) {
138116
- if (typeof date5 === "function") return date5(value);
138117
- if (date5 && typeof date5 === "object" && constructFromSymbol in date5)
138118
- return date5[constructFromSymbol](value);
138119
- if (date5 instanceof Date) return new date5.constructor(value);
138120
- return new Date(value);
138121
- }
138122
-
138123
- // node_modules/date-fns/toDate.js
138124
- function toDate(argument, context) {
138125
- return constructFrom(context || argument, argument);
138126
- }
138127
-
138128
- // node_modules/date-fns/compareDesc.js
138129
- function compareDesc(dateLeft, dateRight) {
138130
- const diff = +toDate(dateLeft) - +toDate(dateRight);
138131
- if (diff > 0) return -1;
138132
- else if (diff < 0) return 1;
138133
- return diff;
138134
- }
138135
-
138136
- // node_modules/date-fns/isDate.js
138137
- function isDate(value) {
138138
- return value instanceof Date || typeof value === "object" && Object.prototype.toString.call(value) === "[object Date]";
138139
- }
138140
-
138141
- // node_modules/date-fns/isValid.js
138142
- function isValid(date5) {
138143
- return !(!isDate(date5) && typeof date5 !== "number" || isNaN(+toDate(date5)));
138144
- }
138145
-
138146
- // node_modules/date-fns/parseISO.js
138147
- function parseISO(argument, options) {
138148
- const invalidDate = () => constructFrom(options?.in, NaN);
138149
- const additionalDigits = options?.additionalDigits ?? 2;
138150
- const dateStrings = splitDateString(argument);
138151
- let date5;
138152
- if (dateStrings.date) {
138153
- const parseYearResult = parseYear(dateStrings.date, additionalDigits);
138154
- date5 = parseDate(parseYearResult.restDateString, parseYearResult.year);
138155
- }
138156
- if (!date5 || isNaN(+date5)) return invalidDate();
138157
- const timestamp = +date5;
138158
- let time3 = 0;
138159
- let offset;
138160
- if (dateStrings.time) {
138161
- time3 = parseTime(dateStrings.time);
138162
- if (isNaN(time3)) return invalidDate();
138163
- }
138164
- if (dateStrings.timezone) {
138165
- offset = parseTimezone(dateStrings.timezone);
138166
- if (isNaN(offset)) return invalidDate();
138167
- } else {
138168
- const tmpDate = new Date(timestamp + time3);
138169
- const result = toDate(0, options?.in);
138170
- result.setFullYear(
138171
- tmpDate.getUTCFullYear(),
138172
- tmpDate.getUTCMonth(),
138173
- tmpDate.getUTCDate()
138174
- );
138175
- result.setHours(
138176
- tmpDate.getUTCHours(),
138177
- tmpDate.getUTCMinutes(),
138178
- tmpDate.getUTCSeconds(),
138179
- tmpDate.getUTCMilliseconds()
138180
- );
138181
- return result;
138182
- }
138183
- return toDate(timestamp + time3 + offset, options?.in);
138184
- }
138185
- var patterns = {
138186
- dateTimeDelimiter: /[T ]/,
138187
- timeZoneDelimiter: /[Z ]/i,
138188
- timezone: /([Z+-].*)$/
138189
- };
138190
- var dateRegex = /^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/;
138191
- var timeRegex = /^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/;
138192
- var timezoneRegex = /^([+-])(\d{2})(?::?(\d{2}))?$/;
138193
- function splitDateString(dateString) {
138194
- const dateStrings = {};
138195
- const array2 = dateString.split(patterns.dateTimeDelimiter);
138196
- let timeString;
138197
- if (array2.length > 2) {
138198
- return dateStrings;
138199
- }
138200
- if (/:/.test(array2[0])) {
138201
- timeString = array2[0];
138202
- } else {
138203
- dateStrings.date = array2[0];
138204
- timeString = array2[1];
138205
- if (patterns.timeZoneDelimiter.test(dateStrings.date)) {
138206
- dateStrings.date = dateString.split(patterns.timeZoneDelimiter)[0];
138207
- timeString = dateString.substr(
138208
- dateStrings.date.length,
138209
- dateString.length
138210
- );
138211
- }
138212
- }
138213
- if (timeString) {
138214
- const token = patterns.timezone.exec(timeString);
138215
- if (token) {
138216
- dateStrings.time = timeString.replace(token[1], "");
138217
- dateStrings.timezone = token[1];
138218
- } else {
138219
- dateStrings.time = timeString;
138220
- }
138221
- }
138222
- return dateStrings;
138223
- }
138224
- function parseYear(dateString, additionalDigits) {
138225
- const regex = new RegExp(
138226
- "^(?:(\\d{4}|[+-]\\d{" + (4 + additionalDigits) + "})|(\\d{2}|[+-]\\d{" + (2 + additionalDigits) + "})$)"
138227
- );
138228
- const captures = dateString.match(regex);
138229
- if (!captures) return { year: NaN, restDateString: "" };
138230
- const year = captures[1] ? parseInt(captures[1]) : null;
138231
- const century = captures[2] ? parseInt(captures[2]) : null;
138232
- return {
138233
- year: century === null ? year : century * 100,
138234
- restDateString: dateString.slice((captures[1] || captures[2]).length)
138235
- };
138236
- }
138237
- function parseDate(dateString, year) {
138238
- if (year === null) return /* @__PURE__ */ new Date(NaN);
138239
- const captures = dateString.match(dateRegex);
138240
- if (!captures) return /* @__PURE__ */ new Date(NaN);
138241
- const isWeekDate = !!captures[4];
138242
- const dayOfYear = parseDateUnit(captures[1]);
138243
- const month = parseDateUnit(captures[2]) - 1;
138244
- const day = parseDateUnit(captures[3]);
138245
- const week = parseDateUnit(captures[4]);
138246
- const dayOfWeek = parseDateUnit(captures[5]) - 1;
138247
- if (isWeekDate) {
138248
- if (!validateWeekDate(year, week, dayOfWeek)) {
138249
- return /* @__PURE__ */ new Date(NaN);
138250
- }
138251
- return dayOfISOWeekYear(year, week, dayOfWeek);
138252
- } else {
138253
- const date5 = /* @__PURE__ */ new Date(0);
138254
- if (!validateDate(year, month, day) || !validateDayOfYearDate(year, dayOfYear)) {
138255
- return /* @__PURE__ */ new Date(NaN);
138256
- }
138257
- date5.setUTCFullYear(year, month, Math.max(dayOfYear, day));
138258
- return date5;
138259
- }
138260
- }
138261
- function parseDateUnit(value) {
138262
- return value ? parseInt(value) : 1;
138263
- }
138264
- function parseTime(timeString) {
138265
- const captures = timeString.match(timeRegex);
138266
- if (!captures) return NaN;
138267
- const hours = parseTimeUnit(captures[1]);
138268
- const minutes = parseTimeUnit(captures[2]);
138269
- const seconds = parseTimeUnit(captures[3]);
138270
- if (!validateTime(hours, minutes, seconds)) {
138271
- return NaN;
138272
- }
138273
- return hours * millisecondsInHour + minutes * millisecondsInMinute + seconds * 1e3;
138274
- }
138275
- function parseTimeUnit(value) {
138276
- return value && parseFloat(value.replace(",", ".")) || 0;
138277
- }
138278
- function parseTimezone(timezoneString) {
138279
- if (timezoneString === "Z") return 0;
138280
- const captures = timezoneString.match(timezoneRegex);
138281
- if (!captures) return 0;
138282
- const sign = captures[1] === "+" ? -1 : 1;
138283
- const hours = parseInt(captures[2]);
138284
- const minutes = captures[3] && parseInt(captures[3]) || 0;
138285
- if (!validateTimezone(hours, minutes)) {
138286
- return NaN;
138287
- }
138288
- return sign * (hours * millisecondsInHour + minutes * millisecondsInMinute);
138289
- }
138290
- function dayOfISOWeekYear(isoWeekYear, week, day) {
138291
- const date5 = /* @__PURE__ */ new Date(0);
138292
- date5.setUTCFullYear(isoWeekYear, 0, 4);
138293
- const fourthOfJanuaryDay = date5.getUTCDay() || 7;
138294
- const diff = (week - 1) * 7 + day + 1 - fourthOfJanuaryDay;
138295
- date5.setUTCDate(date5.getUTCDate() + diff);
138296
- return date5;
138297
- }
138298
- var daysInMonths = [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
138299
- function isLeapYearIndex(year) {
138300
- return year % 400 === 0 || year % 4 === 0 && year % 100 !== 0;
138301
- }
138302
- function validateDate(year, month, date5) {
138303
- return month >= 0 && month <= 11 && date5 >= 1 && date5 <= (daysInMonths[month] || (isLeapYearIndex(year) ? 29 : 28));
138304
- }
138305
- function validateDayOfYearDate(year, dayOfYear) {
138306
- return dayOfYear >= 1 && dayOfYear <= (isLeapYearIndex(year) ? 366 : 365);
138307
- }
138308
- function validateWeekDate(_year, week, day) {
138309
- return week >= 1 && week <= 53 && day >= 0 && day <= 6;
138310
- }
138311
- function validateTime(hours, minutes, seconds) {
138312
- if (hours === 24) {
138313
- return minutes === 0 && seconds === 0;
138314
- }
138315
- return seconds >= 0 && seconds < 60 && minutes >= 0 && minutes < 60 && hours >= 0 && hours < 25;
138316
- }
138317
- function validateTimezone(_hours, minutes) {
138318
- return minutes >= 0 && minutes <= 59;
138319
- }
138320
-
138321
- // src/utils/dates.ts
138322
- function parseIsoDateOrNull(value) {
138323
- if (!value) return null;
138324
- const parsed = parseISO(value);
138325
- return isValid(parsed) ? parsed : null;
138326
- }
138327
- function compareIsoDesc(a, b2) {
138328
- const dateA = parseIsoDateOrNull(a);
138329
- const dateB = parseIsoDateOrNull(b2);
138330
- if (!dateA && !dateB) return 0;
138331
- if (!dateA) return 1;
138332
- if (!dateB) return -1;
138333
- return compareDesc(dateA, dateB);
138334
- }
138335
-
138336
- // src/services/projects/ensureProjectsForConversations.ts
138337
- function ensureProjectsForConversations(repo, conversations) {
138338
- const conversationsByPath = /* @__PURE__ */ new Map();
138339
- for (const conversation of conversations) {
138340
- if (!conversation.projectPath) continue;
138341
- const canonical = canonicalizeProjectPath(conversation.projectPath);
138342
- if (!canonical) continue;
138343
- const existing = conversationsByPath.get(canonical) ?? [];
138344
- existing.push(conversation);
138345
- conversationsByPath.set(canonical, existing);
138346
- }
138347
- const pathToProjectId = /* @__PURE__ */ new Map();
138348
- for (const [path2, projectConversations] of conversationsByPath) {
138349
- const latest = pickLatestConversation(projectConversations);
138350
- const project = repo.upsertProjectByPath(path2, {
138351
- lastConversationId: latest?.id ?? null,
138352
- lastConversationCreatedAt: latest?.createdAt ?? null,
138353
- latestMessageAt: latest?.latestMessageAt ?? null
138354
- });
138355
- pathToProjectId.set(path2, project.id);
138356
- }
138357
- return pathToProjectId;
138358
- }
138359
- function pickLatestConversation(conversations) {
138360
- if (conversations.length === 0) return void 0;
138361
- return [...conversations].sort((a, b2) => {
138362
- const cmp = compareIsoDesc(a.latestMessageAt ?? null, b2.latestMessageAt ?? null);
138363
- if (cmp !== 0) return cmp;
138364
- return compareIsoDesc(a.createdAt ?? null, b2.createdAt ?? null);
138365
- })[0];
138366
- }
138367
-
138368
- // src/services/conversations/refreshConversationCache.ts
138369
- function refreshConversationCache(deps) {
138370
- const { projectsRepo, conversationsRepo, cacheMetadataRepo } = deps;
138371
- const conversations = conversationsRepo.listConversationsForProjectBackfill();
138372
- const pathToProjectId = ensureProjectsForConversations(
138373
- projectsRepo,
138374
- conversations.map((c) => ({
138375
- id: c.id,
138376
- projectPath: c.projectPath,
138377
- latestMessageAt: c.lastActivity ?? null,
138378
- createdAt: c.lastActivity ?? null
138379
- }))
138380
- );
138381
- let conversationsBackfilled = 0;
138382
- for (const conversation of conversations) {
138383
- if (!conversation.projectPath) continue;
138384
- if (conversation.projectId) continue;
138385
- const projectId = pathToProjectId.get(canonicalizeProjectPath(conversation.projectPath));
138386
- if (!projectId) continue;
138387
- conversationsRepo.updateConversationProjectId({
138388
- conversationId: conversation.id,
138389
- projectId
138390
- });
138391
- conversationsBackfilled += 1;
138392
- }
138393
- const latest = conversationsRepo.getLatestConversation();
138394
- if (latest) {
138395
- setCacheMetadata(cacheMetadataRepo, "last_conversation_id", latest.id);
138396
- if (latest.lastActivity) {
138397
- setCacheMetadata(cacheMetadataRepo, "last_conversation_created_at", latest.lastActivity);
138398
- }
138399
- }
138400
- setCacheMetadata(cacheMetadataRepo, "conversations_last_indexed_at", (/* @__PURE__ */ new Date()).toISOString());
138401
- return {
138402
- projectsTouched: pathToProjectId.size,
138403
- conversationsBackfilled,
138404
- latestConversationId: latest?.id ?? null
138405
- };
138406
- }
138407
-
138408
- // src/services/conversations/shouldRefreshProjectsFromHdd.ts
138108
+ // src/handlers/handleListProjects.ts
138409
138109
  var import_fs18 = require("fs");
138410
138110
  var import_os7 = require("os");
138411
138111
  var import_path14 = require("path");
138412
- var DEFAULT_PROJECTS_DIR = (0, import_path14.join)((0, import_os7.homedir)(), ".claude", "projects");
138413
- function shouldRefreshProjectsFromHdd(conversationsRepo, cacheMetadataRepo, opts = {}) {
138414
- if (conversationsRepo.hasOrphanRows()) return true;
138415
- const projectsDir = opts.projectsDir ?? DEFAULT_PROJECTS_DIR;
138416
- let dirMtimeMs;
138112
+ function decodeProjectPath(dirName) {
138113
+ return dirName.replace(/-/g, "/");
138114
+ }
138115
+ function handleListProjects(url2, res) {
138116
+ const limit = Math.max(1, parseInt(url2.searchParams.get("limit") ?? "50", 10) || 50);
138117
+ const offset = Math.max(0, parseInt(url2.searchParams.get("offset") ?? "0", 10) || 0);
138118
+ const projectsDir = (0, import_path14.join)((0, import_os7.homedir)(), ".claude", "projects");
138119
+ let entries;
138417
138120
  try {
138418
- dirMtimeMs = (0, import_fs18.statSync)(projectsDir).mtimeMs;
138121
+ entries = (0, import_fs18.readdirSync)(projectsDir).map((dirName) => {
138122
+ const fullPath = (0, import_path14.join)(projectsDir, dirName);
138123
+ let mtime = 0;
138124
+ try {
138125
+ mtime = (0, import_fs18.statSync)(fullPath).mtimeMs;
138126
+ } catch {
138127
+ }
138128
+ const path2 = decodeProjectPath(dirName);
138129
+ const name = path2.split("/").filter(Boolean).pop() ?? dirName;
138130
+ return { name, path: path2, dirName, mtime };
138131
+ }).sort((a, b2) => b2.mtime - a.mtime);
138419
138132
  } catch {
138420
- return false;
138421
- }
138422
- const lastIndexedIso = getCacheMetadata(cacheMetadataRepo, "conversations_last_indexed_at");
138423
- if (!lastIndexedIso) return true;
138424
- const lastIndexedMs = Date.parse(lastIndexedIso);
138425
- if (Number.isNaN(lastIndexedMs)) return true;
138426
- return dirMtimeMs > lastIndexedMs;
138427
- }
138428
-
138429
- // src/services/sessions/ensureSessionProjectIdsFromExistingProjects.ts
138430
- function ensureSessionProjectIdsFromExistingProjects(projectsRepo, sessionsRepo) {
138431
- let linked = 0;
138432
- let missing = 0;
138433
- for (const session of sessionsRepo.listManagedSessions()) {
138434
- if (session.projectId) continue;
138435
- const canonical = canonicalizeProjectPath(session.projectPath);
138436
- if (!canonical) continue;
138437
- const project = projectsRepo.getProjectByPath(canonical);
138438
- if (!project) {
138439
- missing += 1;
138440
- continue;
138441
- }
138442
- sessionsRepo.updateSessionProjectId({ sessionId: session.id, projectId: project.id });
138443
- linked += 1;
138444
- }
138445
- return { linked, missing };
138446
- }
138447
-
138448
- // src/services/projectChats/sortProjectChats.ts
138449
- function sortProjectChats(a, b2) {
138450
- const byLatest = compareIsoDesc(a.latestMessageAt, b2.latestMessageAt);
138451
- if (byLatest !== 0) return byLatest;
138452
- const byUpdated = compareIsoDesc(a.updatedAt ?? null, b2.updatedAt ?? null);
138453
- if (byUpdated !== 0) return byUpdated;
138454
- const byCreated = compareIsoDesc(a.createdAt ?? null, b2.createdAt ?? null);
138455
- if (byCreated !== 0) return byCreated;
138456
- return a.title.localeCompare(b2.title);
138457
- }
138458
-
138459
- // src/services/projectChats/mergeProjectChats.ts
138460
- function mergeProjectChats(args) {
138461
- const { sessions, conversations } = args;
138462
- const resumedConversationIds = new Set(
138463
- sessions.filter((c) => c.type === "session").map((c) => c.resumedFromConversationId).filter((id) => Boolean(id))
138464
- );
138465
- const visibleConversations = conversations.filter((c) => {
138466
- if (c.type !== "conversation") return true;
138467
- return !resumedConversationIds.has(c.id);
138468
- });
138469
- return [...sessions, ...visibleConversations].sort(sortProjectChats);
138470
- }
138471
-
138472
- // src/services/projectChats/deriveProjectChatTitle.ts
138473
- function deriveProjectChatTitle(input) {
138474
- const trimmed = input.title?.trim();
138475
- if (trimmed) return trimmed;
138476
- const name = input.projectName?.trim();
138477
- if (name) return name;
138478
- const pathSuffix = input.projectPath ? input.projectPath.split(/[/\\]/).filter(Boolean).slice(-2).join("/") : "";
138479
- if (pathSuffix) return pathSuffix;
138480
- return `Untitled \xB7 ${input.id.slice(0, 8)}`;
138481
- }
138482
-
138483
- // src/services/projectChats/normalizeConversationToProjectChat.ts
138484
- function normalizeConversationToProjectChat(conversation) {
138485
- if (!conversation.projectId) {
138486
- throw new Error(
138487
- `normalizeConversationToProjectChat: conversation ${conversation.id} has no projectId \u2014 resolve it before normalizing`
138488
- );
138489
- }
138490
- return {
138491
- type: "conversation",
138492
- id: conversation.id,
138493
- projectId: conversation.projectId,
138494
- projectPath: conversation.projectPath ?? null,
138495
- title: deriveProjectChatTitle({
138496
- title: conversation.title,
138497
- projectName: conversation.projectName,
138498
- projectPath: conversation.projectPath,
138499
- id: conversation.id
138500
- }),
138501
- latestMessageAt: conversation.lastActivity ?? null,
138502
- updatedAt: conversation.lastActivity ?? null,
138503
- createdAt: null,
138504
- status: "resumable",
138505
- source: "hdd-cache",
138506
- provider: conversation.provider ?? CLAUDE_CODE_PROVIDER2,
138507
- indexedAt: null,
138508
- fileMtime: null,
138509
- filePath: conversation.filePath ?? null,
138510
- sourceHash: null
138511
- };
138512
- }
138513
-
138514
- // src/services/projectChats/normalizeSessionToProjectChat.ts
138515
- function normalizeSessionToProjectChat(session) {
138516
- if (!session.projectId) {
138517
- throw new Error(
138518
- `normalizeSessionToProjectChat: session ${session.id} has no projectId \u2014 resolve it before normalizing`
138519
- );
138520
- }
138521
- return {
138522
- type: "session",
138523
- id: session.id,
138524
- projectId: session.projectId,
138525
- projectPath: session.projectPath ?? null,
138526
- title: deriveProjectChatTitle({
138527
- title: session.sessionName,
138528
- projectName: session.projectName,
138529
- projectPath: session.projectPath,
138530
- id: session.id
138531
- }),
138532
- latestMessageAt: session.lastMessageAt ?? session.lastActivityAt ?? null,
138533
- updatedAt: session.lastActivityAt ?? null,
138534
- createdAt: session.startedAt ?? null,
138535
- status: "active",
138536
- source: "session-store",
138537
- resumedFromConversationId: session.resumedFromConversationId ?? null
138538
- };
138539
- }
138540
-
138541
- // src/services/projectChats/listProjectChats.ts
138542
- async function listProjectChats(deps, args) {
138543
- const {
138544
- cache,
138545
- projectsRepo,
138546
- conversationsRepo,
138547
- sessionsRepo,
138548
- cacheMetadataRepo,
138549
- getSessionResponses,
138550
- getFreshScanner,
138551
- projectsDir
138552
- } = deps;
138553
- const needsRefresh = args.refreshConversations || shouldRefreshProjectsFromHdd(conversationsRepo, cacheMetadataRepo, { projectsDir });
138554
- if (needsRefresh) {
138555
- const scanner = await getFreshScanner();
138556
- const metas = [...scanner.getMetadataCache().values()];
138557
- if (metas.length > 0) {
138558
- cache.upsertFromScannerMeta(metas);
138559
- }
138560
- refreshConversationCache({ cache, projectsRepo, conversationsRepo, cacheMetadataRepo });
138561
- }
138562
- ensureSessionProjectIdsFromExistingProjects(projectsRepo, sessionsRepo);
138563
- const sessionResponses = getSessionResponses();
138564
- const sessionChats = [];
138565
- for (const s3 of sessionResponses) {
138566
- if (!s3.projectId) continue;
138567
- sessionChats.push(normalizeSessionToProjectChat(s3));
138568
- }
138569
- const conversationChats = [];
138570
- const { conversations } = cache.listConversations({ limit: 1e3, offset: 0 });
138571
- for (const c of conversations) {
138572
- if (!c.projectId) continue;
138573
- conversationChats.push(normalizeConversationToProjectChat(c));
138574
- }
138575
- return mergeProjectChats({ sessions: sessionChats, conversations: conversationChats });
138576
- }
138577
-
138578
- // src/handlers/handleListProjectChats.ts
138579
- async function handleListProjectChats(url2, res, deps) {
138580
- const queryObj = Object.fromEntries(url2.searchParams.entries());
138581
- const parsed = ListProjectChatsQuerySchema.safeParse(queryObj);
138582
- if (!parsed.success) {
138583
- res.writeHead(400, { "Content-Type": "application/json" });
138584
- res.end(
138585
- JSON.stringify({
138586
- error: "Invalid query parameters",
138587
- details: parsed.error.flatten()
138588
- })
138589
- );
138133
+ res.writeHead(200, { "Content-Type": "application/json" });
138134
+ res.end(JSON.stringify({ projects: [], total: 0 }));
138590
138135
  return;
138591
138136
  }
138592
- const refreshConversations = parsed.data.refreshConversations === "1" || parsed.data.refresh === "1";
138593
- const chats = await listProjectChats(deps, { refreshConversations });
138137
+ const total = entries.length;
138138
+ const page = entries.slice(offset, offset + limit).map(({ name, path: path2, dirName }) => ({ name, path: path2, dirName }));
138594
138139
  res.writeHead(200, { "Content-Type": "application/json" });
138595
- res.end(JSON.stringify({ projectChats: chats }));
138140
+ res.end(JSON.stringify({ projects: page, total }));
138596
138141
  }
138597
138142
 
138598
138143
  // src/server.ts
@@ -141512,6 +141057,17 @@ function pruneAgentConversations(cache) {
141512
141057
  return { scanned: rows.length, pruned, missing };
141513
141058
  }
141514
141059
 
141060
+ // src/services/projectChats/deriveProjectChatTitle.ts
141061
+ function deriveProjectChatTitle(input) {
141062
+ const trimmed = input.title?.trim();
141063
+ if (trimmed) return trimmed;
141064
+ const name = input.projectName?.trim();
141065
+ if (name) return name;
141066
+ const pathSuffix = input.projectPath ? input.projectPath.split(/[/\\]/).filter(Boolean).slice(-2).join("/") : "";
141067
+ if (pathSuffix) return pathSuffix;
141068
+ return `Untitled \xB7 ${input.id.slice(0, 8)}`;
141069
+ }
141070
+
141515
141071
  // src/services/questions/detectAskUserQuestion.ts
141516
141072
  function normalizeContent2(raw2) {
141517
141073
  if (Array.isArray(raw2)) return raw2;
@@ -141941,6 +141497,226 @@ function debounce(fn, waitMs) {
141941
141497
  return debounced;
141942
141498
  }
141943
141499
 
141500
+ // node_modules/date-fns/constants.js
141501
+ var daysInYear = 365.2425;
141502
+ var maxTime = Math.pow(10, 8) * 24 * 60 * 60 * 1e3;
141503
+ var minTime = -maxTime;
141504
+ var millisecondsInMinute = 6e4;
141505
+ var millisecondsInHour = 36e5;
141506
+ var secondsInHour = 3600;
141507
+ var secondsInDay = secondsInHour * 24;
141508
+ var secondsInWeek = secondsInDay * 7;
141509
+ var secondsInYear = secondsInDay * daysInYear;
141510
+ var secondsInMonth = secondsInYear / 12;
141511
+ var secondsInQuarter = secondsInMonth * 3;
141512
+ var constructFromSymbol = /* @__PURE__ */ Symbol.for("constructDateFrom");
141513
+
141514
+ // node_modules/date-fns/constructFrom.js
141515
+ function constructFrom(date5, value) {
141516
+ if (typeof date5 === "function") return date5(value);
141517
+ if (date5 && typeof date5 === "object" && constructFromSymbol in date5)
141518
+ return date5[constructFromSymbol](value);
141519
+ if (date5 instanceof Date) return new date5.constructor(value);
141520
+ return new Date(value);
141521
+ }
141522
+
141523
+ // node_modules/date-fns/toDate.js
141524
+ function toDate(argument, context) {
141525
+ return constructFrom(context || argument, argument);
141526
+ }
141527
+
141528
+ // node_modules/date-fns/isDate.js
141529
+ function isDate(value) {
141530
+ return value instanceof Date || typeof value === "object" && Object.prototype.toString.call(value) === "[object Date]";
141531
+ }
141532
+
141533
+ // node_modules/date-fns/isValid.js
141534
+ function isValid(date5) {
141535
+ return !(!isDate(date5) && typeof date5 !== "number" || isNaN(+toDate(date5)));
141536
+ }
141537
+
141538
+ // node_modules/date-fns/parseISO.js
141539
+ function parseISO(argument, options) {
141540
+ const invalidDate = () => constructFrom(options?.in, NaN);
141541
+ const additionalDigits = options?.additionalDigits ?? 2;
141542
+ const dateStrings = splitDateString(argument);
141543
+ let date5;
141544
+ if (dateStrings.date) {
141545
+ const parseYearResult = parseYear(dateStrings.date, additionalDigits);
141546
+ date5 = parseDate(parseYearResult.restDateString, parseYearResult.year);
141547
+ }
141548
+ if (!date5 || isNaN(+date5)) return invalidDate();
141549
+ const timestamp = +date5;
141550
+ let time3 = 0;
141551
+ let offset;
141552
+ if (dateStrings.time) {
141553
+ time3 = parseTime(dateStrings.time);
141554
+ if (isNaN(time3)) return invalidDate();
141555
+ }
141556
+ if (dateStrings.timezone) {
141557
+ offset = parseTimezone(dateStrings.timezone);
141558
+ if (isNaN(offset)) return invalidDate();
141559
+ } else {
141560
+ const tmpDate = new Date(timestamp + time3);
141561
+ const result = toDate(0, options?.in);
141562
+ result.setFullYear(
141563
+ tmpDate.getUTCFullYear(),
141564
+ tmpDate.getUTCMonth(),
141565
+ tmpDate.getUTCDate()
141566
+ );
141567
+ result.setHours(
141568
+ tmpDate.getUTCHours(),
141569
+ tmpDate.getUTCMinutes(),
141570
+ tmpDate.getUTCSeconds(),
141571
+ tmpDate.getUTCMilliseconds()
141572
+ );
141573
+ return result;
141574
+ }
141575
+ return toDate(timestamp + time3 + offset, options?.in);
141576
+ }
141577
+ var patterns = {
141578
+ dateTimeDelimiter: /[T ]/,
141579
+ timeZoneDelimiter: /[Z ]/i,
141580
+ timezone: /([Z+-].*)$/
141581
+ };
141582
+ var dateRegex = /^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/;
141583
+ var timeRegex = /^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/;
141584
+ var timezoneRegex = /^([+-])(\d{2})(?::?(\d{2}))?$/;
141585
+ function splitDateString(dateString) {
141586
+ const dateStrings = {};
141587
+ const array2 = dateString.split(patterns.dateTimeDelimiter);
141588
+ let timeString;
141589
+ if (array2.length > 2) {
141590
+ return dateStrings;
141591
+ }
141592
+ if (/:/.test(array2[0])) {
141593
+ timeString = array2[0];
141594
+ } else {
141595
+ dateStrings.date = array2[0];
141596
+ timeString = array2[1];
141597
+ if (patterns.timeZoneDelimiter.test(dateStrings.date)) {
141598
+ dateStrings.date = dateString.split(patterns.timeZoneDelimiter)[0];
141599
+ timeString = dateString.substr(
141600
+ dateStrings.date.length,
141601
+ dateString.length
141602
+ );
141603
+ }
141604
+ }
141605
+ if (timeString) {
141606
+ const token = patterns.timezone.exec(timeString);
141607
+ if (token) {
141608
+ dateStrings.time = timeString.replace(token[1], "");
141609
+ dateStrings.timezone = token[1];
141610
+ } else {
141611
+ dateStrings.time = timeString;
141612
+ }
141613
+ }
141614
+ return dateStrings;
141615
+ }
141616
+ function parseYear(dateString, additionalDigits) {
141617
+ const regex = new RegExp(
141618
+ "^(?:(\\d{4}|[+-]\\d{" + (4 + additionalDigits) + "})|(\\d{2}|[+-]\\d{" + (2 + additionalDigits) + "})$)"
141619
+ );
141620
+ const captures = dateString.match(regex);
141621
+ if (!captures) return { year: NaN, restDateString: "" };
141622
+ const year = captures[1] ? parseInt(captures[1]) : null;
141623
+ const century = captures[2] ? parseInt(captures[2]) : null;
141624
+ return {
141625
+ year: century === null ? year : century * 100,
141626
+ restDateString: dateString.slice((captures[1] || captures[2]).length)
141627
+ };
141628
+ }
141629
+ function parseDate(dateString, year) {
141630
+ if (year === null) return /* @__PURE__ */ new Date(NaN);
141631
+ const captures = dateString.match(dateRegex);
141632
+ if (!captures) return /* @__PURE__ */ new Date(NaN);
141633
+ const isWeekDate = !!captures[4];
141634
+ const dayOfYear = parseDateUnit(captures[1]);
141635
+ const month = parseDateUnit(captures[2]) - 1;
141636
+ const day = parseDateUnit(captures[3]);
141637
+ const week = parseDateUnit(captures[4]);
141638
+ const dayOfWeek = parseDateUnit(captures[5]) - 1;
141639
+ if (isWeekDate) {
141640
+ if (!validateWeekDate(year, week, dayOfWeek)) {
141641
+ return /* @__PURE__ */ new Date(NaN);
141642
+ }
141643
+ return dayOfISOWeekYear(year, week, dayOfWeek);
141644
+ } else {
141645
+ const date5 = /* @__PURE__ */ new Date(0);
141646
+ if (!validateDate(year, month, day) || !validateDayOfYearDate(year, dayOfYear)) {
141647
+ return /* @__PURE__ */ new Date(NaN);
141648
+ }
141649
+ date5.setUTCFullYear(year, month, Math.max(dayOfYear, day));
141650
+ return date5;
141651
+ }
141652
+ }
141653
+ function parseDateUnit(value) {
141654
+ return value ? parseInt(value) : 1;
141655
+ }
141656
+ function parseTime(timeString) {
141657
+ const captures = timeString.match(timeRegex);
141658
+ if (!captures) return NaN;
141659
+ const hours = parseTimeUnit(captures[1]);
141660
+ const minutes = parseTimeUnit(captures[2]);
141661
+ const seconds = parseTimeUnit(captures[3]);
141662
+ if (!validateTime(hours, minutes, seconds)) {
141663
+ return NaN;
141664
+ }
141665
+ return hours * millisecondsInHour + minutes * millisecondsInMinute + seconds * 1e3;
141666
+ }
141667
+ function parseTimeUnit(value) {
141668
+ return value && parseFloat(value.replace(",", ".")) || 0;
141669
+ }
141670
+ function parseTimezone(timezoneString) {
141671
+ if (timezoneString === "Z") return 0;
141672
+ const captures = timezoneString.match(timezoneRegex);
141673
+ if (!captures) return 0;
141674
+ const sign = captures[1] === "+" ? -1 : 1;
141675
+ const hours = parseInt(captures[2]);
141676
+ const minutes = captures[3] && parseInt(captures[3]) || 0;
141677
+ if (!validateTimezone(hours, minutes)) {
141678
+ return NaN;
141679
+ }
141680
+ return sign * (hours * millisecondsInHour + minutes * millisecondsInMinute);
141681
+ }
141682
+ function dayOfISOWeekYear(isoWeekYear, week, day) {
141683
+ const date5 = /* @__PURE__ */ new Date(0);
141684
+ date5.setUTCFullYear(isoWeekYear, 0, 4);
141685
+ const fourthOfJanuaryDay = date5.getUTCDay() || 7;
141686
+ const diff = (week - 1) * 7 + day + 1 - fourthOfJanuaryDay;
141687
+ date5.setUTCDate(date5.getUTCDate() + diff);
141688
+ return date5;
141689
+ }
141690
+ var daysInMonths = [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
141691
+ function isLeapYearIndex(year) {
141692
+ return year % 400 === 0 || year % 4 === 0 && year % 100 !== 0;
141693
+ }
141694
+ function validateDate(year, month, date5) {
141695
+ return month >= 0 && month <= 11 && date5 >= 1 && date5 <= (daysInMonths[month] || (isLeapYearIndex(year) ? 29 : 28));
141696
+ }
141697
+ function validateDayOfYearDate(year, dayOfYear) {
141698
+ return dayOfYear >= 1 && dayOfYear <= (isLeapYearIndex(year) ? 366 : 365);
141699
+ }
141700
+ function validateWeekDate(_year, week, day) {
141701
+ return week >= 1 && week <= 53 && day >= 0 && day <= 6;
141702
+ }
141703
+ function validateTime(hours, minutes, seconds) {
141704
+ if (hours === 24) {
141705
+ return minutes === 0 && seconds === 0;
141706
+ }
141707
+ return seconds >= 0 && seconds < 60 && minutes >= 0 && minutes < 60 && hours >= 0 && hours < 25;
141708
+ }
141709
+ function validateTimezone(_hours, minutes) {
141710
+ return minutes >= 0 && minutes <= 59;
141711
+ }
141712
+
141713
+ // src/utils/dates.ts
141714
+ function parseIsoDateOrNull(value) {
141715
+ if (!value) return null;
141716
+ const parsed = parseISO(value);
141717
+ return isValid(parsed) ? parsed : null;
141718
+ }
141719
+
141944
141720
  // src/utils/isScannedSnapshotStale.ts
141945
141721
  var STALENESS_TOLERANCE_MS = 1e3;
141946
141722
  function isScannedSnapshotStale(snapshotTimestamp, fileMtimeMs) {
@@ -142116,6 +141892,7 @@ var StreamerServer = class {
142116
141892
  binding = false;
142117
141893
  cacheReady = false;
142118
141894
  apiKey;
141895
+ apiKeySource;
142119
141896
  localNoAuth;
142120
141897
  logMenubarRequests;
142121
141898
  verbose;
@@ -142127,6 +141904,8 @@ var StreamerServer = class {
142127
141904
  publicUrl = null;
142128
141905
  pairTokens = new PairTokenStore();
142129
141906
  exchangeAttempts = /* @__PURE__ */ new Map();
141907
+ sessionStartAttempts = /* @__PURE__ */ new Map();
141908
+ sessionInputAttempts = /* @__PURE__ */ new Map();
142130
141909
  ptyGracePeriodMs;
142131
141910
  defaultSystemPrompt;
142132
141911
  // Map of sessionId → grace timer; fires to kill PTY after WS disconnect
@@ -142162,8 +141941,14 @@ var StreamerServer = class {
142162
141941
  constructor(config2) {
142163
141942
  this.sessionStatusBus.setMaxListeners(0);
142164
141943
  this.apiKey = config2.apiKey;
141944
+ this.apiKeySource = config2.apiKeySource ?? "config";
142165
141945
  this.localNoAuth = config2.localNoAuth ?? false;
142166
141946
  this.logMenubarRequests = config2.logMenubarRequests ?? false;
141947
+ if (this.localNoAuth) {
141948
+ console.warn(
141949
+ "[WARN] localNoAuth is ENABLED \u2014 all requests from localhost bypass authentication. Do not run with --local-no-auth in shared or production environments."
141950
+ );
141951
+ }
142167
141952
  this.verbose = config2.verbose ?? false;
142168
141953
  this.disableDb = config2.disableDb ?? false;
142169
141954
  this.scanProfiles = config2.scanProfiles;
@@ -142341,10 +142126,15 @@ var StreamerServer = class {
142341
142126
  });
142342
142127
  }
142343
142128
  const agentClient = this.agentClient;
142129
+ const self2 = this;
142344
142130
  const apiDeps = {
142345
- apiKey: this.apiKey,
142131
+ // ponytail: getter so rotateApiKey() takes effect without restarting the server
142132
+ get apiKey() {
142133
+ return self2.apiKey;
142134
+ },
142346
142135
  localNoAuth: this.localNoAuth,
142347
142136
  logMenubarRequests: this.logMenubarRequests,
142137
+ rotateApiKey: () => this.rotateApiKey(),
142348
142138
  publicUrl: this.publicUrl,
142349
142139
  browseRoot: this.browseRoot,
142350
142140
  ptyManager: this.ptyManager,
@@ -142375,8 +142165,8 @@ var StreamerServer = class {
142375
142165
  handleConversationsCount: (url2, res) => this.handleConversationsCount(url2, res),
142376
142166
  handleGetConversation: (id, url2, res, ifNoneMatch) => this.handleGetConversation(id, url2, res, ifNoneMatch),
142377
142167
  handleSearch: (url2, res) => this.handleSearch(url2, res),
142168
+ handleListProjects: (url2, res) => handleListProjects(url2, res),
142378
142169
  handleGetPopularProjects: (url2, res) => this.handleGetPopularProjects(url2, res),
142379
- handleListProjectChats: (url2, res) => this.handleListProjectChats(url2, res),
142380
142170
  handlePairStart: (res) => this.handlePairStart(res),
142381
142171
  handlePairExchange: (req, res) => this.handlePairExchange(req, res),
142382
142172
  handleBrowse: (url2, res) => this.handleBrowse(url2, res),
@@ -142811,19 +142601,38 @@ var StreamerServer = class {
142811
142601
  machineName: hostname4()
142812
142602
  });
142813
142603
  }
142814
- checkExchangeRateLimit(ip) {
142604
+ rotateApiKey() {
142605
+ const newKey = generateApiKey();
142606
+ const persisted = this.apiKeySource === "config";
142607
+ if (persisted) setApiKey(newKey);
142608
+ this.apiKey = newKey;
142609
+ return { newKey, persisted };
142610
+ }
142611
+ checkRateLimit(map2, key, limit, windowMs) {
142815
142612
  const now = Date.now();
142816
- const windowMs = 6e4;
142817
- const limit = 5;
142818
- const arr = (this.exchangeAttempts.get(ip) ?? []).filter((t2) => now - t2 < windowMs);
142613
+ const arr = (map2.get(key) ?? []).filter((t2) => now - t2 < windowMs);
142819
142614
  if (arr.length >= limit) {
142820
- this.exchangeAttempts.set(ip, arr);
142615
+ map2.set(key, arr);
142821
142616
  return false;
142822
142617
  }
142823
142618
  arr.push(now);
142824
- this.exchangeAttempts.set(ip, arr);
142619
+ map2.set(key, arr);
142620
+ setTimeout(() => {
142621
+ const remaining = (map2.get(key) ?? []).filter((t2) => Date.now() - t2 < windowMs);
142622
+ if (remaining.length === 0) map2.delete(key);
142623
+ else map2.set(key, remaining);
142624
+ }, windowMs);
142825
142625
  return true;
142826
142626
  }
142627
+ checkExchangeRateLimit(ip) {
142628
+ return this.checkRateLimit(this.exchangeAttempts, ip, 5, 6e4);
142629
+ }
142630
+ checkSessionStartRateLimit(ip) {
142631
+ return this.checkRateLimit(this.sessionStartAttempts, ip, 10, 6e4);
142632
+ }
142633
+ checkSessionInputRateLimit(sessionId) {
142634
+ return this.checkRateLimit(this.sessionInputAttempts, sessionId, 500, 6e4);
142635
+ }
142827
142636
  async handleListConversations(url2, res) {
142828
142637
  const limit = intParam(url2, "limit", 50);
142829
142638
  const offset = intParam(url2, "offset", 0);
@@ -142985,21 +142794,6 @@ var StreamerServer = class {
142985
142794
  const projects = this.cache.getPopularProjects(limit);
142986
142795
  json2(res, 200, { projects, total: projects.length });
142987
142796
  }
142988
- async handleListProjectChats(url2, res) {
142989
- if (!this.cache || !this.projectsRepo || !this.conversationsRepo || !this.sessionsRepo || !this.cacheMetadataRepo) {
142990
- json2(res, 503, { error: "Cache not available" });
142991
- return;
142992
- }
142993
- await handleListProjectChats(url2, res, {
142994
- cache: this.cache,
142995
- projectsRepo: this.projectsRepo,
142996
- conversationsRepo: this.conversationsRepo,
142997
- sessionsRepo: this.sessionsRepo,
142998
- cacheMetadataRepo: this.cacheMetadataRepo,
142999
- getSessionResponses: () => this.sessionStore.list(this.ptyAttachedIds()),
143000
- getFreshScanner: () => this.getFreshScanner()
143001
- });
143002
- }
143003
142797
  buildStatCache(previousScanner) {
143004
142798
  if (!this.cache) return void 0;
143005
142799
  const dbStats = this.cache.getFileStats();
@@ -143476,6 +143270,10 @@ var StreamerServer = class {
143476
143270
  }
143477
143271
  }
143478
143272
  async handleSendInput(sessionId, req, res) {
143273
+ if (!this.checkSessionInputRateLimit(sessionId)) {
143274
+ json2(res, 429, { error: "Too many input requests for this session. Please slow down." });
143275
+ return;
143276
+ }
143479
143277
  if (this.agentConfig.enabled) {
143480
143278
  const body2 = await readBody(req);
143481
143279
  const cache = this.cache;
@@ -143757,6 +143555,13 @@ var StreamerServer = class {
143757
143555
  json2(res, 201, { sessionId: session.id });
143758
143556
  }
143759
143557
  async handleStartSession(req, res) {
143558
+ const ip = req.socket?.remoteAddress ?? "unknown";
143559
+ if (!this.checkSessionStartRateLimit(ip)) {
143560
+ json2(res, 429, {
143561
+ error: "Too many session start requests. Please wait before trying again."
143562
+ });
143563
+ return;
143564
+ }
143760
143565
  if (this.agentConfig.enabled) {
143761
143566
  const body2 = await readBody(req);
143762
143567
  const result = await handleStartAgentSession(body2, {
@@ -144280,7 +144085,7 @@ async function checkForUpdate(opts) {
144280
144085
  }
144281
144086
 
144282
144087
  // src/updater/install.ts
144283
- var import_node_fs17 = require("fs");
144088
+ var import_node_fs18 = require("fs");
144284
144089
 
144285
144090
  // src/updater/active-sessions.ts
144286
144091
  async function countActiveSessions(opts) {
@@ -147564,14 +147369,29 @@ async function unpackTarball(opts) {
147564
147369
  await So({ file: tarballPath, cwd: destDir });
147565
147370
  }
147566
147371
 
147372
+ // src/updater/update-log.ts
147373
+ var import_node_fs17 = require("fs");
147374
+ var import_node_path21 = require("path");
147375
+ var LOG_PATH = (0, import_node_path21.join)(THREADBASE_ROOT, "update.log");
147376
+ function appendUpdateLog(line) {
147377
+ try {
147378
+ (0, import_node_fs17.mkdirSync)(THREADBASE_ROOT, { recursive: true });
147379
+ (0, import_node_fs17.appendFileSync)(LOG_PATH, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
147380
+ `, "utf8");
147381
+ } catch {
147382
+ }
147383
+ }
147384
+
147567
147385
  // src/updater/install.ts
147568
147386
  async function runInstall(opts) {
147569
147387
  if (isBrewInstall()) {
147570
- return {
147388
+ const result = {
147571
147389
  kind: "unsupported-install",
147572
147390
  reason: "Installed via Homebrew \u2014 run `brew upgrade tb-streamer` to update.",
147573
147391
  current: opts.currentVersion
147574
147392
  };
147393
+ appendUpdateLog(`[no-op] ${result.reason}`);
147394
+ return result;
147575
147395
  }
147576
147396
  const check2 = await checkForUpdate({
147577
147397
  currentVersion: opts.currentVersion,
@@ -147579,6 +147399,9 @@ async function runInstall(opts) {
147579
147399
  pinnedVersion: opts.pinnedVersion,
147580
147400
  allowMajor: opts.allowMajor
147581
147401
  });
147402
+ appendUpdateLog(
147403
+ `[check] current=${check2.current} latest=${check2.latest ?? "none"} status=${check2.reason}`
147404
+ );
147582
147405
  if (!check2.wouldInstall) {
147583
147406
  return { kind: "no-op", reason: check2.reason, current: check2.current, latest: check2.latest };
147584
147407
  }
@@ -147604,33 +147427,27 @@ async function runInstall(opts) {
147604
147427
  const artifact = pickArtifact(manifest);
147605
147428
  if (opts.dryRun) {
147606
147429
  const asset = target.assets.find((a) => a.name === artifact.filename);
147607
- return {
147608
- kind: "dry-run",
147609
- latest: targetVersion,
147610
- tarballUrl: asset?.browserDownloadUrl ?? "(missing)"
147611
- };
147430
+ const tarballUrl = asset?.browserDownloadUrl ?? "(missing)";
147431
+ appendUpdateLog(
147432
+ `[dry-run] would install ${check2.current} \u2192 ${targetVersion} from ${tarballUrl}`
147433
+ );
147434
+ return { kind: "dry-run", latest: targetVersion, tarballUrl };
147612
147435
  }
147613
147436
  if (opts.config.defer_if_active_sessions && !opts.force && opts.runningServer) {
147614
147437
  const active = await countActiveSessions(opts.runningServer);
147615
147438
  if (active.kind === "count" && active.count > 0) {
147616
- return {
147617
- kind: "deferred",
147618
- reason: `${active.count} active session(s); use --force to interrupt`,
147619
- activeSessions: active.count,
147620
- latest: targetVersion
147621
- };
147439
+ const reason = `${active.count} active session(s); use --force to interrupt`;
147440
+ appendUpdateLog(`[deferred] ${check2.current} \u2192 ${targetVersion}: ${reason}`);
147441
+ return { kind: "deferred", reason, activeSessions: active.count, latest: targetVersion };
147622
147442
  }
147623
147443
  if (active.kind === "error") {
147624
- return {
147625
- kind: "deferred",
147626
- reason: `cannot determine active sessions (${active.reason}); use --force to override`,
147627
- activeSessions: -1,
147628
- latest: targetVersion
147629
- };
147444
+ const reason = `cannot determine active sessions (${active.reason}); use --force to override`;
147445
+ appendUpdateLog(`[deferred] ${check2.current} \u2192 ${targetVersion}: ${reason}`);
147446
+ return { kind: "deferred", reason, activeSessions: -1, latest: targetVersion };
147630
147447
  }
147631
147448
  }
147632
147449
  ensureReleasesDir();
147633
- (0, import_node_fs17.mkdirSync)(DOWNLOAD_DIR, { recursive: true });
147450
+ (0, import_node_fs18.mkdirSync)(DOWNLOAD_DIR, { recursive: true });
147634
147451
  const tarballPath = downloadPath(targetVersion, artifact.filename);
147635
147452
  await downloadAndVerify({ manifest, assets: target.assets, targetPath: tarballPath });
147636
147453
  const destDir = releaseDir(targetVersion);
@@ -147649,18 +147466,22 @@ async function runInstall(opts) {
147649
147466
  } catch (err) {
147650
147467
  restartMethod = `failed: ${err instanceof Error ? err.message : String(err)}`;
147651
147468
  }
147652
- return {
147469
+ const installed = {
147653
147470
  kind: "installed",
147654
147471
  previous: check2.current,
147655
147472
  installed: targetVersion,
147656
147473
  pruned,
147657
147474
  restart: { method: restartMethod }
147658
147475
  };
147476
+ appendUpdateLog(
147477
+ `[installed] ${installed.previous} \u2192 ${installed.installed} restart=${installed.restart.method}${pruned.length > 0 ? ` pruned=${pruned.length}` : ""}`
147478
+ );
147479
+ return installed;
147659
147480
  }
147660
147481
 
147661
147482
  // cli/prod.ts
147662
147483
  var import_node_child_process6 = require("child_process");
147663
- var import_node_fs19 = require("fs");
147484
+ var import_node_fs20 = require("fs");
147664
147485
  init_constants();
147665
147486
  init_launchd();
147666
147487
  init_marker();
@@ -147746,7 +147567,7 @@ async function runProdDoctor(opts) {
147746
147567
  return { findings, repairs };
147747
147568
  }
147748
147569
  var defaultSpawnTail = ({ files, lines, follow }) => {
147749
- const existing = files.filter((f2) => (0, import_node_fs19.existsSync)(f2));
147570
+ const existing = files.filter((f2) => (0, import_node_fs20.existsSync)(f2));
147750
147571
  if (existing.length === 0) {
147751
147572
  return Promise.resolve({
147752
147573
  ok: false,
@@ -147902,6 +147723,7 @@ program2.command("serve").description("Start the streamer server").option("-p, -
147902
147723
  const server = new StreamerServer({
147903
147724
  port: resolvedPort,
147904
147725
  apiKey,
147726
+ apiKeySource: opts.apiKey ? "cli" : "config",
147905
147727
  localNoAuth: opts.localNoAuth,
147906
147728
  verbose: opts.verbose,
147907
147729
  logMenubarRequests: opts.logMenubarRequests,
@@ -147965,8 +147787,8 @@ program2.command("cache").description("Manage the local SQLite conversation cach
147965
147787
  `${process.env.HOME}/.threadbase/cache`
147966
147788
  ).action((opts) => {
147967
147789
  const { rmSync: rmSync6, existsSync: existsSync11 } = require("fs");
147968
- const { join: join24 } = require("path");
147969
- const dbPath = join24(opts.cacheDir, "cache.db");
147790
+ const { join: join25 } = require("path");
147791
+ const dbPath = join25(opts.cacheDir, "cache.db");
147970
147792
  for (const suffix of ["", "-shm", "-wal"]) {
147971
147793
  const f2 = dbPath + suffix;
147972
147794
  if (existsSync11(f2)) {
@@ -148015,6 +147837,9 @@ program2.command("update").description("Check for streamer updates from GitHub R
148015
147837
  pinnedVersion: opts.version,
148016
147838
  allowMajor: opts.allowMajor
148017
147839
  });
147840
+ appendUpdateLog(
147841
+ `[check] current=${result2.current} latest=${result2.latest ?? "none"} status=${result2.reason}`
147842
+ );
148018
147843
  log7.info(`Current : ${result2.current}`, void 0, "console");
148019
147844
  log7.info(`Latest : ${result2.latest ?? "(none)"}`, void 0, "console");
148020
147845
  log7.info(`Channel : ${cfg.channel}`, void 0, "console");
@@ -148067,6 +147892,7 @@ program2.command("update").description("Check for streamer updates from GitHub R
148067
147892
  }
148068
147893
  } catch (err) {
148069
147894
  const message = err instanceof Error ? err.message : String(err);
147895
+ appendUpdateLog(`[error] ${message}`);
148070
147896
  log7.error(`Update failed: ${message}`, { error: message }, "console");
148071
147897
  process.exitCode = 1;
148072
147898
  }