@dosu/cli 0.53.0-beta.4 → 0.53.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/dosu.js +433 -306
  2. package/package.json +1 -1
package/bin/dosu.js CHANGED
@@ -5006,7 +5006,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
5006
5006
  function getVersionString() {
5007
5007
  return `v${VERSION}`;
5008
5008
  }
5009
- var VERSION = "0.53.0-beta.4", INSTALL_CHANNEL = "npm";
5009
+ var VERSION = "0.53.0-beta.6", INSTALL_CHANNEL = "npm";
5010
5010
 
5011
5011
  // src/debug/logger.ts
5012
5012
  import {
@@ -8346,258 +8346,25 @@ var init_agents = __esm(() => {
8346
8346
  init_formats();
8347
8347
  });
8348
8348
 
8349
- // src/sync/lock.ts
8350
- import { existsSync as existsSync9, readFileSync as readFileSync9, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync6 } from "node:fs";
8351
- import { join as join8 } from "node:path";
8352
- function processAlive(pid) {
8353
- try {
8354
- process.kill(pid, 0);
8355
- return true;
8356
- } catch (err) {
8357
- return err.code === "EPERM";
8358
- }
8359
- }
8360
- function lockPath(configDir = getConfigDir()) {
8361
- return join8(configDir, LOCK_FILENAME);
8362
- }
8363
- function fileLock(configDir = getConfigDir(), now = () => new Date) {
8364
- const path3 = lockPath(configDir);
8365
- let held = false;
8366
- const tryCreate = () => {
8367
- try {
8368
- writeFileSync6(path3, String(process.pid), { flag: "wx" });
8369
- return true;
8370
- } catch {
8371
- return false;
8372
- }
8373
- };
8374
- return {
8375
- acquire() {
8376
- if (tryCreate()) {
8377
- held = true;
8378
- return true;
8379
- }
8380
- try {
8381
- const age = now().getTime() - statSync3(path3).mtimeMs;
8382
- const holder = Number.parseInt(readFileSync9(path3, "utf8"), 10);
8383
- const holderDead = Number.isInteger(holder) && holder > 0 && !processAlive(holder);
8384
- if (age > STALE_LOCK_MS || holderDead) {
8385
- rmSync2(path3, { force: true });
8386
- held = tryCreate();
8387
- return held;
8388
- }
8389
- } catch {}
8390
- return false;
8391
- },
8392
- release() {
8393
- if (!held)
8394
- return;
8395
- held = false;
8396
- try {
8397
- if (existsSync9(path3) && readFileSync9(path3, "utf8") === String(process.pid)) {
8398
- rmSync2(path3, { force: true });
8399
- }
8400
- } catch {}
8401
- }
8402
- };
8403
- }
8404
- var LOCK_FILENAME = "knowledge-sync.lock", STALE_LOCK_MS;
8405
- var init_lock = __esm(() => {
8406
- init_config();
8407
- STALE_LOCK_MS = 15 * 60 * 1000;
8408
- });
8409
-
8410
- // src/sync/watermark.ts
8411
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
8412
- import { join as join9 } from "node:path";
8413
- function syncStatePath(configDir = getConfigDir()) {
8414
- return join9(configDir, STATE_FILENAME);
8415
- }
8416
- function loadSyncState(configDir = getConfigDir()) {
8417
- const empty = {
8418
- schema_version: STATE_SCHEMA_VERSION,
8419
- watermark: null,
8420
- consecutive_failures: 0
8421
- };
8422
- const path3 = syncStatePath(configDir);
8423
- if (!existsSync10(path3))
8424
- return empty;
8425
- try {
8426
- const raw = JSON.parse(readFileSync10(path3, "utf-8"));
8427
- if (raw.schema_version !== STATE_SCHEMA_VERSION)
8428
- return empty;
8429
- const minedSessions = Array.isArray(raw.mined_sessions) ? raw.mined_sessions.filter((record) => typeof record === "object" && record !== null && typeof record.at === "string" && typeof record.session === "string").map((record) => ({
8430
- at: record.at,
8431
- session: record.session,
8432
- ...typeof record.project === "string" ? { project: record.project } : {}
8433
- })) : [];
8434
- const rawRefusal = raw.last_refusal;
8435
- const lastRefusal = rawRefusal && typeof rawRefusal.at === "string" && typeof rawRefusal.outcome === "string" && typeof rawRefusal.message === "string" ? { at: rawRefusal.at, outcome: rawRefusal.outcome, message: rawRefusal.message } : undefined;
8436
- const rawRun = raw.run;
8437
- const run2 = rawRun && typeof rawRun.pid === "number" && typeof rawRun.started_at === "string" && typeof rawRun.baseline_mined === "number" && rawRun.baseline_mined >= 0 ? { pid: rawRun.pid, started_at: rawRun.started_at, baseline_mined: rawRun.baseline_mined } : undefined;
8438
- return {
8439
- schema_version: STATE_SCHEMA_VERSION,
8440
- watermark: typeof raw.watermark === "string" ? raw.watermark : null,
8441
- last_attempt_at: typeof raw.last_attempt_at === "string" ? raw.last_attempt_at : undefined,
8442
- consecutive_failures: typeof raw.consecutive_failures === "number" && raw.consecutive_failures >= 0 ? raw.consecutive_failures : 0,
8443
- mined_sessions: minedSessions,
8444
- total_mined: typeof raw.total_mined === "number" && raw.total_mined >= 0 ? raw.total_mined : minedSessions.length,
8445
- total_notes: typeof raw.total_notes === "number" && raw.total_notes >= 0 ? raw.total_notes : 0,
8446
- total_learning_tokens: typeof raw.total_learning_tokens === "number" && raw.total_learning_tokens >= 0 ? raw.total_learning_tokens : 0,
8447
- ...lastRefusal ? { last_refusal: lastRefusal } : {},
8448
- ...run2 ? { run: run2 } : {},
8449
- ...Array.isArray(raw.project_filter) ? {
8450
- project_filter: raw.project_filter.filter((p2) => typeof p2 === "string")
8451
- } : {}
8452
- };
8453
- } catch {
8454
- return empty;
8455
- }
8456
- }
8457
- function saveSyncState(state, configDir = getConfigDir()) {
8458
- if (!existsSync10(configDir))
8459
- mkdirSync6(configDir, { recursive: true, mode: 448 });
8460
- const path3 = syncStatePath(configDir);
8461
- const tmp = `${path3}.${process.pid}.tmp`;
8462
- writeFileSync7(tmp, JSON.stringify(state, null, 2), { mode: 384 });
8463
- renameSync2(tmp, path3);
8464
- }
8465
- function backoffUntil(state) {
8466
- if (state.consecutive_failures === 0 || !state.last_attempt_at)
8467
- return null;
8468
- const last = Date.parse(state.last_attempt_at);
8469
- if (Number.isNaN(last))
8470
- return null;
8471
- const delay = Math.min(BACKOFF_BASE_MS * 2 ** (state.consecutive_failures - 1), BACKOFF_MAX_MS);
8472
- return new Date(last + delay);
8473
- }
8474
- function isUnderDir(dir, base) {
8475
- const root = base.endsWith("/") ? base.slice(0, -1) : base;
8476
- return dir === root || dir.startsWith(`${root}/`);
8477
- }
8478
- function filterSessionsByProject(sessions, filter, resolveDir) {
8479
- if (!filter || filter.length === 0)
8480
- return [...sessions];
8481
- const includeUnknown = filter.includes(UNKNOWN_PROJECT);
8482
- const dirs = filter.filter((entry) => entry !== UNKNOWN_PROJECT);
8483
- return sessions.filter((session) => {
8484
- const dir = resolveDir(session);
8485
- if (!dir)
8486
- return includeUnknown;
8487
- return dirs.some((base) => isUnderDir(dir, base));
8488
- });
8489
- }
8490
- function gateSessions(sessions, watermark, now = new Date, quietPeriodMs = DEFAULT_QUIET_PERIOD_MS) {
8491
- const watermarkMs = watermark ? Date.parse(watermark) : Number.NEGATIVE_INFINITY;
8492
- const completedBefore = now.getTime() - quietPeriodMs;
8493
- const ready = [];
8494
- const open2 = [];
8495
- for (const session of sessions) {
8496
- const updated = Date.parse(session.updated);
8497
- if (Number.isNaN(updated) || updated <= watermarkMs)
8498
- continue;
8499
- if (updated > completedBefore) {
8500
- open2.push(session);
8501
- } else {
8502
- ready.push(session);
8503
- }
8504
- }
8505
- return { ready, open: open2 };
8506
- }
8507
- var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS, MINED_HISTORY_LIMIT = 500, UNKNOWN_PROJECT = "(unknown)";
8508
- var init_watermark = __esm(() => {
8509
- init_config();
8510
- DEFAULT_QUIET_PERIOD_MS = 5 * 60 * 1000;
8511
- BACKOFF_BASE_MS = 15 * 60 * 1000;
8512
- BACKOFF_MAX_MS = 24 * 60 * 60 * 1000;
8513
- });
8514
-
8515
- // src/sync/status.ts
8516
- import { readFileSync as readFileSync11, statSync as statSync4 } from "node:fs";
8517
- function formatTokenCount(tokens) {
8518
- if (tokens < 1000)
8519
- return String(tokens);
8520
- if (tokens < 1e6) {
8521
- const k = tokens / 1000;
8522
- return `~${k >= 100 ? Math.round(k) : k.toFixed(1).replace(/\.0$/, "")}k`;
8523
- }
8524
- const m3 = tokens / 1e6;
8525
- return `~${m3 >= 100 ? Math.round(m3) : m3.toFixed(1).replace(/\.0$/, "")}M`;
8526
- }
8527
- function defaultIsProcessAlive2(pid) {
8528
- if (!Number.isInteger(pid) || pid <= 0)
8529
- return false;
8530
- try {
8531
- process.kill(pid, 0);
8532
- return true;
8533
- } catch (err) {
8534
- return err.code === "EPERM";
8535
- }
8536
- }
8537
- function defaultReadLog() {
8538
- try {
8539
- return readFileSync11(logger.getLogPath(), "utf-8");
8540
- } catch {
8541
- return "";
8542
- }
8543
- }
8544
- function readLockFile(configDir) {
8545
- const path3 = lockPath(configDir);
8546
- try {
8547
- const pid = Number.parseInt(readFileSync11(path3, "utf-8"), 10);
8548
- return { pid: Number.isNaN(pid) ? -1 : pid, mtime: statSync4(path3).mtime };
8549
- } catch {
8550
- return null;
8551
- }
8552
- }
8553
- function recentSyncActivity(log3) {
8554
- return log3.split(`
8555
- `).filter((line) => line.includes("[sync]") || line.includes("[miner]")).slice(-ACTIVITY_LINES).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
8556
- }
8557
- function getSyncStatus(deps = {}) {
8558
- const configDir = deps.configDir ?? getConfigDir();
8559
- const isProcessAlive = deps.isProcessAlive ?? defaultIsProcessAlive2;
8560
- const readLog = deps.readLog ?? defaultReadLog;
8561
- const lock = readLockFile(configDir);
8562
- const running = lock !== null && isProcessAlive(lock.pid);
8563
- const state = loadSyncState(configDir);
8564
- const retryAt = backoffUntil(state);
8565
- return {
8566
- running,
8567
- ...lock ? { pid: lock.pid, startedAt: lock.mtime.toISOString() } : {},
8568
- ...lock && !running ? { staleLock: true } : {},
8569
- state,
8570
- ...retryAt ? { backoffUntil: retryAt.toISOString() } : {},
8571
- recentActivity: recentSyncActivity(readLog())
8572
- };
8573
- }
8574
- var ACTIVITY_LINES = 10;
8575
- var init_status = __esm(() => {
8576
- init_config();
8577
- init_logger();
8578
- init_lock();
8579
- init_watermark();
8580
- });
8581
-
8582
8349
  // src/sessions/project-dir.ts
8583
8350
  import {
8584
8351
  closeSync as closeSync2,
8585
- existsSync as existsSync11,
8586
- mkdirSync as mkdirSync7,
8352
+ existsSync as existsSync9,
8353
+ mkdirSync as mkdirSync6,
8587
8354
  openSync as openSync2,
8588
- readFileSync as readFileSync12,
8355
+ readFileSync as readFileSync9,
8589
8356
  readSync,
8590
- renameSync as renameSync3,
8591
- statSync as statSync5,
8592
- writeFileSync as writeFileSync8
8357
+ renameSync as renameSync2,
8358
+ statSync as statSync3,
8359
+ writeFileSync as writeFileSync6
8593
8360
  } from "node:fs";
8594
- import { join as join10 } from "node:path";
8361
+ import { join as join8 } from "node:path";
8595
8362
  function cachePath(configDir) {
8596
- return join10(configDir, CACHE_FILENAME2);
8363
+ return join8(configDir, CACHE_FILENAME2);
8597
8364
  }
8598
8365
  function loadCacheFile(configDir) {
8599
8366
  try {
8600
- const raw = JSON.parse(readFileSync12(cachePath(configDir), "utf-8"));
8367
+ const raw = JSON.parse(readFileSync9(cachePath(configDir), "utf-8"));
8601
8368
  if (raw.schema_version !== CACHE_SCHEMA_VERSION || typeof raw.entries !== "object")
8602
8369
  return {};
8603
8370
  return raw.entries;
@@ -8607,13 +8374,13 @@ function loadCacheFile(configDir) {
8607
8374
  }
8608
8375
  function saveCacheFile(configDir, entries) {
8609
8376
  try {
8610
- if (!existsSync11(configDir))
8611
- mkdirSync7(configDir, { recursive: true, mode: 448 });
8377
+ if (!existsSync9(configDir))
8378
+ mkdirSync6(configDir, { recursive: true, mode: 448 });
8612
8379
  const path3 = cachePath(configDir);
8613
8380
  const tmp = `${path3}.${process.pid}.tmp`;
8614
8381
  const file = { schema_version: CACHE_SCHEMA_VERSION, entries };
8615
- writeFileSync8(tmp, JSON.stringify(file), { mode: 384 });
8616
- renameSync3(tmp, path3);
8382
+ writeFileSync6(tmp, JSON.stringify(file), { mode: 384 });
8383
+ renameSync2(tmp, path3);
8617
8384
  } catch {}
8618
8385
  }
8619
8386
  function readHead(path3, bytes = HEAD_BYTES) {
@@ -8647,7 +8414,7 @@ function cwdFromJsonlHead(text) {
8647
8414
  }
8648
8415
  return null;
8649
8416
  }
8650
- function unmungeSlug(slug, exists = existsSync11) {
8417
+ function unmungeSlug(slug, exists = existsSync9) {
8651
8418
  const tokens = slug.replace(/^-/, "").split("-");
8652
8419
  if (tokens.length === 0 || tokens[0] === "")
8653
8420
  return null;
@@ -8666,13 +8433,13 @@ function unmungeSlug(slug, exists = existsSync11) {
8666
8433
  }
8667
8434
  function fileMtime(path3) {
8668
8435
  try {
8669
- return statSync5(path3).mtime.toISOString();
8436
+ return statSync3(path3).mtime.toISOString();
8670
8437
  } catch {
8671
8438
  return "";
8672
8439
  }
8673
8440
  }
8674
8441
  function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
8675
- const exists = deps.exists ?? existsSync11;
8442
+ const exists = deps.exists ?? existsSync9;
8676
8443
  const head = deps.readHead ?? readHead;
8677
8444
  const mtime = deps.mtime ?? fileMtime;
8678
8445
  const entries = loadCacheFile(configDir);
@@ -8725,15 +8492,15 @@ var init_project_dir = __esm(() => {
8725
8492
  });
8726
8493
 
8727
8494
  // src/sessions/scan.ts
8728
- import { existsSync as existsSync12, readdirSync, statSync as statSync6 } from "node:fs";
8495
+ import { existsSync as existsSync10, readdirSync, statSync as statSync4 } from "node:fs";
8729
8496
  import { createRequire as createRequire2 } from "node:module";
8730
8497
  import { homedir as homedir3 } from "node:os";
8731
- import { basename, join as join11 } from "node:path";
8498
+ import { basename, join as join9 } from "node:path";
8732
8499
  function listDir(dir) {
8733
8500
  try {
8734
8501
  return readdirSync(dir, { withFileTypes: true }).map((entry) => ({
8735
8502
  name: entry.name,
8736
- path: join11(dir, entry.name),
8503
+ path: join9(dir, entry.name),
8737
8504
  isDir: entry.isDirectory()
8738
8505
  }));
8739
8506
  } catch {
@@ -8746,7 +8513,7 @@ function sessionFromFile(filePath, harness, project) {
8746
8513
  return null;
8747
8514
  let mtime;
8748
8515
  try {
8749
- mtime = statSync6(filePath).mtime;
8516
+ mtime = statSync4(filePath).mtime;
8750
8517
  } catch {
8751
8518
  return null;
8752
8519
  }
@@ -8760,7 +8527,7 @@ function sessionFromFile(filePath, harness, project) {
8760
8527
  }
8761
8528
  function scanClaude(home) {
8762
8529
  const sessions = [];
8763
- for (const project of listDir(join11(home, ".claude", "projects"))) {
8530
+ for (const project of listDir(join9(home, ".claude", "projects"))) {
8764
8531
  if (!project.isDir)
8765
8532
  continue;
8766
8533
  for (const entry of listDir(project.path)) {
@@ -8775,10 +8542,10 @@ function scanClaude(home) {
8775
8542
  }
8776
8543
  function scanCursor(home) {
8777
8544
  const sessions = [];
8778
- for (const project of listDir(join11(home, ".cursor", "projects"))) {
8545
+ for (const project of listDir(join9(home, ".cursor", "projects"))) {
8779
8546
  if (!project.isDir)
8780
8547
  continue;
8781
- for (const entry of listDir(join11(project.path, "agent-transcripts"))) {
8548
+ for (const entry of listDir(join9(project.path, "agent-transcripts"))) {
8782
8549
  const files = entry.isDir ? listDir(entry.path).filter((f2) => !f2.isDir) : [entry];
8783
8550
  for (const file of files) {
8784
8551
  const session = sessionFromFile(file.path, "cursor", project.name);
@@ -8790,9 +8557,9 @@ function scanCursor(home) {
8790
8557
  return sessions;
8791
8558
  }
8792
8559
  function scanCodex(home, env) {
8793
- const codexHome2 = env.CODEX_HOME ?? join11(home, ".codex");
8560
+ const codexHome2 = env.CODEX_HOME ?? join9(home, ".codex");
8794
8561
  const sessions = [];
8795
- for (const year of listDir(join11(codexHome2, "sessions"))) {
8562
+ for (const year of listDir(join9(codexHome2, "sessions"))) {
8796
8563
  if (!year.isDir)
8797
8564
  continue;
8798
8565
  for (const month of listDir(year.path)) {
@@ -8848,9 +8615,9 @@ function querySqlite(dbPath, sql) {
8848
8615
  }
8849
8616
  }
8850
8617
  function scanOpencode(home, env) {
8851
- const dataDir = env.XDG_DATA_HOME ?? join11(home, ".local", "share");
8852
- const dbPath = join11(dataDir, "opencode", "opencode.db");
8853
- if (!existsSync12(dbPath))
8618
+ const dataDir = env.XDG_DATA_HOME ?? join9(home, ".local", "share");
8619
+ const dbPath = join9(dataDir, "opencode", "opencode.db");
8620
+ if (!existsSync10(dbPath))
8854
8621
  return [];
8855
8622
  const rows = querySqlite(dbPath, "SELECT id, directory, time_updated FROM session WHERE parent_id IS NULL");
8856
8623
  if (!rows)
@@ -8887,6 +8654,291 @@ function scanAgentSessions(options = {}) {
8887
8654
  }
8888
8655
  var init_scan = () => {};
8889
8656
 
8657
+ // src/sync/watermark.ts
8658
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "node:fs";
8659
+ import { join as join10 } from "node:path";
8660
+ function syncStatePath(configDir = getConfigDir()) {
8661
+ return join10(configDir, STATE_FILENAME);
8662
+ }
8663
+ function loadSyncState(configDir = getConfigDir()) {
8664
+ const empty = {
8665
+ schema_version: STATE_SCHEMA_VERSION,
8666
+ watermark: null,
8667
+ consecutive_failures: 0
8668
+ };
8669
+ const path3 = syncStatePath(configDir);
8670
+ if (!existsSync11(path3))
8671
+ return empty;
8672
+ try {
8673
+ const raw = JSON.parse(readFileSync10(path3, "utf-8"));
8674
+ if (raw.schema_version !== STATE_SCHEMA_VERSION)
8675
+ return empty;
8676
+ const minedSessions = Array.isArray(raw.mined_sessions) ? raw.mined_sessions.filter((record) => typeof record === "object" && record !== null && typeof record.at === "string" && typeof record.session === "string").map((record) => ({
8677
+ at: record.at,
8678
+ session: record.session,
8679
+ ...typeof record.project === "string" ? { project: record.project } : {}
8680
+ })) : [];
8681
+ const rawRefusal = raw.last_refusal;
8682
+ const lastRefusal = rawRefusal && typeof rawRefusal.at === "string" && typeof rawRefusal.outcome === "string" && typeof rawRefusal.message === "string" ? { at: rawRefusal.at, outcome: rawRefusal.outcome, message: rawRefusal.message } : undefined;
8683
+ const rawRun = raw.run;
8684
+ const run2 = rawRun && typeof rawRun.pid === "number" && typeof rawRun.started_at === "string" && typeof rawRun.baseline_mined === "number" && rawRun.baseline_mined >= 0 ? { pid: rawRun.pid, started_at: rawRun.started_at, baseline_mined: rawRun.baseline_mined } : undefined;
8685
+ return {
8686
+ schema_version: STATE_SCHEMA_VERSION,
8687
+ watermark: typeof raw.watermark === "string" ? raw.watermark : null,
8688
+ last_attempt_at: typeof raw.last_attempt_at === "string" ? raw.last_attempt_at : undefined,
8689
+ consecutive_failures: typeof raw.consecutive_failures === "number" && raw.consecutive_failures >= 0 ? raw.consecutive_failures : 0,
8690
+ mined_sessions: minedSessions,
8691
+ total_mined: typeof raw.total_mined === "number" && raw.total_mined >= 0 ? raw.total_mined : minedSessions.length,
8692
+ total_notes: typeof raw.total_notes === "number" && raw.total_notes >= 0 ? raw.total_notes : 0,
8693
+ total_learning_tokens: typeof raw.total_learning_tokens === "number" && raw.total_learning_tokens >= 0 ? raw.total_learning_tokens : 0,
8694
+ ...lastRefusal ? { last_refusal: lastRefusal } : {},
8695
+ ...run2 ? { run: run2 } : {},
8696
+ ...raw.paused === true ? { paused: true } : {},
8697
+ ...Array.isArray(raw.project_filter) ? {
8698
+ project_filter: raw.project_filter.filter((p2) => typeof p2 === "string")
8699
+ } : {}
8700
+ };
8701
+ } catch {
8702
+ return empty;
8703
+ }
8704
+ }
8705
+ function setSyncPaused(paused, configDir = getConfigDir()) {
8706
+ const state = loadSyncState(configDir);
8707
+ if (paused)
8708
+ state.paused = true;
8709
+ else
8710
+ delete state.paused;
8711
+ saveSyncState(state, configDir);
8712
+ }
8713
+ function saveSyncState(state, configDir = getConfigDir()) {
8714
+ if (!existsSync11(configDir))
8715
+ mkdirSync7(configDir, { recursive: true, mode: 448 });
8716
+ const path3 = syncStatePath(configDir);
8717
+ const tmp = `${path3}.${process.pid}.tmp`;
8718
+ writeFileSync7(tmp, JSON.stringify(state, null, 2), { mode: 384 });
8719
+ renameSync3(tmp, path3);
8720
+ }
8721
+ function backoffUntil(state) {
8722
+ if (state.consecutive_failures === 0 || !state.last_attempt_at)
8723
+ return null;
8724
+ const last = Date.parse(state.last_attempt_at);
8725
+ if (Number.isNaN(last))
8726
+ return null;
8727
+ const delay = Math.min(BACKOFF_BASE_MS * 2 ** (state.consecutive_failures - 1), BACKOFF_MAX_MS);
8728
+ return new Date(last + delay);
8729
+ }
8730
+ function isUnderDir(dir, base) {
8731
+ const root = base.endsWith("/") ? base.slice(0, -1) : base;
8732
+ return dir === root || dir.startsWith(`${root}/`);
8733
+ }
8734
+ function filterSessionsByProject(sessions, filter, resolveDir) {
8735
+ if (!filter || filter.length === 0)
8736
+ return [...sessions];
8737
+ const includeUnknown = filter.includes(UNKNOWN_PROJECT);
8738
+ const dirs = filter.filter((entry) => entry !== UNKNOWN_PROJECT);
8739
+ return sessions.filter((session) => {
8740
+ const dir = resolveDir(session);
8741
+ if (!dir)
8742
+ return includeUnknown;
8743
+ return dirs.some((base) => isUnderDir(dir, base));
8744
+ });
8745
+ }
8746
+ function gateSessions(sessions, watermark, now = new Date, quietPeriodMs = DEFAULT_QUIET_PERIOD_MS) {
8747
+ const watermarkMs = watermark ? Date.parse(watermark) : Number.NEGATIVE_INFINITY;
8748
+ const completedBefore = now.getTime() - quietPeriodMs;
8749
+ const ready = [];
8750
+ const open2 = [];
8751
+ for (const session of sessions) {
8752
+ const updated = Date.parse(session.updated);
8753
+ if (Number.isNaN(updated) || updated <= watermarkMs)
8754
+ continue;
8755
+ if (updated > completedBefore) {
8756
+ open2.push(session);
8757
+ } else {
8758
+ ready.push(session);
8759
+ }
8760
+ }
8761
+ return { ready, open: open2 };
8762
+ }
8763
+ var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS, MINED_HISTORY_LIMIT = 500, UNKNOWN_PROJECT = "(unknown)";
8764
+ var init_watermark = __esm(() => {
8765
+ init_config();
8766
+ DEFAULT_QUIET_PERIOD_MS = 5 * 60 * 1000;
8767
+ BACKOFF_BASE_MS = 15 * 60 * 1000;
8768
+ BACKOFF_MAX_MS = 24 * 60 * 60 * 1000;
8769
+ });
8770
+
8771
+ // src/sync/backlog.ts
8772
+ function listSessionBacklog() {
8773
+ try {
8774
+ const state = loadSyncState();
8775
+ let sessions = scanAgentSessions({});
8776
+ if (state.project_filter?.length) {
8777
+ const resolver = createProjectDirResolver();
8778
+ sessions = filterSessionsByProject(sessions, state.project_filter, resolver.resolve);
8779
+ resolver.flush();
8780
+ }
8781
+ const gate = gateSessions(sessions, state.watermark);
8782
+ return { queued: gate.ready.reverse(), open: gate.open.reverse() };
8783
+ } catch {
8784
+ return { queued: [], open: [] };
8785
+ }
8786
+ }
8787
+ var init_backlog = __esm(() => {
8788
+ init_project_dir();
8789
+ init_scan();
8790
+ init_watermark();
8791
+ });
8792
+
8793
+ // src/sync/lock.ts
8794
+ import { existsSync as existsSync12, readFileSync as readFileSync11, rmSync as rmSync2, statSync as statSync5, writeFileSync as writeFileSync8 } from "node:fs";
8795
+ import { join as join11 } from "node:path";
8796
+ function processAlive(pid) {
8797
+ try {
8798
+ process.kill(pid, 0);
8799
+ return true;
8800
+ } catch (err) {
8801
+ return err.code === "EPERM";
8802
+ }
8803
+ }
8804
+ function lockPath(configDir = getConfigDir()) {
8805
+ return join11(configDir, LOCK_FILENAME);
8806
+ }
8807
+ function stopSyncRun(pid, configDir = getConfigDir(), kill = process.kill.bind(process)) {
8808
+ if (!Number.isInteger(pid) || pid <= 0)
8809
+ return false;
8810
+ let killed = false;
8811
+ try {
8812
+ kill(-pid, "SIGTERM");
8813
+ killed = true;
8814
+ } catch {
8815
+ try {
8816
+ kill(pid, "SIGTERM");
8817
+ killed = true;
8818
+ } catch {}
8819
+ }
8820
+ try {
8821
+ const path3 = lockPath(configDir);
8822
+ if (existsSync12(path3) && readFileSync11(path3, "utf8") === String(pid)) {
8823
+ rmSync2(path3, { force: true });
8824
+ }
8825
+ } catch {}
8826
+ return killed;
8827
+ }
8828
+ function fileLock(configDir = getConfigDir(), now = () => new Date) {
8829
+ const path3 = lockPath(configDir);
8830
+ let held = false;
8831
+ const tryCreate = () => {
8832
+ try {
8833
+ writeFileSync8(path3, String(process.pid), { flag: "wx" });
8834
+ return true;
8835
+ } catch {
8836
+ return false;
8837
+ }
8838
+ };
8839
+ return {
8840
+ acquire() {
8841
+ if (tryCreate()) {
8842
+ held = true;
8843
+ return true;
8844
+ }
8845
+ try {
8846
+ const age = now().getTime() - statSync5(path3).mtimeMs;
8847
+ const holder = Number.parseInt(readFileSync11(path3, "utf8"), 10);
8848
+ const holderDead = Number.isInteger(holder) && holder > 0 && !processAlive(holder);
8849
+ if (age > STALE_LOCK_MS || holderDead) {
8850
+ rmSync2(path3, { force: true });
8851
+ held = tryCreate();
8852
+ return held;
8853
+ }
8854
+ } catch {}
8855
+ return false;
8856
+ },
8857
+ release() {
8858
+ if (!held)
8859
+ return;
8860
+ held = false;
8861
+ try {
8862
+ if (existsSync12(path3) && readFileSync11(path3, "utf8") === String(process.pid)) {
8863
+ rmSync2(path3, { force: true });
8864
+ }
8865
+ } catch {}
8866
+ }
8867
+ };
8868
+ }
8869
+ var LOCK_FILENAME = "knowledge-sync.lock", STALE_LOCK_MS;
8870
+ var init_lock = __esm(() => {
8871
+ init_config();
8872
+ STALE_LOCK_MS = 15 * 60 * 1000;
8873
+ });
8874
+
8875
+ // src/sync/status.ts
8876
+ import { readFileSync as readFileSync12, statSync as statSync6 } from "node:fs";
8877
+ function formatTokenCount(tokens) {
8878
+ if (tokens < 1000)
8879
+ return String(tokens);
8880
+ if (tokens < 1e6) {
8881
+ const k = tokens / 1000;
8882
+ return `~${k >= 100 ? Math.round(k) : k.toFixed(1).replace(/\.0$/, "")}k`;
8883
+ }
8884
+ const m3 = tokens / 1e6;
8885
+ return `~${m3 >= 100 ? Math.round(m3) : m3.toFixed(1).replace(/\.0$/, "")}M`;
8886
+ }
8887
+ function defaultIsProcessAlive2(pid) {
8888
+ if (!Number.isInteger(pid) || pid <= 0)
8889
+ return false;
8890
+ try {
8891
+ process.kill(pid, 0);
8892
+ return true;
8893
+ } catch (err) {
8894
+ return err.code === "EPERM";
8895
+ }
8896
+ }
8897
+ function defaultReadLog() {
8898
+ try {
8899
+ return readFileSync12(logger.getLogPath(), "utf-8");
8900
+ } catch {
8901
+ return "";
8902
+ }
8903
+ }
8904
+ function readLockFile(configDir) {
8905
+ const path3 = lockPath(configDir);
8906
+ try {
8907
+ const pid = Number.parseInt(readFileSync12(path3, "utf-8"), 10);
8908
+ return { pid: Number.isNaN(pid) ? -1 : pid, mtime: statSync6(path3).mtime };
8909
+ } catch {
8910
+ return null;
8911
+ }
8912
+ }
8913
+ function recentSyncActivity(log3) {
8914
+ return log3.split(`
8915
+ `).filter((line) => line.includes("[sync]") || line.includes("[miner]")).slice(-ACTIVITY_LINES).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
8916
+ }
8917
+ function getSyncStatus(deps = {}) {
8918
+ const configDir = deps.configDir ?? getConfigDir();
8919
+ const isProcessAlive = deps.isProcessAlive ?? defaultIsProcessAlive2;
8920
+ const readLog = deps.readLog ?? defaultReadLog;
8921
+ const lock = readLockFile(configDir);
8922
+ const running = lock !== null && isProcessAlive(lock.pid);
8923
+ const state = loadSyncState(configDir);
8924
+ const retryAt = backoffUntil(state);
8925
+ return {
8926
+ running,
8927
+ ...lock ? { pid: lock.pid, startedAt: lock.mtime.toISOString() } : {},
8928
+ ...lock && !running ? { staleLock: true } : {},
8929
+ state,
8930
+ ...retryAt ? { backoffUntil: retryAt.toISOString() } : {},
8931
+ recentActivity: recentSyncActivity(readLog())
8932
+ };
8933
+ }
8934
+ var ACTIVITY_LINES = 10;
8935
+ var init_status = __esm(() => {
8936
+ init_config();
8937
+ init_logger();
8938
+ init_lock();
8939
+ init_watermark();
8940
+ });
8941
+
8890
8942
  // src/sessions/read.ts
8891
8943
  import { readFileSync as readFileSync13 } from "node:fs";
8892
8944
  function asRecord(value) {
@@ -9062,11 +9114,18 @@ async function runKnowledgeSync(options = {}) {
9062
9114
  const now = deps.now ?? (() => new Date);
9063
9115
  const state = loadState();
9064
9116
  if (options.quiet) {
9117
+ if (state.paused) {
9118
+ logger.debug("sync", "skipping quiet sync: mining is paused");
9119
+ return { status: "skipped-paused", readySessions: 0, inFlightSessions: 0, sessions: [] };
9120
+ }
9065
9121
  const retryAt = backoffUntil(state);
9066
9122
  if (retryAt && now() < retryAt) {
9067
9123
  logger.debug("sync", `skipping quiet sync: backoff until ${retryAt.toISOString()}`);
9068
9124
  return { status: "skipped-backoff", readySessions: 0, inFlightSessions: 0, sessions: [] };
9069
9125
  }
9126
+ } else if (state.paused) {
9127
+ delete state.paused;
9128
+ logger.debug("sync", "manual sync resumes paused mining");
9070
9129
  }
9071
9130
  let ready;
9072
9131
  let open2;
@@ -71924,7 +71983,7 @@ var init_settings = __esm(() => {
71924
71983
  });
71925
71984
 
71926
71985
  // src/telemetry/debug-id.ts
71927
- var BUNDLE_DEBUG_ID_PLACEHOLDER = "6c656d2d-4b9b-0be3-6475-6e2164756e21";
71986
+ var BUNDLE_DEBUG_ID_PLACEHOLDER = "1952c680-18dc-c6aa-6475-6e2164756e21";
71928
71987
 
71929
71988
  // src/telemetry/telemetry.ts
71930
71989
  import { randomUUID as randomUUID4 } from "node:crypto";
@@ -73852,6 +73911,13 @@ function reduceSyncConfirmKey(key) {
73852
73911
  return "cancel";
73853
73912
  return "none";
73854
73913
  }
73914
+ function syncConfirmMode(status) {
73915
+ if (status.running)
73916
+ return "stop";
73917
+ if (status.state.paused)
73918
+ return "resume";
73919
+ return "start";
73920
+ }
73855
73921
  function formatActivityLine(line, width) {
73856
73922
  const compact = stripAnsiCodes(line).replace(/^\[(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})[^\]]*\]/, (_match, _date2, time3) => time3);
73857
73923
  if (compact.length <= width)
@@ -73880,21 +73946,6 @@ function formatQueuedRow(session) {
73880
73946
  const project = clip(session.project ?? "-", 28);
73881
73947
  return `${session.harness.padEnd(8)} ${stamp} ${project} ${clip(session.id, 24)}`;
73882
73948
  }
73883
- function defaultListBacklog() {
73884
- try {
73885
- const state = loadSyncState();
73886
- let sessions = scanAgentSessions({});
73887
- if (state.project_filter?.length) {
73888
- const resolver = createProjectDirResolver();
73889
- sessions = filterSessionsByProject(sessions, state.project_filter, resolver.resolve);
73890
- resolver.flush();
73891
- }
73892
- const gate = gateSessions(sessions, state.watermark);
73893
- return { queued: gate.ready.reverse(), open: gate.open.reverse() };
73894
- } catch {
73895
- return { queued: [], open: [] };
73896
- }
73897
- }
73898
73949
  function windowList(lines, scroll, height = ACTIVITY_VIEW_LIST_LINES) {
73899
73950
  const clamped = Math.max(0, Math.min(scroll, Math.max(0, lines.length - height)));
73900
73951
  const end = lines.length - clamped;
@@ -73953,6 +74004,9 @@ function statusLine(status) {
73953
74004
  const pid = status.pid !== undefined ? ` (pid ${status.pid})` : "";
73954
74005
  return `⛏️ ${import_picocolors30.default.bold(brand("Mining sessions..."))}${import_picocolors30.default.dim(`${pid}${since}`)}`;
73955
74006
  }
74007
+ if (status.state.paused) {
74008
+ return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Paused")} ${import_picocolors30.default.dim("· mining stays off until you resume")}`;
74009
+ }
73956
74010
  if (status.staleLock) {
73957
74011
  return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Not running")} ${import_picocolors30.default.dim("· a previous run exited without cleaning up")}`;
73958
74012
  }
@@ -73996,15 +74050,17 @@ function wrapLine(text, width, indent = " ") {
73996
74050
  out.push(line);
73997
74051
  return out;
73998
74052
  }
73999
- function confirmBox(queuedCount, backlog, width) {
74053
+ function confirmBox(queuedCount, backlog, width, mode = "start") {
74000
74054
  const inFlight = backlog && backlog.inFlight > 0 ? ` (+${backlog.inFlight} open, mined once ${backlog.inFlight === 1 ? "it goes" : "they go"} quiet)` : "";
74001
- const scope = queuedCount > 0 ? `${queuedCount} session${queuedCount === 1 ? "" : "s"} queued${inFlight} · runs in the background` : `queue empty${inFlight} · a run would only pick up sessions that finish from here`;
74055
+ const scope = mode === "stop" ? "the run is killed mid-batch · mining stays paused until you resume" : queuedCount > 0 ? `${queuedCount} session${queuedCount === 1 ? "" : "s"} queued${inFlight} · runs in the background` : `queue empty${inFlight} · a run would only pick up sessions that finish from here`;
74056
+ const title = mode === "stop" ? "Stop mining?" : mode === "resume" ? "Resume mining?" : "Start mining now?";
74057
+ const verb = mode === "stop" ? "stop" : mode === "resume" ? "resume" : "start";
74002
74058
  const maxInner = Math.max(20, Math.min(width, contentWidth()) - 4);
74003
74059
  const rows = [
74004
- `⛏ ${import_picocolors30.default.bold(brand("Start mining now?"))}`,
74060
+ import_picocolors30.default.bold(brand(title)),
74005
74061
  ...wrapLine(scope, maxInner, "").map((line) => import_picocolors30.default.dim(line)),
74006
74062
  "",
74007
- `${import_picocolors30.default.bold("enter")} start · ${import_picocolors30.default.bold("esc")} cancel`
74063
+ `${import_picocolors30.default.bold("enter")} ${verb} · ${import_picocolors30.default.bold("esc")} cancel`
74008
74064
  ];
74009
74065
  const inner = Math.min(maxInner, Math.max(...rows.map(visibleWidth)));
74010
74066
  const pad = (row) => row + " ".repeat(Math.max(0, inner - visibleWidth(row)));
@@ -74018,7 +74074,7 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
74018
74074
  const mined = status.state.watermark ? `Mined sessions up to ${localTime(status.state.watermark)}` : "Nothing mined yet";
74019
74075
  const queueDetail = [];
74020
74076
  if (status.backoffUntil) {
74021
- queueDetail.push(`retrying after ${localTime(status.backoffUntil)}`);
74077
+ queueDetail.push(`retrying after ${localTime(status.backoffUntil)} · s syncs now`);
74022
74078
  }
74023
74079
  let progress = null;
74024
74080
  if (status.running && backlog) {
@@ -74041,6 +74097,9 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
74041
74097
  scrollParts.push(`↑ ${above} earlier`);
74042
74098
  if (below > 0)
74043
74099
  scrollParts.push(`↓ ${below} newer`);
74100
+ if (pane.tab !== "activity" && visible.length > 0) {
74101
+ scrollParts.push(`full rows: dosu knowledge sessions --${pane.tab}`);
74102
+ }
74044
74103
  const lines = [
74045
74104
  breadcrumb(["Home", "Activity"], width),
74046
74105
  "",
@@ -74054,11 +74113,11 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
74054
74113
  ...listRows,
74055
74114
  ...scrollParts.length > 0 ? [import_picocolors30.default.dim(scrollParts.join(" · "))] : [],
74056
74115
  "",
74057
- ...pane.confirm ? centerBlock(confirmBox(queued.length, backlog, width), width) : [
74116
+ ...pane.confirm ? centerBlock(confirmBox(queued.length, backlog, width, pane.confirm), width) : [
74058
74117
  import_picocolors30.default.dim([
74059
74118
  "tab switch",
74060
74119
  "↑↓ scroll",
74061
- ...status.running ? [] : ["s sync now"],
74120
+ status.running ? "s stop" : status.state.paused ? "s resume" : "s sync now",
74062
74121
  "esc back"
74063
74122
  ].join(" · "))
74064
74123
  ]
@@ -74081,8 +74140,10 @@ function runActivityView(io2 = {}) {
74081
74140
  const getStatus = io2.getStatus ?? (() => getSyncStatus({ readLog: () => "" }));
74082
74141
  const readLog = io2.readLog ?? defaultReadLog2;
74083
74142
  const createFollower = io2.createFollower ?? ((emit) => createLogFollower(logger.getLogPath(), emit));
74084
- const startSync = io2.startSync ?? (() => spawnDetachedSelf(["knowledge", "sync", "--quiet", "--bootstrap"]));
74085
- const listBacklog = io2.listBacklog ?? defaultListBacklog;
74143
+ const startSync = io2.startSync ?? (() => spawnDetachedSelf(["knowledge", "sync", "--bootstrap"]));
74144
+ const stopSync = io2.stopSync ?? stopSyncRun;
74145
+ const setPaused = io2.setPaused ?? setSyncPaused;
74146
+ const listBacklog = io2.listBacklog ?? listSessionBacklog;
74086
74147
  const pollMs = io2.pollMs ?? ACTIVITY_VIEW_POLL_MS;
74087
74148
  const seed = readLog();
74088
74149
  let activity = appendSyncActivity([], seed);
@@ -74090,7 +74151,7 @@ function runActivityView(io2 = {}) {
74090
74151
  let runProgress = foldRunProgress(null, seed);
74091
74152
  let tab = "activity";
74092
74153
  let scroll = 0;
74093
- let confirmSync = false;
74154
+ let confirmSync = null;
74094
74155
  let status = getStatus();
74095
74156
  let sessions = listBacklog();
74096
74157
  let queuedWatermark = status.state.watermark;
@@ -74126,10 +74187,12 @@ function runActivityView(io2 = {}) {
74126
74187
  queuedWatermark = status.state.watermark;
74127
74188
  sessions = listBacklog();
74128
74189
  }
74129
- if (confirmSync && status.running)
74130
- confirmSync = false;
74190
+ if ((confirmSync === "start" || confirmSync === "resume") && status.running)
74191
+ confirmSync = null;
74192
+ if (confirmSync === "stop" && !status.running)
74193
+ confirmSync = null;
74131
74194
  const width = activityWidth(output2.columns ?? 80);
74132
- const frame = renderActivityFrame(status, activity, width, backlog, { tab, scroll, confirm: confirmSync }, sessions.queued, minedBeforeRun ?? 0, sessions.open, runProgress);
74195
+ const frame = renderActivityFrame(status, activity, width, backlog, { tab, scroll, confirm: confirmSync ?? undefined }, sessions.queued, minedBeforeRun ?? 0, sessions.open, runProgress);
74133
74196
  if (frame === lastFrame)
74134
74197
  return;
74135
74198
  lastFrame = frame;
@@ -74173,14 +74236,25 @@ ${CLEAR_BELOW}`);
74173
74236
  if (confirmSync) {
74174
74237
  const decision = reduceSyncConfirmKey(key);
74175
74238
  if (decision === "start") {
74176
- confirmSync = false;
74177
- const ok2 = startSync();
74178
- const note = ok2 ? "[sync] sync requested · starting a background run" : "[sync] could not start a background run · try `dosu knowledge sync`";
74239
+ const mode = confirmSync;
74240
+ confirmSync = null;
74241
+ let note;
74242
+ if (mode === "stop") {
74243
+ const ok2 = status.pid !== undefined && stopSync(status.pid);
74244
+ if (ok2)
74245
+ setPaused(true);
74246
+ note = ok2 ? "[sync] mining stopped · paused until you resume" : "[sync] could not stop the run · it may have just finished";
74247
+ } else {
74248
+ if (mode === "resume")
74249
+ setPaused(false);
74250
+ const ok2 = startSync();
74251
+ note = ok2 ? "[sync] sync requested · starting a background run" : "[sync] could not start a background run · try `dosu knowledge sync`";
74252
+ }
74179
74253
  activity = appendSyncActivity(activity, `[${new Date().toISOString()}] [INFO] ${note}
74180
74254
  `);
74181
74255
  draw();
74182
74256
  } else if (decision === "cancel") {
74183
- confirmSync = false;
74257
+ confirmSync = null;
74184
74258
  draw();
74185
74259
  }
74186
74260
  continue;
@@ -74191,10 +74265,8 @@ ${CLEAR_BELOW}`);
74191
74265
  return;
74192
74266
  }
74193
74267
  if (action === "sync") {
74194
- if (!status.running) {
74195
- confirmSync = true;
74196
- draw();
74197
- }
74268
+ confirmSync = syncConfirmMode(status);
74269
+ draw();
74198
74270
  } else if (action === "tab" || action === "tab-back") {
74199
74271
  tab = cycleTab(tab, action === "tab" ? 1 : -1);
74200
74272
  scroll = 0;
@@ -74222,10 +74294,10 @@ var import_picocolors30, ESC7, HIDE_CURSOR3, SHOW_CURSOR3, CTRL_C4, KEY_UP4, KEY
74222
74294
  var init_activity_view = __esm(() => {
74223
74295
  init_follow();
74224
74296
  init_logger();
74225
- init_project_dir();
74226
- init_scan();
74227
74297
  init_styles();
74298
+ init_backlog();
74228
74299
  init_detach();
74300
+ init_lock();
74229
74301
  init_status();
74230
74302
  init_watermark();
74231
74303
  init_alt_screen();
@@ -75934,18 +76006,19 @@ function projectRows(state) {
75934
76006
  const key = record2.project ?? "(unknown)";
75935
76007
  byProject.set(key, (byProject.get(key) ?? 0) + 1);
75936
76008
  }
75937
- return [...byProject.entries()].sort((a9, b2) => b2[1] - a9[1]).map(([project, count]) => `${label(clip2(project, 24))}${count}`);
76009
+ const data = [...byProject.entries()].sort((a9, b2) => b2[1] - a9[1]).map(([project, count]) => `${label(clip2(project, 24))}${count}`);
76010
+ return data.length > 0 ? [`${label("Project")}Sessions`, ...data] : [];
75938
76011
  }
75939
76012
  function pageRows(pageStats) {
75940
76013
  const rows = [];
75941
76014
  if (pageStats && pageStats.topCited.length > 0) {
75942
- rows.push(`Top cited pages (${TOP_CITED_DAYS}d)`);
76015
+ rows.push(`${label(`Top cited (${TOP_CITED_DAYS}d)`)}Citations`);
75943
76016
  for (const row of pageStats.topCited) {
75944
76017
  rows.push(`${label(clip2(row.title, 24))}${row.citation_count}`);
75945
76018
  }
75946
76019
  }
75947
76020
  if (pageStats && pageStats.topUpdated.length > 0) {
75948
- rows.push(...rows.length > 0 ? [""] : [], "Recently updated pages");
76021
+ rows.push(...rows.length > 0 ? [""] : [], `${label("Recently updated")}Updated`);
75949
76022
  for (const row of pageStats.topUpdated) {
75950
76023
  rows.push(`${label(clip2(row.title, 24))}${row.updated_at.slice(0, 10)}`);
75951
76024
  }
@@ -75985,6 +76058,7 @@ function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending
75985
76058
  ["projects", "Projects"],
75986
76059
  ["pages", "Pages"]
75987
76060
  ], tab, width, { spread: false }),
76061
+ "",
75988
76062
  ...listRows,
75989
76063
  ...scrollParts.length > 0 ? [import_picocolors32.default.dim(scrollParts.join(" · "))] : [],
75990
76064
  "",
@@ -76083,7 +76157,7 @@ ${CLEAR_BELOW2}`);
76083
76157
  input2.on("data", onData);
76084
76158
  });
76085
76159
  }
76086
- var import_picocolors32, ESC8, HIDE_CURSOR4, SHOW_CURSOR4, CTRL_C5, KEY_UP5, KEY_DOWN5, KEY_RIGHT4, KEY_LEFT4, CURSOR_HOME2, CLEAR_BELOW2, CLEAR_EOL2, ANALYTICS_VIEW_POLL_MS = 1000, ANALYTICS_VIEW_LINES = 12, ANALYTICS_VIEW_TABS, TOP_PAGES_LIMIT = 5, TOP_CITED_DAYS = 30, label = (text) => text.padEnd(26);
76160
+ var import_picocolors32, ESC8, HIDE_CURSOR4, SHOW_CURSOR4, CTRL_C5, KEY_UP5, KEY_DOWN5, KEY_RIGHT4, KEY_LEFT4, CURSOR_HOME2, CLEAR_BELOW2, CLEAR_EOL2, ANALYTICS_VIEW_POLL_MS = 1000, ANALYTICS_VIEW_LINES = 14, ANALYTICS_VIEW_TABS, TOP_PAGES_LIMIT = 5, TOP_CITED_DAYS = 30, label = (text) => text.padEnd(26);
76087
76161
  var init_analytics_view = __esm(() => {
76088
76162
  init_trpc();
76089
76163
  init_config();
@@ -80634,9 +80708,11 @@ init_trpc();
80634
80708
  init_config();
80635
80709
  init_agents();
80636
80710
  init_formats();
80711
+ init_backlog();
80637
80712
  init_detach();
80638
80713
  init_status();
80639
80714
  init_sync();
80715
+ init_watermark();
80640
80716
  var import_picocolors18 = __toESM(require_picocolors(), 1);
80641
80717
  import { existsSync as existsSync16 } from "node:fs";
80642
80718
  import { homedir as homedir5 } from "node:os";
@@ -80703,6 +80779,50 @@ ${results.length - opts.limit} more results not shown.`));
80703
80779
  console.log(` ID: ${store.id}`);
80704
80780
  console.log(` Space ID: ${store.space_id}`);
80705
80781
  });
80782
+ cmd.command("sessions").description("List local agent sessions with full project and session ids (the untruncated view of the Activity screen's tabs)").option("--queued", "Only sessions queued for mining").option("--open", "Only live sessions still inside the quiet period").option("--mined", "Only recent mined-session history").option("--json", "Output as JSON").action((opts) => {
80783
+ const all = !opts.queued && !opts.open && !opts.mined;
80784
+ const wantQueued = all || Boolean(opts.queued);
80785
+ const wantOpen = all || Boolean(opts.open);
80786
+ const wantMined = all || Boolean(opts.mined);
80787
+ const backlog = wantQueued || wantOpen ? listSessionBacklog() : { queued: [], open: [] };
80788
+ const mined = wantMined ? loadSyncState().mined_sessions ?? [] : [];
80789
+ if (opts.json) {
80790
+ printResult({
80791
+ ...wantQueued ? { queued: backlog.queued } : {},
80792
+ ...wantOpen ? { open: backlog.open } : {},
80793
+ ...wantMined ? { mined } : {}
80794
+ }, opts);
80795
+ return;
80796
+ }
80797
+ const sessionRows = (sessions) => sessions.map((s) => [s.harness, s.updated, s.project ?? "-", s.id]);
80798
+ const minedRows = mined.map((record2) => {
80799
+ const slash = record2.session.indexOf("/");
80800
+ const harness = slash > 0 ? record2.session.slice(0, slash) : "-";
80801
+ const id2 = slash > 0 ? record2.session.slice(slash + 1) : record2.session;
80802
+ return [harness, record2.at, record2.project ?? "-", id2];
80803
+ });
80804
+ let first = true;
80805
+ const section = (title, rows, stamp, emptyMsg) => {
80806
+ if (!first)
80807
+ console.log();
80808
+ first = false;
80809
+ console.log(import_picocolors18.default.bold(`${title} (${rows.length})`));
80810
+ if (rows.length === 0) {
80811
+ console.log(import_picocolors18.default.dim(` ${emptyMsg}`));
80812
+ return;
80813
+ }
80814
+ printTable(["Agent", stamp, "Project", "Session"], rows);
80815
+ };
80816
+ if (wantQueued) {
80817
+ section("Queued", sessionRows(backlog.queued), "Updated", "Queue empty. Finished agent sessions appear here.");
80818
+ }
80819
+ if (wantOpen) {
80820
+ section("Open", sessionRows(backlog.open), "Updated", "No open sessions. Live agent sessions sit here until they go quiet.");
80821
+ }
80822
+ if (wantMined) {
80823
+ section("Mined", minedRows, "Mined at", "No mined sessions recorded yet.");
80824
+ }
80825
+ });
80706
80826
  cmd.command("sync").description("Scan local agent session history and report the mining backlog").option("--quiet", "Background mode for hooks: honor backoff, exit 0, print nothing").option("--detach", "Re-spawn detached and return immediately (used by agent hooks)").option("--bootstrap", "Backfill mode: mine the full local session history regardless of age and drain the backlog (used by setup)").option("--status", "Show whether a sync is running now, plus watermark and recent activity").option("--json", "Output as JSON").action(async (opts) => {
80707
80827
  if (opts.status) {
80708
80828
  const status = getSyncStatus();
@@ -80784,6 +80904,9 @@ function printSyncStatus(status, now = new Date) {
80784
80904
  } else {
80785
80905
  console.log("○ No sync running.");
80786
80906
  }
80907
+ if (status.state.paused) {
80908
+ console.log(import_picocolors18.default.yellow(" Mining paused: stopped by you. Resume from the Activity screen or run 'dosu knowledge sync'."));
80909
+ }
80787
80910
  const wm2 = status.state.watermark;
80788
80911
  console.log(` Mined through: ${wm2 ? `${wm2} (${formatAge(wm2, now)})` : "nothing mined yet"}`);
80789
80912
  if (status.state.project_filter?.length) {
@@ -80860,6 +80983,10 @@ function printSyncOutcome(outcome) {
80860
80983
  console.log(import_picocolors18.default.dim("Skipped: a recent sync failed; waiting out the retry backoff."));
80861
80984
  break;
80862
80985
  }
80986
+ case "skipped-paused": {
80987
+ console.log(import_picocolors18.default.dim("Skipped: mining is paused. Run 'dosu knowledge sync' to resume."));
80988
+ break;
80989
+ }
80863
80990
  }
80864
80991
  }
80865
80992
  function resolveHookAgents(ids) {
@@ -82702,4 +82829,4 @@ execute().catch((err) => {
82702
82829
  process.exit(1);
82703
82830
  });
82704
82831
 
82705
- //# debugId=6c656d2d-4b9b-0be3-6475-6e2164756e21
82832
+ //# debugId=1952c680-18dc-c6aa-6475-6e2164756e21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dosu/cli",
3
- "version": "0.53.0-beta.4",
3
+ "version": "0.53.0-beta.6",
4
4
  "type": "module",
5
5
  "description": "Dosu CLI - Manage MCP servers for AI tools",
6
6
  "license": "MIT",