sidekick-agent-hub 0.17.6 → 0.18.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 (2) hide show
  1. package/dist/sidekick-cli.mjs +887 -96
  2. package/package.json +1 -1
@@ -12864,7 +12864,7 @@ var require_phrases = __commonJS({
12864
12864
  "../sidekick-shared/dist/phrases.js"(exports) {
12865
12865
  "use strict";
12866
12866
  Object.defineProperty(exports, "__esModule", { value: true });
12867
- exports.ALL_PHRASES = exports.STACK_OVERFLOW = exports.DEPENDENCY_HELL = exports.RUBBER_DUCK = exports.TONGUE_TWISTERS = exports.INTERVIEW_HELL = exports.FORTUNE_COOKIE = exports.COMMIT_SINS = exports.OVERHEARD = exports.ERROR_MESSAGES = exports.LOADING_TIPS = exports.AFTER_HOURS = exports.VIBE_CODING = exports.OBSOLESCENCE = exports.SYCOPHANCY = exports.MOTIVATION = exports.AI_INDUSTRY = exports.THE_ECOSYSTEM = exports.FOURTH_WALL = exports.META_MONITORING = exports.PUNCHY = exports.DRAMATIC = exports.DEV_RELATABLE = exports.AI_COMMENTARY = exports.SIDEKICK_ENERGY = void 0;
12867
+ exports.ALL_PHRASES = exports.PHRASE_CATEGORIES = exports.STACK_OVERFLOW = exports.DEPENDENCY_HELL = exports.RUBBER_DUCK = exports.TONGUE_TWISTERS = exports.INTERVIEW_HELL = exports.FORTUNE_COOKIE = exports.COMMIT_SINS = exports.OVERHEARD = exports.ERROR_MESSAGES = exports.LOADING_TIPS = exports.AFTER_HOURS = exports.VIBE_CODING = exports.OBSOLESCENCE = exports.SYCOPHANCY = exports.MOTIVATION = exports.AI_INDUSTRY = exports.THE_ECOSYSTEM = exports.FOURTH_WALL = exports.META_MONITORING = exports.PUNCHY = exports.DRAMATIC = exports.DEV_RELATABLE = exports.AI_COMMENTARY = exports.SIDEKICK_ENERGY = void 0;
12868
12868
  exports.getRandomPhrase = getRandomPhrase3;
12869
12869
  exports.SIDEKICK_ENERGY = [
12870
12870
  "Your friendly neighborhood sidekick, reporting for duty.",
@@ -14140,32 +14140,33 @@ var require_phrases = __commonJS({
14140
14140
  "The real dependency is not npm. It is the mass-copied Stack Overflow snippets holding your app together.",
14141
14141
  "You mass-searched, mass-copied, and mass-deployed. The Stack Overflow-driven development lifecycle."
14142
14142
  ];
14143
- exports.ALL_PHRASES = [
14144
- ...exports.SIDEKICK_ENERGY,
14145
- ...exports.AI_COMMENTARY,
14146
- ...exports.DEV_RELATABLE,
14147
- ...exports.DRAMATIC,
14148
- ...exports.PUNCHY,
14149
- ...exports.META_MONITORING,
14150
- ...exports.FOURTH_WALL,
14151
- ...exports.THE_ECOSYSTEM,
14152
- ...exports.AI_INDUSTRY,
14153
- ...exports.MOTIVATION,
14154
- ...exports.SYCOPHANCY,
14155
- ...exports.OBSOLESCENCE,
14156
- ...exports.VIBE_CODING,
14157
- ...exports.AFTER_HOURS,
14158
- ...exports.LOADING_TIPS,
14159
- ...exports.ERROR_MESSAGES,
14160
- ...exports.OVERHEARD,
14161
- ...exports.COMMIT_SINS,
14162
- ...exports.FORTUNE_COOKIE,
14163
- ...exports.INTERVIEW_HELL,
14164
- ...exports.TONGUE_TWISTERS,
14165
- ...exports.RUBBER_DUCK,
14166
- ...exports.DEPENDENCY_HELL,
14167
- ...exports.STACK_OVERFLOW
14143
+ exports.PHRASE_CATEGORIES = [
14144
+ { id: "sidekick-energy", phrases: exports.SIDEKICK_ENERGY },
14145
+ { id: "ai-commentary", phrases: exports.AI_COMMENTARY },
14146
+ { id: "dev-relatable", phrases: exports.DEV_RELATABLE },
14147
+ { id: "dramatic", phrases: exports.DRAMATIC },
14148
+ { id: "punchy", phrases: exports.PUNCHY },
14149
+ { id: "meta-monitoring", phrases: exports.META_MONITORING },
14150
+ { id: "fourth-wall", phrases: exports.FOURTH_WALL },
14151
+ { id: "ecosystem", phrases: exports.THE_ECOSYSTEM },
14152
+ { id: "ai-industry", phrases: exports.AI_INDUSTRY },
14153
+ { id: "motivation", phrases: exports.MOTIVATION },
14154
+ { id: "sycophancy", phrases: exports.SYCOPHANCY },
14155
+ { id: "obsolescence", phrases: exports.OBSOLESCENCE },
14156
+ { id: "vibe-coding", phrases: exports.VIBE_CODING },
14157
+ { id: "after-hours", phrases: exports.AFTER_HOURS },
14158
+ { id: "loading-tips", phrases: exports.LOADING_TIPS },
14159
+ { id: "error-messages", phrases: exports.ERROR_MESSAGES },
14160
+ { id: "overheard", phrases: exports.OVERHEARD },
14161
+ { id: "commit-sins", phrases: exports.COMMIT_SINS },
14162
+ { id: "fortune-cookie", phrases: exports.FORTUNE_COOKIE },
14163
+ { id: "interview-hell", phrases: exports.INTERVIEW_HELL },
14164
+ { id: "tongue-twisters", phrases: exports.TONGUE_TWISTERS },
14165
+ { id: "rubber-duck", phrases: exports.RUBBER_DUCK },
14166
+ { id: "dependency-hell", phrases: exports.DEPENDENCY_HELL },
14167
+ { id: "stack-overflow", phrases: exports.STACK_OVERFLOW }
14168
14168
  ];
14169
+ exports.ALL_PHRASES = exports.PHRASE_CATEGORIES.flatMap((category) => category.phrases);
14169
14170
  function getRandomPhrase3() {
14170
14171
  return exports.ALL_PHRASES[Math.floor(Math.random() * exports.ALL_PHRASES.length)];
14171
14172
  }
@@ -14502,6 +14503,12 @@ var require_modelInfo = __commonJS({
14502
14503
  exports.getModelInfo = getModelInfo2;
14503
14504
  exports.calculateCostWithPricing = calculateCostWithPricing;
14504
14505
  exports.calculateCost = calculateCost;
14506
+ exports.calculateCostWithProvenance = calculateCostWithProvenance;
14507
+ exports.mergeCostSources = mergeCostSources;
14508
+ exports.shortModelName = shortModelName;
14509
+ exports.getModelDisplayInfo = getModelDisplayInfo;
14510
+ exports.compareModelIds = compareModelIds;
14511
+ exports.sortModelIds = sortModelIds;
14505
14512
  exports.formatCost = formatCost5;
14506
14513
  var modelContext_1 = require_modelContext();
14507
14514
  var PRICING_TABLE = {
@@ -14647,6 +14654,7 @@ var require_modelInfo = __commonJS({
14647
14654
  overrideSortedKeys = [];
14648
14655
  }
14649
14656
  var CLAUDE_RE = /^claude-(haiku|sonnet|opus)-([0-9.]+)/i;
14657
+ var LEGACY_CLAUDE_RE = /^claude-([0-9]+(?:[-.][0-9]+)?)-(haiku|sonnet|opus)(?:-|$)/i;
14650
14658
  var GPT_RE = /^gpt-([0-9][0-9.A-Za-z-]*)/i;
14651
14659
  var O_SERIES_RE = /^o([0-9]+)(-mini|-pro)?/i;
14652
14660
  var GEMINI_RE = /^gemini-([0-9][0-9.A-Za-z-]*)/i;
@@ -14658,6 +14666,14 @@ var require_modelInfo = __commonJS({
14658
14666
  if (claude) {
14659
14667
  return { provider: "anthropic", family: claude[1].toLowerCase(), version: claude[2] };
14660
14668
  }
14669
+ const legacyClaude = normalized.match(LEGACY_CLAUDE_RE);
14670
+ if (legacyClaude) {
14671
+ return {
14672
+ provider: "anthropic",
14673
+ family: legacyClaude[2].toLowerCase(),
14674
+ version: legacyClaude[1].replace("-", ".")
14675
+ };
14676
+ }
14661
14677
  const gpt = normalized.match(GPT_RE);
14662
14678
  if (gpt) {
14663
14679
  return { provider: "openai", family: "gpt", version: gpt[1] };
@@ -14716,6 +14732,92 @@ var require_modelInfo = __commonJS({
14716
14732
  return null;
14717
14733
  return calculateCostWithPricing(usage, pricing);
14718
14734
  }
14735
+ function calculateCostWithProvenance(input) {
14736
+ if (typeof input.reportedCostUsd === "number" && Number.isFinite(input.reportedCostUsd)) {
14737
+ return { costUsd: input.reportedCostUsd, source: "reported" };
14738
+ }
14739
+ const estimated = calculateCost(input.usage, input.modelId);
14740
+ if (estimated === null)
14741
+ return { source: "unpriced" };
14742
+ return { costUsd: estimated, source: "estimated" };
14743
+ }
14744
+ function mergeCostSources(a, b) {
14745
+ const rank = {
14746
+ reported: 0,
14747
+ estimated: 1,
14748
+ unpriced: 2
14749
+ };
14750
+ return rank[a] >= rank[b] ? a : b;
14751
+ }
14752
+ function normalizeModelId(modelId) {
14753
+ return modelId.trim().toLowerCase().replace(/^[a-z]+\//, "").replace(/-\d{8}$/, "").replace(/-latest$/, "").replace(/\[1m\]/gi, "");
14754
+ }
14755
+ function shortModelName(modelId) {
14756
+ const normalized = normalizeModelId(modelId);
14757
+ const parsed = parseModelId(normalized);
14758
+ if (normalized.includes("codex"))
14759
+ return "Codex";
14760
+ if (parsed?.provider === "anthropic") {
14761
+ const family = parsed.family.toLowerCase();
14762
+ return family.charAt(0).toUpperCase() + family.slice(1);
14763
+ }
14764
+ if (parsed?.provider === "openai") {
14765
+ if (parsed.family === "o")
14766
+ return `o${parsed.version}`;
14767
+ if (normalized.startsWith("gpt-4o-mini"))
14768
+ return "GPT-4o mini";
14769
+ if (normalized.startsWith("gpt-4o"))
14770
+ return "GPT-4o";
14771
+ if (parsed.family === "gpt")
14772
+ return `GPT-${parsed.version}`;
14773
+ }
14774
+ return modelId;
14775
+ }
14776
+ var CLAUDE_FAMILY_RANK = {
14777
+ opus: 0,
14778
+ sonnet: 1,
14779
+ haiku: 2
14780
+ };
14781
+ function versionRank(version) {
14782
+ if (!version)
14783
+ return Number.MAX_SAFE_INTEGER;
14784
+ const numeric = Number(version.replace("-", ".").match(/[0-9]+(?:\.[0-9]+)?/)?.[0]);
14785
+ if (!Number.isFinite(numeric))
14786
+ return Number.MAX_SAFE_INTEGER;
14787
+ return -numeric;
14788
+ }
14789
+ function getModelDisplayInfo(modelId) {
14790
+ const parsed = parseModelId(modelId);
14791
+ const normalized = normalizeModelId(modelId);
14792
+ let rank = 1e3;
14793
+ if (parsed?.provider === "anthropic") {
14794
+ rank = (CLAUDE_FAMILY_RANK[parsed.family] ?? 9) * 100 + versionRank(parsed.version);
14795
+ } else if (normalized.includes("codex")) {
14796
+ rank = 200;
14797
+ } else if (parsed?.provider === "openai") {
14798
+ rank = parsed.family === "gpt" ? 300 + versionRank(parsed.version) : 400 + versionRank(parsed.version);
14799
+ } else if (parsed?.provider === "google") {
14800
+ rank = 500 + versionRank(parsed.version);
14801
+ }
14802
+ return {
14803
+ modelId,
14804
+ provider: parsed?.provider ?? null,
14805
+ family: parsed?.family ?? null,
14806
+ version: parsed?.version ?? null,
14807
+ shortName: shortModelName(modelId),
14808
+ rank
14809
+ };
14810
+ }
14811
+ function compareModelIds(a, b) {
14812
+ const left = getModelDisplayInfo(a);
14813
+ const right = getModelDisplayInfo(b);
14814
+ if (left.rank !== right.rank)
14815
+ return left.rank - right.rank;
14816
+ return a.localeCompare(b);
14817
+ }
14818
+ function sortModelIds(modelIds) {
14819
+ return [...modelIds].sort(compareModelIds);
14820
+ }
14719
14821
  function formatCost5(cost) {
14720
14822
  if (cost === null || cost === void 0)
14721
14823
  return "\u2014";
@@ -18128,6 +18230,49 @@ var require_ensureDefaultAccounts = __commonJS({
18128
18230
  }
18129
18231
  });
18130
18232
 
18233
+ // ../sidekick-shared/dist/accountStatus.js
18234
+ var require_accountStatus = __commonJS({
18235
+ "../sidekick-shared/dist/accountStatus.js"(exports) {
18236
+ "use strict";
18237
+ Object.defineProperty(exports, "__esModule", { value: true });
18238
+ exports.getActiveAccountStatus = getActiveAccountStatus;
18239
+ var accounts_1 = require_accounts();
18240
+ var codexProfiles_1 = require_codexProfiles();
18241
+ function errorMessage(error) {
18242
+ return error instanceof Error ? error.message : String(error);
18243
+ }
18244
+ function getActiveAccountStatus(error) {
18245
+ try {
18246
+ const claudeAccount = (0, accounts_1.readActiveClaudeAccount)();
18247
+ const codexAccount = (0, codexProfiles_1.getActiveCodexAccount)();
18248
+ const claude = claudeAccount ? {
18249
+ present: true,
18250
+ email: claudeAccount.email,
18251
+ label: claudeAccount.email
18252
+ } : { present: false };
18253
+ const codex = codexAccount ? {
18254
+ present: true,
18255
+ email: codexAccount.email ?? codexAccount.metadata?.email,
18256
+ label: codexAccount.label ?? codexAccount.email ?? codexAccount.metadata?.email ?? codexAccount.id
18257
+ } : { present: false };
18258
+ return {
18259
+ ok: claude.present || codex.present,
18260
+ claude,
18261
+ codex,
18262
+ error
18263
+ };
18264
+ } catch (caught) {
18265
+ return {
18266
+ ok: false,
18267
+ claude: { present: false },
18268
+ codex: { present: false },
18269
+ error: error ?? errorMessage(caught)
18270
+ };
18271
+ }
18272
+ }
18273
+ }
18274
+ });
18275
+
18131
18276
  // ../sidekick-shared/dist/quota.js
18132
18277
  var require_quota = __commonJS({
18133
18278
  "../sidekick-shared/dist/quota.js"(exports) {
@@ -18485,6 +18630,7 @@ var require_quotaSnapshots = __commonJS({
18485
18630
  Object.defineProperty(exports, "__esModule", { value: true });
18486
18631
  exports.writeQuotaSnapshot = writeQuotaSnapshot2;
18487
18632
  exports.readQuotaSnapshot = readQuotaSnapshot2;
18633
+ var crypto = __importStar(__require("crypto"));
18488
18634
  var fs9 = __importStar(__require("fs"));
18489
18635
  var path8 = __importStar(__require("path"));
18490
18636
  var paths_1 = require_paths();
@@ -18495,11 +18641,19 @@ var require_quotaSnapshots = __commonJS({
18495
18641
  fs9.mkdirSync((0, paths_1.getConfigDir)(), { recursive: true, mode: 448 });
18496
18642
  }
18497
18643
  function atomicWriteJson(filePath, data, mode = 384) {
18498
- const tmp = filePath + ".tmp";
18644
+ const tmp = `${filePath}.${process.pid}.${Date.now()}.${crypto.randomBytes(8).toString("hex")}.tmp`;
18499
18645
  const json = JSON.stringify(data, null, 2);
18500
18646
  JSON.parse(json);
18501
- fs9.writeFileSync(tmp, json, { encoding: "utf8", mode });
18502
- fs9.renameSync(tmp, filePath);
18647
+ try {
18648
+ fs9.writeFileSync(tmp, json, { encoding: "utf8", mode });
18649
+ fs9.renameSync(tmp, filePath);
18650
+ } catch (error) {
18651
+ try {
18652
+ fs9.rmSync(tmp, { force: true });
18653
+ } catch {
18654
+ }
18655
+ throw error;
18656
+ }
18503
18657
  }
18504
18658
  function readStore() {
18505
18659
  try {
@@ -18581,6 +18735,656 @@ var require_codexQuota = __commonJS({
18581
18735
  }
18582
18736
  });
18583
18737
 
18738
+ // ../sidekick-shared/dist/codexQuotaWatcher.js
18739
+ var require_codexQuotaWatcher = __commonJS({
18740
+ "../sidekick-shared/dist/codexQuotaWatcher.js"(exports) {
18741
+ "use strict";
18742
+ var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
18743
+ if (k2 === void 0) k2 = k;
18744
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18745
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18746
+ desc = { enumerable: true, get: function() {
18747
+ return m[k];
18748
+ } };
18749
+ }
18750
+ Object.defineProperty(o, k2, desc);
18751
+ } : function(o, m, k, k2) {
18752
+ if (k2 === void 0) k2 = k;
18753
+ o[k2] = m[k];
18754
+ });
18755
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
18756
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18757
+ } : function(o, v) {
18758
+ o["default"] = v;
18759
+ });
18760
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
18761
+ var ownKeys = function(o) {
18762
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
18763
+ var ar = [];
18764
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
18765
+ return ar;
18766
+ };
18767
+ return ownKeys(o);
18768
+ };
18769
+ return function(mod) {
18770
+ if (mod && mod.__esModule) return mod;
18771
+ var result = {};
18772
+ if (mod != null) {
18773
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
18774
+ }
18775
+ __setModuleDefault(result, mod);
18776
+ return result;
18777
+ };
18778
+ }();
18779
+ Object.defineProperty(exports, "__esModule", { value: true });
18780
+ exports.CodexQuotaWatcher = void 0;
18781
+ var fs9 = __importStar(__require("fs"));
18782
+ var codexProfiles_1 = require_codexProfiles();
18783
+ var codexQuota_1 = require_codexQuota();
18784
+ var quotaSnapshots_1 = require_quotaSnapshots();
18785
+ var codex_1 = require_codex();
18786
+ var DEFAULT_DISCOVERY_POLL_INTERVAL_MS = 3e4;
18787
+ function accountEmail(account) {
18788
+ return account?.email ?? account?.metadata?.email;
18789
+ }
18790
+ function enrichQuotaState(state, account) {
18791
+ return {
18792
+ ...state,
18793
+ runtimeProvider: "codex",
18794
+ providerId: "codex",
18795
+ accountLabel: account?.label,
18796
+ accountDetail: accountEmail(account)
18797
+ };
18798
+ }
18799
+ function makeUnavailableState(account, error = "Run a Codex session to view rate limits") {
18800
+ return {
18801
+ runtimeProvider: "codex",
18802
+ fiveHour: { utilization: 0, resetsAt: "" },
18803
+ sevenDay: { utilization: 0, resetsAt: "" },
18804
+ available: false,
18805
+ error,
18806
+ providerId: "codex",
18807
+ accountLabel: account?.label,
18808
+ accountDetail: accountEmail(account),
18809
+ fiveHourLabel: "Primary",
18810
+ sevenDayLabel: "Secondary"
18811
+ };
18812
+ }
18813
+ var CodexQuotaWatcher = class {
18814
+ workspacePath;
18815
+ discoveryPollIntervalMs;
18816
+ providerFactory;
18817
+ getActiveAccount;
18818
+ readSnapshot;
18819
+ writeSnapshot;
18820
+ watchFile;
18821
+ listeners = [];
18822
+ discoveryTimer;
18823
+ provider = null;
18824
+ reader = null;
18825
+ fileWatcher = null;
18826
+ sessionPath = null;
18827
+ lastEmissionKey = null;
18828
+ running = false;
18829
+ constructor(workspacePath, options = {}) {
18830
+ this.workspacePath = workspacePath;
18831
+ this.discoveryPollIntervalMs = options.discoveryPollIntervalMs ?? DEFAULT_DISCOVERY_POLL_INTERVAL_MS;
18832
+ this.providerFactory = options.providerFactory ?? (() => new codex_1.CodexProvider());
18833
+ this.getActiveAccount = options.getActiveAccount ?? codexProfiles_1.getActiveCodexAccount;
18834
+ this.readSnapshot = options.readSnapshot ?? quotaSnapshots_1.readQuotaSnapshot;
18835
+ this.writeSnapshot = options.writeSnapshot ?? quotaSnapshots_1.writeQuotaSnapshot;
18836
+ this.watchFile = options.watchFile ?? fs9.watch;
18837
+ }
18838
+ start() {
18839
+ if (this.running)
18840
+ return;
18841
+ this.running = true;
18842
+ this.refreshActiveSession();
18843
+ this.discoveryTimer = setInterval(() => {
18844
+ this.refreshActiveSession();
18845
+ }, this.discoveryPollIntervalMs);
18846
+ }
18847
+ stop() {
18848
+ if (!this.running)
18849
+ return;
18850
+ this.running = false;
18851
+ if (this.discoveryTimer) {
18852
+ clearInterval(this.discoveryTimer);
18853
+ this.discoveryTimer = void 0;
18854
+ }
18855
+ this.teardownActiveSession();
18856
+ }
18857
+ dispose() {
18858
+ this.stop();
18859
+ this.listeners.splice(0, this.listeners.length);
18860
+ }
18861
+ onUpdate(cb) {
18862
+ this.listeners.push(cb);
18863
+ return {
18864
+ dispose: () => {
18865
+ const index = this.listeners.indexOf(cb);
18866
+ if (index >= 0)
18867
+ this.listeners.splice(index, 1);
18868
+ }
18869
+ };
18870
+ }
18871
+ onQuotaUpdate(cb) {
18872
+ return this.onUpdate(cb);
18873
+ }
18874
+ refresh() {
18875
+ this.refreshActiveSession();
18876
+ }
18877
+ refreshActiveSession() {
18878
+ const provider = this.getProvider();
18879
+ const nextSessionPath = provider.findActiveSession(this.workspacePath);
18880
+ if (!nextSessionPath) {
18881
+ this.teardownActiveSession();
18882
+ this.emitCachedOrUnavailable();
18883
+ return;
18884
+ }
18885
+ if (nextSessionPath !== this.sessionPath || this.reader == null) {
18886
+ this.attachToSession(nextSessionPath);
18887
+ return;
18888
+ }
18889
+ this.ingestSessionUpdate("readNew");
18890
+ }
18891
+ attachToSession(nextSessionPath) {
18892
+ this.teardownActiveSession();
18893
+ this.provider = this.providerFactory();
18894
+ this.reader = this.provider.createReader(nextSessionPath);
18895
+ this.sessionPath = nextSessionPath;
18896
+ this.ingestSessionUpdate("readAll");
18897
+ try {
18898
+ this.fileWatcher = this.watchFile(nextSessionPath, (eventType) => {
18899
+ if (!this.running)
18900
+ return;
18901
+ if (eventType === "change") {
18902
+ this.ingestSessionUpdate("readNew");
18903
+ return;
18904
+ }
18905
+ this.refreshActiveSession();
18906
+ });
18907
+ } catch {
18908
+ this.emitCachedOrUnavailable();
18909
+ }
18910
+ }
18911
+ ingestSessionUpdate(mode) {
18912
+ if (!this.provider || !this.reader) {
18913
+ this.emitCachedOrUnavailable();
18914
+ return;
18915
+ }
18916
+ if (!this.reader.exists()) {
18917
+ this.refreshActiveSession();
18918
+ return;
18919
+ }
18920
+ if (mode === "readAll") {
18921
+ this.reader.readAll();
18922
+ } else {
18923
+ this.reader.readNew();
18924
+ }
18925
+ const liveQuota = (0, codexQuota_1.quotaFromCodexRateLimits)(this.provider.getLastRateLimits(), "session");
18926
+ if (!liveQuota) {
18927
+ this.emitCachedOrUnavailable();
18928
+ return;
18929
+ }
18930
+ const account = this.getActiveAccount();
18931
+ if (account) {
18932
+ this.writeSnapshot("codex", account.id, liveQuota);
18933
+ }
18934
+ this.emitState(enrichQuotaState({
18935
+ ...liveQuota,
18936
+ runtimeProvider: "codex",
18937
+ providerId: "codex"
18938
+ }, account));
18939
+ }
18940
+ emitCachedOrUnavailable() {
18941
+ const account = this.getActiveAccount();
18942
+ const cached = account ? this.readSnapshot("codex", account.id) : null;
18943
+ if (cached) {
18944
+ this.emitState(enrichQuotaState({
18945
+ ...cached,
18946
+ runtimeProvider: "codex",
18947
+ providerId: "codex",
18948
+ source: "cache",
18949
+ stale: true,
18950
+ fiveHourLabel: cached.fiveHourLabel ?? "Primary",
18951
+ sevenDayLabel: cached.sevenDayLabel ?? "Secondary"
18952
+ }, account));
18953
+ return;
18954
+ }
18955
+ this.emitState(makeUnavailableState(account));
18956
+ }
18957
+ emitState(state) {
18958
+ const nextKey = JSON.stringify(state);
18959
+ if (this.lastEmissionKey === nextKey)
18960
+ return;
18961
+ this.lastEmissionKey = nextKey;
18962
+ for (const listener of this.listeners) {
18963
+ try {
18964
+ listener(state);
18965
+ } catch {
18966
+ }
18967
+ }
18968
+ }
18969
+ getProvider() {
18970
+ if (!this.provider) {
18971
+ this.provider = this.providerFactory();
18972
+ }
18973
+ return this.provider;
18974
+ }
18975
+ teardownActiveSession() {
18976
+ if (this.fileWatcher) {
18977
+ this.fileWatcher.close();
18978
+ this.fileWatcher = null;
18979
+ }
18980
+ this.reader?.flush();
18981
+ this.reader = null;
18982
+ this.provider?.dispose();
18983
+ this.provider = null;
18984
+ this.sessionPath = null;
18985
+ }
18986
+ };
18987
+ exports.CodexQuotaWatcher = CodexQuotaWatcher;
18988
+ }
18989
+ });
18990
+
18991
+ // ../sidekick-shared/dist/peakHours.js
18992
+ var require_peakHours = __commonJS({
18993
+ "../sidekick-shared/dist/peakHours.js"(exports) {
18994
+ "use strict";
18995
+ Object.defineProperty(exports, "__esModule", { value: true });
18996
+ exports.fetchPeakHoursStatus = fetchPeakHoursStatus4;
18997
+ var PROMOCLOCK_ENDPOINT = "https://promoclock.co/api/status";
18998
+ function unavailableState() {
18999
+ return {
19000
+ status: "unknown",
19001
+ isPeak: false,
19002
+ sessionLimitSpeed: "unknown",
19003
+ label: "Peak-hours status unavailable",
19004
+ peakHoursDescription: "",
19005
+ nextChange: null,
19006
+ minutesUntilChange: null,
19007
+ note: "",
19008
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
19009
+ unavailable: true
19010
+ };
19011
+ }
19012
+ function normalizeStatus(raw) {
19013
+ if (raw === "peak" || raw === "off_peak")
19014
+ return raw;
19015
+ return "unknown";
19016
+ }
19017
+ function normalizeSpeed(raw) {
19018
+ if (raw === "normal" || raw === "faster")
19019
+ return raw;
19020
+ return "unknown";
19021
+ }
19022
+ async function fetchPeakHoursStatus4() {
19023
+ try {
19024
+ const res = await fetch(PROMOCLOCK_ENDPOINT);
19025
+ if (!res.ok)
19026
+ return unavailableState();
19027
+ const data = await res.json();
19028
+ const status = normalizeStatus(data.status);
19029
+ const sessionLimitSpeed = normalizeSpeed(data.sessionLimitSpeed);
19030
+ const isPeak = typeof data.isPeak === "boolean" ? data.isPeak : status === "peak";
19031
+ return {
19032
+ status,
19033
+ isPeak,
19034
+ sessionLimitSpeed,
19035
+ label: data.label ?? (isPeak ? "Peak Hours" : "Off-Peak"),
19036
+ peakHoursDescription: data.peakHours ?? "",
19037
+ nextChange: data.nextChange ?? null,
19038
+ minutesUntilChange: typeof data.minutesUntilChange === "number" ? data.minutesUntilChange : null,
19039
+ note: data.note ?? "",
19040
+ updatedAt: data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
19041
+ unavailable: false
19042
+ };
19043
+ } catch {
19044
+ return unavailableState();
19045
+ }
19046
+ }
19047
+ }
19048
+ });
19049
+
19050
+ // ../sidekick-shared/dist/multiProviderQuotaService.js
19051
+ var require_multiProviderQuotaService = __commonJS({
19052
+ "../sidekick-shared/dist/multiProviderQuotaService.js"(exports) {
19053
+ "use strict";
19054
+ Object.defineProperty(exports, "__esModule", { value: true });
19055
+ exports.MultiProviderQuotaService = void 0;
19056
+ var accounts_1 = require_accounts();
19057
+ var codexQuotaWatcher_1 = require_codexQuotaWatcher();
19058
+ var credentials_1 = require_credentials();
19059
+ var peakHours_1 = require_peakHours();
19060
+ var quota_1 = require_quota();
19061
+ var quotaPresentation_1 = require_quotaPresentation();
19062
+ var ACTIVE_POLL_INTERVAL_MS = 6e4;
19063
+ var IDLE_POLL_INTERVAL_MS = 3e5;
19064
+ var TRANSIENT_FAILURE_BACKOFF_MS = [3e4, 6e4, 12e4, 24e4, 3e5];
19065
+ var PEAK_HOURS_CACHE_MAX_AGE_MS = 10 * 60 * 1e3;
19066
+ function makeUnavailableClaudeState(error, failureKind) {
19067
+ return {
19068
+ runtimeProvider: "claude",
19069
+ fiveHour: { utilization: 0, resetsAt: "" },
19070
+ sevenDay: { utilization: 0, resetsAt: "" },
19071
+ available: false,
19072
+ error,
19073
+ failureKind,
19074
+ providerId: "claude-code",
19075
+ source: "api"
19076
+ };
19077
+ }
19078
+ function formatDelay(ms) {
19079
+ if (ms % 6e4 === 0)
19080
+ return `${ms / 6e4}m`;
19081
+ if (ms % 1e3 === 0)
19082
+ return `${ms / 1e3}s`;
19083
+ return `${ms}ms`;
19084
+ }
19085
+ function unavailablePeakHoursState() {
19086
+ return {
19087
+ status: "unknown",
19088
+ isPeak: false,
19089
+ sessionLimitSpeed: "unknown",
19090
+ label: "Peak hours unavailable",
19091
+ peakHoursDescription: "",
19092
+ nextChange: null,
19093
+ minutesUntilChange: null,
19094
+ note: "",
19095
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
19096
+ unavailable: true
19097
+ };
19098
+ }
19099
+ var MultiProviderQuotaService = class {
19100
+ activeIntervalMs;
19101
+ idleIntervalMs;
19102
+ transientFailureBackoffMs;
19103
+ peakHoursCacheMaxAgeMs;
19104
+ includePeakHours;
19105
+ readClaudeCredentials;
19106
+ readClaudeAccount;
19107
+ fetchClaudeQuota;
19108
+ fetchPeakHours;
19109
+ log;
19110
+ listeners = [];
19111
+ codexWatcher;
19112
+ ownsCodexWatcher;
19113
+ codexSubscription = null;
19114
+ timer;
19115
+ polling = false;
19116
+ quotaByProvider = {};
19117
+ lastEmittedClaudeState = null;
19118
+ lastSuccessfulClaudeState = null;
19119
+ pollingMode = "idle";
19120
+ transientFailureStreak = 0;
19121
+ cachedFallbackActive = false;
19122
+ authUnavailableActive = false;
19123
+ currentPeakHours;
19124
+ lastFreshPeakHoursAt = 0;
19125
+ peakHoursErrorLogged = false;
19126
+ constructor(options = {}) {
19127
+ this.activeIntervalMs = options.activeIntervalMs ?? ACTIVE_POLL_INTERVAL_MS;
19128
+ this.idleIntervalMs = options.idleIntervalMs ?? IDLE_POLL_INTERVAL_MS;
19129
+ this.transientFailureBackoffMs = options.transientFailureBackoffMs ?? TRANSIENT_FAILURE_BACKOFF_MS;
19130
+ this.peakHoursCacheMaxAgeMs = options.peakHoursCacheMaxAgeMs ?? PEAK_HOURS_CACHE_MAX_AGE_MS;
19131
+ this.includePeakHours = options.includePeakHours ?? true;
19132
+ this.readClaudeCredentials = options.readClaudeCredentials ?? credentials_1.readClaudeMaxCredentials;
19133
+ this.readClaudeAccount = options.readClaudeAccount ?? accounts_1.readActiveClaudeAccount;
19134
+ this.fetchClaudeQuota = options.fetchClaudeQuota ?? quota_1.fetchQuota;
19135
+ this.fetchPeakHours = options.fetchPeakHours ?? peakHours_1.fetchPeakHoursStatus;
19136
+ this.log = options.log;
19137
+ if (options.codexWatcher !== void 0) {
19138
+ this.codexWatcher = options.codexWatcher;
19139
+ this.ownsCodexWatcher = false;
19140
+ } else if (options.codexWorkspacePath) {
19141
+ this.codexWatcher = new codexQuotaWatcher_1.CodexQuotaWatcher(options.codexWorkspacePath);
19142
+ this.ownsCodexWatcher = true;
19143
+ } else {
19144
+ this.codexWatcher = null;
19145
+ this.ownsCodexWatcher = false;
19146
+ }
19147
+ this.codexSubscription = this.codexWatcher?.onUpdate((state) => {
19148
+ this.updateProviderQuota("codex", state);
19149
+ }) ?? null;
19150
+ }
19151
+ startPolling() {
19152
+ if (this.polling)
19153
+ return;
19154
+ this.polling = true;
19155
+ this.codexWatcher?.start();
19156
+ void this.poll();
19157
+ this.log?.("[Quota] Polling started");
19158
+ }
19159
+ stopPolling() {
19160
+ if (!this.polling)
19161
+ return;
19162
+ this.polling = false;
19163
+ if (this.timer) {
19164
+ clearTimeout(this.timer);
19165
+ this.timer = void 0;
19166
+ }
19167
+ this.codexWatcher?.stop();
19168
+ this.log?.("[Quota] Polling stopped");
19169
+ }
19170
+ setPollingMode(mode) {
19171
+ if (this.pollingMode === mode)
19172
+ return;
19173
+ this.pollingMode = mode;
19174
+ if (!this.polling)
19175
+ return;
19176
+ if (this.transientFailureStreak > 0 && this.timer)
19177
+ return;
19178
+ if (this.timer) {
19179
+ clearTimeout(this.timer);
19180
+ this.timer = void 0;
19181
+ }
19182
+ this.scheduleNextPoll();
19183
+ }
19184
+ onUpdate(cb) {
19185
+ this.listeners.push(cb);
19186
+ return {
19187
+ dispose: () => {
19188
+ const index = this.listeners.indexOf(cb);
19189
+ if (index >= 0)
19190
+ this.listeners.splice(index, 1);
19191
+ }
19192
+ };
19193
+ }
19194
+ onQuotaUpdate(cb) {
19195
+ return this.onUpdate(cb);
19196
+ }
19197
+ getLatest() {
19198
+ return { ...this.quotaByProvider };
19199
+ }
19200
+ updateProviderQuota(provider, state) {
19201
+ if (provider === "claude") {
19202
+ const nextState2 = this.withClaudeAccountDetails({
19203
+ ...state,
19204
+ runtimeProvider: "claude",
19205
+ providerId: "claude-code"
19206
+ });
19207
+ this.lastEmittedClaudeState = nextState2;
19208
+ if (nextState2.available) {
19209
+ this.lastSuccessfulClaudeState = nextState2;
19210
+ }
19211
+ this.quotaByProvider = {
19212
+ ...this.quotaByProvider,
19213
+ claude: nextState2
19214
+ };
19215
+ this.emit();
19216
+ return;
19217
+ }
19218
+ const nextState = {
19219
+ ...state,
19220
+ runtimeProvider: "codex",
19221
+ providerId: "codex"
19222
+ };
19223
+ this.quotaByProvider = {
19224
+ ...this.quotaByProvider,
19225
+ codex: nextState
19226
+ };
19227
+ this.emit();
19228
+ }
19229
+ dispose() {
19230
+ this.stopPolling();
19231
+ this.codexSubscription?.dispose();
19232
+ this.codexSubscription = null;
19233
+ if (this.ownsCodexWatcher) {
19234
+ this.codexWatcher?.dispose();
19235
+ }
19236
+ this.listeners.splice(0, this.listeners.length);
19237
+ }
19238
+ async refreshPeakHours() {
19239
+ if (!this.includePeakHours)
19240
+ return;
19241
+ let result;
19242
+ try {
19243
+ result = await this.fetchPeakHours();
19244
+ } catch (error) {
19245
+ if (!this.peakHoursErrorLogged) {
19246
+ this.log?.("[Quota] Peak-hours fetch threw unexpectedly", error);
19247
+ this.peakHoursErrorLogged = true;
19248
+ }
19249
+ result = unavailablePeakHoursState();
19250
+ }
19251
+ if (!result.unavailable) {
19252
+ this.currentPeakHours = result;
19253
+ this.lastFreshPeakHoursAt = Date.now();
19254
+ this.peakHoursErrorLogged = false;
19255
+ return;
19256
+ }
19257
+ const cachedIsFresh = this.currentPeakHours !== void 0 && !this.currentPeakHours.unavailable && Date.now() - this.lastFreshPeakHoursAt < this.peakHoursCacheMaxAgeMs;
19258
+ if (cachedIsFresh)
19259
+ return;
19260
+ this.currentPeakHours = result;
19261
+ }
19262
+ async poll() {
19263
+ await this.refreshPeakHours();
19264
+ try {
19265
+ const creds = await this.readClaudeCredentials();
19266
+ if (!creds) {
19267
+ this.handleUnavailableState(makeUnavailableClaudeState("Renew your Claude Max OAuth to view quota", "auth"));
19268
+ return;
19269
+ }
19270
+ const state = await this.fetchClaudeQuota(creds.accessToken);
19271
+ if (!state.available) {
19272
+ this.handleUnavailableState({
19273
+ ...state,
19274
+ runtimeProvider: "claude",
19275
+ providerId: "claude-code",
19276
+ source: state.source ?? "api"
19277
+ });
19278
+ return;
19279
+ }
19280
+ const recoveredAttempts = this.transientFailureStreak;
19281
+ const recoveredFromFallback = recoveredAttempts > 0;
19282
+ this.resetTransientFailureState();
19283
+ this.authUnavailableActive = false;
19284
+ if (recoveredFromFallback) {
19285
+ this.log?.(`[Quota] Fetch recovered after ${recoveredAttempts} failed attempt(s)`);
19286
+ }
19287
+ this.emitClaudeState({
19288
+ ...state,
19289
+ runtimeProvider: "claude",
19290
+ providerId: "claude-code",
19291
+ source: state.source ?? "api"
19292
+ });
19293
+ } catch (error) {
19294
+ if (this.transientFailureStreak === 0) {
19295
+ this.log?.("[Quota] Poll error", error);
19296
+ }
19297
+ const message = error instanceof Error ? error.message : "Unknown error";
19298
+ this.handleTransientFailure(makeUnavailableClaudeState(message));
19299
+ } finally {
19300
+ this.scheduleNextPoll();
19301
+ }
19302
+ }
19303
+ emitClaudeState(state) {
19304
+ const withPeakHours = this.currentPeakHours !== void 0 ? { ...state, peakHours: this.currentPeakHours } : state;
19305
+ const withFailure = {
19306
+ ...withPeakHours,
19307
+ failure: (0, quotaPresentation_1.describeQuotaFailure)(withPeakHours)
19308
+ };
19309
+ this.updateProviderQuota("claude", withFailure);
19310
+ }
19311
+ handleUnavailableState(state) {
19312
+ if (state.failureKind === "auth") {
19313
+ this.resetTransientFailureState();
19314
+ this.authUnavailableActive = true;
19315
+ this.emitClaudeState(state);
19316
+ return;
19317
+ }
19318
+ this.handleTransientFailure(state);
19319
+ }
19320
+ handleTransientFailure(state) {
19321
+ this.authUnavailableActive = false;
19322
+ this.transientFailureStreak += 1;
19323
+ const retryDelay = this.getTransientFailureDelay();
19324
+ const hasVisibleCachedSuccess = this.lastSuccessfulClaudeState !== null && this.lastEmittedClaudeState?.available === true;
19325
+ if (this.transientFailureStreak === 1) {
19326
+ if (hasVisibleCachedSuccess) {
19327
+ this.log?.(`[Quota] Fetch failed, using cached data; retrying in ${formatDelay(retryDelay)}`);
19328
+ } else {
19329
+ this.log?.(`[Quota] Fetch unavailable; retrying in ${formatDelay(retryDelay)}`);
19330
+ }
19331
+ }
19332
+ this.cachedFallbackActive = hasVisibleCachedSuccess;
19333
+ if (!hasVisibleCachedSuccess) {
19334
+ this.emitClaudeState(state);
19335
+ }
19336
+ }
19337
+ getTransientFailureDelay() {
19338
+ return this.transientFailureBackoffMs[Math.min(this.transientFailureStreak - 1, this.transientFailureBackoffMs.length - 1)];
19339
+ }
19340
+ resetTransientFailureState() {
19341
+ this.transientFailureStreak = 0;
19342
+ this.cachedFallbackActive = false;
19343
+ }
19344
+ scheduleNextPoll() {
19345
+ if (!this.polling)
19346
+ return;
19347
+ if (this.timer) {
19348
+ clearTimeout(this.timer);
19349
+ }
19350
+ let delay = this.pollingMode === "active" ? this.activeIntervalMs : this.idleIntervalMs;
19351
+ if (this.transientFailureStreak > 0) {
19352
+ delay = this.getTransientFailureDelay();
19353
+ } else if (this.authUnavailableActive || this.lastEmittedClaudeState?.available !== true) {
19354
+ delay = this.idleIntervalMs;
19355
+ } else if (this.cachedFallbackActive) {
19356
+ delay = this.idleIntervalMs;
19357
+ }
19358
+ this.timer = setTimeout(() => void this.poll(), delay);
19359
+ }
19360
+ withClaudeAccountDetails(state) {
19361
+ const accountEmail = this.readClaudeAccount()?.email;
19362
+ if (!accountEmail) {
19363
+ const nextState = { ...state };
19364
+ delete nextState.accountLabel;
19365
+ delete nextState.accountDetail;
19366
+ return nextState;
19367
+ }
19368
+ return {
19369
+ ...state,
19370
+ accountLabel: accountEmail,
19371
+ accountDetail: accountEmail
19372
+ };
19373
+ }
19374
+ emit() {
19375
+ const snapshot = { ...this.quotaByProvider };
19376
+ for (const listener of this.listeners) {
19377
+ try {
19378
+ listener(snapshot);
19379
+ } catch {
19380
+ }
19381
+ }
19382
+ }
19383
+ };
19384
+ exports.MultiProviderQuotaService = MultiProviderQuotaService;
19385
+ }
19386
+ });
19387
+
18584
19388
  // ../sidekick-shared/dist/pricingCatalog.js
18585
19389
  var require_pricingCatalog = __commonJS({
18586
19390
  "../sidekick-shared/dist/pricingCatalog.js"(exports) {
@@ -18784,6 +19588,7 @@ var require_toolCall = __commonJS({
18784
19588
  "use strict";
18785
19589
  Object.defineProperty(exports, "__esModule", { value: true });
18786
19590
  exports.extractToolCalls = extractToolCalls;
19591
+ exports.extractToolCall = extractToolCall;
18787
19592
  function extractToolCalls(event) {
18788
19593
  if (event.type !== "assistant")
18789
19594
  return [];
@@ -18807,6 +19612,15 @@ var require_toolCall = __commonJS({
18807
19612
  }
18808
19613
  return calls;
18809
19614
  }
19615
+ function extractToolCall(event) {
19616
+ if (event.type !== "tool_use" || !event.tool?.name)
19617
+ return null;
19618
+ return {
19619
+ name: event.tool.name,
19620
+ input: event.tool.input ?? {},
19621
+ timestamp: new Date(event.timestamp)
19622
+ };
19623
+ }
18810
19624
  }
18811
19625
  });
18812
19626
 
@@ -34929,74 +35743,15 @@ var require_providerStatus = __commonJS({
34929
35743
  }
34930
35744
  });
34931
35745
 
34932
- // ../sidekick-shared/dist/peakHours.js
34933
- var require_peakHours = __commonJS({
34934
- "../sidekick-shared/dist/peakHours.js"(exports) {
34935
- "use strict";
34936
- Object.defineProperty(exports, "__esModule", { value: true });
34937
- exports.fetchPeakHoursStatus = fetchPeakHoursStatus4;
34938
- var PROMOCLOCK_ENDPOINT = "https://promoclock.co/api/status";
34939
- function unavailableState() {
34940
- return {
34941
- status: "unknown",
34942
- isPeak: false,
34943
- sessionLimitSpeed: "unknown",
34944
- label: "Peak-hours status unavailable",
34945
- peakHoursDescription: "",
34946
- nextChange: null,
34947
- minutesUntilChange: null,
34948
- note: "",
34949
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
34950
- unavailable: true
34951
- };
34952
- }
34953
- function normalizeStatus(raw) {
34954
- if (raw === "peak" || raw === "off_peak")
34955
- return raw;
34956
- return "unknown";
34957
- }
34958
- function normalizeSpeed(raw) {
34959
- if (raw === "normal" || raw === "faster")
34960
- return raw;
34961
- return "unknown";
34962
- }
34963
- async function fetchPeakHoursStatus4() {
34964
- try {
34965
- const res = await fetch(PROMOCLOCK_ENDPOINT);
34966
- if (!res.ok)
34967
- return unavailableState();
34968
- const data = await res.json();
34969
- const status = normalizeStatus(data.status);
34970
- const sessionLimitSpeed = normalizeSpeed(data.sessionLimitSpeed);
34971
- const isPeak = typeof data.isPeak === "boolean" ? data.isPeak : status === "peak";
34972
- return {
34973
- status,
34974
- isPeak,
34975
- sessionLimitSpeed,
34976
- label: data.label ?? (isPeak ? "Peak Hours" : "Off-Peak"),
34977
- peakHoursDescription: data.peakHours ?? "",
34978
- nextChange: data.nextChange ?? null,
34979
- minutesUntilChange: typeof data.minutesUntilChange === "number" ? data.minutesUntilChange : null,
34980
- note: data.note ?? "",
34981
- updatedAt: data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
34982
- unavailable: false
34983
- };
34984
- } catch {
34985
- return unavailableState();
34986
- }
34987
- }
34988
- }
34989
- });
34990
-
34991
35746
  // ../sidekick-shared/dist/index.js
34992
35747
  var require_dist = __commonJS({
34993
35748
  "../sidekick-shared/dist/index.js"(exports) {
34994
35749
  "use strict";
34995
35750
  Object.defineProperty(exports, "__esModule", { value: true });
34996
35751
  exports.findActiveClaudeSession = exports.discoverSessionDirectory = exports.getClaudeSessionDirectory = exports.encodeClaudeWorkspacePath = exports.detectSessionActivity = exports.extractTaskInfo = exports.scanSubagentDir = exports.normalizeCodexToolInput = exports.normalizeCodexToolName = exports.extractPatchFilePaths = exports.CodexRolloutParser = exports.parseDbPartData = exports.parseDbMessageData = exports.convertOpenCodeMessage = exports.detectPlanModeFromText = exports.normalizeToolInput = exports.normalizeToolName = exports.TRUNCATION_PATTERNS = exports.JsonlParser = exports.CodexProvider = exports.OpenCodeProvider = exports.ClaudeCodeProvider = exports.getAllDetectedProviders = exports.detectProvider = exports.readClaudeCodePlanFiles = exports.getPlanAnalytics = exports.writePlans = exports.getLatestPlan = exports.readPlans = exports.readLatestHandoff = exports.readHistory = exports.readNotes = exports.readDecisions = exports.readTasks = exports.getProjectSlugRaw = exports.getProjectSlug = exports.encodeWorkspacePath = exports.getGlobalDataPath = exports.getProjectDataPath = exports.getConfigDir = exports.MAX_PLANS_PER_PROJECT = exports.PLAN_SCHEMA_VERSION = exports.createEmptyTokenTotals = exports.HISTORICAL_DATA_SCHEMA_VERSION = exports.STALENESS_THRESHOLDS = exports.IMPORTANCE_DECAY_FACTORS = exports.KNOWLEDGE_NOTE_SCHEMA_VERSION = exports.DECISION_LOG_SCHEMA_VERSION = exports.normalizeTaskStatus = exports.TASK_PERSISTENCE_SCHEMA_VERSION = void 0;
34997
- exports.HeatmapTracker = exports.FrequencyTracker = exports.getSnapshotPath = exports.isSnapshotValid = exports.deleteSnapshot = exports.loadSnapshot = exports.saveSnapshot = exports.parseTodoDependencies = exports.EventAggregator = exports.getRandomPhrase = exports.ALL_PHRASES = exports.HIGHLIGHT_CSS = exports.clearHighlightCache = exports.highlightEvent = exports.formatSessionJson = exports.formatSessionMarkdown = exports.formatSessionText = exports.classifyNoise = exports.shouldMergeWithPrevious = exports.classifyFollowEvent = exports.classifyMessage = exports.getSoftNoiseReason = exports.isHardNoiseFollowEvent = exports.isHardNoise = exports.formatToolSummary = exports.toFollowEvents = exports.createWatcher = exports.parseChangelog = exports.extractProposedPlanShared = exports.parsePlanMarkdownShared = exports.PlanExtractor = exports.composeContext = exports.FilterEngine = exports.searchSessions = exports.CodexDatabase = exports.OpenCodeDatabase = exports.discoverDebugLogs = exports.collapseDuplicates = exports.filterByLevel = exports.parseDebugLog = exports.scanSubagentTraces = exports.findAllSessionsWithWorktrees = exports.discoverWorktreeSiblings = exports.resolveWorktreeMainRepo = exports.getAllClaudeProjectFolders = exports.decodeEncodedPath = exports.getMostRecentlyActiveSessionDir = exports.findSubdirectorySessionDirs = exports.findSessionsInDirectory = exports.findAllClaudeSessions = void 0;
34998
- exports.getModelInfo = exports.getModelPricing = exports.parseModelId = exports.DEFAULT_CONTEXT_WINDOW = exports.getModelContextWindowSize = exports.quotaFromCodexRateLimits = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.QuotaPoller = exports.describeQuotaFailure = exports.fetchQuota = exports.removeCodexAccount = exports.switchToCodexAccount = exports.finalizeCodexAccount = exports.prepareCodexAccount = exports.getCodexExecutionEnv = exports.resolveSidekickCodexHome = exports.getActiveCodexAccount = exports.listCodexAccounts = exports.getSystemCodexHome = exports.getCodexMonitoringHomes = exports.getCodexProfileHome = exports.getCodexProfilesDir = exports.removeSavedAccountProfile = exports.replaceSavedAccountProfiles = exports.setActiveSavedAccount = exports.upsertSavedAccountProfile = exports.getActiveSavedAccount = exports.listSavedAccountProfiles = exports.writeSavedAccountRegistry = exports.readSavedAccountRegistry = exports.getAccountsDir = exports.isMultiAccountEnabled = exports.getActiveAccount = exports.listAccounts = exports.removeAccount = exports.switchToAccount = exports.addCurrentAccount = exports.readActiveClaudeAccount = exports.writeAccountRegistry = exports.readAccountRegistry = exports.ensureDefaultAccounts = exports.readClaudeMaxAccessTokenSync = exports.readClaudeMaxCredentials = exports.writeActiveCredentials = exports.readActiveCredentials = exports.openInBrowser = exports.parseTranscript = exports.generateHtmlReport = exports.PatternExtractor = void 0;
34999
- exports.fetchPeakHoursStatus = exports.fetchOpenAIStatus = exports.fetchProviderStatus = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = exports.extractToolCalls = exports.extractTokenUsage = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = exports.formatCost = exports.calculateCostWithPricing = exports.calculateCost = void 0;
35752
+ exports.FrequencyTracker = exports.getSnapshotPath = exports.isSnapshotValid = exports.deleteSnapshot = exports.loadSnapshot = exports.saveSnapshot = exports.parseTodoDependencies = exports.EventAggregator = exports.getRandomPhrase = exports.PHRASE_CATEGORIES = exports.ALL_PHRASES = exports.HIGHLIGHT_CSS = exports.clearHighlightCache = exports.highlightEvent = exports.formatSessionJson = exports.formatSessionMarkdown = exports.formatSessionText = exports.classifyNoise = exports.shouldMergeWithPrevious = exports.classifyFollowEvent = exports.classifyMessage = exports.getSoftNoiseReason = exports.isHardNoiseFollowEvent = exports.isHardNoise = exports.formatToolSummary = exports.toFollowEvents = exports.createWatcher = exports.parseChangelog = exports.extractProposedPlanShared = exports.parsePlanMarkdownShared = exports.PlanExtractor = exports.composeContext = exports.FilterEngine = exports.searchSessions = exports.CodexDatabase = exports.OpenCodeDatabase = exports.discoverDebugLogs = exports.collapseDuplicates = exports.filterByLevel = exports.parseDebugLog = exports.scanSubagentTraces = exports.findAllSessionsWithWorktrees = exports.discoverWorktreeSiblings = exports.resolveWorktreeMainRepo = exports.getAllClaudeProjectFolders = exports.decodeEncodedPath = exports.getMostRecentlyActiveSessionDir = exports.findSubdirectorySessionDirs = exports.findSessionsInDirectory = exports.findAllClaudeSessions = void 0;
35753
+ exports.getModelContextWindowSize = exports.MultiProviderQuotaService = exports.CodexQuotaWatcher = exports.quotaFromCodexRateLimits = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.QuotaPoller = exports.describeQuotaFailure = exports.fetchQuota = exports.removeCodexAccount = exports.switchToCodexAccount = exports.finalizeCodexAccount = exports.prepareCodexAccount = exports.getCodexExecutionEnv = exports.resolveSidekickCodexHome = exports.getActiveCodexAccount = exports.listCodexAccounts = exports.getSystemCodexHome = exports.getCodexMonitoringHomes = exports.getCodexProfileHome = exports.getCodexProfilesDir = exports.getActiveAccountStatus = exports.removeSavedAccountProfile = exports.replaceSavedAccountProfiles = exports.setActiveSavedAccount = exports.upsertSavedAccountProfile = exports.getActiveSavedAccount = exports.listSavedAccountProfiles = exports.writeSavedAccountRegistry = exports.readSavedAccountRegistry = exports.getAccountsDir = exports.isMultiAccountEnabled = exports.getActiveAccount = exports.listAccounts = exports.removeAccount = exports.switchToAccount = exports.addCurrentAccount = exports.readActiveClaudeAccount = exports.writeAccountRegistry = exports.readAccountRegistry = exports.ensureDefaultAccounts = exports.readClaudeMaxAccessTokenSync = exports.readClaudeMaxCredentials = exports.writeActiveCredentials = exports.readActiveCredentials = exports.openInBrowser = exports.parseTranscript = exports.generateHtmlReport = exports.PatternExtractor = exports.HeatmapTracker = void 0;
35754
+ exports.fetchPeakHoursStatus = exports.fetchOpenAIStatus = exports.fetchProviderStatus = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = exports.extractToolCalls = exports.extractToolCall = exports.extractTokenUsage = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = exports.formatCost = exports.sortModelIds = exports.compareModelIds = exports.getModelDisplayInfo = exports.shortModelName = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getModelInfo = exports.getModelPricing = exports.parseModelId = exports.DEFAULT_CONTEXT_WINDOW = void 0;
35000
35755
  var taskPersistence_1 = require_taskPersistence();
35001
35756
  Object.defineProperty(exports, "TASK_PERSISTENCE_SCHEMA_VERSION", { enumerable: true, get: function() {
35002
35757
  return taskPersistence_1.TASK_PERSISTENCE_SCHEMA_VERSION;
@@ -35305,6 +36060,9 @@ var require_dist = __commonJS({
35305
36060
  Object.defineProperty(exports, "ALL_PHRASES", { enumerable: true, get: function() {
35306
36061
  return phrases_1.ALL_PHRASES;
35307
36062
  } });
36063
+ Object.defineProperty(exports, "PHRASE_CATEGORIES", { enumerable: true, get: function() {
36064
+ return phrases_1.PHRASE_CATEGORIES;
36065
+ } });
35308
36066
  Object.defineProperty(exports, "getRandomPhrase", { enumerable: true, get: function() {
35309
36067
  return phrases_1.getRandomPhrase;
35310
36068
  } });
@@ -35427,6 +36185,10 @@ var require_dist = __commonJS({
35427
36185
  Object.defineProperty(exports, "removeSavedAccountProfile", { enumerable: true, get: function() {
35428
36186
  return accountRegistry_1.removeSavedAccountProfile;
35429
36187
  } });
36188
+ var accountStatus_1 = require_accountStatus();
36189
+ Object.defineProperty(exports, "getActiveAccountStatus", { enumerable: true, get: function() {
36190
+ return accountStatus_1.getActiveAccountStatus;
36191
+ } });
35430
36192
  var codexProfiles_1 = require_codexProfiles();
35431
36193
  Object.defineProperty(exports, "getCodexProfilesDir", { enumerable: true, get: function() {
35432
36194
  return codexProfiles_1.getCodexProfilesDir;
@@ -35487,6 +36249,14 @@ var require_dist = __commonJS({
35487
36249
  Object.defineProperty(exports, "quotaFromCodexRateLimits", { enumerable: true, get: function() {
35488
36250
  return codexQuota_1.quotaFromCodexRateLimits;
35489
36251
  } });
36252
+ var codexQuotaWatcher_1 = require_codexQuotaWatcher();
36253
+ Object.defineProperty(exports, "CodexQuotaWatcher", { enumerable: true, get: function() {
36254
+ return codexQuotaWatcher_1.CodexQuotaWatcher;
36255
+ } });
36256
+ var multiProviderQuotaService_1 = require_multiProviderQuotaService();
36257
+ Object.defineProperty(exports, "MultiProviderQuotaService", { enumerable: true, get: function() {
36258
+ return multiProviderQuotaService_1.MultiProviderQuotaService;
36259
+ } });
35490
36260
  var modelContext_1 = require_modelContext();
35491
36261
  Object.defineProperty(exports, "getModelContextWindowSize", { enumerable: true, get: function() {
35492
36262
  return modelContext_1.getModelContextWindowSize;
@@ -35510,6 +36280,24 @@ var require_dist = __commonJS({
35510
36280
  Object.defineProperty(exports, "calculateCostWithPricing", { enumerable: true, get: function() {
35511
36281
  return modelInfo_1.calculateCostWithPricing;
35512
36282
  } });
36283
+ Object.defineProperty(exports, "calculateCostWithProvenance", { enumerable: true, get: function() {
36284
+ return modelInfo_1.calculateCostWithProvenance;
36285
+ } });
36286
+ Object.defineProperty(exports, "mergeCostSources", { enumerable: true, get: function() {
36287
+ return modelInfo_1.mergeCostSources;
36288
+ } });
36289
+ Object.defineProperty(exports, "shortModelName", { enumerable: true, get: function() {
36290
+ return modelInfo_1.shortModelName;
36291
+ } });
36292
+ Object.defineProperty(exports, "getModelDisplayInfo", { enumerable: true, get: function() {
36293
+ return modelInfo_1.getModelDisplayInfo;
36294
+ } });
36295
+ Object.defineProperty(exports, "compareModelIds", { enumerable: true, get: function() {
36296
+ return modelInfo_1.compareModelIds;
36297
+ } });
36298
+ Object.defineProperty(exports, "sortModelIds", { enumerable: true, get: function() {
36299
+ return modelInfo_1.sortModelIds;
36300
+ } });
35513
36301
  Object.defineProperty(exports, "formatCost", { enumerable: true, get: function() {
35514
36302
  return modelInfo_1.formatCost;
35515
36303
  } });
@@ -35528,6 +36316,9 @@ var require_dist = __commonJS({
35528
36316
  return tokenUsage_1.extractTokenUsage;
35529
36317
  } });
35530
36318
  var toolCall_1 = require_toolCall();
36319
+ Object.defineProperty(exports, "extractToolCall", { enumerable: true, get: function() {
36320
+ return toolCall_1.extractToolCall;
36321
+ } });
35531
36322
  Object.defineProperty(exports, "extractToolCalls", { enumerable: true, get: function() {
35532
36323
  return toolCall_1.extractToolCalls;
35533
36324
  } });
@@ -37790,7 +38581,7 @@ var init_UpdateCheckService = __esm({
37790
38581
  /** Run the update check (one-shot). */
37791
38582
  async check() {
37792
38583
  try {
37793
- const current = "0.17.6";
38584
+ const current = "0.18.0";
37794
38585
  const cached = this.readCache();
37795
38586
  let latest;
37796
38587
  if (cached && Date.now() - cached.checkedAt < CACHE_TTL_MS) {
@@ -78408,7 +79199,7 @@ function StatusBar({
78408
79199
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: parseBlessedTags(BRAND_INLINE) }),
78409
79200
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { dimColor: true, children: [
78410
79201
  " v",
78411
- "0.17.6"
79202
+ "0.18.0"
78412
79203
  ] }),
78413
79204
  updateInfo && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { color: "yellow", children: [
78414
79205
  " (v",
@@ -78798,7 +79589,7 @@ function ChangelogOverlay({ entries, scrollOffset }) {
78798
79589
  " ",
78799
79590
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { bold: true, color: "cyan", children: [
78800
79591
  "Terminal Dashboard v",
78801
- "0.17.6"
79592
+ "0.18.0"
78802
79593
  ] }),
78803
79594
  latestDate ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { color: "gray", children: [
78804
79595
  " \u2014 ",
@@ -79120,7 +79911,7 @@ var init_mouse = __esm({
79120
79911
  var CHANGELOG_default;
79121
79912
  var init_CHANGELOG = __esm({
79122
79913
  "CHANGELOG.md"() {
79123
- CHANGELOG_default = '# Changelog\n\nAll notable changes to the Sidekick Agent Hub CLI will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [0.17.6] - 2026-04-19\n\n### Added\n\n- **`sidekick peak` command**: One-shot check for Claude\'s current peak-hours state \u2014 weekdays 13:00\u201319:00 UTC, when session limits drain faster on Free/Pro/Max/Team subscriptions. Prints a color-coded status block with a countdown to the next transition. Data comes from the public `promoclock.co/api/status` endpoint (third-party, unaffiliated with Anthropic) with a graceful fallback when unreachable. `--json` emits the full raw state\n- **Peak-hours block in `sidekick status`**: When the active provider is `claude-code`, the Claude + OpenAI health blocks are now followed by a **Claude Peak Hours** block (off-peak or in-peak, with countdown). Gated on the provider so OpenCode / Codex users don\'t trigger an unnecessary third-party fetch. `--json` output includes the new `peak` field\n- **Peak-hours summary in `sidekick quota`**: Claude subscription quota output now shows a **Peak** line under the 5-hour / 7-day bars \u2014 green dot off-peak, orange dot during an active peak, with a countdown to the next transition. `--json` output includes the new `peak` field\n\n## [0.17.5] - 2026-04-18\n\n### Added\n\n- **Default account bootstrap at CLI startup**: The CLI now calls `ensureDefaultAccounts()` from `sidekick-shared` at module load and awaits the result inside a Commander `preAction` hook, so the first real subcommand blocks briefly on the bootstrap while `--version` and `--help` stay instant. When a system Claude Code or Codex credential exists and no saved account is active for that provider yet, the CLI registers it as "Default" \u2014 `sidekick quota`, `sidekick account`, and `sidekick stats` now reflect the active account on first run without requiring an explicit `sidekick account --add` first. Idempotent, never overwrites manually saved accounts, and all errors are swallowed so startup is never blocked\n\nThanks to [@B33pBeeps](https://github.com/B33pBeeps) (Juan Fourie) for contributing this feature in [#16](https://github.com/cesarandreslopez/sidekick-agent-hub/pull/16).\n\n## [0.17.4] - 2026-04-17\n\n### Changed\n\n- **Pricing hydration import migrated to `sidekick-shared/node`**: `cli.ts` now imports `hydratePricingCatalog` from the new Node-only subpath and keeps `detectProvider` on the package root. Runtime behavior is unchanged; the split makes the CLI\'s import surface self-documenting (hydration is explicitly a Node API) and aligns the CLI with the shared library\'s new versioned public API contract\n\n## [0.17.3] - 2026-04-17\n\n### Changed\n\n- **Version sync with the VS Code extension**: Republished to keep CLI, extension, and shared-library versions aligned after a cosmetic changelog fix in 0.17.3. No CLI code changes \u2014 functionally identical to 0.17.2\n\n## [0.17.2] - 2026-04-17\n\n### Added\n\n- **LiteLLM pricing hydration on startup**: The CLI now fetches the LiteLLM pricing catalog on startup and caches to `~/.config/sidekick/pricing-catalog.json` with a 24-hour TTL, 3s timeout, and stale-cache fallback \u2014 new model prices are picked up without a CLI upgrade\n- **Expanded pricing coverage**: GPT-4o, GPT-4.1, GPT-5.x, o1, o3, and o3-mini families are now priced alongside the existing Claude entries\n- **Real-dollar Codex / Claude Code costs**: `EventAggregator` computes cost from the pricing table when the session provider doesn\'t report one, so `sidekick` live dashboards now show actual dollars for Codex and Claude Code sessions\n- **`stats` footer lists unpriced models**: `sidekick stats` prints any models encountered with no pricing entry so missing coverage is visible\n\n### Fixed\n\n- **Context-gauge % wrong for Opus 4.7 (1M) and other new models**: The dashboard\'s context gauge was dividing by 200K for Claude Opus 4.7 (native 1M), inflating the displayed %. The shared model \u2192 context-window map now includes Opus/Sonnet 4.7 (1M), GPT-5.4 (1.05M), GPT-5.3-Codex (400K), and GPT-5.3-Codex-Spark (128K). Claude Code\'s `[1m]` suffix is now also honored as an explicit 1M marker\n- **Silent Sonnet-priced fallback for unknown models**: Codex, GPT-5.x, and o-series rows were being rendered at Sonnet rates. Unknown-model rows now render as `\u2014` in yellow instead of inventing a dollar figure\n\n### Changed\n\n- **`historical-data.json` schema v2**: reads `priced` flag and `unpricedModelIds` from records written by the latest VS Code extension; v1 records still read correctly\n\n## [0.17.1] - 2026-04-13\n\n### Fixed\n\n- **Codex multi-home session discovery**: Provider detection now scans all candidate Codex home directories, fixing missed sessions when the managed profile home is empty but the system `~/.codex/` has activity\n\n## [0.17.0] - 2026-04-13\n\n### Added\n\n- **Multi-provider account management**: `sidekick account` now supports `--provider codex` for Codex profile management alongside Claude Code accounts\n- **Codex account lifecycle**: `--add` prepares a profile and spawns `codex login`; `--switch-to` and `--remove` accept email, label, or profile ID\n- **Quota snapshot fallback**: `sidekick quota` for Codex shows cached rate-limit snapshots when no active session exists, with "cached from" timestamp\n\n### Fixed\n\n- **Email normalization**: Claude account lookup normalizes email case for reliable matching\n\n## [0.16.1] - 2026-03-27\n\n### Fixed\n\n- **Dashboard provider status scoping**: The TUI now shows degraded-service notices only for the monitored provider \u2014 Claude for Claude Code sessions, OpenAI for Codex sessions, and no status banner for OpenCode\n\n## [0.16.0] - 2026-03-23\n\n### Changed\n\n- **Consistent cost formatting**: All cost displays (`stats`, `context`, Sessions panel, narrative prompt) now use shared `formatCost()` with intelligent decimal precision (4 places for < $0.01, 2 otherwise)\n- **QuotaService**: Rewritten to wrap shared `QuotaPoller` with exponential backoff instead of manual polling loop\n- **modelContext**: Now re-exports `getModelInfo` from shared library alongside `getContextWindowSize`\n\n## [0.15.2] - 2026-03-18\n\n### Fixed\n\n- **CLI help descriptions**: Updated `quota` and `status` command descriptions to reflect provider-aware behavior\n- **`sidekick quota --provider`**: Added local `--provider` option so `sidekick quota --provider codex` works naturally\n\n## [0.15.0] - 2026-03-18\n\n### Added\n\n- **OpenAI status page monitoring**: CLI dashboard now shows OpenAI API status alongside Claude API status\n- **Codex rate limits in dashboard**: Sessions panel displays Codex rate-limit data with "Rate Limits" header instead of "Quota"\n- **Provider-aware `sidekick quota` command**: Detects active provider and shows Codex rate limits, Claude subscription quota, or an informational message for OpenCode\n\n### Fixed\n\n- **QuotaService polling for Codex**: Dashboard no longer starts Claude OAuth quota polling when the active provider is Codex\n\n## [0.14.2] - 2026-03-16\n\n### Fixed\n\n- **Quota polling interval**: Reduced quota refresh from every 30 seconds to every 5 minutes to avoid unnecessary API calls\n- **SessionsPanel `detailWidth()` call**: Removed unused parameter from `detailWidth()` in the Sessions panel quota rendering\n\n## [0.14.1] - 2026-03-14\n\n### Fixed\n\n- **Per-model context window sizes**: Dashboard context gauge now shows correct utilization for Claude Opus 4.6 (1M context) and other models with non-200K windows\n\n### Changed\n\n- **Shared model context lookup**: CLI dashboard now uses the centralized `getModelContextWindowSize()` from `sidekick-shared` instead of a local duplicate map\n\n## [0.14.0] - 2026-03-12\n\n### Added\n\n- **`sidekick account` Command**: Manage Claude Code accounts from the terminal \u2014 list saved accounts, add the current account with an optional label, switch to the next or a specific account, and remove accounts. Supports `--json` output for scripting\n- **Quota Account Label**: `sidekick quota` now shows the active account email and label above the quota bars when multi-account is enabled\n- **macOS Keychain Support**: `sidekick account` and `sidekick quota` now read and write credentials via the system Keychain on macOS, fixing account switching and quota checks on Mac\n\n## [0.13.8] - 2026-03-12\n\n### Changed\n\n- **Structured quota failure output**: `sidekick quota` now renders consistent auth, rate-limit, server, network, and unexpected-failure copy from shared quota failure descriptors while preserving `--json` machine-readable output\n- **Dashboard unavailable quota rendering**: The Sessions panel now shows Claude Code quota failures inline instead of hiding the quota section whenever subscription data is unavailable\n- **Quota transition toasts**: The Ink dashboard now fires low-noise toast notifications only when Claude Code quota failure state changes, avoiding repeated alerts every polling interval\n\n## [0.13.7] - 2026-03-11\n\n### Changed\n\n- **npm README sync**: Updated the published CLI package README to reflect current OpenCode monitoring behavior, platform-specific data directories, and the `sqlite3` runtime requirement\n- **README badge cleanup**: Removed the Ask DeepWiki badge from the published CLI package README; the repo root README still keeps it\n\n## [0.13.6] - 2026-03-11\n\n### Changed\n\n- **Refreshed CLI Dashboard Wordmark**: Updated the dashboard wordmark/header styling for a cleaner splash and dashboard identity\n\n### Fixed\n\n- **OpenCode dashboard startup**: OpenCode DB-backed session discovery now resolves projects by worktree, sandboxes, and session directory instead of quietly behaving like no session exists\n- **OpenCode runtime notices**: The CLI now prints an OpenCode-only actionable notice when `opencode.db` exists but `sqlite3` is missing, blocked, or otherwise unusable in the current shell environment\n\n## [0.13.5] - 2026-03-10\n\n### Added\n\n- **`sidekick status` Command**: One-shot Claude API status check with color-coded text output and `--json` mode\n- **Dashboard Status Banner**: Status bar shows a colored `\u25CF API minor/major/critical` indicator when Claude is degraded; Sessions panel Summary tab shows an "API Status" section with affected components and active incident details. Polls every 60s\n\n## [0.13.4] - 2026-03-08\n\n### Fixed\n\n- **Onboarding Phrase Spam**: Splash screen and detail pane motivational phrases memoized \u2014 no longer flicker every render tick (fixes [#13](https://github.com/cesarandreslopez/sidekick-agent-hub/issues/13))\n\n### Changed\n\n- **Simplified Logo**: Replaced 6-line ASCII robot art with compact text header in splash, help, and changelog overlays\n- **Removed Dead Code**: Removed unused `getSplashContent()` and `HELP_HEADER` exports from branding module\n\n## [0.13.3] - 2026-03-04\n\n_No CLI-specific changes in this release._\n\n## [0.13.2] - 2026-03-04\n\n_No CLI-specific changes in this release._\n\n## [0.13.1] - 2026-03-04\n\n### Added\n\n- **`sidekick quota` Command**: One-shot subscription quota check showing 5-hour and 7-day utilization with color-coded progress bars and reset countdowns \u2014 supports `--json` for machine-readable output\n- **Quota Projections**: Elapsed-time projections shown in `sidekick quota` output and TUI dashboard quota section \u2014 displays projected end-of-window utilization next to current value (e.g., `40% \u2192 100%`), included in `--json` output as `projectedFiveHour` / `projectedSevenDay`\n\n## [0.13.0] - 2026-03-03\n\n_No CLI-specific changes in this release._\n\n## [0.12.10] - 2026-03-01\n\n### Added\n\n- **Events Panel** (key 7): Scrollable live event stream with colored type badges (`[USR]`, `[AST]`, `[TOOL]`, `[RES]`), timestamps, and keyword-highlighted summaries; detail tabs for full event JSON and surrounding context\n- **Charts Panel** (key 8): Tool frequency horizontal bars, event type distribution, 60-minute activity heatmap using `\u2591\u2592\u2593\u2588` intensity characters, and pattern analysis with frequency bars and template text\n- **Multi-Mode Filter**: `/` filter overlay now supports four modes \u2014 substring, fuzzy, regex, and date range \u2014 Tab cycles modes, regex mode shows red validation errors\n- **Search Term Highlighting**: Active filter terms highlighted in blue within side list items\n- **Timeline Keyword Coloring**: Event summaries in the Sessions panel Timeline tab now use semantic keyword coloring \u2014 errors red, success green, tool names cyan, file paths magenta\n\n### Removed\n\n- **Search Panel**: Removed redundant Search panel (previously key 7) \u2014 the `/` filter with multi-mode support serves the same purpose\n\n## [0.12.9] - 2026-02-28\n\n### Added\n\n- **Standalone Data Commands**: `sidekick tasks`, `sidekick decisions`, `sidekick notes`, `sidekick stats`, `sidekick handoff` for accessing project data without launching the TUI\n- **`sidekick search <query>`**: Cross-session full-text search from the terminal\n- **`sidekick context`**: Composite output of tasks, decisions, notes, and handoff for piping into other tools\n- **`--list` flag on `sidekick dump`**: Discover available session IDs before requiring `--session <id>`\n- **Search Panel**: Search panel (panel 7) wired into the TUI dashboard\n\n### Changed\n\n- **`taskMerger` utility**: Duplicate `mergeTasks` logic extracted into shared `taskMerger` utility\n- **Model constants**: Hardcoded model IDs extracted to named constants\n\n### Fixed\n\n- **`convention` icon**: Notes panel icon replaced with valid `tip` type\n- **Linux clipboard**: Now supports Wayland (`wl-copy`) and `xsel` fallbacks, with error messages instead of silent failure\n- **`provider.dispose()`**: Added to `dump` and `report` commands (prevents SQLite connection leaks)\n\n## [0.12.8] - 2026-02-28\n\n### Changed\n\n- **Dashboard UI/UX Polish**: Visual overhaul for better hierarchy, consistency, and readability\n - Splash screen and help overlay now display the robot ASCII logo\n - Toast notifications show severity icons (\u2718 error, \u26A0 warning, \u25CF info) with inner padding\n - Focused pane uses double-border for clear focus indication\n - Section dividers (`\u2500\u2500 Title \u2500\u2500\u2500\u2500`) replace bare bold headers in summary, agents, and context attribution\n - Tab bar: active tab underlined in magenta, inactive tabs dimmed, bracket syntax removed\n - Status bar: segmented layout with `\u2502` separators; keys bold, labels dim\n - Summary metrics condensed: elapsed/events/compactions on one line, tokens on one line with cache rate and cost\n - Sparklines display peak metadata annotations\n - Progress bars use blessed color tags for consistent coloring\n - Help overlay uses dot-leader alignment for all keybinding rows\n - Empty state hints per panel (e.g. "Tasks appear as your agent works.")\n - Session picker groups sessions by provider with section headers when multiple providers are present\n\n## [0.12.7] - 2026-02-27\n\n### Added\n\n- **HTML Session Report**: `sidekick report` command generates a self-contained HTML report and opens it in the default browser\n - Options: `--session`, `--output`, `--theme` (dark/light), `--no-open`, `--no-thinking`\n - TUI Dashboard: press `r` to generate and open an HTML report for the current session\n\n## [0.12.6] - 2026-02-26\n\n### Added\n\n- **Session Dump Command**: `sidekick dump` exports session data in text, markdown, or JSON format with `--format`, `--width`, and `--expand` options\n- **Plans Panel Re-enabled**: Plans panel restored in CLI dashboard with plan file discovery from `~/.claude/plans/`\n- **Enhanced Status Bar**: Session info display improved with richer metadata\n\n### Fixed\n\n- **Old snapshot format migration**: Restoring pre-0.12.3 session snapshots no longer shows empty timeline entries\n\n### Changed\n\n- **Phrase library moved to shared**: CLI-specific phrase formatting kept local, all phrase content now from `sidekick-shared`\n\n## [0.12.5] - 2026-02-24\n\n### Fixed\n\n- **Update check too slow to notice new versions**: Reduced npm registry cache TTL from 24 hours to 4 hours so upgrade notices appear sooner after a new release\n\n## [0.12.4] - 2026-02-24\n\n### Fixed\n\n- **Session crash on upgrade**: Fixed `d.timestamp.getTime is not a function` error when restoring tool call data from session snapshots \u2014 `Date` objects were serialized to strings by JSON but not rehydrated on restore, causing the session monitor to crash on first run after upgrading from 0.12.2 to 0.12.3\n\n## [0.12.3] - 2026-02-24\n\n### Added\n\n- **Latest-node indicator**: The most recently added node in tree and boxed mind map views is now marked with a yellow indicator\n- **Plan analytics in mind map**: Tree and boxed views now display plan progress and per-step metrics\n - Tree view: plan header shows completion stats; steps show complexity, duration, tokens, tool calls, and errors in metadata brackets\n - Box view: progress bar with completion percentage; steps show right-aligned metrics; subtitle shows step count and total duration\n- **Cross-provider plan extraction**: Shared `PlanExtractor` now handles Claude Code (EnterPlanMode/ExitPlanMode) and OpenCode (`<proposed_plan>` XML) plans \u2014 previously only Codex plans were shown\n- **Enriched plan data model**: Plan steps include duration, token count, tool call count, and error messages\n- **Phase-grouped plan display**: When a plan has phase structure, tree and boxed views group steps under phase headers with context lines from the original plan markdown\n- **Node type filter**: Press `f` on the Mind Map tab to cycle through node type filters (file, tool, task, subagent, command, plan, knowledge-note) \u2014 non-matching sections render dimmed in grey\n\n### Fixed\n\n- **Kanban board regression**: Subagent and plan-step tasks now correctly appear in the kanban board\n\n### Changed\n\n- **Plans panel temporarily disabled**: The Plans panel in the CLI dashboard is disabled until plan-mode event capture is reliably working end-to-end. Plan nodes in the mind map remain active.\n- `DashboardState` now delegates to shared `EventAggregator` instead of maintaining its own aggregation logic\n\n## [0.12.2] - 2026-02-23\n\n### Added\n\n- **Update notifications**: The dashboard now checks the npm registry for newer versions on startup and shows a yellow banner in the status bar when an update is available (e.g., `v0.13.0 available \u2014 npm i -g sidekick-agent-hub`). Results are cached for 24 hours to avoid repeated network requests.\n\n## [0.12.1] - 2026-02-23\n\n### Fixed\n\n- **VS Code integration**: Fixed exit code 127 when the extension launches the CLI dashboard on systems using nvm or volta (node binary not found when shell init is bypassed)\n\n## [0.12.0] - 2026-02-22\n\n### Added\n\n- **"Open CLI Dashboard" VS Code Integration**: New VS Code command `Sidekick: Open CLI Dashboard` launches the TUI dashboard in an integrated terminal\n - Install the CLI with `npm install -g sidekick-agent-hub`\n\n## [0.11.0] - 2026-02-19\n\n### Added\n\n- **Initial Release**: Full-screen TUI dashboard for monitoring agent sessions from the terminal\n - Ink-based terminal UI with panels for sessions, tasks, kanban, mind map, notes, decisions, search, files, and git diff\n - Multi-provider support: auto-detects Claude Code, OpenCode, and Codex sessions\n - Reads from `~/.config/sidekick/` \u2014 the same data files the VS Code extension writes\n - Usage: `sidekick dashboard [--project <path>] [--provider <id>]`\n';
79914
+ CHANGELOG_default = '# Changelog\n\nAll notable changes to the Sidekick Agent Hub CLI will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [0.18.0] - 2026-05-08\n\n### Changed\n\n- **Bundled `sidekick-shared` 0.18.0**: Picks up the new provider-aware quota orchestration surface \u2014 `MultiProviderQuotaService`, `CodexQuotaWatcher`, `getActiveAccountStatus()`, `extractToolCall()`, cost-provenance helpers (`calculateCostWithProvenance`, `mergeCostSources`), and model display helpers (`shortModelName`, `getModelDisplayInfo`, `compareModelIds`, `sortModelIds`). `parseModelId()` also now recognizes legacy Claude IDs such as `claude-3-opus-20240229` and `claude-3-5-sonnet-20241022`\n- **No CLI runtime changes**: This release ships the shared library upgrade for downstream tooling alignment; `sidekick quota`, `sidekick status`, and the live dashboard keep using the existing polling path. Wiring the new orchestrator into the CLI will land in a follow-up release\n\n## [0.17.7] - 2026-04-28\n\n### Fixed\n\n- **Quota snapshot write race**: Updated the bundled `sidekick-shared` snapshot writer so concurrent `sidekick quota` / Codex session updates no longer collide on `quota-snapshots.json.tmp` or throw `ENOENT`. Failed writes now also clean up their partial temp files instead of leaving orphans in `~/.config/sidekick/`\n\n## [0.17.6] - 2026-04-19\n\n### Added\n\n- **`sidekick peak` command**: One-shot check for Claude\'s current peak-hours state \u2014 weekdays 13:00\u201319:00 UTC, when session limits drain faster on Free/Pro/Max/Team subscriptions. Prints a color-coded status block with a countdown to the next transition. Data comes from the public `promoclock.co/api/status` endpoint (third-party, unaffiliated with Anthropic) with a graceful fallback when unreachable. `--json` emits the full raw state\n- **Peak-hours block in `sidekick status`**: When the active provider is `claude-code`, the Claude + OpenAI health blocks are now followed by a **Claude Peak Hours** block (off-peak or in-peak, with countdown). Gated on the provider so OpenCode / Codex users don\'t trigger an unnecessary third-party fetch. `--json` output includes the new `peak` field\n- **Peak-hours summary in `sidekick quota`**: Claude subscription quota output now shows a **Peak** line under the 5-hour / 7-day bars \u2014 green dot off-peak, orange dot during an active peak, with a countdown to the next transition. `--json` output includes the new `peak` field\n\n## [0.17.5] - 2026-04-18\n\n### Added\n\n- **Default account bootstrap at CLI startup**: The CLI now calls `ensureDefaultAccounts()` from `sidekick-shared` at module load and awaits the result inside a Commander `preAction` hook, so the first real subcommand blocks briefly on the bootstrap while `--version` and `--help` stay instant. When a system Claude Code or Codex credential exists and no saved account is active for that provider yet, the CLI registers it as "Default" \u2014 `sidekick quota`, `sidekick account`, and `sidekick stats` now reflect the active account on first run without requiring an explicit `sidekick account --add` first. Idempotent, never overwrites manually saved accounts, and all errors are swallowed so startup is never blocked\n\nThanks to [@B33pBeeps](https://github.com/B33pBeeps) (Juan Fourie) for contributing this feature in [#16](https://github.com/cesarandreslopez/sidekick-agent-hub/pull/16).\n\n## [0.17.4] - 2026-04-17\n\n### Changed\n\n- **Pricing hydration import migrated to `sidekick-shared/node`**: `cli.ts` now imports `hydratePricingCatalog` from the new Node-only subpath and keeps `detectProvider` on the package root. Runtime behavior is unchanged; the split makes the CLI\'s import surface self-documenting (hydration is explicitly a Node API) and aligns the CLI with the shared library\'s new versioned public API contract\n\n## [0.17.3] - 2026-04-17\n\n### Changed\n\n- **Version sync with the VS Code extension**: Republished to keep CLI, extension, and shared-library versions aligned after a cosmetic changelog fix in 0.17.3. No CLI code changes \u2014 functionally identical to 0.17.2\n\n## [0.17.2] - 2026-04-17\n\n### Added\n\n- **LiteLLM pricing hydration on startup**: The CLI now fetches the LiteLLM pricing catalog on startup and caches to `~/.config/sidekick/pricing-catalog.json` with a 24-hour TTL, 3s timeout, and stale-cache fallback \u2014 new model prices are picked up without a CLI upgrade\n- **Expanded pricing coverage**: GPT-4o, GPT-4.1, GPT-5.x, o1, o3, and o3-mini families are now priced alongside the existing Claude entries\n- **Real-dollar Codex / Claude Code costs**: `EventAggregator` computes cost from the pricing table when the session provider doesn\'t report one, so `sidekick` live dashboards now show actual dollars for Codex and Claude Code sessions\n- **`stats` footer lists unpriced models**: `sidekick stats` prints any models encountered with no pricing entry so missing coverage is visible\n\n### Fixed\n\n- **Context-gauge % wrong for Opus 4.7 (1M) and other new models**: The dashboard\'s context gauge was dividing by 200K for Claude Opus 4.7 (native 1M), inflating the displayed %. The shared model \u2192 context-window map now includes Opus/Sonnet 4.7 (1M), GPT-5.4 (1.05M), GPT-5.3-Codex (400K), and GPT-5.3-Codex-Spark (128K). Claude Code\'s `[1m]` suffix is now also honored as an explicit 1M marker\n- **Silent Sonnet-priced fallback for unknown models**: Codex, GPT-5.x, and o-series rows were being rendered at Sonnet rates. Unknown-model rows now render as `\u2014` in yellow instead of inventing a dollar figure\n\n### Changed\n\n- **`historical-data.json` schema v2**: reads `priced` flag and `unpricedModelIds` from records written by the latest VS Code extension; v1 records still read correctly\n\n## [0.17.1] - 2026-04-13\n\n### Fixed\n\n- **Codex multi-home session discovery**: Provider detection now scans all candidate Codex home directories, fixing missed sessions when the managed profile home is empty but the system `~/.codex/` has activity\n\n## [0.17.0] - 2026-04-13\n\n### Added\n\n- **Multi-provider account management**: `sidekick account` now supports `--provider codex` for Codex profile management alongside Claude Code accounts\n- **Codex account lifecycle**: `--add` prepares a profile and spawns `codex login`; `--switch-to` and `--remove` accept email, label, or profile ID\n- **Quota snapshot fallback**: `sidekick quota` for Codex shows cached rate-limit snapshots when no active session exists, with "cached from" timestamp\n\n### Fixed\n\n- **Email normalization**: Claude account lookup normalizes email case for reliable matching\n\n## [0.16.1] - 2026-03-27\n\n### Fixed\n\n- **Dashboard provider status scoping**: The TUI now shows degraded-service notices only for the monitored provider \u2014 Claude for Claude Code sessions, OpenAI for Codex sessions, and no status banner for OpenCode\n\n## [0.16.0] - 2026-03-23\n\n### Changed\n\n- **Consistent cost formatting**: All cost displays (`stats`, `context`, Sessions panel, narrative prompt) now use shared `formatCost()` with intelligent decimal precision (4 places for < $0.01, 2 otherwise)\n- **QuotaService**: Rewritten to wrap shared `QuotaPoller` with exponential backoff instead of manual polling loop\n- **modelContext**: Now re-exports `getModelInfo` from shared library alongside `getContextWindowSize`\n\n## [0.15.2] - 2026-03-18\n\n### Fixed\n\n- **CLI help descriptions**: Updated `quota` and `status` command descriptions to reflect provider-aware behavior\n- **`sidekick quota --provider`**: Added local `--provider` option so `sidekick quota --provider codex` works naturally\n\n## [0.15.0] - 2026-03-18\n\n### Added\n\n- **OpenAI status page monitoring**: CLI dashboard now shows OpenAI API status alongside Claude API status\n- **Codex rate limits in dashboard**: Sessions panel displays Codex rate-limit data with "Rate Limits" header instead of "Quota"\n- **Provider-aware `sidekick quota` command**: Detects active provider and shows Codex rate limits, Claude subscription quota, or an informational message for OpenCode\n\n### Fixed\n\n- **QuotaService polling for Codex**: Dashboard no longer starts Claude OAuth quota polling when the active provider is Codex\n\n## [0.14.2] - 2026-03-16\n\n### Fixed\n\n- **Quota polling interval**: Reduced quota refresh from every 30 seconds to every 5 minutes to avoid unnecessary API calls\n- **SessionsPanel `detailWidth()` call**: Removed unused parameter from `detailWidth()` in the Sessions panel quota rendering\n\n## [0.14.1] - 2026-03-14\n\n### Fixed\n\n- **Per-model context window sizes**: Dashboard context gauge now shows correct utilization for Claude Opus 4.6 (1M context) and other models with non-200K windows\n\n### Changed\n\n- **Shared model context lookup**: CLI dashboard now uses the centralized `getModelContextWindowSize()` from `sidekick-shared` instead of a local duplicate map\n\n## [0.14.0] - 2026-03-12\n\n### Added\n\n- **`sidekick account` Command**: Manage Claude Code accounts from the terminal \u2014 list saved accounts, add the current account with an optional label, switch to the next or a specific account, and remove accounts. Supports `--json` output for scripting\n- **Quota Account Label**: `sidekick quota` now shows the active account email and label above the quota bars when multi-account is enabled\n- **macOS Keychain Support**: `sidekick account` and `sidekick quota` now read and write credentials via the system Keychain on macOS, fixing account switching and quota checks on Mac\n\n## [0.13.8] - 2026-03-12\n\n### Changed\n\n- **Structured quota failure output**: `sidekick quota` now renders consistent auth, rate-limit, server, network, and unexpected-failure copy from shared quota failure descriptors while preserving `--json` machine-readable output\n- **Dashboard unavailable quota rendering**: The Sessions panel now shows Claude Code quota failures inline instead of hiding the quota section whenever subscription data is unavailable\n- **Quota transition toasts**: The Ink dashboard now fires low-noise toast notifications only when Claude Code quota failure state changes, avoiding repeated alerts every polling interval\n\n## [0.13.7] - 2026-03-11\n\n### Changed\n\n- **npm README sync**: Updated the published CLI package README to reflect current OpenCode monitoring behavior, platform-specific data directories, and the `sqlite3` runtime requirement\n- **README badge cleanup**: Removed the Ask DeepWiki badge from the published CLI package README; the repo root README still keeps it\n\n## [0.13.6] - 2026-03-11\n\n### Changed\n\n- **Refreshed CLI Dashboard Wordmark**: Updated the dashboard wordmark/header styling for a cleaner splash and dashboard identity\n\n### Fixed\n\n- **OpenCode dashboard startup**: OpenCode DB-backed session discovery now resolves projects by worktree, sandboxes, and session directory instead of quietly behaving like no session exists\n- **OpenCode runtime notices**: The CLI now prints an OpenCode-only actionable notice when `opencode.db` exists but `sqlite3` is missing, blocked, or otherwise unusable in the current shell environment\n\n## [0.13.5] - 2026-03-10\n\n### Added\n\n- **`sidekick status` Command**: One-shot Claude API status check with color-coded text output and `--json` mode\n- **Dashboard Status Banner**: Status bar shows a colored `\u25CF API minor/major/critical` indicator when Claude is degraded; Sessions panel Summary tab shows an "API Status" section with affected components and active incident details. Polls every 60s\n\n## [0.13.4] - 2026-03-08\n\n### Fixed\n\n- **Onboarding Phrase Spam**: Splash screen and detail pane motivational phrases memoized \u2014 no longer flicker every render tick (fixes [#13](https://github.com/cesarandreslopez/sidekick-agent-hub/issues/13))\n\n### Changed\n\n- **Simplified Logo**: Replaced 6-line ASCII robot art with compact text header in splash, help, and changelog overlays\n- **Removed Dead Code**: Removed unused `getSplashContent()` and `HELP_HEADER` exports from branding module\n\n## [0.13.3] - 2026-03-04\n\n_No CLI-specific changes in this release._\n\n## [0.13.2] - 2026-03-04\n\n_No CLI-specific changes in this release._\n\n## [0.13.1] - 2026-03-04\n\n### Added\n\n- **`sidekick quota` Command**: One-shot subscription quota check showing 5-hour and 7-day utilization with color-coded progress bars and reset countdowns \u2014 supports `--json` for machine-readable output\n- **Quota Projections**: Elapsed-time projections shown in `sidekick quota` output and TUI dashboard quota section \u2014 displays projected end-of-window utilization next to current value (e.g., `40% \u2192 100%`), included in `--json` output as `projectedFiveHour` / `projectedSevenDay`\n\n## [0.13.0] - 2026-03-03\n\n_No CLI-specific changes in this release._\n\n## [0.12.10] - 2026-03-01\n\n### Added\n\n- **Events Panel** (key 7): Scrollable live event stream with colored type badges (`[USR]`, `[AST]`, `[TOOL]`, `[RES]`), timestamps, and keyword-highlighted summaries; detail tabs for full event JSON and surrounding context\n- **Charts Panel** (key 8): Tool frequency horizontal bars, event type distribution, 60-minute activity heatmap using `\u2591\u2592\u2593\u2588` intensity characters, and pattern analysis with frequency bars and template text\n- **Multi-Mode Filter**: `/` filter overlay now supports four modes \u2014 substring, fuzzy, regex, and date range \u2014 Tab cycles modes, regex mode shows red validation errors\n- **Search Term Highlighting**: Active filter terms highlighted in blue within side list items\n- **Timeline Keyword Coloring**: Event summaries in the Sessions panel Timeline tab now use semantic keyword coloring \u2014 errors red, success green, tool names cyan, file paths magenta\n\n### Removed\n\n- **Search Panel**: Removed redundant Search panel (previously key 7) \u2014 the `/` filter with multi-mode support serves the same purpose\n\n## [0.12.9] - 2026-02-28\n\n### Added\n\n- **Standalone Data Commands**: `sidekick tasks`, `sidekick decisions`, `sidekick notes`, `sidekick stats`, `sidekick handoff` for accessing project data without launching the TUI\n- **`sidekick search <query>`**: Cross-session full-text search from the terminal\n- **`sidekick context`**: Composite output of tasks, decisions, notes, and handoff for piping into other tools\n- **`--list` flag on `sidekick dump`**: Discover available session IDs before requiring `--session <id>`\n- **Search Panel**: Search panel (panel 7) wired into the TUI dashboard\n\n### Changed\n\n- **`taskMerger` utility**: Duplicate `mergeTasks` logic extracted into shared `taskMerger` utility\n- **Model constants**: Hardcoded model IDs extracted to named constants\n\n### Fixed\n\n- **`convention` icon**: Notes panel icon replaced with valid `tip` type\n- **Linux clipboard**: Now supports Wayland (`wl-copy`) and `xsel` fallbacks, with error messages instead of silent failure\n- **`provider.dispose()`**: Added to `dump` and `report` commands (prevents SQLite connection leaks)\n\n## [0.12.8] - 2026-02-28\n\n### Changed\n\n- **Dashboard UI/UX Polish**: Visual overhaul for better hierarchy, consistency, and readability\n - Splash screen and help overlay now display the robot ASCII logo\n - Toast notifications show severity icons (\u2718 error, \u26A0 warning, \u25CF info) with inner padding\n - Focused pane uses double-border for clear focus indication\n - Section dividers (`\u2500\u2500 Title \u2500\u2500\u2500\u2500`) replace bare bold headers in summary, agents, and context attribution\n - Tab bar: active tab underlined in magenta, inactive tabs dimmed, bracket syntax removed\n - Status bar: segmented layout with `\u2502` separators; keys bold, labels dim\n - Summary metrics condensed: elapsed/events/compactions on one line, tokens on one line with cache rate and cost\n - Sparklines display peak metadata annotations\n - Progress bars use blessed color tags for consistent coloring\n - Help overlay uses dot-leader alignment for all keybinding rows\n - Empty state hints per panel (e.g. "Tasks appear as your agent works.")\n - Session picker groups sessions by provider with section headers when multiple providers are present\n\n## [0.12.7] - 2026-02-27\n\n### Added\n\n- **HTML Session Report**: `sidekick report` command generates a self-contained HTML report and opens it in the default browser\n - Options: `--session`, `--output`, `--theme` (dark/light), `--no-open`, `--no-thinking`\n - TUI Dashboard: press `r` to generate and open an HTML report for the current session\n\n## [0.12.6] - 2026-02-26\n\n### Added\n\n- **Session Dump Command**: `sidekick dump` exports session data in text, markdown, or JSON format with `--format`, `--width`, and `--expand` options\n- **Plans Panel Re-enabled**: Plans panel restored in CLI dashboard with plan file discovery from `~/.claude/plans/`\n- **Enhanced Status Bar**: Session info display improved with richer metadata\n\n### Fixed\n\n- **Old snapshot format migration**: Restoring pre-0.12.3 session snapshots no longer shows empty timeline entries\n\n### Changed\n\n- **Phrase library moved to shared**: CLI-specific phrase formatting kept local, all phrase content now from `sidekick-shared`\n\n## [0.12.5] - 2026-02-24\n\n### Fixed\n\n- **Update check too slow to notice new versions**: Reduced npm registry cache TTL from 24 hours to 4 hours so upgrade notices appear sooner after a new release\n\n## [0.12.4] - 2026-02-24\n\n### Fixed\n\n- **Session crash on upgrade**: Fixed `d.timestamp.getTime is not a function` error when restoring tool call data from session snapshots \u2014 `Date` objects were serialized to strings by JSON but not rehydrated on restore, causing the session monitor to crash on first run after upgrading from 0.12.2 to 0.12.3\n\n## [0.12.3] - 2026-02-24\n\n### Added\n\n- **Latest-node indicator**: The most recently added node in tree and boxed mind map views is now marked with a yellow indicator\n- **Plan analytics in mind map**: Tree and boxed views now display plan progress and per-step metrics\n - Tree view: plan header shows completion stats; steps show complexity, duration, tokens, tool calls, and errors in metadata brackets\n - Box view: progress bar with completion percentage; steps show right-aligned metrics; subtitle shows step count and total duration\n- **Cross-provider plan extraction**: Shared `PlanExtractor` now handles Claude Code (EnterPlanMode/ExitPlanMode) and OpenCode (`<proposed_plan>` XML) plans \u2014 previously only Codex plans were shown\n- **Enriched plan data model**: Plan steps include duration, token count, tool call count, and error messages\n- **Phase-grouped plan display**: When a plan has phase structure, tree and boxed views group steps under phase headers with context lines from the original plan markdown\n- **Node type filter**: Press `f` on the Mind Map tab to cycle through node type filters (file, tool, task, subagent, command, plan, knowledge-note) \u2014 non-matching sections render dimmed in grey\n\n### Fixed\n\n- **Kanban board regression**: Subagent and plan-step tasks now correctly appear in the kanban board\n\n### Changed\n\n- **Plans panel temporarily disabled**: The Plans panel in the CLI dashboard is disabled until plan-mode event capture is reliably working end-to-end. Plan nodes in the mind map remain active.\n- `DashboardState` now delegates to shared `EventAggregator` instead of maintaining its own aggregation logic\n\n## [0.12.2] - 2026-02-23\n\n### Added\n\n- **Update notifications**: The dashboard now checks the npm registry for newer versions on startup and shows a yellow banner in the status bar when an update is available (e.g., `v0.13.0 available \u2014 npm i -g sidekick-agent-hub`). Results are cached for 24 hours to avoid repeated network requests.\n\n## [0.12.1] - 2026-02-23\n\n### Fixed\n\n- **VS Code integration**: Fixed exit code 127 when the extension launches the CLI dashboard on systems using nvm or volta (node binary not found when shell init is bypassed)\n\n## [0.12.0] - 2026-02-22\n\n### Added\n\n- **"Open CLI Dashboard" VS Code Integration**: New VS Code command `Sidekick: Open CLI Dashboard` launches the TUI dashboard in an integrated terminal\n - Install the CLI with `npm install -g sidekick-agent-hub`\n\n## [0.11.0] - 2026-02-19\n\n### Added\n\n- **Initial Release**: Full-screen TUI dashboard for monitoring agent sessions from the terminal\n - Ink-based terminal UI with panels for sessions, tasks, kanban, mind map, notes, decisions, search, files, and git diff\n - Multi-provider support: auto-detects Claude Code, OpenCode, and Codex sessions\n - Reads from `~/.config/sidekick/` \u2014 the same data files the VS Code extension writes\n - Usage: `sidekick dashboard [--project <path>] [--provider <id>]`\n';
79124
79915
  }
79125
79916
  });
79126
79917
 
@@ -81977,7 +82768,7 @@ var init_cli = __esm({
81977
82768
  defaultAccountsReady = (0, import_sidekick_shared33.ensureDefaultAccounts)().catch(() => {
81978
82769
  });
81979
82770
  program2 = new Command();
81980
- program2.name("sidekick").description("Query Sidekick project intelligence from the command line").version("0.17.6").option("--json", "Output as JSON").option("--project <path>", "Override project path (default: cwd)").option("--provider <id>", "Provider: claude-code, opencode, codex, auto (default: auto)");
82771
+ program2.name("sidekick").description("Query Sidekick project intelligence from the command line").version("0.18.0").option("--json", "Output as JSON").option("--project <path>", "Override project path (default: cwd)").option("--provider <id>", "Provider: claude-code, opencode, codex, auto (default: auto)");
81981
82772
  program2.hook("preAction", async () => {
81982
82773
  await defaultAccountsReady;
81983
82774
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sidekick-agent-hub",
3
- "version": "0.17.6",
3
+ "version": "0.18.0",
4
4
  "description": "Terminal dashboard for monitoring AI coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {