@dosu/cli 0.56.0 → 0.57.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.
Files changed (3) hide show
  1. package/README.md +3 -2
  2. package/bin/dosu.js +625 -253
  3. package/package.json +1 -1
package/bin/dosu.js CHANGED
@@ -636,7 +636,18 @@ function numberValue(value) {
636
636
  var init_config_v1_migration = () => {};
637
637
 
638
638
  // src/config/config.ts
639
- import { existsSync as existsSync2, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
639
+ import {
640
+ closeSync,
641
+ constants,
642
+ existsSync as existsSync2,
643
+ fstatSync,
644
+ mkdirSync,
645
+ openSync,
646
+ readFileSync,
647
+ readSync,
648
+ renameSync,
649
+ writeFileSync
650
+ } from "node:fs";
640
651
  import { homedir as homedir2 } from "node:os";
641
652
  import { join as join2 } from "node:path";
642
653
  function getConfigUserID(cfg) {
@@ -701,6 +712,26 @@ function loadConfig() {
701
712
  } catch {}
702
713
  return parsed;
703
714
  }
715
+ function loadConfigNonBlocking() {
716
+ let fd;
717
+ try {
718
+ fd = openSync(getConfigPath(), constants.O_RDONLY | constants.O_NONBLOCK);
719
+ const file = fstatSync(fd);
720
+ if (!file.isFile() || file.size > MAX_BACKGROUND_CONFIG_BYTES)
721
+ return;
722
+ const content = Buffer.alloc(MAX_BACKGROUND_CONFIG_BYTES);
723
+ const bytesRead = readSync(fd, content, 0, content.byteLength, 0);
724
+ return parseConfig(JSON.parse(content.subarray(0, bytesRead).toString("utf8")));
725
+ } catch {
726
+ return;
727
+ } finally {
728
+ if (fd !== undefined) {
729
+ try {
730
+ closeSync(fd);
731
+ } catch {}
732
+ }
733
+ }
734
+ }
704
735
  function parseConfig(raw) {
705
736
  if (isConfigV2(raw))
706
737
  return normalizeV2(raw);
@@ -796,8 +827,10 @@ function stringValue2(value) {
796
827
  function numberValue2(value) {
797
828
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
798
829
  }
830
+ var MAX_BACKGROUND_CONFIG_BYTES;
799
831
  var init_config = __esm(() => {
800
832
  init_config_v1_migration();
833
+ MAX_BACKGROUND_CONFIG_BYTES = 64 * 1024;
801
834
  });
802
835
 
803
836
  // src/mcp/config-helpers.ts
@@ -1050,7 +1083,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
1050
1083
  function getVersionString() {
1051
1084
  return `v${VERSION}`;
1052
1085
  }
1053
- var VERSION = "0.56.0", INSTALL_CHANNEL = "npm";
1086
+ var VERSION = "0.57.0", INSTALL_CHANNEL = "npm";
1054
1087
 
1055
1088
  // src/debug/logger.ts
1056
1089
  import {
@@ -1829,21 +1862,21 @@ var init_read = __esm(() => {
1829
1862
  });
1830
1863
 
1831
1864
  // src/sync/incognito.ts
1832
- import { closeSync, fstatSync, openSync, readSync } from "node:fs";
1865
+ import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readSync as readSync2 } from "node:fs";
1833
1866
  function textHasIncognitoMarker(text) {
1834
1867
  return text.includes(INCOGNITO_MARKER) || text.includes(COMMAND_NAME_MARKER);
1835
1868
  }
1836
1869
  function transcriptHasIncognitoMarker(path, maxBytes = MAX_SCAN_BYTES) {
1837
1870
  let fd;
1838
1871
  try {
1839
- fd = openSync(path, "r");
1840
- const size = Math.min(fstatSync(fd).size, maxBytes);
1872
+ fd = openSync2(path, "r");
1873
+ const size = Math.min(fstatSync2(fd).size, maxBytes);
1841
1874
  if (size === 0)
1842
1875
  return false;
1843
1876
  const buffer = Buffer.allocUnsafe(size);
1844
1877
  let offset = 0;
1845
1878
  while (offset < size) {
1846
- const read = readSync(fd, buffer, offset, size - offset, offset);
1879
+ const read = readSync2(fd, buffer, offset, size - offset, offset);
1847
1880
  if (read === 0)
1848
1881
  break;
1849
1882
  offset += read;
@@ -1854,7 +1887,7 @@ function transcriptHasIncognitoMarker(path, maxBytes = MAX_SCAN_BYTES) {
1854
1887
  } finally {
1855
1888
  if (fd !== undefined) {
1856
1889
  try {
1857
- closeSync(fd);
1890
+ closeSync2(fd);
1858
1891
  } catch {}
1859
1892
  }
1860
1893
  }
@@ -1902,6 +1935,7 @@ function loadSyncState(configDir = getConfigDir()) {
1902
1935
  const studiedSessions = 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) => ({
1903
1936
  at: record.at,
1904
1937
  session: record.session,
1938
+ ...typeof record.updated === "string" ? { updated: record.updated } : {},
1905
1939
  ...typeof record.project === "string" ? { project: record.project } : {}
1906
1940
  })) : [];
1907
1941
  const rawRefusal = raw.last_refusal;
@@ -4204,11 +4238,11 @@ var init_session_errors = __esm(() => {
4204
4238
  // src/config/config-lock.ts
4205
4239
  import { randomUUID } from "node:crypto";
4206
4240
  import {
4207
- closeSync as closeSync2,
4241
+ closeSync as closeSync3,
4208
4242
  existsSync as existsSync8,
4209
4243
  linkSync,
4210
4244
  mkdirSync as mkdirSync5,
4211
- openSync as openSync2,
4245
+ openSync as openSync3,
4212
4246
  readFileSync as readFileSync7,
4213
4247
  statSync as statSync3,
4214
4248
  unlinkSync,
@@ -4261,12 +4295,12 @@ async function acquireConfigRefreshLock(options) {
4261
4295
  }
4262
4296
  }
4263
4297
  function createLockFile(lockPath, metadata) {
4264
- const fd = openSync2(lockPath, "wx", 384);
4298
+ const fd = openSync3(lockPath, "wx", 384);
4265
4299
  try {
4266
4300
  writeFileSync4(fd, JSON.stringify(metadata));
4267
4301
  } catch (cause) {
4268
4302
  try {
4269
- closeSync2(fd);
4303
+ closeSync3(fd);
4270
4304
  } catch {}
4271
4305
  try {
4272
4306
  unlinkSync(lockPath);
@@ -4274,7 +4308,7 @@ function createLockFile(lockPath, metadata) {
4274
4308
  throw cause;
4275
4309
  }
4276
4310
  try {
4277
- closeSync2(fd);
4311
+ closeSync3(fd);
4278
4312
  } catch (cause) {
4279
4313
  try {
4280
4314
  unlinkSync(lockPath);
@@ -11820,12 +11854,12 @@ var init_integrations = __esm(() => {
11820
11854
 
11821
11855
  // src/sessions/project-dir.ts
11822
11856
  import {
11823
- closeSync as closeSync3,
11857
+ closeSync as closeSync4,
11824
11858
  existsSync as existsSync11,
11825
11859
  mkdirSync as mkdirSync7,
11826
- openSync as openSync3,
11860
+ openSync as openSync4,
11827
11861
  readFileSync as readFileSync11,
11828
- readSync as readSync2,
11862
+ readSync as readSync3,
11829
11863
  renameSync as renameSync3,
11830
11864
  statSync as statSync4,
11831
11865
  writeFileSync as writeFileSync7
@@ -11858,18 +11892,18 @@ function saveCacheFile(configDir, entries) {
11858
11892
  function readHead(path, bytes = HEAD_BYTES) {
11859
11893
  let fd;
11860
11894
  try {
11861
- fd = openSync3(path, "r");
11895
+ fd = openSync4(path, "r");
11862
11896
  } catch {
11863
11897
  return null;
11864
11898
  }
11865
11899
  try {
11866
11900
  const buf = Buffer.alloc(bytes);
11867
- const read = readSync2(fd, buf, 0, bytes, 0);
11901
+ const read = readSync3(fd, buf, 0, bytes, 0);
11868
11902
  return buf.subarray(0, read).toString("utf-8");
11869
11903
  } catch {
11870
11904
  return null;
11871
11905
  } finally {
11872
- closeSync3(fd);
11906
+ closeSync4(fd);
11873
11907
  }
11874
11908
  }
11875
11909
  function cwdFromJsonlHead(text) {
@@ -13429,6 +13463,27 @@ function batchWatermark(batch) {
13429
13463
  }
13430
13464
  return newest;
13431
13465
  }
13466
+ function sessionKey(session) {
13467
+ return `${session.harness}/${session.id}`;
13468
+ }
13469
+ function studiedRecords(sessions, at) {
13470
+ return sessions.map((s) => ({
13471
+ at,
13472
+ session: sessionKey(s),
13473
+ updated: s.updated,
13474
+ ...s.project ? { project: s.project } : {}
13475
+ }));
13476
+ }
13477
+ function lastStudiedSnapshot(history) {
13478
+ const latest = new Map;
13479
+ for (const record of history ?? []) {
13480
+ const updated = record.updated ? Date.parse(record.updated) : Number.NaN;
13481
+ if (!Number.isNaN(updated) && updated > (latest.get(record.session) ?? Number.NEGATIVE_INFINITY)) {
13482
+ latest.set(record.session, updated);
13483
+ }
13484
+ }
13485
+ return latest;
13486
+ }
13432
13487
  async function runKnowledgeSync(options = {}) {
13433
13488
  const deps = options.deps ?? {};
13434
13489
  const loadState = deps.loadState ?? loadSyncState;
@@ -13511,14 +13566,20 @@ async function runKnowledgeSync(options = {}) {
13511
13566
  }
13512
13567
  const worthStudying = deps.worthStudying ?? isWorthStudying;
13513
13568
  const isIncognito = deps.isIncognito ?? isIncognitoSession;
13569
+ const studiedSnapshot = lastStudiedSnapshot(state.mined_sessions);
13514
13570
  const examined = [];
13515
13571
  const batch = [];
13516
13572
  let trivial = 0;
13517
13573
  let incognito = 0;
13574
+ let alreadyStudied = 0;
13518
13575
  for (let i = ready.length - 1;i >= 0 && batch.length < MINE_BATCH_LIMIT; i--) {
13519
13576
  const candidate = ready[i];
13520
13577
  examined.push(candidate);
13521
- if (isIncognito(candidate)) {
13578
+ const key = sessionKey(candidate);
13579
+ if (Date.parse(candidate.updated) <= (studiedSnapshot.get(key) ?? Number.NEGATIVE_INFINITY)) {
13580
+ alreadyStudied += 1;
13581
+ logger.debug("sync", `skipping already-studied session ${key}`);
13582
+ } else if (isIncognito(candidate)) {
13522
13583
  incognito += 1;
13523
13584
  logger.debug("sync", `skipping incognito session ${candidate.harness}/${candidate.id}`);
13524
13585
  } else if (worthStudying(candidate)) {
@@ -13527,7 +13588,7 @@ async function runKnowledgeSync(options = {}) {
13527
13588
  trivial += 1;
13528
13589
  }
13529
13590
  }
13530
- const skippedNote = `${trivial} trivial, ${incognito} incognito skipped`;
13591
+ const skippedNote = `${trivial} trivial, ${incognito} incognito${alreadyStudied > 0 ? `, ${alreadyStudied} already studied` : ""} skipped`;
13531
13592
  if (batch.length === 0) {
13532
13593
  saveState({
13533
13594
  ...state,
@@ -13546,29 +13607,41 @@ async function runKnowledgeSync(options = {}) {
13546
13607
  }
13547
13608
  logger.debug("sync", `studying ${batch.length} of ${ready.length} ready sessions (${skippedNote})`);
13548
13609
  const learner = await deps.mine(batch);
13610
+ const sessionTokens = deps.sessionTokens ?? estimateSessionTokens;
13611
+ const noted = new Set(learner.notedSessions ?? []);
13612
+ const notedBatch = batch.filter((s) => noted.has(sessionKey(s)));
13613
+ const partialProgress = (at) => {
13614
+ if (notedBatch.length === 0)
13615
+ return {};
13616
+ let tokens = 0;
13617
+ for (const s of notedBatch)
13618
+ tokens += sessionTokens(s);
13619
+ logger.debug("sync", `recorded ${notedBatch.length} noted sessions from the ${learner.outcome} run`);
13620
+ return {
13621
+ mined_sessions: [...state.mined_sessions ?? [], ...studiedRecords(notedBatch, at)].slice(-STUDIED_HISTORY_LIMIT),
13622
+ total_mined: (state.total_mined ?? 0) + notedBatch.length,
13623
+ total_notes: (state.total_notes ?? 0) + learner.notesWritten,
13624
+ total_learning_tokens: (state.total_learning_tokens ?? 0) + tokens
13625
+ };
13626
+ };
13549
13627
  switch (learner.outcome) {
13550
13628
  case "completed": {
13551
13629
  for (const s of batch) {
13552
13630
  logger.debug("sync", `studied session ${s.harness}/${s.id}`);
13553
13631
  }
13554
- const studiedAt = now().toISOString();
13632
+ const completedAt = now().toISOString();
13555
13633
  const history = [
13556
13634
  ...state.mined_sessions ?? [],
13557
- ...batch.map((s) => ({
13558
- at: studiedAt,
13559
- session: `${s.harness}/${s.id}`,
13560
- ...s.project ? { project: s.project } : {}
13561
- }))
13635
+ ...studiedRecords(batch, completedAt)
13562
13636
  ].slice(-STUDIED_HISTORY_LIMIT);
13563
13637
  const watermark = batchWatermark(examined);
13564
- const sessionTokens = deps.sessionTokens ?? estimateSessionTokens;
13565
13638
  let batchTokens = 0;
13566
13639
  for (const s of batch)
13567
13640
  batchTokens += sessionTokens(s);
13568
13641
  saveState({
13569
13642
  ...state,
13570
13643
  watermark,
13571
- last_attempt_at: studiedAt,
13644
+ last_attempt_at: completedAt,
13572
13645
  consecutive_failures: 0,
13573
13646
  mined_sessions: history,
13574
13647
  total_mined: (state.total_mined ?? 0) + batch.length,
@@ -13588,10 +13661,12 @@ async function runKnowledgeSync(options = {}) {
13588
13661
  }
13589
13662
  case "consent_off":
13590
13663
  case "credit_limit":
13591
- case "quota_exceeded": {
13664
+ case "quota_exceeded":
13665
+ case "claude_code_missing": {
13592
13666
  const at = now().toISOString();
13593
13667
  saveState({
13594
13668
  ...state,
13669
+ ...partialProgress(at),
13595
13670
  last_attempt_at: at,
13596
13671
  consecutive_failures: 0,
13597
13672
  last_refusal: {
@@ -13600,13 +13675,14 @@ async function runKnowledgeSync(options = {}) {
13600
13675
  message: learner.message ?? "Studying unavailable right now."
13601
13676
  }
13602
13677
  });
13603
- logger.debug("sync", `studying skipped by gateway: ${learner.outcome}`);
13678
+ logger.debug("sync", `studying skipped: ${learner.outcome}`);
13604
13679
  return { status: "skipped-gateway", ...base, studiedSessions: 0, learner };
13605
13680
  }
13606
13681
  default: {
13607
13682
  const at = now().toISOString();
13608
13683
  saveState({
13609
13684
  ...state,
13685
+ ...partialProgress(at),
13610
13686
  last_attempt_at: at,
13611
13687
  consecutive_failures: state.consecutive_failures + 1,
13612
13688
  ...learner.outcome === "gateway_rejected" && learner.message ? { last_refusal: { at, outcome: learner.outcome, message: learner.message } } : {}
@@ -13637,7 +13713,7 @@ var init_sync = __esm(() => {
13637
13713
  });
13638
13714
 
13639
13715
  // src/telemetry/debug-id.ts
13640
- var BUNDLE_DEBUG_ID_PLACEHOLDER = "2f023ff0-da66-217c-6475-6e2164756e21";
13716
+ var BUNDLE_DEBUG_ID_PLACEHOLDER = "6712b11a-8117-30cb-6475-6e2164756e21";
13641
13717
 
13642
13718
  // src/telemetry/telemetry.ts
13643
13719
  import { randomUUID as randomUUID2 } from "node:crypto";
@@ -13862,12 +13938,19 @@ function consumeCommandFacets() {
13862
13938
  function allowlisted(value, allowed) {
13863
13939
  return typeof value === "string" && allowed.has(value) ? value : undefined;
13864
13940
  }
13941
+ function matching(value, pattern) {
13942
+ return typeof value === "string" && pattern.test(value) ? value : undefined;
13943
+ }
13865
13944
  function sanitizeFacets(facets) {
13866
13945
  if (!facets || typeof facets !== "object")
13867
13946
  return {};
13868
13947
  const trigger = allowlisted(facets.sync_trigger, SYNC_TRIGGERS);
13869
13948
  const status = allowlisted(facets.sync_status, SYNC_STATUSES);
13870
13949
  const learner = allowlisted(facets.learner_outcome, LEARNER_OUTCOMES);
13950
+ const gatewayReason = allowlisted(facets.gateway_reason, GATEWAY_REASONS);
13951
+ const claudeSource = allowlisted(facets.claude_code_source, CLAUDE_CODE_SOURCES);
13952
+ const claudeVersion = matching(facets.claude_code_version, CLAUDE_CODE_VERSION_PATTERN);
13953
+ const model = matching(facets.learner_model, LEARNER_MODEL_PATTERN);
13871
13954
  const backfill = allowlisted(facets.backfill_offer, BACKFILL_OFFERS);
13872
13955
  return {
13873
13956
  ...trigger ? { sync_trigger: trigger } : {},
@@ -13875,6 +13958,10 @@ function sanitizeFacets(facets) {
13875
13958
  ...facets.sessions_studied !== undefined ? { sessions_studied: countBucket(facets.sessions_studied) } : {},
13876
13959
  ...facets.notes_written !== undefined ? { notes_written: countBucket(facets.notes_written) } : {},
13877
13960
  ...learner ? { learner_outcome: learner } : {},
13961
+ ...gatewayReason ? { gateway_reason: gatewayReason } : {},
13962
+ ...claudeSource ? { claude_code_source: claudeSource } : {},
13963
+ ...claudeVersion ? { claude_code_version: claudeVersion } : {},
13964
+ ...model ? { learner_model: model } : {},
13878
13965
  ...backfill ? { backfill_offer: backfill } : {}
13879
13966
  };
13880
13967
  }
@@ -13964,7 +14051,13 @@ function parseSentryDsn(value) {
13964
14051
  return null;
13965
14052
  }
13966
14053
  }
13967
- function sentryTags(command, context, runtime, error) {
14054
+ function sentryTags(command, context, runtime, error, facets) {
14055
+ const facetTags = {};
14056
+ for (const key of SENTRY_FACET_TAGS) {
14057
+ const value = facets[key];
14058
+ if (value)
14059
+ facetTags[key] = value;
14060
+ }
13968
14061
  return {
13969
14062
  schema_version: "1",
13970
14063
  command,
@@ -13980,9 +14073,21 @@ function sentryTags(command, context, runtime, error) {
13980
14073
  is_authenticated: String(context.isAuthenticated),
13981
14074
  ...error.code ? { error_code: error.code } : {},
13982
14075
  ...error.status ? { http_status: String(error.status) } : {},
13983
- ...error.exitCode === undefined ? {} : { exit_code: String(error.exitCode) }
14076
+ ...error.exitCode === undefined ? {} : { exit_code: String(error.exitCode) },
14077
+ ...facetTags
13984
14078
  };
13985
14079
  }
14080
+ function failedLearnerOutcome(facets) {
14081
+ return facets.learner_outcome === "completed" ? undefined : facets.learner_outcome;
14082
+ }
14083
+ function exceptionSummary(command, error, facets) {
14084
+ if (facets.learner_outcome === "completed")
14085
+ return error.code ?? error.type;
14086
+ const outcome = facets.learner_outcome ?? facets.sync_status;
14087
+ if (!outcome)
14088
+ return error.code ?? error.type;
14089
+ return `${command}: ${outcome}${facets.gateway_reason ? ` (${facets.gateway_reason})` : ""}`;
14090
+ }
13986
14091
  function buildSentryEnvelope(input) {
13987
14092
  const parsedDsn = parseSentryDsn(input.dsn);
13988
14093
  if (!parsedDsn || !validEventId(input.eventId))
@@ -13990,6 +14095,7 @@ function buildSentryEnvelope(input) {
13990
14095
  const context = normalizeContext(input.context);
13991
14096
  const runtime = normalizeRuntime(input.runtime);
13992
14097
  const command = canonicalCommand(input.command);
14098
+ const facets = sanitizeFacets(input.facets);
13993
14099
  const debugId = validDebugId(input.debugId) ? input.debugId.toLowerCase() : undefined;
13994
14100
  const error = {
13995
14101
  type: validErrorType(input.error.type) ? input.error.type : "Error",
@@ -14009,7 +14115,7 @@ function buildSentryEnvelope(input) {
14009
14115
  };
14010
14116
  const exceptionValue = {
14011
14117
  type: error.type,
14012
- value: error.code ?? error.type,
14118
+ value: exceptionSummary(command, error, facets),
14013
14119
  ...error.frames.length > 0 ? { stacktrace: { frames: error.frames } } : {}
14014
14120
  };
14015
14121
  const newestFrame = error.frames.at(-1);
@@ -14022,7 +14128,7 @@ function buildSentryEnvelope(input) {
14022
14128
  platform: "node",
14023
14129
  level: "error",
14024
14130
  release: `dosu-cli@${runtime.version}`,
14025
- tags: sentryTags(command, context, runtime, error),
14131
+ tags: sentryTags(command, context, runtime, error, facets),
14026
14132
  ...context.user ? {
14027
14133
  user: {
14028
14134
  id: context.user.id,
@@ -14034,7 +14140,15 @@ function buildSentryEnvelope(input) {
14034
14140
  images: [{ type: "sourcemap", code_file: BUNDLE_CODE_FILE, debug_id: debugId }]
14035
14141
  }
14036
14142
  } : {},
14037
- fingerprint: ["dosu-cli", command, error.type, error.code ?? "unknown", safeCallsite],
14143
+ fingerprint: [
14144
+ "dosu-cli",
14145
+ command,
14146
+ error.type,
14147
+ error.code ?? "unknown",
14148
+ safeCallsite,
14149
+ ...failedLearnerOutcome(facets) ? [facets.learner_outcome] : [],
14150
+ ...facets.gateway_reason ? [facets.gateway_reason] : []
14151
+ ],
14038
14152
  exception: { values: [exceptionValue] }
14039
14153
  };
14040
14154
  const envelopeHeader = {
@@ -14275,15 +14389,17 @@ function createCommandTelemetry(settings, dependencies = {}) {
14275
14389
  async function dispatch(result, exitCode, error) {
14276
14390
  try {
14277
14391
  const tasks = [];
14392
+ let facets;
14393
+ if (!disabled) {
14394
+ try {
14395
+ facets = resolveFacets();
14396
+ } catch {
14397
+ facets = undefined;
14398
+ }
14399
+ }
14278
14400
  if (!disabled && analyticsToken) {
14279
14401
  const id = context.user?.id ?? installId();
14280
14402
  if (id) {
14281
- let facets;
14282
- try {
14283
- facets = resolveFacets();
14284
- } catch {
14285
- facets = undefined;
14286
- }
14287
14403
  const payload = buildPostHogPayload({
14288
14404
  apiKey: analyticsToken,
14289
14405
  ...context.user ? {} : { installId: id },
@@ -14311,14 +14427,16 @@ function createCommandTelemetry(settings, dependencies = {}) {
14311
14427
  }
14312
14428
  }
14313
14429
  }
14314
- if (!disabled && result === "failure" && error && shouldSendToSentry(error) && sentryDsn) {
14430
+ const sentryError = result === "failure" && error && shouldSendToSentry(error) ? error : result === "success" && sanitizeFacets(facets).sync_status === "mine-failed" ? { type: "LearnerRunFailed", frames: [] } : undefined;
14431
+ if (!disabled && sentryError && sentryDsn) {
14315
14432
  const id = eventId(generateUuid);
14316
14433
  const envelope = id ? buildSentryEnvelope({
14317
14434
  dsn: sentryDsn,
14318
14435
  command,
14319
14436
  context,
14320
14437
  runtime,
14321
- error,
14438
+ error: sentryError,
14439
+ ...facets ? { facets } : {},
14322
14440
  eventId: id,
14323
14441
  timestampMs: safeNow(now),
14324
14442
  debugId: BUNDLE_DEBUG_ID_PLACEHOLDER
@@ -14370,7 +14488,7 @@ function createCommandTelemetry(settings, dependencies = {}) {
14370
14488
  function shouldSendToSentry(error) {
14371
14489
  return !["SESSION_EXPIRED", "SESSION_PERSISTENCE_ERROR"].includes(error.code ?? "");
14372
14490
  }
14373
- var REQUEST_TIMEOUT_MS = 500, MAX_RESPONSE_BYTES, MAX_STACK_FRAMES = 20, UNKNOWN_COMMAND = "unknown", FALLBACK_INSTALL_ID = "00000000-0000-4000-8000-000000000000", BUNDLE_CODE_FILE = "app:///bin/dosu.js", CLI_PACKAGE_ROOT, SAFE_ERROR_TYPES, SAFE_ERROR_CODES, SYNC_TRIGGERS, SYNC_STATUSES, LEARNER_OUTCOMES, BACKFILL_OFFERS, pendingFacets;
14491
+ var REQUEST_TIMEOUT_MS = 500, MAX_RESPONSE_BYTES, MAX_STACK_FRAMES = 20, UNKNOWN_COMMAND = "unknown", FALLBACK_INSTALL_ID = "00000000-0000-4000-8000-000000000000", BUNDLE_CODE_FILE = "app:///bin/dosu.js", CLI_PACKAGE_ROOT, SAFE_ERROR_TYPES, SAFE_ERROR_CODES, SYNC_TRIGGERS, SYNC_STATUSES, LEARNER_OUTCOMES, GATEWAY_REASONS, CLAUDE_CODE_SOURCES, CLAUDE_CODE_VERSION_PATTERN, LEARNER_MODEL_PATTERN, BACKFILL_OFFERS, SENTRY_FACET_TAGS, pendingFacets;
14374
14492
  var init_telemetry = __esm(() => {
14375
14493
  MAX_RESPONSE_BYTES = 1024 * 1024;
14376
14494
  CLI_PACKAGE_ROOT = detectCliPackageRoot();
@@ -14381,6 +14499,7 @@ var init_telemetry = __esm(() => {
14381
14499
  "CliUsageError",
14382
14500
  "CommandExitError",
14383
14501
  "Error",
14502
+ "LearnerRunFailed",
14384
14503
  "OAuthCallbackError",
14385
14504
  "RangeError",
14386
14505
  "ReferenceError",
@@ -14474,8 +14593,21 @@ var init_telemetry = __esm(() => {
14474
14593
  "credit_limit",
14475
14594
  "quota_exceeded",
14476
14595
  "gateway_rejected",
14596
+ "claude_code_missing",
14477
14597
  "error"
14478
14598
  ]);
14599
+ GATEWAY_REASONS = new Set([
14600
+ "system_role_unsupported",
14601
+ "adaptive_thinking_unsupported",
14602
+ "effort_unsupported",
14603
+ "unsupported_request",
14604
+ "max_tokens",
14605
+ "context_length",
14606
+ "other"
14607
+ ]);
14608
+ CLAUDE_CODE_SOURCES = new Set(["sdk", "system", "missing"]);
14609
+ CLAUDE_CODE_VERSION_PATTERN = /^\d{1,4}\.\d{1,4}\.\d{1,6}(?:-[0-9A-Za-z]{1,16}(?:\.[0-9A-Za-z]{1,16}){0,3})?$/;
14610
+ LEARNER_MODEL_PATTERN = /^claude-[a-z0-9.-]{1,56}$/;
14479
14611
  BACKFILL_OFFERS = new Set([
14480
14612
  "not-offered",
14481
14613
  "accepted",
@@ -14483,6 +14615,15 @@ var init_telemetry = __esm(() => {
14483
14615
  "cancelled",
14484
14616
  "spawn-failed"
14485
14617
  ]);
14618
+ SENTRY_FACET_TAGS = [
14619
+ "sync_trigger",
14620
+ "sync_status",
14621
+ "learner_outcome",
14622
+ "gateway_reason",
14623
+ "claude_code_source",
14624
+ "claude_code_version",
14625
+ "learner_model"
14626
+ ];
14486
14627
  });
14487
14628
 
14488
14629
  // src/commands/agent-select.ts
@@ -15073,12 +15214,14 @@ function buildLearnerEnv(options) {
15073
15214
  const headers = [
15074
15215
  `x-dosu-run-id: ${options.runID}`,
15075
15216
  `x-dosu-trigger: ${options.trigger}`,
15076
- `x-dosu-cli-version: ${options.cliVersion}`
15217
+ `x-dosu-cli-version: ${options.cliVersion}`,
15218
+ `x-dosu-expected-model: ${options.model}`
15077
15219
  ];
15078
15220
  if (options.deploymentID)
15079
15221
  headers.push(`x-dosu-deployment-id: ${options.deploymentID}`);
15080
15222
  env.ANTHROPIC_BASE_URL = options.gatewayURL;
15081
15223
  env.ANTHROPIC_AUTH_TOKEN = options.apiKey;
15224
+ env.ANTHROPIC_MODEL = options.model;
15082
15225
  env.ANTHROPIC_CUSTOM_HEADERS = headers.join(`
15083
15226
  `);
15084
15227
  env.CLAUDE_CONFIG_DIR = options.configDir;
@@ -15095,7 +15238,7 @@ var init_env = __esm(() => {
15095
15238
  });
15096
15239
 
15097
15240
  // src/learner/executable.ts
15098
- import { accessSync, constants, existsSync as existsSync16 } from "node:fs";
15241
+ import { accessSync, constants as constants2, existsSync as existsSync16 } from "node:fs";
15099
15242
  import { createRequire as createRequire3 } from "node:module";
15100
15243
  import { homedir as homedir6 } from "node:os";
15101
15244
  import { delimiter as delimiter2, dirname as dirname6, join as join16 } from "node:path";
@@ -15114,7 +15257,7 @@ function sdkNativeBinaryExists() {
15114
15257
  }
15115
15258
  function isExecutable(path) {
15116
15259
  try {
15117
- accessSync(path, constants.X_OK);
15260
+ accessSync(path, constants2.X_OK);
15118
15261
  return true;
15119
15262
  } catch {
15120
15263
  return false;
@@ -15136,11 +15279,44 @@ function findSystemClaude(env = process.env, homeDir = homedir6()) {
15136
15279
  function resolveClaudeExecutable(options = {}) {
15137
15280
  const hasSdkBinary = options.sdkBinaryExists ?? sdkNativeBinaryExists;
15138
15281
  if (hasSdkBinary())
15139
- return;
15140
- return findSystemClaude(options.env, options.homeDir);
15282
+ return { kind: "sdk" };
15283
+ const path = findSystemClaude(options.env, options.homeDir);
15284
+ return path ? { kind: "system", path } : { kind: "missing" };
15141
15285
  }
15142
15286
  var init_executable = () => {};
15143
15287
 
15288
+ // src/learner/model.ts
15289
+ async function resolveServedModel(options) {
15290
+ const fetchImpl = options.fetchImpl ?? fetch;
15291
+ try {
15292
+ const base = options.gatewayURL.replace(/\/+$/, "");
15293
+ const resp = await fetchImpl(`${base}/capabilities`, {
15294
+ method: "GET",
15295
+ headers: { Authorization: `Bearer ${options.apiKey}` },
15296
+ signal: AbortSignal.timeout(options.timeoutMs ?? CAPABILITIES_TIMEOUT_MS)
15297
+ });
15298
+ if (!resp.ok) {
15299
+ logger.debug("learner", `gateway capabilities returned ${resp.status}; using default model`);
15300
+ return DEFAULT_LEARNER_MODEL;
15301
+ }
15302
+ const body = await resp.json();
15303
+ const model = body?.model;
15304
+ if (typeof model === "string" && CLAUDE_MODEL_PATTERN.test(model))
15305
+ return model;
15306
+ logger.debug("learner", "gateway capabilities named no usable model; using default model");
15307
+ return DEFAULT_LEARNER_MODEL;
15308
+ } catch (error) {
15309
+ const text = error instanceof Error ? error.message : String(error);
15310
+ logger.debug("learner", `gateway capabilities failed (${text}); using default model`);
15311
+ return DEFAULT_LEARNER_MODEL;
15312
+ }
15313
+ }
15314
+ var DEFAULT_LEARNER_MODEL = "claude-haiku-4-5", CAPABILITIES_TIMEOUT_MS = 5000, CLAUDE_MODEL_PATTERN;
15315
+ var init_model = __esm(() => {
15316
+ init_logger();
15317
+ CLAUDE_MODEL_PATTERN = /^claude-[a-z0-9.-]+$/;
15318
+ });
15319
+
15144
15320
  // src/learner/prompt.ts
15145
15321
  function buildLearnerSystemPrompt(coreRules) {
15146
15322
  return `You are Dosu's knowledge learner. You read a developer's recent coding-agent sessions and save the durable, non-obvious findings to the team's shared knowledge base so future teammates and agents do not have to rediscover them.
@@ -58104,6 +58280,13 @@ function snippet(value) {
58104
58280
  const flat = (text ?? "").replace(/\s+/g, " ").trim();
58105
58281
  return flat.length > TRACE_SNIPPET_LIMIT ? `${flat.slice(0, TRACE_SNIPPET_LIMIT)}…` : flat;
58106
58282
  }
58283
+ function classifyRejectionReason(detail) {
58284
+ for (const [pattern, reason] of REJECTION_REASONS) {
58285
+ if (pattern.test(detail))
58286
+ return reason;
58287
+ }
58288
+ return "other";
58289
+ }
58107
58290
  function classifyGatewayRejection(text) {
58108
58291
  const match = API_400_PATTERN.exec(text);
58109
58292
  if (!match)
@@ -58116,7 +58299,8 @@ function classifyGatewayRejection(text) {
58116
58299
  } catch {}
58117
58300
  return {
58118
58301
  outcome: "gateway_rejected",
58119
- message: `LLM gateway rejected the study run: ${snippet(detail)}`
58302
+ message: `LLM gateway rejected the study run: ${snippet(detail)}`,
58303
+ reason: classifyRejectionReason(detail.trim())
58120
58304
  };
58121
58305
  }
58122
58306
  function traceAgentMessage(message) {
@@ -58164,11 +58348,26 @@ async function runLearner(options) {
58164
58348
  message: "LLM gateway URL is not set. From source, use `bun run dev` (production endpoints) or `bun run dev:local` (local stack)."
58165
58349
  };
58166
58350
  }
58167
- await Promise.resolve().then(() => init_sdk());
58168
58351
  const claudeExecutable = resolveClaudeExecutable();
58169
- if (claudeExecutable) {
58170
- logger.debug("learner", `using system Claude Code executable: ${claudeExecutable}`);
58352
+ if (claudeExecutable.kind === "missing") {
58353
+ return {
58354
+ outcome: "claude_code_missing",
58355
+ notesWritten: 0,
58356
+ turns: 0,
58357
+ claudeCodeSource: "missing",
58358
+ message: "Studying runs on Claude Code, which isn't installed here. Install Claude Code, then run `dosu knowledge sync`."
58359
+ };
58171
58360
  }
58361
+ if (claudeExecutable.kind === "system") {
58362
+ logger.debug("learner", `using system Claude Code executable: ${claudeExecutable.path}`);
58363
+ }
58364
+ const model = await resolveServedModel({ gatewayURL, apiKey: options.apiKey });
58365
+ logger.debug("learner", `study run model: ${model}`);
58366
+ const diagnostics = {
58367
+ claudeCodeSource: claudeExecutable.kind,
58368
+ model
58369
+ };
58370
+ await Promise.resolve().then(() => init_sdk());
58172
58371
  const configDir = createRunConfigDir();
58173
58372
  const runID = options.runID ?? crypto.randomUUID();
58174
58373
  const sessionStartedAt = options.sessions.map((s) => s.updated).sort()[0];
@@ -58179,7 +58378,19 @@ async function runLearner(options) {
58179
58378
  let notesWritten = 0;
58180
58379
  let currentSession;
58181
58380
  const readsSinceWrite = new Set;
58381
+ const sessionKeys = new Map(options.sessions.map((s) => [s.id, `${s.harness}/${s.id}`]));
58382
+ const notedSessions = new Set;
58383
+ const pendingNotes = new Map;
58182
58384
  let turns = 0;
58385
+ const finish = (outcome, message, gatewayReason) => ({
58386
+ outcome,
58387
+ notesWritten,
58388
+ notedSessions: [...notedSessions],
58389
+ turns,
58390
+ ...message === undefined ? {} : { message },
58391
+ ...gatewayReason ? { gatewayReason } : {},
58392
+ ...diagnostics
58393
+ });
58183
58394
  const env = buildLearnerEnv({
58184
58395
  apiKey: options.apiKey,
58185
58396
  gatewayURL,
@@ -58187,19 +58398,21 @@ async function runLearner(options) {
58187
58398
  runID,
58188
58399
  trigger: options.trigger,
58189
58400
  cliVersion: getVersionString(),
58190
- deploymentID: options.deploymentID
58401
+ deploymentID: options.deploymentID,
58402
+ model
58191
58403
  });
58192
58404
  try {
58193
58405
  const run = PGt({
58194
58406
  prompt: buildLearnerPrompt(options.sessions),
58195
58407
  options: {
58196
58408
  systemPrompt: buildLearnerSystemPrompt(LEARNER_CORE_RULES),
58409
+ model,
58197
58410
  env,
58198
58411
  abortController: abort,
58199
58412
  maxTurns: options.maxTurns ?? DEFAULT_MAX_TURNS,
58200
58413
  settingSources: [],
58201
58414
  persistSession: false,
58202
- ...claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
58415
+ ...claudeExecutable.kind === "system" ? { pathToClaudeCodeExecutable: claudeExecutable.path } : {},
58203
58416
  sandbox: { enabled: true, failIfUnavailable: false },
58204
58417
  mcpServers: {
58205
58418
  [SESSIONS_SERVER_NAME]: createSessionToolsServer(options.sessions),
@@ -58215,7 +58428,7 @@ async function runLearner(options) {
58215
58428
  }
58216
58429
  },
58217
58430
  stderr: (data) => logger.debug("learner", `[sdk] ${data}`),
58218
- canUseTool: async (toolName, input) => {
58431
+ canUseTool: async (toolName, input, { toolUseID }) => {
58219
58432
  if (!allowed.has(toolName)) {
58220
58433
  return {
58221
58434
  behavior: "deny",
@@ -58246,6 +58459,9 @@ async function runLearner(options) {
58246
58459
  }
58247
58460
  notesWritten += 1;
58248
58461
  readsSinceWrite.clear();
58462
+ const noted = currentSession && sessionKeys.get(currentSession);
58463
+ if (noted)
58464
+ pendingNotes.set(toolUseID, noted);
58249
58465
  const { transcript_id: _authoredByModel, ...clean } = input;
58250
58466
  return {
58251
58467
  behavior: "allow",
@@ -58258,56 +58474,46 @@ async function runLearner(options) {
58258
58474
  });
58259
58475
  for await (const message of run) {
58260
58476
  traceAgentMessage(message);
58477
+ if (message.type === "user" && Array.isArray(message.message.content)) {
58478
+ for (const block of message.message.content) {
58479
+ const noted = block.type === "tool_result" && pendingNotes.get(block.tool_use_id);
58480
+ if (noted && block.is_error !== true)
58481
+ notedSessions.add(noted);
58482
+ }
58483
+ }
58484
+ if (message.type === "system" && message.subtype === "init" && typeof message.claude_code_version === "string") {
58485
+ diagnostics.claudeCodeVersion = message.claude_code_version;
58486
+ }
58261
58487
  if (message.type === "result") {
58262
58488
  turns = message.num_turns;
58263
58489
  const text = message.subtype === "success" ? message.result : message.subtype;
58264
58490
  const gatewayError = classifyGatewayError(text ?? "") ?? (message.is_error ? classifyGatewayRejection(text ?? "") : null);
58265
58491
  if (gatewayError) {
58266
- return {
58267
- outcome: gatewayError.outcome,
58268
- notesWritten,
58269
- turns,
58270
- message: gatewayError.message
58271
- };
58492
+ return finish(gatewayError.outcome, gatewayError.message, gatewayError.reason);
58272
58493
  }
58273
58494
  if (message.subtype !== "success" || message.is_error) {
58274
58495
  logger.debug("learner", `run ${runID} failed: ${text}`);
58275
- return {
58276
- outcome: "error",
58277
- notesWritten,
58278
- turns,
58279
- message: "Study run failed; see debug log for details."
58280
- };
58496
+ return finish("error", "Study run failed; see debug log for details.");
58281
58497
  }
58282
58498
  logger.debug("learner", `run ${runID} completed: ${turns} turns, ${notesWritten} suggested pages`);
58283
- return { outcome: "completed", notesWritten, turns, message: text };
58499
+ return finish("completed", text);
58284
58500
  }
58285
58501
  }
58286
- return {
58287
- outcome: "error",
58288
- notesWritten,
58289
- turns,
58290
- message: "Study run ended without a result."
58291
- };
58502
+ return finish("error", "Study run ended without a result.");
58292
58503
  } catch (error) {
58293
58504
  const text = error instanceof Error ? error.message : String(error);
58294
58505
  const gatewayError = classifyGatewayError(text) ?? classifyGatewayRejection(text);
58295
58506
  if (gatewayError) {
58296
- return { outcome: gatewayError.outcome, notesWritten, turns, message: gatewayError.message };
58507
+ return finish(gatewayError.outcome, gatewayError.message, gatewayError.reason);
58297
58508
  }
58298
58509
  logger.debug("learner", `run ${runID} threw: ${text}`);
58299
- return {
58300
- outcome: "error",
58301
- notesWritten,
58302
- turns,
58303
- message: abort.signal.aborted ? "Study run timed out and was aborted." : "Study run failed; see debug log for details."
58304
- };
58510
+ return finish("error", abort.signal.aborted ? "Study run timed out and was aborted." : "Study run failed; see debug log for details.");
58305
58511
  } finally {
58306
58512
  clearTimeout(timer);
58307
58513
  configDir.cleanup();
58308
58514
  }
58309
58515
  }
58310
- var DEFAULT_MAX_TURNS = 160, DEFAULT_MAX_NOTES = 80, DEFAULT_TIMEOUT_MS2, KNOWLEDGE_SERVER_NAME = "dosu", GATEWAY_ERRORS, TRACE_SNIPPET_LIMIT = 400, API_400_PATTERN;
58516
+ var DEFAULT_MAX_TURNS = 160, DEFAULT_MAX_NOTES = 80, DEFAULT_TIMEOUT_MS2, KNOWLEDGE_SERVER_NAME = "dosu", GATEWAY_ERRORS, TRACE_SNIPPET_LIMIT = 400, REJECTION_REASONS, API_400_PATTERN;
58311
58517
  var init_runner = __esm(() => {
58312
58518
  init_logger();
58313
58519
  init_config_helpers();
@@ -58316,6 +58522,7 @@ var init_runner = __esm(() => {
58316
58522
  init_conflicts();
58317
58523
  init_env();
58318
58524
  init_executable();
58525
+ init_model();
58319
58526
  init_tools();
58320
58527
  DEFAULT_TIMEOUT_MS2 = 30 * 60 * 1000;
58321
58528
  GATEWAY_ERRORS = {
@@ -58332,6 +58539,14 @@ var init_runner = __esm(() => {
58332
58539
  message: "Daily Dosu Remote Sessions budget reached; runs resume tomorrow."
58333
58540
  }
58334
58541
  };
58542
+ REJECTION_REASONS = [
58543
+ [/^dosu_unsupported_request:/, "unsupported_request"],
58544
+ [/\beffort\b|output_config/i, "effort_unsupported"],
58545
+ [/\brole\b.*\bsystem\b|\bsystem\b.*\brole\b|messages\.\d+\.role/i, "system_role_unsupported"],
58546
+ [/\badaptive\b/i, "adaptive_thinking_unsupported"],
58547
+ [/prompt is too long|context (?:limit|length|window)/i, "context_length"],
58548
+ [/max_tokens/i, "max_tokens"]
58549
+ ];
58335
58550
  API_400_PATTERN = /API Error: 400\b\s*(.*)/s;
58336
58551
  });
58337
58552
 
@@ -58495,7 +58710,7 @@ ${results.length - opts.limit} more results not shown.`));
58495
58710
  sync_status: outcome.status,
58496
58711
  sessions_studied: sessionsStudied,
58497
58712
  notes_written: notesWritten,
58498
- ...outcome.learner ? { learner_outcome: outcome.learner.outcome } : {}
58713
+ ...outcome.learner ? learnerFacets(outcome.learner) : {}
58499
58714
  });
58500
58715
  if (opts.quiet)
58501
58716
  return;
@@ -58555,6 +58770,15 @@ ${results.length - opts.limit} more results not shown.`));
58555
58770
  cmd.addCommand(statuslineCommand2());
58556
58771
  return cmd;
58557
58772
  }
58773
+ function learnerFacets(learner) {
58774
+ return {
58775
+ learner_outcome: learner.outcome,
58776
+ ...learner.gatewayReason ? { gateway_reason: learner.gatewayReason } : {},
58777
+ ...learner.claudeCodeSource ? { claude_code_source: learner.claudeCodeSource } : {},
58778
+ ...learner.claudeCodeVersion ? { claude_code_version: learner.claudeCodeVersion } : {},
58779
+ ...learner.model ? { learner_model: learner.model } : {}
58780
+ };
58781
+ }
58558
58782
  function buildLearner(trigger) {
58559
58783
  const cfg = loadConfig();
58560
58784
  if (cfg.mode === "oss")
@@ -60533,6 +60757,10 @@ function runUpgrade(channel = INSTALL_CHANNEL, options = {}) {
60533
60757
  }
60534
60758
  manager = owningPackageManager(options.entrypoint ?? process.argv[1], platform, options.comSpec, env, cwd);
60535
60759
  if (!manager) {
60760
+ if (env.DOSU_DEV === "true") {
60761
+ console.log("DOSU_DEV=true and this copy is not a global package: skipping the package install.");
60762
+ return 0;
60763
+ }
60536
60764
  printNonGlobalPackageGuidance();
60537
60765
  return 1;
60538
60766
  }
@@ -60565,6 +60793,41 @@ Could not update Dosu automatically.`);
60565
60793
  console.log('Run "dosu --version" to verify.');
60566
60794
  return 0;
60567
60795
  }
60796
+ function postUpgradeArgs(interactive) {
60797
+ return interactive ? ["setup"] : ["mcp", "refresh"];
60798
+ }
60799
+ function newBinaryInvocation(channel, args, entrypoint = process.argv[1], execPath = process.execPath) {
60800
+ if (channel === "npm") {
60801
+ return entrypoint ? { command: execPath, args: [entrypoint, ...args] } : null;
60802
+ }
60803
+ if (channel === "homebrew") {
60804
+ return { command: "dosu", args };
60805
+ }
60806
+ return null;
60807
+ }
60808
+ function isInteractiveTerminal() {
60809
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
60810
+ }
60811
+ function setupAgentsWithNewBinary(channel, options) {
60812
+ const interactive = options.interactive ?? isInteractiveTerminal();
60813
+ const invocation = newBinaryInvocation(channel, postUpgradeArgs(interactive), options.entrypoint ?? process.argv[1]);
60814
+ if (!invocation) {
60815
+ console.error(SETUP_FALLBACK);
60816
+ return;
60817
+ }
60818
+ console.log(interactive ? `
60819
+ Running setup with the new version to update your AI agents...
60820
+ ` : `
60821
+ Refreshing agent MCP configs with the new version...`);
60822
+ const result = spawnSync(invocation.command, invocation.args, {
60823
+ cwd: options.cwd ?? homedir9(),
60824
+ shell: false,
60825
+ stdio: "inherit"
60826
+ });
60827
+ if (result.error || result.status !== 0) {
60828
+ console.error(SETUP_FALLBACK);
60829
+ }
60830
+ }
60568
60831
  async function completeUpgrade(channel = INSTALL_CHANNEL, options = {}) {
60569
60832
  const status = runUpgrade(channel, options);
60570
60833
  if (status !== 0)
@@ -60580,16 +60843,17 @@ async function completeUpgrade(channel = INSTALL_CHANNEL, options = {}) {
60580
60843
  } catch {
60581
60844
  console.error('Skills could not be refreshed. Run "dosu skill update" to retry.');
60582
60845
  }
60846
+ setupAgentsWithNewBinary(channel, options);
60583
60847
  return 0;
60584
60848
  }
60585
60849
  function upgradeCommand() {
60586
- return new Command("upgrade").description("Update Dosu to the latest version and refresh agent skills").action(async () => {
60850
+ return new Command("upgrade").description("Update Dosu to the latest version, then re-run setup to update your AI agents").action(async () => {
60587
60851
  const status = await completeUpgrade();
60588
60852
  if (status !== 0)
60589
60853
  process.exitCode = status;
60590
60854
  });
60591
60855
  }
60592
- var import_picocolors31, PACKAGE_NAME = "@dosu/cli", LATEST_PACKAGE, BREW_MANUAL_COMMAND = "brew upgrade dosu-ai/dosu/dosu", NPX_COMMAND = "npx -y @dosu/cli@latest", PROBE_TIMEOUT_MS = 5000, RELEASES_URL = "https://github.com/dosu-ai/dosu-cli/releases/latest", PACKAGE_MANAGERS;
60856
+ var import_picocolors31, PACKAGE_NAME = "@dosu/cli", LATEST_PACKAGE, BREW_MANUAL_COMMAND = "brew upgrade dosu-ai/dosu/dosu", NPX_COMMAND = "npx -y @dosu/cli@latest", PROBE_TIMEOUT_MS = 5000, RELEASES_URL = "https://github.com/dosu-ai/dosu-cli/releases/latest", PACKAGE_MANAGERS, SETUP_FALLBACK = 'Run "dosu setup" to finish updating your AI agents.';
60593
60857
  var init_upgrade = __esm(() => {
60594
60858
  init_commander();
60595
60859
  init_skill();
@@ -60615,7 +60879,7 @@ var init_upgrade = __esm(() => {
60615
60879
  });
60616
60880
 
60617
60881
  // src/debug/follow.ts
60618
- import { closeSync as closeSync5, openSync as openSync5, readSync as readSync4, statSync as statSync8 } from "node:fs";
60882
+ import { closeSync as closeSync6, openSync as openSync6, readSync as readSync5, statSync as statSync8 } from "node:fs";
60619
60883
  function createLogFollower(path, emit) {
60620
60884
  const sizeOf = () => {
60621
60885
  try {
@@ -60633,15 +60897,15 @@ function createLogFollower(path, emit) {
60633
60897
  if (size === offset)
60634
60898
  return;
60635
60899
  try {
60636
- const fd = openSync5(path, "r");
60900
+ const fd = openSync6(path, "r");
60637
60901
  try {
60638
60902
  const buf = Buffer.alloc(size - offset);
60639
- const read = readSync4(fd, buf, 0, buf.length, offset);
60903
+ const read = readSync5(fd, buf, 0, buf.length, offset);
60640
60904
  offset += read;
60641
60905
  if (read > 0)
60642
60906
  emit(buf.toString("utf-8", 0, read));
60643
60907
  } finally {
60644
- closeSync5(fd);
60908
+ closeSync6(fd);
60645
60909
  }
60646
60910
  } catch {}
60647
60911
  }
@@ -61319,6 +61583,36 @@ var init_providers = __esm(() => {
61319
61583
  init_zed();
61320
61584
  });
61321
61585
 
61586
+ // src/mcp/refresh.ts
61587
+ function configuredProviders() {
61588
+ return allSetupProviders().filter((provider) => {
61589
+ try {
61590
+ return provider.isInstalled() && provider.isConfigured();
61591
+ } catch {
61592
+ return false;
61593
+ }
61594
+ });
61595
+ }
61596
+ function refreshConfiguredProviders(cfg) {
61597
+ const result = { updated: [], failed: [] };
61598
+ for (const provider of configuredProviders()) {
61599
+ try {
61600
+ provider.install(cfg, true);
61601
+ logger.info("mcp", `Refreshed ${provider.name()} MCP entry`);
61602
+ result.updated.push(provider);
61603
+ } catch (err) {
61604
+ const error = err instanceof Error ? err : new Error(String(err));
61605
+ logger.error("mcp", `Refresh failed for ${provider.name()}: ${error.message}`);
61606
+ result.failed.push({ provider, error });
61607
+ }
61608
+ }
61609
+ return result;
61610
+ }
61611
+ var init_refresh = __esm(() => {
61612
+ init_logger();
61613
+ init_providers();
61614
+ });
61615
+
61322
61616
  // src/version/update-check.ts
61323
61617
  import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "node:fs";
61324
61618
  import { join as join39 } from "node:path";
@@ -61474,15 +61768,98 @@ var init_update_check = __esm(() => {
61474
61768
  SEMVER_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
61475
61769
  });
61476
61770
 
61771
+ // src/version/mcp-refresh-check.ts
61772
+ import { existsSync as existsSync24, mkdirSync as mkdirSync14, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "node:fs";
61773
+ import { join as join40 } from "node:path";
61774
+ function needsMcpRefresh(previous, current) {
61775
+ if (previous === null)
61776
+ return true;
61777
+ if (previous === current)
61778
+ return false;
61779
+ const [older, newer] = isNewerVersion(current, previous) ? [previous, current] : [current, previous];
61780
+ return MCP_FORMAT_CHANGES.some((change) => isNewerVersion(change, older) && !isNewerVersion(change, newer));
61781
+ }
61782
+ function getCachePath4() {
61783
+ return join40(getConfigDir(), CACHE_FILENAME5);
61784
+ }
61785
+ function readMcpRefreshCache() {
61786
+ try {
61787
+ const path = getCachePath4();
61788
+ if (!existsSync24(path))
61789
+ return null;
61790
+ const data = JSON.parse(readFileSync23(path, "utf-8"));
61791
+ if (typeof data?.version === "string")
61792
+ return { version: data.version };
61793
+ return null;
61794
+ } catch {
61795
+ return null;
61796
+ }
61797
+ }
61798
+ function writeMcpRefreshCache(cache) {
61799
+ try {
61800
+ const dir = getConfigDir();
61801
+ if (!existsSync24(dir)) {
61802
+ mkdirSync14(dir, { recursive: true, mode: 448 });
61803
+ }
61804
+ writeFileSync15(getCachePath4(), JSON.stringify(cache), { mode: 384 });
61805
+ } catch {}
61806
+ }
61807
+ function canRefreshMcp(cfg) {
61808
+ const target = cfg.active_account?.target;
61809
+ if (!target?.api_key)
61810
+ return false;
61811
+ return cfg.mode === MODE_OSS || Boolean(target.deployment_id);
61812
+ }
61813
+ function displayNotice3(names) {
61814
+ console.error(`
61815
+ ${import_picocolors33.default.green(`✓ Dosu ${VERSION}: refreshed MCP config for ${names.join(", ")}`)}
61816
+ ` + `${import_picocolors33.default.dim(` Run ${import_picocolors33.default.cyan('"dosu setup"')} to also update skills, hooks, and rules, then restart your AI agents.`)}
61817
+ `);
61818
+ }
61819
+ function checkForMcpRefresh(options = {}) {
61820
+ const notify = options.notify ?? true;
61821
+ try {
61822
+ const previous = readMcpRefreshCache()?.version ?? null;
61823
+ if (previous === VERSION)
61824
+ return;
61825
+ if (!needsMcpRefresh(previous, VERSION)) {
61826
+ writeMcpRefreshCache({ version: VERSION });
61827
+ return;
61828
+ }
61829
+ const cfg = loadConfigNonBlocking();
61830
+ if (!cfg || !canRefreshMcp(cfg)) {
61831
+ logger.debug("mcp-refresh", "Skipping MCP refresh: no API key or deployment configured");
61832
+ return;
61833
+ }
61834
+ const result = refreshConfiguredProviders(cfg);
61835
+ writeMcpRefreshCache({ version: VERSION });
61836
+ logger.info("mcp-refresh", `Version ${VERSION}: refreshed ${result.updated.length}, failed ${result.failed.length}`);
61837
+ if (notify && result.updated.length > 0) {
61838
+ displayNotice3(result.updated.map((provider) => provider.name()));
61839
+ }
61840
+ } catch (err) {
61841
+ logger.error("mcp-refresh", `MCP refresh check failed: ${err}`);
61842
+ }
61843
+ }
61844
+ var import_picocolors33, CACHE_FILENAME5 = "mcp-refresh.json", MCP_FORMAT_CHANGES;
61845
+ var init_mcp_refresh_check = __esm(() => {
61846
+ init_config();
61847
+ init_logger();
61848
+ init_refresh();
61849
+ init_update_check();
61850
+ import_picocolors33 = __toESM(require_picocolors(), 1);
61851
+ MCP_FORMAT_CHANGES = ["0.53.0"];
61852
+ });
61853
+
61477
61854
  // src/rules/installer.ts
61478
- import { existsSync as existsSync24, mkdirSync as mkdirSync14, readFileSync as readFileSync23, unlinkSync as unlinkSync5, writeFileSync as writeFileSync15 } from "node:fs";
61855
+ import { existsSync as existsSync25, mkdirSync as mkdirSync15, readFileSync as readFileSync24, unlinkSync as unlinkSync5, writeFileSync as writeFileSync16 } from "node:fs";
61479
61856
  import { homedir as homedir12 } from "node:os";
61480
- import { dirname as dirname11, join as join40 } from "node:path";
61857
+ import { dirname as dirname11, join as join41 } from "node:path";
61481
61858
  function claudeConfigDir4() {
61482
- return process.env.CLAUDE_CONFIG_DIR || join40(homedir12(), ".claude");
61859
+ return process.env.CLAUDE_CONFIG_DIR || join41(homedir12(), ".claude");
61483
61860
  }
61484
61861
  function codexHome4() {
61485
- return process.env.CODEX_HOME || join40(homedir12(), ".codex");
61862
+ return process.env.CODEX_HOME || join41(homedir12(), ".codex");
61486
61863
  }
61487
61864
  function isRuleAgent(agent) {
61488
61865
  return Object.hasOwn(RULE_TARGETS, agent);
@@ -61508,17 +61885,17 @@ async function fetchDosuRule(fetchImpl = globalThis.fetch) {
61508
61885
  return normalizeRule(FALLBACK_DOSU_RULE);
61509
61886
  }
61510
61887
  function ensureParent(path) {
61511
- mkdirSync14(dirname11(path), { recursive: true });
61888
+ mkdirSync15(dirname11(path), { recursive: true });
61512
61889
  }
61513
61890
  function writeIfChanged(path, content) {
61514
- if (!existsSync24(path)) {
61891
+ if (!existsSync25(path)) {
61515
61892
  ensureParent(path);
61516
- writeFileSync15(path, content, "utf-8");
61893
+ writeFileSync16(path, content, "utf-8");
61517
61894
  return "created";
61518
61895
  }
61519
- if (readFileSync23(path, "utf-8") === content)
61896
+ if (readFileSync24(path, "utf-8") === content)
61520
61897
  return "unchanged";
61521
- writeFileSync15(path, content, "utf-8");
61898
+ writeFileSync16(path, content, "utf-8");
61522
61899
  return "updated";
61523
61900
  }
61524
61901
  function findSection(content) {
@@ -61544,14 +61921,14 @@ function buildSection(content, lineEnding = `
61544
61921
  return `${DOSU_RULE_SECTION_START}${lineEnding}${body}${lineEnding}${DOSU_RULE_SECTION_END}`;
61545
61922
  }
61546
61923
  function upsertSection(path, content) {
61547
- if (!existsSync24(path)) {
61924
+ if (!existsSync25(path)) {
61548
61925
  const section = buildSection(content);
61549
61926
  ensureParent(path);
61550
- writeFileSync15(path, `${section}
61927
+ writeFileSync16(path, `${section}
61551
61928
  `, "utf-8");
61552
61929
  return "created";
61553
61930
  }
61554
- const existing = readFileSync23(path, "utf-8");
61931
+ const existing = readFileSync24(path, "utf-8");
61555
61932
  const lineEnding = lineEndingFor(existing);
61556
61933
  const section = buildSection(content, lineEnding);
61557
61934
  const location = findSection(existing);
@@ -61564,7 +61941,7 @@ function upsertSection(path, content) {
61564
61941
  }
61565
61942
  if (next === existing)
61566
61943
  return "unchanged";
61567
- writeFileSync15(path, next, "utf-8");
61944
+ writeFileSync16(path, next, "utf-8");
61568
61945
  return "updated";
61569
61946
  }
61570
61947
  function installRuleForAgent(agent, content) {
@@ -61581,13 +61958,13 @@ function removeRuleForAgent(agent) {
61581
61958
  return null;
61582
61959
  const target = RULE_TARGETS[agent];
61583
61960
  const path = target.path();
61584
- if (!existsSync24(path))
61961
+ if (!existsSync25(path))
61585
61962
  return { agent, path, action: "not_found" };
61586
61963
  if (target.kind === "file") {
61587
61964
  unlinkSync5(path);
61588
61965
  return { agent, path, action: "removed" };
61589
61966
  }
61590
- const existing = readFileSync23(path, "utf-8");
61967
+ const existing = readFileSync24(path, "utf-8");
61591
61968
  const lineEnding = lineEndingFor(existing);
61592
61969
  const location = findSection(existing);
61593
61970
  if (!location)
@@ -61596,7 +61973,7 @@ function removeRuleForAgent(agent) {
61596
61973
  if (!next) {
61597
61974
  unlinkSync5(path);
61598
61975
  } else {
61599
- writeFileSync15(path, `${next}${lineEnding}`, "utf-8");
61976
+ writeFileSync16(path, `${next}${lineEnding}`, "utf-8");
61600
61977
  }
61601
61978
  return { agent, path, action: "removed" };
61602
61979
  }
@@ -61624,36 +62001,36 @@ var init_installer = __esm(() => {
61624
62001
  RULE_TARGETS = {
61625
62002
  claude: {
61626
62003
  kind: "file",
61627
- path: () => join40(claudeConfigDir4(), "rules", "dosu.md")
62004
+ path: () => join41(claudeConfigDir4(), "rules", "dosu.md")
61628
62005
  },
61629
62006
  cursor: {
61630
62007
  kind: "file",
61631
- path: () => join40(homedir12(), ".cursor", "rules", "dosu.mdc"),
62008
+ path: () => join41(homedir12(), ".cursor", "rules", "dosu.mdc"),
61632
62009
  cursorFrontmatter: true
61633
62010
  },
61634
62011
  codex: {
61635
62012
  kind: "section",
61636
- path: () => join40(codexHome4(), "AGENTS.md")
62013
+ path: () => join41(codexHome4(), "AGENTS.md")
61637
62014
  },
61638
62015
  opencode: {
61639
62016
  kind: "section",
61640
- path: () => join40(homedir12(), ".config", "opencode", "AGENTS.md")
62017
+ path: () => join41(homedir12(), ".config", "opencode", "AGENTS.md")
61641
62018
  },
61642
62019
  gemini: {
61643
62020
  kind: "section",
61644
- path: () => join40(homedir12(), ".gemini", "GEMINI.md")
62021
+ path: () => join41(homedir12(), ".gemini", "GEMINI.md")
61645
62022
  },
61646
62023
  antigravity: {
61647
62024
  kind: "section",
61648
- path: () => join40(homedir12(), ".gemini", "GEMINI.md")
62025
+ path: () => join41(homedir12(), ".gemini", "GEMINI.md")
61649
62026
  }
61650
62027
  };
61651
62028
  });
61652
62029
 
61653
62030
  // src/setup/agents-md-step.ts
61654
62031
  import { execSync as execSync3 } from "node:child_process";
61655
- import { existsSync as existsSync25, readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "node:fs";
61656
- import { join as join41 } from "node:path";
62032
+ import { existsSync as existsSync26, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "node:fs";
62033
+ import { join as join42 } from "node:path";
61657
62034
  function inGitWorkTree(cwd = process.cwd()) {
61658
62035
  try {
61659
62036
  const stdout = execSync3("git rev-parse --is-inside-work-tree", {
@@ -61667,10 +62044,10 @@ function inGitWorkTree(cwd = process.cwd()) {
61667
62044
  }
61668
62045
  function dosuAgentsSectionState(cwd = process.cwd()) {
61669
62046
  try {
61670
- const path = join41(cwd, "AGENTS.md");
61671
- if (!existsSync25(path))
62047
+ const path = join42(cwd, "AGENTS.md");
62048
+ if (!existsSync26(path))
61672
62049
  return "missing";
61673
- const content = readFileSync24(path, "utf-8");
62050
+ const content = readFileSync25(path, "utf-8");
61674
62051
  const startMatch = content.match(SECTION_START_RE);
61675
62052
  if (!startMatch || !content.includes(DOSU_SECTION_END))
61676
62053
  return "missing";
@@ -61705,14 +62082,14 @@ function findSection2(content) {
61705
62082
  return { start, end };
61706
62083
  }
61707
62084
  function upsertDosuAgentsSection(cwd = process.cwd(), content = FALLBACK_DOSU_RULE) {
61708
- const path = join41(cwd, "AGENTS.md");
61709
- if (!existsSync25(path)) {
62085
+ const path = join42(cwd, "AGENTS.md");
62086
+ if (!existsSync26(path)) {
61710
62087
  const section = buildDosuAgentsSection(content);
61711
- writeFileSync16(path, `${section}
62088
+ writeFileSync17(path, `${section}
61712
62089
  `);
61713
62090
  return { path, action: "created" };
61714
62091
  }
61715
- const existing = readFileSync24(path, "utf-8");
62092
+ const existing = readFileSync25(path, "utf-8");
61716
62093
  const lineEnding = lineEndingFor2(existing);
61717
62094
  const section = renderDosuAgentsSection(content, lineEnding);
61718
62095
  const location = findSection2(existing);
@@ -61724,7 +62101,7 @@ function upsertDosuAgentsSection(cwd = process.cwd(), content = FALLBACK_DOSU_RU
61724
62101
  }
61725
62102
  if (next === existing)
61726
62103
  return { path, action: "unchanged" };
61727
- writeFileSync16(path, next);
62104
+ writeFileSync17(path, next);
61728
62105
  return { path, action: "updated" };
61729
62106
  }
61730
62107
  async function stepUpdateAgentsMd(cwd = process.cwd(), content) {
@@ -61780,15 +62157,15 @@ var init_errors3 = __esm(() => {
61780
62157
  var MCP_PROVIDER_SLUG2 = "dosu_mcp";
61781
62158
 
61782
62159
  // src/sessions/session-title.ts
61783
- import { existsSync as existsSync26, mkdirSync as mkdirSync15, readFileSync as readFileSync25, renameSync as renameSync6, statSync as statSync9, writeFileSync as writeFileSync17 } from "node:fs";
62160
+ import { existsSync as existsSync27, mkdirSync as mkdirSync16, readFileSync as readFileSync26, renameSync as renameSync6, statSync as statSync9, writeFileSync as writeFileSync18 } from "node:fs";
61784
62161
  import { homedir as homedir13 } from "node:os";
61785
- import { join as join42 } from "node:path";
62162
+ import { join as join43 } from "node:path";
61786
62163
  function cachePath2(configDir) {
61787
- return join42(configDir, CACHE_FILENAME5);
62164
+ return join43(configDir, CACHE_FILENAME6);
61788
62165
  }
61789
62166
  function loadCacheFile2(configDir) {
61790
62167
  try {
61791
- const raw = JSON.parse(readFileSync25(cachePath2(configDir), "utf-8"));
62168
+ const raw = JSON.parse(readFileSync26(cachePath2(configDir), "utf-8"));
61792
62169
  if (raw.schema_version !== CACHE_SCHEMA_VERSION2 || typeof raw.entries !== "object")
61793
62170
  return {};
61794
62171
  return raw.entries;
@@ -61798,12 +62175,12 @@ function loadCacheFile2(configDir) {
61798
62175
  }
61799
62176
  function saveCacheFile2(configDir, entries) {
61800
62177
  try {
61801
- if (!existsSync26(configDir))
61802
- mkdirSync15(configDir, { recursive: true, mode: 448 });
62178
+ if (!existsSync27(configDir))
62179
+ mkdirSync16(configDir, { recursive: true, mode: 448 });
61803
62180
  const path = cachePath2(configDir);
61804
62181
  const tmp = `${path}.${process.pid}.tmp`;
61805
62182
  const file = { schema_version: CACHE_SCHEMA_VERSION2, entries };
61806
- writeFileSync17(tmp, JSON.stringify(file), { mode: 384 });
62183
+ writeFileSync18(tmp, JSON.stringify(file), { mode: 384 });
61807
62184
  renameSync6(tmp, path);
61808
62185
  } catch {}
61809
62186
  }
@@ -61836,15 +62213,15 @@ function claudeSummaryFromHead(text) {
61836
62213
  }
61837
62214
  return null;
61838
62215
  }
61839
- function reconstructSession(harness, id, slug, home = homedir13(), exists = existsSync26) {
62216
+ function reconstructSession(harness, id, slug, home = homedir13(), exists = existsSync27) {
61840
62217
  if (!slug)
61841
62218
  return null;
61842
62219
  const candidates = [];
61843
62220
  if (harness === "claude") {
61844
- candidates.push(join42(home, ".claude", "projects", slug, `${id}.jsonl`));
62221
+ candidates.push(join43(home, ".claude", "projects", slug, `${id}.jsonl`));
61845
62222
  } else if (harness === "cursor") {
61846
- const project = join42(home, ".cursor", "projects", slug);
61847
- candidates.push(join42(project, "agent-transcripts", id, `${id}.jsonl`), join42(project, "agent-transcripts", `${id}.jsonl`));
62223
+ const project = join43(home, ".cursor", "projects", slug);
62224
+ candidates.push(join43(project, "agent-transcripts", id, `${id}.jsonl`), join43(project, "agent-transcripts", `${id}.jsonl`));
61848
62225
  } else {
61849
62226
  return null;
61850
62227
  }
@@ -61856,7 +62233,7 @@ function reconstructSession(harness, id, slug, home = homedir13(), exists = exis
61856
62233
  }
61857
62234
  function readHead2(path) {
61858
62235
  try {
61859
- return readFileSync25(path, "utf-8").slice(0, 128 * 1024);
62236
+ return readFileSync26(path, "utf-8").slice(0, 128 * 1024);
61860
62237
  } catch {
61861
62238
  return null;
61862
62239
  }
@@ -61907,7 +62284,7 @@ function createSessionTitleResolver(configDir = getConfigDir(), deps = {}) {
61907
62284
  }
61908
62285
  };
61909
62286
  }
61910
- var CACHE_FILENAME5 = "session-titles.json", CACHE_SCHEMA_VERSION2 = 3, HEAD_LINES2 = 50, SESSION_NAME_MAX = 80;
62287
+ var CACHE_FILENAME6 = "session-titles.json", CACHE_SCHEMA_VERSION2 = 3, HEAD_LINES2 = 50, SESSION_NAME_MAX = 80;
61911
62288
  var init_session_title = __esm(() => {
61912
62289
  init_config();
61913
62290
  init_read();
@@ -61936,7 +62313,7 @@ var init_alt_screen = __esm(() => {
61936
62313
  });
61937
62314
 
61938
62315
  // src/tui/activity-view.ts
61939
- import { existsSync as existsSync27, readFileSync as readFileSync26 } from "node:fs";
62316
+ import { existsSync as existsSync28, readFileSync as readFileSync27 } from "node:fs";
61940
62317
  import { basename as basename5 } from "node:path";
61941
62318
  function reduceActivityViewKey(key) {
61942
62319
  if (key === "q" || key === ESC7 || key === CTRL_C4)
@@ -62077,15 +62454,15 @@ function statusLine(status) {
62077
62454
  if (status.running) {
62078
62455
  const since = status.startedAt ? ` · since ${localTime(status.startedAt)}` : "";
62079
62456
  const pid = status.pid !== undefined ? ` (pid ${status.pid})` : "";
62080
- return `\uD83D\uDCDA ${import_picocolors33.default.bold(brand("Studying sessions..."))}${import_picocolors33.default.dim(`${pid}${since}`)}`;
62457
+ return `\uD83D\uDCDA ${import_picocolors34.default.bold(brand("Studying sessions..."))}${import_picocolors34.default.dim(`${pid}${since}`)}`;
62081
62458
  }
62082
62459
  if (status.state.paused) {
62083
- return `${import_picocolors33.default.yellow("○")} ${import_picocolors33.default.bold("Paused")} ${import_picocolors33.default.dim("· studying stays off until you resume")}`;
62460
+ return `${import_picocolors34.default.yellow("○")} ${import_picocolors34.default.bold("Paused")} ${import_picocolors34.default.dim("· studying stays off until you resume")}`;
62084
62461
  }
62085
62462
  if (status.staleLock) {
62086
- return `${import_picocolors33.default.yellow("○")} ${import_picocolors33.default.bold("Not running")} ${import_picocolors33.default.dim("· a previous run exited without cleaning up")}`;
62463
+ return `${import_picocolors34.default.yellow("○")} ${import_picocolors34.default.bold("Not running")} ${import_picocolors34.default.dim("· a previous run exited without cleaning up")}`;
62087
62464
  }
62088
- return `${import_picocolors33.default.dim("○")} ${import_picocolors33.default.bold("Idle")} ${import_picocolors33.default.dim("· hooks study new sessions automatically")}`;
62465
+ return `${import_picocolors34.default.dim("○")} ${import_picocolors34.default.bold("Idle")} ${import_picocolors34.default.dim("· hooks study new sessions automatically")}`;
62089
62466
  }
62090
62467
  function progressLine(done, ready, width, notes = 0) {
62091
62468
  const total = done + ready;
@@ -62094,7 +62471,7 @@ function progressLine(done, ready, width, notes = 0) {
62094
62471
  const cells = Math.max(10, Math.min(30, width - 44));
62095
62472
  const ratio = Math.max(0, Math.min(1, done / total));
62096
62473
  const filled = Math.min(cells, Math.round(ratio * cells));
62097
- const bar = brand("█".repeat(filled)) + import_picocolors33.default.dim("░".repeat(cells - filled));
62474
+ const bar = brand("█".repeat(filled)) + import_picocolors34.default.dim("░".repeat(cells - filled));
62098
62475
  const pct = Math.floor(ratio * 100);
62099
62476
  const suffix = notes > 0 ? ` · ${notes} suggested page${notes === 1 ? "" : "s"}` : "";
62100
62477
  return `${bar} ${done}/${total} studied · ${pct}%${suffix}`;
@@ -62133,17 +62510,17 @@ function confirmBox(queuedCount, backlog, width, mode = "start") {
62133
62510
  const verb = mode === "clear" ? "clear" : mode === "stop" ? "stop" : mode === "resume" ? "resume" : "start";
62134
62511
  const maxInner = Math.max(20, Math.min(width, contentWidth()) - 4);
62135
62512
  const rows = [
62136
- import_picocolors33.default.bold(brand(title)),
62137
- ...wrapLine(scope, maxInner, "").map((line) => import_picocolors33.default.dim(line)),
62513
+ import_picocolors34.default.bold(brand(title)),
62514
+ ...wrapLine(scope, maxInner, "").map((line) => import_picocolors34.default.dim(line)),
62138
62515
  "",
62139
- `${import_picocolors33.default.bold("enter")} ${verb} · ${import_picocolors33.default.bold("esc")} cancel`
62516
+ `${import_picocolors34.default.bold("enter")} ${verb} · ${import_picocolors34.default.bold("esc")} cancel`
62140
62517
  ];
62141
62518
  const inner = Math.min(maxInner, Math.max(...rows.map(visibleWidth2)));
62142
62519
  const pad = (row) => row + " ".repeat(Math.max(0, inner - visibleWidth2(row)));
62143
62520
  return [
62144
- import_picocolors33.default.dim(`╭${"─".repeat(inner + 2)}╮`),
62145
- ...rows.map((row) => `${import_picocolors33.default.dim("│")} ${pad(row)} ${import_picocolors33.default.dim("│")}`),
62146
- import_picocolors33.default.dim(`╰${"─".repeat(inner + 2)}╯`)
62521
+ import_picocolors34.default.dim(`╭${"─".repeat(inner + 2)}╮`),
62522
+ ...rows.map((row) => `${import_picocolors34.default.dim("│")} ${pad(row)} ${import_picocolors34.default.dim("│")}`),
62523
+ import_picocolors34.default.dim(`╰${"─".repeat(inner + 2)}╯`)
62147
62524
  ];
62148
62525
  }
62149
62526
  function renderActivityFrame(status, activity, width, backlog = null, pane = DEFAULT_PANE, queued = [], studiedBeforeRun = 0, open = [], runProgress = null, projectNames = {}, sessionNames = {}) {
@@ -62159,7 +62536,7 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
62159
62536
  progress = progressLine(studiedDelta + stepDone, Math.max(0, backlog.ready - stepDone), width, runProgress?.notes ?? 0);
62160
62537
  }
62161
62538
  const refusal = !status.running && status.state.last_refusal;
62162
- const refusalLines = refusal ? wrapLine(`! Studying paused: ${refusal.message} (${localTime(refusal.at)})`, width).map((line) => import_picocolors33.default.yellow(line)) : [];
62539
+ const refusalLines = refusal ? wrapLine(`! Studying paused: ${refusal.message} (${localTime(refusal.at)})`, width).map((line) => import_picocolors34.default.yellow(line)) : [];
62163
62540
  const fullRows = Boolean(pane.fullRows);
62164
62541
  const withName = (item, key) => projectNames[key] ? { ...item, project: projectNames[key] } : item;
62165
62542
  const latestPass = new Map;
@@ -62184,7 +62561,7 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
62184
62561
  const height = fullRows ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
62185
62562
  const { visible, above, below } = windowList(source, pane.scroll, height);
62186
62563
  const display = fullRows ? visible.flatMap((row) => wrapRow(row, width)) : visible;
62187
- const listRows = display.length > 0 ? display.map((row) => import_picocolors33.default.dim(row)) : [import_picocolors33.default.dim(empty)];
62564
+ const listRows = display.length > 0 ? display.map((row) => import_picocolors34.default.dim(row)) : [import_picocolors34.default.dim(empty)];
62188
62565
  const scrollParts = [];
62189
62566
  if (above > 0)
62190
62567
  scrollParts.push(`↑ ${above} earlier`);
@@ -62194,14 +62571,14 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
62194
62571
  breadcrumb(["home", "activity"], width),
62195
62572
  "",
62196
62573
  statusLine(status),
62197
- import_picocolors33.default.dim(studied),
62198
- ...queueDetail.length > 0 ? wrapLine(queueDetail.join(" · "), width).map((line) => import_picocolors33.default.dim(line)) : [],
62574
+ import_picocolors34.default.dim(studied),
62575
+ ...queueDetail.length > 0 ? wrapLine(queueDetail.join(" · "), width).map((line) => import_picocolors34.default.dim(line)) : [],
62199
62576
  ...progress ? [progress] : [],
62200
62577
  ...refusalLines,
62201
62578
  "",
62202
62579
  ...tabBar(pane.tab, queued.length, open.length, latestPass.size, width),
62203
62580
  ...listRows,
62204
- ...scrollParts.length > 0 ? [import_picocolors33.default.dim(clip(scrollParts.join(" · "), width))] : [],
62581
+ ...scrollParts.length > 0 ? [import_picocolors34.default.dim(clip(scrollParts.join(" · "), width))] : [],
62205
62582
  "",
62206
62583
  ...pane.confirm ? centerBlock(confirmBox(queued.length, backlog, width, pane.confirm), width) : [
62207
62584
  ...wrapLine([
@@ -62211,7 +62588,7 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
62211
62588
  status.running ? "s stop" : status.state.paused ? "s resume" : "s sync now",
62212
62589
  ...!status.running && status.state.watermark ? ["c clear"] : [],
62213
62590
  "esc back"
62214
- ].join(" · "), width, "").map((line) => import_picocolors33.default.dim(line))
62591
+ ].join(" · "), width, "").map((line) => import_picocolors34.default.dim(line))
62215
62592
  ]
62216
62593
  ];
62217
62594
  return lines.join(`
@@ -62245,7 +62622,7 @@ function createRowNamer() {
62245
62622
  names.titles[key] = t;
62246
62623
  };
62247
62624
  for (const s of [...backlog.queued, ...backlog.open]) {
62248
- if (!existsSync27(s.path))
62625
+ if (!existsSync28(s.path))
62249
62626
  continue;
62250
62627
  const key = `${s.harness}/${s.id}`;
62251
62628
  const dir = dirs.resolve(s);
@@ -62282,7 +62659,7 @@ function createRowNamer() {
62282
62659
  }
62283
62660
  function defaultReadLog2() {
62284
62661
  try {
62285
- return readFileSync26(logger.getLogPath(), "utf-8");
62662
+ return readFileSync27(logger.getLogPath(), "utf-8");
62286
62663
  } catch {
62287
62664
  return "";
62288
62665
  }
@@ -62463,7 +62840,7 @@ ${CLEAR_BELOW}`);
62463
62840
  input.on("data", onData);
62464
62841
  });
62465
62842
  }
62466
- var import_picocolors33, ESC7, HIDE_CURSOR3, SHOW_CURSOR3, CTRL_C4, KEY_UP4, KEY_DOWN4, KEY_RIGHT3, KEY_LEFT3, CURSOR_HOME, CLEAR_BELOW, CLEAR_EOL, ACTIVITY_VIEW_POLL_MS = 500, ACTIVITY_VIEW_LIST_LINES = 10, ACTIVITY_VIEW_FULL_LIST_ROWS = 5, ACTIVITY_VIEW_BUFFER_LINES = 200, ACTIVITY_VIEW_TABS, DEFAULT_PANE, TITLE_READS_PER_DRAW = 25;
62843
+ var import_picocolors34, ESC7, HIDE_CURSOR3, SHOW_CURSOR3, CTRL_C4, KEY_UP4, KEY_DOWN4, KEY_RIGHT3, KEY_LEFT3, CURSOR_HOME, CLEAR_BELOW, CLEAR_EOL, ACTIVITY_VIEW_POLL_MS = 500, ACTIVITY_VIEW_LIST_LINES = 10, ACTIVITY_VIEW_FULL_LIST_ROWS = 5, ACTIVITY_VIEW_BUFFER_LINES = 200, ACTIVITY_VIEW_TABS, DEFAULT_PANE, TITLE_READS_PER_DRAW = 25;
62467
62844
  var init_activity_view = __esm(() => {
62468
62845
  init_follow();
62469
62846
  init_logger();
@@ -62478,7 +62855,7 @@ var init_activity_view = __esm(() => {
62478
62855
  init_alt_screen();
62479
62856
  init_layout();
62480
62857
  init_menu();
62481
- import_picocolors33 = __toESM(require_picocolors(), 1);
62858
+ import_picocolors34 = __toESM(require_picocolors(), 1);
62482
62859
  ESC7 = String.fromCharCode(27);
62483
62860
  HIDE_CURSOR3 = `${ESC7}[?25l`;
62484
62861
  SHOW_CURSOR3 = `${ESC7}[?25h`;
@@ -63259,6 +63636,7 @@ async function runSetupFlow(opts = {}) {
63259
63636
  }));
63260
63637
  return;
63261
63638
  }
63639
+ writeMcpRefreshCache({ version: VERSION });
63262
63640
  const configuredProviders = configured.filter((result) => (result.action === "install" || result.action === "skip") && !result.error);
63263
63641
  const mcpCompleted = configuredProviders.length > 0;
63264
63642
  if (mcpCompleted) {
@@ -63703,25 +64081,14 @@ async function runSwitchTarget(scope = "org") {
63703
64081
  return;
63704
64082
  updateTarget(cfg, { api_key: apiKey });
63705
64083
  saveConfig(cfg);
63706
- const configured = allSetupProviders().filter((provider) => {
63707
- try {
63708
- return provider.isInstalled() && provider.isConfigured();
63709
- } catch {
63710
- return false;
63711
- }
63712
- });
63713
- for (const provider of configured) {
63714
- try {
63715
- provider.install(cfg, true);
63716
- logger.info("setup", `Switch: updated ${provider.name()}`);
63717
- log2.success(`${provider.name()} ${dim("· updated")}`);
63718
- } catch (err) {
63719
- const error = err instanceof Error ? err : new Error(String(err));
63720
- logger.error("setup", `Switch: update failed for ${provider.name()}: ${error.message}`);
63721
- log2.error(`Failed to update ${provider.name()}: ${error.message}`);
63722
- }
64084
+ const refreshed = refreshConfiguredProviders(cfg);
64085
+ for (const provider of refreshed.updated) {
64086
+ log2.success(`${provider.name()} ${dim("· updated")}`);
64087
+ }
64088
+ for (const { provider, error } of refreshed.failed) {
64089
+ log2.error(`Failed to update ${provider.name()}: ${error.message}`);
63723
64090
  }
63724
- if (configured.length > 0) {
64091
+ if (refreshed.updated.length + refreshed.failed.length > 0) {
63725
64092
  log2.info("Restart your AI agents so they pick up the new MCP target.");
63726
64093
  }
63727
64094
  }
@@ -63971,7 +64338,7 @@ async function stepSelectTools(detected) {
63971
64338
  initialValues: preselected,
63972
64339
  statusFor: (id, picked) => {
63973
64340
  if (configuredMap.get(id))
63974
- return picked ? dim("configured") : import_picocolors34.default.yellow("will remove");
64341
+ return picked ? dim("configured") : import_picocolors35.default.yellow("will remove");
63975
64342
  return picked ? brand("will configure") : undefined;
63976
64343
  },
63977
64344
  summary: (picked) => {
@@ -64146,7 +64513,7 @@ ${dim(wrapLog("Run it inside a session to keep that session out of studying. Rem
64146
64513
  log2.success("All agents already configured. No changes needed.");
64147
64514
  }
64148
64515
  }
64149
- var import_picocolors34, OAUTH_ANALYTICS_REASONS, SETUP_HANDSHAKE_TIMEOUT_MS = 1800000, LIBRARY_SELECT_CANCELLED;
64516
+ var import_picocolors35, OAUTH_ANALYTICS_REASONS, SETUP_HANDSHAKE_TIMEOUT_MS = 1800000, LIBRARY_SELECT_CANCELLED;
64150
64517
  var init_flow2 = __esm(() => {
64151
64518
  init_dist();
64152
64519
  init_errors3();
@@ -64157,6 +64524,7 @@ var init_flow2 = __esm(() => {
64157
64524
  init_agents();
64158
64525
  init_agents3();
64159
64526
  init_providers();
64527
+ init_refresh();
64160
64528
  init_agents4();
64161
64529
  init_detach();
64162
64530
  init_sync();
@@ -64164,12 +64532,13 @@ var init_flow2 = __esm(() => {
64164
64532
  init_activity_view();
64165
64533
  init_layout();
64166
64534
  init_prompts();
64535
+ init_mcp_refresh_check();
64167
64536
  init_agents_md_step();
64168
64537
  init_analytics2();
64169
64538
  init_github_step();
64170
64539
  init_rules_step();
64171
64540
  init_styles();
64172
- import_picocolors34 = __toESM(require_picocolors(), 1);
64541
+ import_picocolors35 = __toESM(require_picocolors(), 1);
64173
64542
  OAUTH_ANALYTICS_REASONS = new Set([
64174
64543
  "access_denied",
64175
64544
  "bad_oauth_state",
@@ -64297,7 +64666,7 @@ function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending
64297
64666
  const rows = analyticsTabRows(tab, state, pageStats);
64298
64667
  const { visible, above, below } = windowReport(rows, scroll);
64299
64668
  const empty = tab === "overview" ? "No analytics yet. They appear after the first study run." : tab === "projects" ? "No per-project history yet. It fills in as sessions are studied." : pagesPending ? "Loading page analytics..." : "No page analytics yet.";
64300
- const listRows = visible.length > 0 ? visible.map((row) => import_picocolors35.default.dim(row)) : [import_picocolors35.default.dim(empty)];
64669
+ const listRows = visible.length > 0 ? visible.map((row) => import_picocolors36.default.dim(row)) : [import_picocolors36.default.dim(empty)];
64301
64670
  const scrollParts = [];
64302
64671
  if (above > 0)
64303
64672
  scrollParts.push(`↑ ${above} earlier`);
@@ -64313,9 +64682,9 @@ function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending
64313
64682
  ], tab, width, { spread: false }),
64314
64683
  "",
64315
64684
  ...listRows,
64316
- ...scrollParts.length > 0 ? [import_picocolors35.default.dim(scrollParts.join(" · "))] : [],
64685
+ ...scrollParts.length > 0 ? [import_picocolors36.default.dim(scrollParts.join(" · "))] : [],
64317
64686
  "",
64318
- import_picocolors35.default.dim("tab switch · ↑↓ scroll · esc back")
64687
+ import_picocolors36.default.dim("tab switch · ↑↓ scroll · esc back")
64319
64688
  ];
64320
64689
  return lines.join(`
64321
64690
  `);
@@ -64410,7 +64779,7 @@ ${CLEAR_BELOW2}`);
64410
64779
  input.on("data", onData);
64411
64780
  });
64412
64781
  }
64413
- var import_picocolors35, 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);
64782
+ var import_picocolors36, 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);
64414
64783
  var init_analytics_view = __esm(() => {
64415
64784
  init_trpc();
64416
64785
  init_config();
@@ -64418,7 +64787,7 @@ var init_analytics_view = __esm(() => {
64418
64787
  init_alt_screen();
64419
64788
  init_layout();
64420
64789
  init_menu();
64421
- import_picocolors35 = __toESM(require_picocolors(), 1);
64790
+ import_picocolors36 = __toESM(require_picocolors(), 1);
64422
64791
  ESC8 = String.fromCharCode(27);
64423
64792
  HIDE_CURSOR4 = `${ESC8}[?25l`;
64424
64793
  SHOW_CURSOR4 = `${ESC8}[?25h`;
@@ -64436,7 +64805,7 @@ var init_analytics_view = __esm(() => {
64436
64805
  // src/tui/banner.ts
64437
64806
  function paintLogoRow(row) {
64438
64807
  return row.map(([tone, art]) => {
64439
- if (!import_picocolors36.default.isColorSupported)
64808
+ if (!import_picocolors37.default.isColorSupported)
64440
64809
  return art;
64441
64810
  const { fg, fallback } = LOGO_TONES[tone];
64442
64811
  return hasTruecolor() ? `${fg}${art}\x1B[39m` : fallback(art);
@@ -64444,14 +64813,14 @@ function paintLogoRow(row) {
64444
64813
  }
64445
64814
  function checklistRows(ctx) {
64446
64815
  const on = brand(CHECK2);
64447
- const off = import_picocolors36.default.dim(CIRCLE);
64816
+ const off = import_picocolors37.default.dim(CIRCLE);
64448
64817
  const rows = [["workspace", ctx.directory]];
64449
64818
  rows.push([
64450
64819
  "account",
64451
- ctx.sessionExpired ? `${off} ${import_picocolors36.default.yellow("session expired")} ${import_picocolors36.default.dim(`${DOT3} run Log in`)}` : ctx.signedIn ? `${on} signed in` : `${off} ${import_picocolors36.default.dim(`not signed in ${DOT3} run Setup`)}`
64820
+ ctx.sessionExpired ? `${off} ${import_picocolors37.default.yellow("session expired")} ${import_picocolors37.default.dim(`${DOT3} run Log in`)}` : ctx.signedIn ? `${on} signed in` : `${off} ${import_picocolors37.default.dim(`not signed in ${DOT3} run Setup`)}`
64452
64821
  ]);
64453
64822
  const missing = new Set(ctx.setupMissing ?? []);
64454
- const warnRow = `${off} ${import_picocolors36.default.yellow("not configured")} ${import_picocolors36.default.dim(`${DOT3} run Setup`)}`;
64823
+ const warnRow = `${off} ${import_picocolors37.default.yellow("not configured")} ${import_picocolors37.default.dim(`${DOT3} run Setup`)}`;
64455
64824
  if (missing.has("MCP"))
64456
64825
  rows.push(["mcp", warnRow]);
64457
64826
  else if (ctx.deploymentName)
@@ -64463,7 +64832,7 @@ function checklistRows(ctx) {
64463
64832
  if (ctx.repoAgentsMd) {
64464
64833
  rows.push([
64465
64834
  "repo",
64466
- ctx.repoAgentsMd === "current" ? `${on} AGENTS.md has the Dosu section` : `${off} ${import_picocolors36.default.yellow(ctx.repoAgentsMd === "outdated" ? "AGENTS.md Dosu section outdated" : "AGENTS.md missing the Dosu section")} ${import_picocolors36.default.dim(`${DOT3} run Setup`)}`
64835
+ ctx.repoAgentsMd === "current" ? `${on} AGENTS.md has the Dosu section` : `${off} ${import_picocolors37.default.yellow(ctx.repoAgentsMd === "outdated" ? "AGENTS.md Dosu section outdated" : "AGENTS.md missing the Dosu section")} ${import_picocolors37.default.dim(`${DOT3} run Setup`)}`
64467
64836
  ]);
64468
64837
  }
64469
64838
  if (missing.has("agents"))
@@ -64475,24 +64844,24 @@ function checklistRows(ctx) {
64475
64844
  if (ctx.studying) {
64476
64845
  rows.push([
64477
64846
  "sync",
64478
- `\uD83D\uDCDA ${brand("studying sessions...")} ${import_picocolors36.default.dim(`${DOT3} see activity`)}`
64847
+ `\uD83D\uDCDA ${brand("studying sessions...")} ${import_picocolors37.default.dim(`${DOT3} see activity`)}`
64479
64848
  ]);
64480
64849
  }
64481
64850
  if (ctx.update) {
64482
64851
  rows.push([
64483
64852
  "update",
64484
- `${import_picocolors36.default.yellow(`↑ ${ctx.update.version} available`)} ${import_picocolors36.default.dim(`${DOT3} ${ctx.update.hint}`)}`
64853
+ `${import_picocolors37.default.yellow(`↑ ${ctx.update.version} available`)} ${import_picocolors37.default.dim(`${DOT3} ${ctx.update.hint}`)}`
64485
64854
  ]);
64486
64855
  }
64487
64856
  const labelWidth = Math.max(...rows.map(([label]) => label.length));
64488
- return rows.map(([label, value]) => `${import_picocolors36.default.dim(label.padEnd(labelWidth))} ${value}`);
64857
+ return rows.map(([label, value]) => `${import_picocolors37.default.dim(label.padEnd(labelWidth))} ${value}`);
64489
64858
  }
64490
64859
  function renderBanner(ctx) {
64491
64860
  const logo = LOGO_ROWS.map(paintLogoRow);
64492
64861
  const text = [
64493
64862
  ...checklistRows(ctx),
64494
64863
  "",
64495
- `${brandBadge("dosu-cli")} ${import_picocolors36.default.dim(`${ctx.version} ${DOT3} ${ctx.webAppHost}`)}`
64864
+ `${brandBadge("dosu-cli")} ${import_picocolors37.default.dim(`${ctx.version} ${DOT3} ${ctx.webAppHost}`)}`
64496
64865
  ];
64497
64866
  const height = Math.max(logo.length, text.length);
64498
64867
  const combined = [];
@@ -64505,10 +64874,10 @@ function renderBanner(ctx) {
64505
64874
  return ["", ...combined, ""].join(`
64506
64875
  `);
64507
64876
  }
64508
- var import_picocolors36, CHECK2 = "✔", CIRCLE = "○", DOT3 = "·", LOGO_ROWS, LOGO_MARK, LOGO_WIDTH, LOGO_TONES, COLUMN_GAP = " ";
64877
+ var import_picocolors37, CHECK2 = "✔", CIRCLE = "○", DOT3 = "·", LOGO_ROWS, LOGO_MARK, LOGO_WIDTH, LOGO_TONES, COLUMN_GAP = " ";
64509
64878
  var init_banner = __esm(() => {
64510
64879
  init_styles();
64511
- import_picocolors36 = __toESM(require_picocolors(), 1);
64880
+ import_picocolors37 = __toESM(require_picocolors(), 1);
64512
64881
  LOGO_ROWS = [
64513
64882
  [["sage", "▄▄▄▄▄▄"]],
64514
64883
  [["sage", "██ ▀▄"]],
@@ -64519,8 +64888,8 @@ var init_banner = __esm(() => {
64519
64888
  LOGO_MARK = LOGO_ROWS.map((row) => row.map(([, art]) => art).join(""));
64520
64889
  LOGO_WIDTH = Math.max(...LOGO_MARK.map((row) => row.length));
64521
64890
  LOGO_TONES = {
64522
- sage: { fg: "\x1B[38;2;180;187;145m", fallback: import_picocolors36.default.green },
64523
- moss: { fg: "\x1B[38;2;119;133;97m", fallback: (art) => import_picocolors36.default.dim(import_picocolors36.default.green(art)) }
64891
+ sage: { fg: "\x1B[38;2;180;187;145m", fallback: import_picocolors37.default.green },
64892
+ moss: { fg: "\x1B[38;2;119;133;97m", fallback: (art) => import_picocolors37.default.dim(import_picocolors37.default.green(art)) }
64524
64893
  };
64525
64894
  });
64526
64895
 
@@ -64632,16 +65001,16 @@ function renderPagesFrame(model, width, selected) {
64632
65001
  const body = [];
64633
65002
  let scrollHint = "";
64634
65003
  if (model.phase === "loading") {
64635
- body.push(import_picocolors37.default.dim("Loading pages…"));
65004
+ body.push(import_picocolors38.default.dim("Loading pages…"));
64636
65005
  } else if (model.phase === "error") {
64637
- body.push(import_picocolors37.default.yellow(clip3(model.message, width)));
65006
+ body.push(import_picocolors38.default.yellow(clip3(model.message, width)));
64638
65007
  } else if (model.pages.length === 0) {
64639
- body.push(import_picocolors37.default.dim("No pages in this Library yet."));
65008
+ body.push(import_picocolors38.default.dim("No pages in this Library yet."));
64640
65009
  } else {
64641
65010
  const rows = pageRows2(model.pages, width - 2);
64642
65011
  const { start, above } = windowSelection(rows.length, selected);
64643
65012
  const visible = rows.slice(start, start + PAGES_VIEW_LINES);
64644
- body.push(...visible.map((row, i) => start + i === selected ? `${brand("▸")} ${import_picocolors37.default.bold(row)}` : ` ${import_picocolors37.default.dim(row)}`));
65013
+ body.push(...visible.map((row, i) => start + i === selected ? `${brand("▸")} ${import_picocolors38.default.bold(row)}` : ` ${import_picocolors38.default.dim(row)}`));
64645
65014
  const below = Math.max(0, Math.max(model.total, rows.length) - start - PAGES_VIEW_LINES);
64646
65015
  const parts = [];
64647
65016
  if (above > 0)
@@ -64650,14 +65019,14 @@ function renderPagesFrame(model, width, selected) {
64650
65019
  parts.push(`↓ ${below} more`);
64651
65020
  scrollHint = parts.join(" · ");
64652
65021
  }
64653
- const count = model.phase === "ready" ? ` ${import_picocolors37.default.dim(model.pages.length < model.total ? `(${model.pages.length} of ${model.total})` : `(${model.pages.length})`)}` : "";
65022
+ const count = model.phase === "ready" ? ` ${import_picocolors38.default.dim(model.pages.length < model.total ? `(${model.pages.length} of ${model.total})` : `(${model.pages.length})`)}` : "";
64654
65023
  const lines = [
64655
65024
  `${breadcrumb(["home", "pages"], width)}${count}`,
64656
65025
  "",
64657
65026
  ...body,
64658
- ...scrollHint ? [import_picocolors37.default.dim(scrollHint)] : [],
65027
+ ...scrollHint ? [import_picocolors38.default.dim(scrollHint)] : [],
64659
65028
  "",
64660
- import_picocolors37.default.dim("↑↓ move · enter open · esc back")
65029
+ import_picocolors38.default.dim("↑↓ move · enter open · esc back")
64661
65030
  ];
64662
65031
  return lines.join(`
64663
65032
  `);
@@ -64666,9 +65035,9 @@ function renderPageFrame(page, content, width, scroll) {
64666
65035
  const body = [];
64667
65036
  let scrollHint = "";
64668
65037
  if (content.phase === "loading") {
64669
- body.push(import_picocolors37.default.dim("Loading page…"));
65038
+ body.push(import_picocolors38.default.dim("Loading page…"));
64670
65039
  } else if (content.phase === "error") {
64671
- body.push(import_picocolors37.default.yellow(clip3(content.message, width)));
65040
+ body.push(import_picocolors38.default.yellow(clip3(content.message, width)));
64672
65041
  } else {
64673
65042
  const rows = wrapBody(content.body, width);
64674
65043
  const { visible, above, below } = windowReport(rows, scroll, PAGE_BODY_LINES);
@@ -64683,12 +65052,12 @@ function renderPageFrame(page, content, width, scroll) {
64683
65052
  const meta = `${page.type} · ${page.published ? "published" : "draft"} · updated ${page.updated_at.slice(0, 10)}`;
64684
65053
  const lines = [
64685
65054
  breadcrumb(["home", "pages", page.title], width),
64686
- import_picocolors37.default.dim(clip3(meta, width)),
65055
+ import_picocolors38.default.dim(clip3(meta, width)),
64687
65056
  "",
64688
65057
  ...body,
64689
- ...scrollHint ? [import_picocolors37.default.dim(scrollHint)] : [],
65058
+ ...scrollHint ? [import_picocolors38.default.dim(scrollHint)] : [],
64690
65059
  "",
64691
- import_picocolors37.default.dim("↑↓ scroll · esc back to pages")
65060
+ import_picocolors38.default.dim("↑↓ scroll · esc back to pages")
64692
65061
  ];
64693
65062
  return lines.join(`
64694
65063
  `);
@@ -64837,7 +65206,7 @@ ${CLEAR_BELOW3}`);
64837
65206
  input.on("data", onData);
64838
65207
  });
64839
65208
  }
64840
- var import_picocolors37, ESC9, HIDE_CURSOR5, SHOW_CURSOR5, CTRL_C6, KEY_UP6, KEY_DOWN6, CURSOR_HOME3, CLEAR_BELOW3, CLEAR_EOL3, PAGES_VIEW_LINES = 12, PAGE_BODY_LINES = 14, PAGES_FETCH_BATCH = 100;
65209
+ var import_picocolors38, ESC9, HIDE_CURSOR5, SHOW_CURSOR5, CTRL_C6, KEY_UP6, KEY_DOWN6, CURSOR_HOME3, CLEAR_BELOW3, CLEAR_EOL3, PAGES_VIEW_LINES = 12, PAGE_BODY_LINES = 14, PAGES_FETCH_BATCH = 100;
64841
65210
  var init_pages_view = __esm(() => {
64842
65211
  init_trpc();
64843
65212
  init_config();
@@ -64847,7 +65216,7 @@ var init_pages_view = __esm(() => {
64847
65216
  init_analytics_view();
64848
65217
  init_layout();
64849
65218
  init_menu();
64850
- import_picocolors37 = __toESM(require_picocolors(), 1);
65219
+ import_picocolors38 = __toESM(require_picocolors(), 1);
64851
65220
  ESC9 = String.fromCharCode(27);
64852
65221
  HIDE_CURSOR5 = `${ESC9}[?25l`;
64853
65222
  SHOW_CURSOR5 = `${ESC9}[?25h`;
@@ -64961,7 +65330,7 @@ function isSetUp(cfg) {
64961
65330
  }
64962
65331
  function setupHint(cfg) {
64963
65332
  const started = Boolean(cfg.active_account?.target);
64964
- return import_picocolors38.default.yellow(started ? `incomplete · missing ${missingSetupSteps(cfg).join(" + ")}` : "not set up yet");
65333
+ return import_picocolors39.default.yellow(started ? `incomplete · missing ${missingSetupSteps(cfg).join(" + ")}` : "not set up yet");
64965
65334
  }
64966
65335
  function isStudying() {
64967
65336
  try {
@@ -65207,7 +65576,7 @@ function handleLogout(cfg) {
65207
65576
  saveConfig(cfg);
65208
65577
  log2.success("Credentials cleared.");
65209
65578
  }
65210
- var import_picocolors38, ESC10, CLEAR_SCREEN;
65579
+ var import_picocolors39, ESC10, CLEAR_SCREEN;
65211
65580
  var init_tui = __esm(() => {
65212
65581
  init_client();
65213
65582
  init_config();
@@ -65230,7 +65599,7 @@ var init_tui = __esm(() => {
65230
65599
  init_menu();
65231
65600
  init_pages_view();
65232
65601
  init_prompts();
65233
- import_picocolors38 = __toESM(require_picocolors(), 1);
65602
+ import_picocolors39 = __toESM(require_picocolors(), 1);
65234
65603
  ESC10 = String.fromCharCode(27);
65235
65604
  CLEAR_SCREEN = `${ESC10}[2J${ESC10}[H`;
65236
65605
  });
@@ -65834,18 +66203,15 @@ var init_flow3 = __esm(() => {
65834
66203
  });
65835
66204
 
65836
66205
  // src/cli/cli.ts
65837
- import {
65838
- closeSync as closeSync6,
65839
- constants as constants3,
65840
- fstatSync as fstatSync2,
65841
- openSync as openSync6,
65842
- readFileSync as readFileSync27,
65843
- readSync as readSync5,
65844
- unlinkSync as unlinkSync6
65845
- } from "node:fs";
66206
+ import { readFileSync as readFileSync28, unlinkSync as unlinkSync6 } from "node:fs";
65846
66207
  function shouldRunBackgroundChecks(actionName) {
65847
66208
  return actionName !== "upgrade";
65848
66209
  }
66210
+ function shouldRunMcpRefreshCheck(actionCommand) {
66211
+ if (actionCommand.name() === "setup")
66212
+ return false;
66213
+ return !(actionCommand.name() === "refresh" && actionCommand.parent?.name() === "mcp");
66214
+ }
65849
66215
  function commandTelemetryName(actionCommand) {
65850
66216
  const segments = [];
65851
66217
  let current = actionCommand;
@@ -65862,7 +66228,7 @@ function shouldTrackCommand(command) {
65862
66228
  }
65863
66229
  function commandTelemetryContext() {
65864
66230
  try {
65865
- const cfg = loadConfigForTelemetry();
66231
+ const cfg = loadConfigNonBlocking();
65866
66232
  if (!cfg)
65867
66233
  return { mode: "cloud", isAuthenticated: false };
65868
66234
  const authenticated = isAuthenticated(cfg);
@@ -65882,29 +66248,6 @@ function commandTelemetryContext() {
65882
66248
  return { mode: "cloud", isAuthenticated: false };
65883
66249
  }
65884
66250
  }
65885
- function loadConfigForTelemetry() {
65886
- let fd;
65887
- try {
65888
- const nonblocking = typeof constants3.O_NONBLOCK === "number" ? constants3.O_NONBLOCK : 0;
65889
- fd = openSync6(getConfigPath(), constants3.O_RDONLY | nonblocking);
65890
- const file = fstatSync2(fd);
65891
- if (!file.isFile() || file.size > MAX_TELEMETRY_CONFIG_BYTES)
65892
- return;
65893
- const content = Buffer.alloc(MAX_TELEMETRY_CONFIG_BYTES + 1);
65894
- const bytesRead = readSync5(fd, content, 0, content.byteLength, 0);
65895
- if (bytesRead > MAX_TELEMETRY_CONFIG_BYTES)
65896
- return;
65897
- return parseConfig(JSON.parse(content.subarray(0, bytesRead).toString("utf8")));
65898
- } catch {
65899
- return;
65900
- } finally {
65901
- if (fd !== undefined) {
65902
- try {
65903
- closeSync6(fd);
65904
- } catch {}
65905
- }
65906
- }
65907
- }
65908
66251
  function startTelemetry(telemetry, command, context) {
65909
66252
  if (!telemetry)
65910
66253
  return false;
@@ -65963,12 +66306,15 @@ function createProgram(options = {}) {
65963
66306
  const opts = thisCommand.optsWithGlobals();
65964
66307
  logger.init({ debug: opts.debug });
65965
66308
  if (shouldRunBackgroundChecks(actionCommand.name())) {
66309
+ const launchesTUI = actionCommand.parent === null;
65966
66310
  if (!process.env.CI) {
65967
- const launchesTUI = actionCommand.parent === null;
65968
66311
  await checkForUpdates({ notify: !launchesTUI });
65969
66312
  }
65970
66313
  checkForSkillUpdates();
65971
66314
  checkForReadyTasks();
66315
+ if (shouldRunMcpRefreshCheck(actionCommand)) {
66316
+ checkForMcpRefresh({ notify: !launchesTUI });
66317
+ }
65972
66318
  }
65973
66319
  const command = commandTelemetryName(actionCommand);
65974
66320
  if (options.telemetry && shouldTrackCommand(command)) {
@@ -66195,6 +66541,31 @@ Start ${provider.name()} in any project to use the Dosu MCP.`);
66195
66541
  Start ${provider.name()} in this project directory to use the Dosu MCP.`);
66196
66542
  }
66197
66543
  });
66544
+ mcp.command("refresh").description("Rewrite the Dosu MCP entry in every configured AI tool from the current setup").action(() => {
66545
+ const cfg = loadConfig();
66546
+ if (!canRefreshMcp(cfg)) {
66547
+ throw new CliUsageError("Dosu is not set up yet. Run 'dosu setup' first");
66548
+ }
66549
+ const configured = configuredProviders();
66550
+ if (configured.length === 0) {
66551
+ console.log("No AI tools with Dosu configured. Run 'dosu setup' to add Dosu to a tool.");
66552
+ return;
66553
+ }
66554
+ console.log(`Refreshing Dosu MCP config for configured AI tools...
66555
+ `);
66556
+ const result = refreshConfiguredProviders(cfg);
66557
+ writeMcpRefreshCache({ version: VERSION });
66558
+ for (const provider of result.updated) {
66559
+ console.log(` ✓ ${provider.name()}`);
66560
+ }
66561
+ for (const { provider, error } of result.failed) {
66562
+ console.log(` ✗ ${provider.name()}: ${error.message}`);
66563
+ }
66564
+ console.log(`
66565
+ Restart your AI agents so they pick up the change.`);
66566
+ if (result.failed.length > 0)
66567
+ process.exitCode = 1;
66568
+ });
66198
66569
  mcp.command("list").description("List available AI tools").action(() => {
66199
66570
  console.log(`Available AI tools:
66200
66571
  `);
@@ -66275,7 +66646,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
66275
66646
  }
66276
66647
  if (opts.follow) {
66277
66648
  try {
66278
- const lines = readFileSync27(logPath, "utf-8").split(`
66649
+ const lines = readFileSync28(logPath, "utf-8").split(`
66279
66650
  `);
66280
66651
  if (lines.at(-1) === "")
66281
66652
  lines.pop();
@@ -66291,7 +66662,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
66291
66662
  if (opts.tail !== undefined) {
66292
66663
  const n = typeof opts.tail === "string" ? parseInt(opts.tail, 10) || 50 : 50;
66293
66664
  try {
66294
- const content = readFileSync27(logPath, "utf-8");
66665
+ const content = readFileSync28(logPath, "utf-8");
66295
66666
  const lines = content.split(`
66296
66667
  `);
66297
66668
  console.log(lines.slice(-n).join(`
@@ -66343,7 +66714,7 @@ function processCommandTelemetry() {
66343
66714
  return;
66344
66715
  }
66345
66716
  }
66346
- var TELEMETRY_FLUSH_TIMEOUT_MS = 750, MAX_TELEMETRY_CONFIG_BYTES, CliUsageError, FOLLOW_POLL_MS = 500, DEFAULT_TAIL_LINES = 50;
66717
+ var TELEMETRY_FLUSH_TIMEOUT_MS = 750, CliUsageError, FOLLOW_POLL_MS = 500, DEFAULT_TAIL_LINES = 50;
66347
66718
  var init_cli = __esm(() => {
66348
66719
  init_commander();
66349
66720
  init_client();
@@ -66369,13 +66740,14 @@ var init_cli = __esm(() => {
66369
66740
  init_follow();
66370
66741
  init_logger();
66371
66742
  init_providers();
66743
+ init_refresh();
66372
66744
  init_styles();
66373
66745
  init_settings();
66374
66746
  init_telemetry();
66747
+ init_mcp_refresh_check();
66375
66748
  init_pending_tasks_check();
66376
66749
  init_skill_update_check();
66377
66750
  init_update_check();
66378
- MAX_TELEMETRY_CONFIG_BYTES = 64 * 1024;
66379
66751
  CliUsageError = class CliUsageError extends Error {
66380
66752
  exitCode = 1;
66381
66753
  constructor(message) {
@@ -66416,4 +66788,4 @@ main().catch((err) => {
66416
66788
  process.exit(1);
66417
66789
  });
66418
66790
 
66419
- //# debugId=2f023ff0-da66-217c-6475-6e2164756e21
66791
+ //# debugId=6712b11a-8117-30cb-6475-6e2164756e21