sidekick-agent-hub 0.19.3 → 0.20.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.
@@ -22349,6 +22349,602 @@ var require_toolCall = __commonJS({
22349
22349
  }
22350
22350
  });
22351
22351
 
22352
+ // ../sidekick-shared/dist/extractors/sessionAssets.js
22353
+ var require_sessionAssets = __commonJS({
22354
+ "../sidekick-shared/dist/extractors/sessionAssets.js"(exports) {
22355
+ "use strict";
22356
+ var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
22357
+ if (k2 === void 0) k2 = k;
22358
+ var desc = Object.getOwnPropertyDescriptor(m, k);
22359
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22360
+ desc = { enumerable: true, get: function() {
22361
+ return m[k];
22362
+ } };
22363
+ }
22364
+ Object.defineProperty(o, k2, desc);
22365
+ } : function(o, m, k, k2) {
22366
+ if (k2 === void 0) k2 = k;
22367
+ o[k2] = m[k];
22368
+ });
22369
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
22370
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22371
+ } : function(o, v) {
22372
+ o["default"] = v;
22373
+ });
22374
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
22375
+ var ownKeys = function(o) {
22376
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
22377
+ var ar = [];
22378
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
22379
+ return ar;
22380
+ };
22381
+ return ownKeys(o);
22382
+ };
22383
+ return function(mod) {
22384
+ if (mod && mod.__esModule) return mod;
22385
+ var result = {};
22386
+ if (mod != null) {
22387
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
22388
+ }
22389
+ __setModuleDefault(result, mod);
22390
+ return result;
22391
+ };
22392
+ }();
22393
+ Object.defineProperty(exports, "__esModule", { value: true });
22394
+ exports.DEFAULT_CAPS = void 0;
22395
+ exports.extractUrls = extractUrls;
22396
+ exports.extractFilePaths = extractFilePaths;
22397
+ exports.extractCommands = extractCommands;
22398
+ exports.urlAsset = urlAsset;
22399
+ exports.commandAsset = commandAsset;
22400
+ exports.pathAsset = pathAsset;
22401
+ exports.planAsset = planAsset;
22402
+ exports.flat = flat;
22403
+ exports.planTitle = planTitle;
22404
+ exports.isExistingFile = isExistingFile;
22405
+ exports.readPlanFile = readPlanFile;
22406
+ exports.byTimestampDesc = byTimestampDesc;
22407
+ exports.dedupeAssets = dedupeAssets;
22408
+ exports.dedupePlansByTitle = dedupePlansByTitle;
22409
+ exports.capped = capped;
22410
+ var fs9 = __importStar(__require("node:fs"));
22411
+ var os6 = __importStar(__require("node:os"));
22412
+ var node_path_1 = __require("node:path");
22413
+ var URL_RE = /(?:https?|file):\/\/[^\s"'<>)\]`\\]+/g;
22414
+ var PATH_RE = /(?:~|\.{0,2}\/)?[A-Za-z0-9_@.-]*(?:\/[A-Za-z0-9_@.-]+)+(?::\d+){0,2}|[A-Za-z0-9_@-]+\.[A-Za-z0-9]{1,8}(?::\d+){0,2}/g;
22415
+ var SHELL_TAGS = /* @__PURE__ */ new Set(["sh", "bash", "shell", "zsh", "console", "shellscript", "shell-session"]);
22416
+ exports.DEFAULT_CAPS = {
22417
+ command: 60,
22418
+ path: 60,
22419
+ url: 40,
22420
+ plan: 25
22421
+ };
22422
+ function extractUrls(text) {
22423
+ if (!text)
22424
+ return [];
22425
+ const urls = [];
22426
+ for (const match of String(text).matchAll(URL_RE)) {
22427
+ urls.push(match[0].replace(/[.,;:!?'"`]+$/, ""));
22428
+ }
22429
+ return urls;
22430
+ }
22431
+ function extractFilePaths(text, cwd2) {
22432
+ if (!text)
22433
+ return [];
22434
+ const home = os6.homedir();
22435
+ const base = cwd2 ? (0, node_path_1.resolve)(cwd2) : process.cwd();
22436
+ const paths = [];
22437
+ for (const match of String(text).matchAll(PATH_RE)) {
22438
+ let token = match[0].replace(/[.,;:)>"]+$/, "");
22439
+ let line;
22440
+ const lineMatch = /^(.+?):(\d+)(?::\d+)?$/.exec(token);
22441
+ if (lineMatch) {
22442
+ token = lineMatch[1];
22443
+ line = Number(lineMatch[2]);
22444
+ }
22445
+ let file = token.startsWith("~") ? token.replace(/^~/, home) : token;
22446
+ if (!(0, node_path_1.isAbsolute)(file))
22447
+ file = (0, node_path_1.resolve)(base, file);
22448
+ if (isExistingFile(file)) {
22449
+ paths.push(line !== void 0 ? { file, line } : { file });
22450
+ }
22451
+ }
22452
+ return paths;
22453
+ }
22454
+ function extractCommands(text) {
22455
+ if (!text)
22456
+ return [];
22457
+ const commands = [];
22458
+ const source = String(text);
22459
+ const fence = /```([\w.-]*)[ \t]*\r?\n([\s\S]*?)```/g;
22460
+ const fencedSpans = [];
22461
+ let match;
22462
+ while (match = fence.exec(source)) {
22463
+ fencedSpans.push([match.index, fence.lastIndex]);
22464
+ const shellTagged = SHELL_TAGS.has(match[1].toLowerCase());
22465
+ let continuation = "";
22466
+ for (const rawLine of match[2].split("\n")) {
22467
+ const promptLine = /^\s*\$\s+(.+)$/.exec(rawLine);
22468
+ if (promptLine) {
22469
+ commands.push(promptLine[1].trim());
22470
+ continue;
22471
+ }
22472
+ if (!shellTagged)
22473
+ continue;
22474
+ let line = rawLine.trim();
22475
+ if (!line || line.startsWith("#"))
22476
+ continue;
22477
+ if (continuation) {
22478
+ line = `${continuation} ${line}`;
22479
+ continuation = "";
22480
+ }
22481
+ if (line.endsWith("\\")) {
22482
+ continuation = line.slice(0, -1).trim();
22483
+ continue;
22484
+ }
22485
+ commands.push(line);
22486
+ }
22487
+ }
22488
+ for (const lineMatch of source.matchAll(/^[ \t]*\$[ \t]+(.+)$/gm)) {
22489
+ const index = lineMatch.index ?? 0;
22490
+ if (fencedSpans.some(([start, end]) => index >= start && index < end))
22491
+ continue;
22492
+ commands.push(lineMatch[1].trim());
22493
+ }
22494
+ return commands;
22495
+ }
22496
+ function urlAsset(url, timestamp, provenance = {}) {
22497
+ return { type: "url", text: url, display: url, timestamp, ...provenance };
22498
+ }
22499
+ function commandAsset(command, timestamp, provenance = {}) {
22500
+ return { type: "command", text: command, display: flat(command), timestamp, ...provenance };
22501
+ }
22502
+ function pathAsset(path8, timestamp, provenance = {}) {
22503
+ const text = path8.line !== void 0 ? `${path8.file}:${path8.line}` : path8.file;
22504
+ return { type: "path", text, display: text, timestamp, ...provenance };
22505
+ }
22506
+ function planAsset(markdown, timestamp, provenance = {}) {
22507
+ return { type: "plan", text: markdown, display: planTitle(markdown), timestamp, ...provenance };
22508
+ }
22509
+ function flat(value) {
22510
+ return value.replace(/\s*\n\s*/g, " ").trim();
22511
+ }
22512
+ function planTitle(markdown) {
22513
+ for (const rawLine of markdown.split("\n")) {
22514
+ const line = rawLine.trim();
22515
+ if (line)
22516
+ return line.replace(/^#{1,6}\s*/, "");
22517
+ }
22518
+ return "Plan";
22519
+ }
22520
+ function isExistingFile(filePath) {
22521
+ try {
22522
+ return fs9.statSync(filePath).isFile();
22523
+ } catch {
22524
+ return false;
22525
+ }
22526
+ }
22527
+ function readPlanFile(filePath) {
22528
+ if (!filePath)
22529
+ return void 0;
22530
+ try {
22531
+ return fs9.readFileSync(filePath, "utf8");
22532
+ } catch {
22533
+ return void 0;
22534
+ }
22535
+ }
22536
+ function byTimestampDesc(a, b) {
22537
+ return (b.timestamp || "").localeCompare(a.timestamp || "");
22538
+ }
22539
+ function dedupeAssets(assets) {
22540
+ const seen = /* @__PURE__ */ new Set();
22541
+ const deduped = [];
22542
+ for (const asset of assets) {
22543
+ if (!asset.text)
22544
+ continue;
22545
+ const key = `${asset.type}\0${asset.text}`;
22546
+ if (seen.has(key))
22547
+ continue;
22548
+ seen.add(key);
22549
+ deduped.push(asset);
22550
+ }
22551
+ return deduped;
22552
+ }
22553
+ function dedupePlansByTitle(plans) {
22554
+ const seen = /* @__PURE__ */ new Set();
22555
+ const deduped = [];
22556
+ for (const plan of plans) {
22557
+ const key = (plan.display || plan.text).trim().toLowerCase();
22558
+ if (!key || seen.has(key))
22559
+ continue;
22560
+ seen.add(key);
22561
+ deduped.push(plan);
22562
+ }
22563
+ return deduped;
22564
+ }
22565
+ function capped(assets, cap) {
22566
+ return cap > 0 ? assets.slice(0, cap) : assets;
22567
+ }
22568
+ }
22569
+ });
22570
+
22571
+ // ../sidekick-shared/dist/extractors/sources/claudeAssets.js
22572
+ var require_claudeAssets = __commonJS({
22573
+ "../sidekick-shared/dist/extractors/sources/claudeAssets.js"(exports) {
22574
+ "use strict";
22575
+ Object.defineProperty(exports, "__esModule", { value: true });
22576
+ exports.claudeSessions = claudeSessions;
22577
+ exports.readClaudeAssets = readClaudeAssets;
22578
+ var node_path_1 = __require("node:path");
22579
+ var node_fs_1 = __require("node:fs");
22580
+ var sessionPathResolver_1 = require_sessionPathResolver();
22581
+ var sessionAssets_1 = require_sessionAssets();
22582
+ var PATH_TOOLS = /* @__PURE__ */ new Set(["Read", "Write", "Edit", "NotebookEdit"]);
22583
+ function dirExists(path8) {
22584
+ try {
22585
+ return (0, node_fs_1.statSync)(path8).isDirectory();
22586
+ } catch {
22587
+ return false;
22588
+ }
22589
+ }
22590
+ function filesByMtimeDesc(dir, filter) {
22591
+ let names;
22592
+ try {
22593
+ names = (0, node_fs_1.readdirSync)(dir);
22594
+ } catch {
22595
+ return [];
22596
+ }
22597
+ return names.filter(filter).map((name) => (0, node_path_1.join)(dir, name)).map((filePath) => {
22598
+ try {
22599
+ return { path: filePath, mtime: (0, node_fs_1.statSync)(filePath).mtimeMs };
22600
+ } catch {
22601
+ return null;
22602
+ }
22603
+ }).filter((entry) => entry !== null).sort((a, b) => b.mtime - a.mtime).map((entry) => entry.path);
22604
+ }
22605
+ function readJsonl(filePath) {
22606
+ let raw;
22607
+ try {
22608
+ raw = (0, node_fs_1.readFileSync)(filePath, "utf8");
22609
+ } catch {
22610
+ return [];
22611
+ }
22612
+ const lines = [];
22613
+ for (const line of raw.split("\n")) {
22614
+ const trimmed = line.trim();
22615
+ if (!trimmed)
22616
+ continue;
22617
+ try {
22618
+ lines.push(JSON.parse(trimmed));
22619
+ } catch {
22620
+ }
22621
+ }
22622
+ return lines;
22623
+ }
22624
+ function claudeSessions(cwd2, limit = 3) {
22625
+ const dir = (0, sessionPathResolver_1.getSessionDirectory)(cwd2);
22626
+ if (!dirExists(dir))
22627
+ return [];
22628
+ return filesByMtimeDesc(dir, (name) => name.endsWith(".jsonl")).slice(0, limit);
22629
+ }
22630
+ function asString(value) {
22631
+ return typeof value === "string" ? value : void 0;
22632
+ }
22633
+ function provenance(sessionPath, source) {
22634
+ return { agent: "claude", sessionPath, source };
22635
+ }
22636
+ function addTextAssets(acc, text, cwd2, timestamp, meta) {
22637
+ for (const url of (0, sessionAssets_1.extractUrls)(text))
22638
+ acc.urls.push((0, sessionAssets_1.urlAsset)(url, timestamp, meta));
22639
+ for (const filePath of (0, sessionAssets_1.extractFilePaths)(text, cwd2))
22640
+ acc.paths.push((0, sessionAssets_1.pathAsset)(filePath, timestamp, meta));
22641
+ for (const command of (0, sessionAssets_1.extractCommands)(text))
22642
+ acc.commands.push((0, sessionAssets_1.commandAsset)(command, timestamp, meta));
22643
+ }
22644
+ function addToolPathAssets(acc, value, cwd2, timestamp, meta) {
22645
+ for (const filePath of (0, sessionAssets_1.extractFilePaths)(value, cwd2)) {
22646
+ acc.paths.push((0, sessionAssets_1.pathAsset)(filePath, timestamp, meta));
22647
+ }
22648
+ }
22649
+ function accumClaude(filePath, cwd2, acc) {
22650
+ for (const line of readJsonl(filePath)) {
22651
+ const timestamp = asString(line.timestamp);
22652
+ if (line.type === "attachment") {
22653
+ const attachment = line.attachment;
22654
+ const planFilePath = asString(attachment?.planFilePath);
22655
+ if (planFilePath && (0, sessionAssets_1.isExistingFile)(planFilePath)) {
22656
+ const text = (0, sessionAssets_1.readPlanFile)(planFilePath);
22657
+ if (text?.trim())
22658
+ acc.plans.push((0, sessionAssets_1.planAsset)(text, timestamp, provenance(filePath, "attachment:plan")));
22659
+ }
22660
+ continue;
22661
+ }
22662
+ const message = line.message;
22663
+ const content = message?.content;
22664
+ if (typeof content === "string") {
22665
+ addTextAssets(acc, content, cwd2, timestamp, provenance(filePath, "message"));
22666
+ continue;
22667
+ }
22668
+ if (!Array.isArray(content))
22669
+ continue;
22670
+ for (const block of content) {
22671
+ const typedBlock = block;
22672
+ if (typedBlock.type === "text") {
22673
+ addTextAssets(acc, typedBlock.text, cwd2, timestamp, provenance(filePath, "message"));
22674
+ continue;
22675
+ }
22676
+ if (typedBlock.type !== "tool_use")
22677
+ continue;
22678
+ const name = asString(typedBlock.name);
22679
+ const input = typedBlock.input || {};
22680
+ const toolSource = name ? `tool:${name}` : "tool";
22681
+ const toolMeta = provenance(filePath, toolSource);
22682
+ if (name === "Bash") {
22683
+ for (const url of (0, sessionAssets_1.extractUrls)(input.command))
22684
+ acc.urls.push((0, sessionAssets_1.urlAsset)(url, timestamp, toolMeta));
22685
+ addToolPathAssets(acc, input.command, cwd2, timestamp, toolMeta);
22686
+ } else if (name && PATH_TOOLS.has(name)) {
22687
+ addToolPathAssets(acc, input.file_path, cwd2, timestamp, toolMeta);
22688
+ } else if (name === "WebFetch" || name === "WebSearch") {
22689
+ for (const url of (0, sessionAssets_1.extractUrls)(JSON.stringify(input)))
22690
+ acc.urls.push((0, sessionAssets_1.urlAsset)(url, timestamp, toolMeta));
22691
+ } else if (name === "ExitPlanMode") {
22692
+ const inline = asString(input.plan);
22693
+ const markdown = inline?.trim() ? inline : (0, sessionAssets_1.readPlanFile)(asString(input.planFilePath));
22694
+ if (markdown?.trim())
22695
+ acc.plans.push((0, sessionAssets_1.planAsset)(markdown, timestamp, toolMeta));
22696
+ }
22697
+ }
22698
+ }
22699
+ }
22700
+ function readClaudeAssets(cwd2, limit = 3) {
22701
+ const sessions = claudeSessions(cwd2, limit);
22702
+ const acc = { urls: [], paths: [], commands: [], plans: [], hadSession: sessions.length > 0 };
22703
+ for (const session of sessions)
22704
+ accumClaude(session, cwd2, acc);
22705
+ return acc;
22706
+ }
22707
+ }
22708
+ });
22709
+
22710
+ // ../sidekick-shared/dist/extractors/sources/codexAssets.js
22711
+ var require_codexAssets = __commonJS({
22712
+ "../sidekick-shared/dist/extractors/sources/codexAssets.js"(exports) {
22713
+ "use strict";
22714
+ Object.defineProperty(exports, "__esModule", { value: true });
22715
+ exports.codexSessions = codexSessions;
22716
+ exports.readCodexAssets = readCodexAssets;
22717
+ var node_path_1 = __require("node:path");
22718
+ var node_fs_1 = __require("node:fs");
22719
+ var codexProfiles_1 = require_codexProfiles();
22720
+ var sessionAssets_1 = require_sessionAssets();
22721
+ var EXEC_NAMES = /* @__PURE__ */ new Set(["exec_command", "shell", "local_shell", "container.exec"]);
22722
+ function dirExists(path8) {
22723
+ try {
22724
+ return (0, node_fs_1.statSync)(path8).isDirectory();
22725
+ } catch {
22726
+ return false;
22727
+ }
22728
+ }
22729
+ function firstJsonLine(filePath) {
22730
+ let fd;
22731
+ try {
22732
+ fd = (0, node_fs_1.openSync)(filePath, "r");
22733
+ } catch {
22734
+ return null;
22735
+ }
22736
+ const buffer = Buffer.alloc(65536);
22737
+ let data = "";
22738
+ try {
22739
+ let bytesRead;
22740
+ while ((bytesRead = (0, node_fs_1.readSync)(fd, buffer, 0, buffer.length, null)) > 0) {
22741
+ data += buffer.toString("utf8", 0, bytesRead);
22742
+ const newlineIndex = data.indexOf("\n");
22743
+ if (newlineIndex >= 0) {
22744
+ data = data.slice(0, newlineIndex);
22745
+ break;
22746
+ }
22747
+ if (data.length > 1e6)
22748
+ break;
22749
+ }
22750
+ } catch {
22751
+ return null;
22752
+ } finally {
22753
+ (0, node_fs_1.closeSync)(fd);
22754
+ }
22755
+ try {
22756
+ return JSON.parse(data);
22757
+ } catch {
22758
+ return null;
22759
+ }
22760
+ }
22761
+ function readJsonl(filePath, skip) {
22762
+ let raw;
22763
+ try {
22764
+ raw = (0, node_fs_1.readFileSync)(filePath, "utf8");
22765
+ } catch {
22766
+ return [];
22767
+ }
22768
+ const lines = [];
22769
+ for (const line of raw.split("\n")) {
22770
+ const trimmed = line.trim();
22771
+ if (!trimmed)
22772
+ continue;
22773
+ if (skip?.(trimmed))
22774
+ continue;
22775
+ try {
22776
+ lines.push(JSON.parse(trimmed));
22777
+ } catch {
22778
+ }
22779
+ }
22780
+ return lines;
22781
+ }
22782
+ function rolloutFiles(limit = 150) {
22783
+ const entries = [];
22784
+ const seen = /* @__PURE__ */ new Set();
22785
+ const walk = (dir) => {
22786
+ let dirEntries;
22787
+ try {
22788
+ dirEntries = (0, node_fs_1.readdirSync)(dir, { withFileTypes: true });
22789
+ } catch {
22790
+ return;
22791
+ }
22792
+ for (const entry of dirEntries) {
22793
+ const fullPath = (0, node_path_1.join)(dir, entry.name);
22794
+ if (entry.isDirectory()) {
22795
+ walk(fullPath);
22796
+ } else if (entry.isFile() && entry.name.startsWith("rollout-") && entry.name.endsWith(".jsonl")) {
22797
+ try {
22798
+ if (seen.has(fullPath))
22799
+ continue;
22800
+ seen.add(fullPath);
22801
+ entries.push({ path: fullPath, mtime: (0, node_fs_1.statSync)(fullPath).mtimeMs });
22802
+ } catch {
22803
+ }
22804
+ }
22805
+ }
22806
+ };
22807
+ for (const home of (0, codexProfiles_1.getCodexMonitoringHomes)()) {
22808
+ const sessionsDir = (0, node_path_1.join)(home, "sessions");
22809
+ if (dirExists(sessionsDir))
22810
+ walk(sessionsDir);
22811
+ }
22812
+ return entries.sort((a, b) => b.mtime - a.mtime).slice(0, limit).map((entry) => entry.path);
22813
+ }
22814
+ function codexSessions(cwd2, limit = 3) {
22815
+ const exactCwd = (0, node_path_1.resolve)(cwd2);
22816
+ const sessions = [];
22817
+ for (const filePath of rolloutFiles()) {
22818
+ const meta = firstJsonLine(filePath);
22819
+ const payload = meta?.payload;
22820
+ if (payload?.cwd === exactCwd) {
22821
+ sessions.push(filePath);
22822
+ if (sessions.length >= limit)
22823
+ break;
22824
+ }
22825
+ }
22826
+ return sessions;
22827
+ }
22828
+ function parseArgs(value) {
22829
+ if (typeof value !== "string")
22830
+ return value && typeof value === "object" ? value : {};
22831
+ try {
22832
+ return JSON.parse(value);
22833
+ } catch {
22834
+ return {};
22835
+ }
22836
+ }
22837
+ function asString(value) {
22838
+ return typeof value === "string" ? value : void 0;
22839
+ }
22840
+ function provenance(sessionPath, source) {
22841
+ return { agent: "codex", sessionPath, source };
22842
+ }
22843
+ function commandFromLocalShell(item) {
22844
+ const action = item.action;
22845
+ const command = action?.command;
22846
+ if (Array.isArray(command))
22847
+ return command.map(String).join(" ");
22848
+ return asString(command);
22849
+ }
22850
+ function patchFiles(patch, cwd2) {
22851
+ const paths = [];
22852
+ for (const match of String(patch).matchAll(/^\*\*\* (?:Update|Add|Delete) File: (.+)$/gm)) {
22853
+ for (const filePath of (0, sessionAssets_1.extractFilePaths)(match[1].trim(), cwd2)) {
22854
+ paths.push(filePath);
22855
+ }
22856
+ }
22857
+ return paths;
22858
+ }
22859
+ function addMessageAssets(acc, text, cwd2, timestamp, meta) {
22860
+ for (const url of (0, sessionAssets_1.extractUrls)(text))
22861
+ acc.urls.push((0, sessionAssets_1.urlAsset)(url, timestamp, meta));
22862
+ for (const filePath of (0, sessionAssets_1.extractFilePaths)(text, cwd2))
22863
+ acc.paths.push((0, sessionAssets_1.pathAsset)(filePath, timestamp, meta));
22864
+ for (const command of (0, sessionAssets_1.extractCommands)(text))
22865
+ acc.commands.push((0, sessionAssets_1.commandAsset)(command, timestamp, meta));
22866
+ }
22867
+ function addExecutedCommandAssets(acc, command, cwd2, timestamp, meta) {
22868
+ for (const url of (0, sessionAssets_1.extractUrls)(command))
22869
+ acc.urls.push((0, sessionAssets_1.urlAsset)(url, timestamp, meta));
22870
+ for (const filePath of (0, sessionAssets_1.extractFilePaths)(command, cwd2))
22871
+ acc.paths.push((0, sessionAssets_1.pathAsset)(filePath, timestamp, meta));
22872
+ }
22873
+ function readCodexAssets(cwd2, limit = 3) {
22874
+ const exactCwd = (0, node_path_1.resolve)(cwd2);
22875
+ const files = codexSessions(exactCwd, limit);
22876
+ const acc = { urls: [], paths: [], commands: [], plans: [], hadSession: files.length > 0 };
22877
+ const skip = (line) => line.includes('"type":"function_call_output"') || line.includes('"type":"reasoning"') || line.includes('"type":"token_count"');
22878
+ for (const filePath of files) {
22879
+ for (const line of readJsonl(filePath, skip)) {
22880
+ const payload = line.payload;
22881
+ if (!payload)
22882
+ continue;
22883
+ const timestamp = asString(line.timestamp);
22884
+ if (payload.type === "function_call") {
22885
+ if (EXEC_NAMES.has(payload.name)) {
22886
+ const args = parseArgs(payload.arguments);
22887
+ addExecutedCommandAssets(acc, args.cmd ?? args.command, exactCwd, timestamp, provenance(filePath, `tool:${String(payload.name)}`));
22888
+ }
22889
+ } else if (payload.type === "local_shell_call") {
22890
+ addExecutedCommandAssets(acc, commandFromLocalShell(payload), exactCwd, timestamp, provenance(filePath, "tool:local_shell"));
22891
+ } else if (payload.type === "item_completed") {
22892
+ const item = payload.item;
22893
+ const text = asString(item?.text);
22894
+ if (item?.type === "Plan" && text?.trim())
22895
+ acc.plans.push((0, sessionAssets_1.planAsset)(text, timestamp, provenance(filePath, "plan")));
22896
+ } else if (payload.type === "custom_tool_call" && payload.name === "apply_patch") {
22897
+ const meta = provenance(filePath, "tool:apply_patch");
22898
+ for (const file of patchFiles(payload.input, exactCwd))
22899
+ acc.paths.push((0, sessionAssets_1.pathAsset)(file, timestamp, meta));
22900
+ } else if (payload.type === "message" && Array.isArray(payload.content)) {
22901
+ for (const block of payload.content) {
22902
+ const typedBlock = block;
22903
+ addMessageAssets(acc, typedBlock.text, exactCwd, timestamp, provenance(filePath, "message"));
22904
+ }
22905
+ } else if (payload.type === "agent_message") {
22906
+ addMessageAssets(acc, payload.message, exactCwd, timestamp, provenance(filePath, "message"));
22907
+ }
22908
+ }
22909
+ }
22910
+ return acc;
22911
+ }
22912
+ }
22913
+ });
22914
+
22915
+ // ../sidekick-shared/dist/extractors/gatherAssets.js
22916
+ var require_gatherAssets = __commonJS({
22917
+ "../sidekick-shared/dist/extractors/gatherAssets.js"(exports) {
22918
+ "use strict";
22919
+ Object.defineProperty(exports, "__esModule", { value: true });
22920
+ exports.gatherAssetsForCwd = gatherAssetsForCwd2;
22921
+ var node_path_1 = __require("node:path");
22922
+ var claudeAssets_1 = require_claudeAssets();
22923
+ var codexAssets_1 = require_codexAssets();
22924
+ var sessionAssets_1 = require_sessionAssets();
22925
+ var ALL_AGENTS = ["claude", "codex"];
22926
+ function gatherAssetsForCwd2(options = {}) {
22927
+ const cwd2 = (0, node_path_1.resolve)(options.cwd || process.cwd());
22928
+ const sessionLimit = options.sessionsPerAgent ?? 3;
22929
+ const agents = options.agents ?? ALL_AGENTS;
22930
+ const caps = { ...sessionAssets_1.DEFAULT_CAPS, ...options.caps || {} };
22931
+ const sources = [];
22932
+ if (agents.includes("claude"))
22933
+ sources.push((0, claudeAssets_1.readClaudeAssets)(cwd2, sessionLimit));
22934
+ if (agents.includes("codex"))
22935
+ sources.push((0, codexAssets_1.readCodexAssets)(cwd2, sessionLimit));
22936
+ const merge = (key, cap) => (0, sessionAssets_1.capped)((0, sessionAssets_1.dedupeAssets)(sources.flatMap((source) => source[key]).sort(sessionAssets_1.byTimestampDesc)), cap);
22937
+ return {
22938
+ urls: merge("urls", caps.url),
22939
+ paths: merge("paths", caps.path),
22940
+ commands: merge("commands", caps.command),
22941
+ plans: (0, sessionAssets_1.capped)((0, sessionAssets_1.dedupePlansByTitle)(sources.flatMap((source) => source.plans).sort(sessionAssets_1.byTimestampDesc)), caps.plan),
22942
+ inChat: sources.some((source) => source.hadSession)
22943
+ };
22944
+ }
22945
+ }
22946
+ });
22947
+
22352
22948
  // ../sidekick-shared/node_modules/zod/v4/core/core.cjs
22353
22949
  var require_core = __commonJS({
22354
22950
  "../sidekick-shared/node_modules/zod/v4/core/core.cjs"(exports) {
@@ -38740,8 +39336,8 @@ var require_dist = __commonJS({
38740
39336
  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;
38741
39337
  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.formatTokenCount = exports.formatDurationMs = exports.createJsonlTail = exports.toFollowEvents = exports.createWatcher = exports.parseChangelog = exports.extractProposedPlanShared = exports.parsePlanMarkdownShared = exports.PlanExtractor = exports.segmentAssistantTurn = exports.reasoningSummary = exports.isAssistantTurnSubagentTool = exports.extractTurnSubagents = exports.assistantTurnEventsFromSessionEvents = exports.readSessionContextSnapshot = exports.createSessionContextProjector = exports.calculateSessionContextPressure = exports.buildSessionContextSnapshot = 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;
38742
39338
  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.parseTranscriptFromEvents = exports.parseTranscript = exports.generateHtmlReport = exports.PatternExtractor = exports.HeatmapTracker = 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 = void 0;
38743
- exports.quotaProviderIdSchema = exports.quotaFailureKindSchema = exports.quotaStateSchema = exports.quotaWindowSchema = exports.extractSessionEvents = 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 = exports.getModelContextWindowSize = exports.MultiProviderQuotaService = exports.CodexQuotaWatcher = exports.resolveCodexQuotaFromLocalSources = exports.resolveCodexQuota = exports.readLatestCodexQuotaFromRollouts = exports.quotaFromCodexRateLimits = exports.fetchCodexQuotaFromApi = exports.getWorkspaceIdFromPath = exports.pruneQuotaHistory = exports.readQuotaHistoryDailyBuckets = exports.readQuotaHistoryRange = exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.QuotaPoller = exports.describeQuotaFailure = exports.fetchQuota = exports.removeCodexAccount = exports.switchToCodexAccount = exports.finalizeCodexAccount = exports.prepareCodexAccount = void 0;
38744
- exports.scopePeakHoursToSessionProvider = exports.isClaudeCodeSessionProvider = exports.fetchPeakHoursStatus = exports.createPeakHoursNotApplicableState = exports.fetchOpenAIStatus = exports.fetchProviderStatus = exports.assistantTurnToolRefSchema = exports.assistantTurnToolGroupStepSchema = exports.assistantTurnTimelineItemSchema = exports.assistantTurnSubagentStatusSchema = exports.assistantTurnSubagentSchema = exports.assistantTurnReasoningTimelineItemSchema = exports.assistantTurnProjectionSchema = exports.assistantTurnProcessStepSchema = exports.assistantTurnProcessSchema = exports.assistantTurnNarrationStepSchema = exports.assistantTurnEventTypeSchema = exports.assistantTurnEventSchema = exports.activeAccountStatusSchema = exports.activeProviderAccountStatusSchema = exports.quotaHistoryDailyBucketSchema = exports.quotaHistorySampleSchema = exports.quotaHistoryRuntimeProviderSchema = exports.providerQuotaMapSchema = exports.codexProviderQuotaStateSchema = exports.claudeProviderQuotaStateSchema = exports.providerQuotaStateSchema = exports.runtimeQuotaProviderSchema = exports.quotaFailureDescriptorSchema = exports.peakHoursStateSchema = exports.quotaSourceSchema = void 0;
39339
+ exports.messageUsageSchema = exports.gatherAssetsForCwd = exports.codexSessions = exports.readCodexAssets = exports.claudeSessions = exports.readClaudeAssets = exports.extractCommands = exports.extractFilePaths = exports.extractUrls = 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 = exports.getModelContextWindowSize = exports.MultiProviderQuotaService = exports.CodexQuotaWatcher = exports.resolveCodexQuotaFromLocalSources = exports.resolveCodexQuota = exports.readLatestCodexQuotaFromRollouts = exports.quotaFromCodexRateLimits = exports.fetchCodexQuotaFromApi = exports.getWorkspaceIdFromPath = exports.pruneQuotaHistory = exports.readQuotaHistoryDailyBuckets = exports.readQuotaHistoryRange = exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.QuotaPoller = exports.describeQuotaFailure = exports.fetchQuota = exports.removeCodexAccount = exports.switchToCodexAccount = exports.finalizeCodexAccount = exports.prepareCodexAccount = void 0;
39340
+ exports.scopePeakHoursToSessionProvider = exports.isClaudeCodeSessionProvider = exports.fetchPeakHoursStatus = exports.createPeakHoursNotApplicableState = exports.fetchOpenAIStatus = exports.fetchProviderStatus = exports.assistantTurnToolRefSchema = exports.assistantTurnToolGroupStepSchema = exports.assistantTurnTimelineItemSchema = exports.assistantTurnSubagentStatusSchema = exports.assistantTurnSubagentSchema = exports.assistantTurnReasoningTimelineItemSchema = exports.assistantTurnProjectionSchema = exports.assistantTurnProcessStepSchema = exports.assistantTurnProcessSchema = exports.assistantTurnNarrationStepSchema = exports.assistantTurnEventTypeSchema = exports.assistantTurnEventSchema = exports.activeAccountStatusSchema = exports.activeProviderAccountStatusSchema = exports.quotaHistoryDailyBucketSchema = exports.quotaHistorySampleSchema = exports.quotaHistoryRuntimeProviderSchema = exports.providerQuotaMapSchema = exports.codexProviderQuotaStateSchema = exports.claudeProviderQuotaStateSchema = exports.providerQuotaStateSchema = exports.runtimeQuotaProviderSchema = exports.quotaFailureDescriptorSchema = exports.peakHoursStateSchema = exports.quotaSourceSchema = exports.quotaProviderIdSchema = exports.quotaFailureKindSchema = exports.quotaStateSchema = exports.quotaWindowSchema = exports.extractSessionEvents = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = void 0;
38745
39341
  var taskPersistence_1 = require_taskPersistence();
38746
39342
  Object.defineProperty(exports, "TASK_PERSISTENCE_SCHEMA_VERSION", { enumerable: true, get: function() {
38747
39343
  return taskPersistence_1.TASK_PERSISTENCE_SCHEMA_VERSION;
@@ -39383,6 +39979,34 @@ var require_dist = __commonJS({
39383
39979
  Object.defineProperty(exports, "extractToolCalls", { enumerable: true, get: function() {
39384
39980
  return toolCall_1.extractToolCalls;
39385
39981
  } });
39982
+ var sessionAssets_1 = require_sessionAssets();
39983
+ Object.defineProperty(exports, "extractUrls", { enumerable: true, get: function() {
39984
+ return sessionAssets_1.extractUrls;
39985
+ } });
39986
+ Object.defineProperty(exports, "extractFilePaths", { enumerable: true, get: function() {
39987
+ return sessionAssets_1.extractFilePaths;
39988
+ } });
39989
+ Object.defineProperty(exports, "extractCommands", { enumerable: true, get: function() {
39990
+ return sessionAssets_1.extractCommands;
39991
+ } });
39992
+ var claudeAssets_1 = require_claudeAssets();
39993
+ Object.defineProperty(exports, "readClaudeAssets", { enumerable: true, get: function() {
39994
+ return claudeAssets_1.readClaudeAssets;
39995
+ } });
39996
+ Object.defineProperty(exports, "claudeSessions", { enumerable: true, get: function() {
39997
+ return claudeAssets_1.claudeSessions;
39998
+ } });
39999
+ var codexAssets_1 = require_codexAssets();
40000
+ Object.defineProperty(exports, "readCodexAssets", { enumerable: true, get: function() {
40001
+ return codexAssets_1.readCodexAssets;
40002
+ } });
40003
+ Object.defineProperty(exports, "codexSessions", { enumerable: true, get: function() {
40004
+ return codexAssets_1.codexSessions;
40005
+ } });
40006
+ var gatherAssets_1 = require_gatherAssets();
40007
+ Object.defineProperty(exports, "gatherAssetsForCwd", { enumerable: true, get: function() {
40008
+ return gatherAssets_1.gatherAssetsForCwd;
40009
+ } });
39386
40010
  var sessionEvent_1 = require_sessionEvent();
39387
40011
  Object.defineProperty(exports, "messageUsageSchema", { enumerable: true, get: function() {
39388
40012
  return sessionEvent_1.messageUsageSchema;
@@ -40397,21 +41021,21 @@ var require_react_development = __commonJS({
40397
41021
  );
40398
41022
  actScopeDepth = prevActScopeDepth;
40399
41023
  }
40400
- function recursivelyFlushAsyncActWork(returnValue, resolve, reject) {
41024
+ function recursivelyFlushAsyncActWork(returnValue, resolve2, reject) {
40401
41025
  var queue = ReactSharedInternals.actQueue;
40402
41026
  if (null !== queue)
40403
41027
  if (0 !== queue.length)
40404
41028
  try {
40405
41029
  flushActQueue(queue);
40406
41030
  enqueueTask(function() {
40407
- return recursivelyFlushAsyncActWork(returnValue, resolve, reject);
41031
+ return recursivelyFlushAsyncActWork(returnValue, resolve2, reject);
40408
41032
  });
40409
41033
  return;
40410
41034
  } catch (error) {
40411
41035
  ReactSharedInternals.thrownErrors.push(error);
40412
41036
  }
40413
41037
  else ReactSharedInternals.actQueue = null;
40414
- 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve(returnValue);
41038
+ 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve2(returnValue);
40415
41039
  }
40416
41040
  function flushActQueue(queue) {
40417
41041
  if (!isFlushing) {
@@ -40598,7 +41222,7 @@ var require_react_development = __commonJS({
40598
41222
  ));
40599
41223
  });
40600
41224
  return {
40601
- then: function(resolve, reject) {
41225
+ then: function(resolve2, reject) {
40602
41226
  didAwaitActCall = true;
40603
41227
  thenable.then(
40604
41228
  function(returnValue) {
@@ -40608,7 +41232,7 @@ var require_react_development = __commonJS({
40608
41232
  flushActQueue(queue), enqueueTask(function() {
40609
41233
  return recursivelyFlushAsyncActWork(
40610
41234
  returnValue,
40611
- resolve,
41235
+ resolve2,
40612
41236
  reject
40613
41237
  );
40614
41238
  });
@@ -40622,7 +41246,7 @@ var require_react_development = __commonJS({
40622
41246
  ReactSharedInternals.thrownErrors.length = 0;
40623
41247
  reject(_thrownError);
40624
41248
  }
40625
- } else resolve(returnValue);
41249
+ } else resolve2(returnValue);
40626
41250
  },
40627
41251
  function(error) {
40628
41252
  popActScope(prevActQueue, prevActScopeDepth);
@@ -40644,15 +41268,15 @@ var require_react_development = __commonJS({
40644
41268
  if (0 < ReactSharedInternals.thrownErrors.length)
40645
41269
  throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
40646
41270
  return {
40647
- then: function(resolve, reject) {
41271
+ then: function(resolve2, reject) {
40648
41272
  didAwaitActCall = true;
40649
41273
  0 === prevActScopeDepth ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
40650
41274
  return recursivelyFlushAsyncActWork(
40651
41275
  returnValue$jscomp$0,
40652
- resolve,
41276
+ resolve2,
40653
41277
  reject
40654
41278
  );
40655
- })) : resolve(returnValue$jscomp$0);
41279
+ })) : resolve2(returnValue$jscomp$0);
40656
41280
  }
40657
41281
  };
40658
41282
  };
@@ -41745,7 +42369,7 @@ var init_UpdateCheckService = __esm({
41745
42369
  /** Run the update check (one-shot). */
41746
42370
  async check() {
41747
42371
  try {
41748
- const current = "0.19.3";
42372
+ const current = "0.20.0";
41749
42373
  const cached = this.readCache();
41750
42374
  let latest;
41751
42375
  if (cached && Date.now() - cached.checkedAt < CACHE_TTL_MS) {
@@ -42734,7 +43358,7 @@ var init_GitDiffCache = __esm({
42734
43358
  // src/inference/CliInferenceClient.ts
42735
43359
  import { spawn, execSync as execSync2 } from "child_process";
42736
43360
  function spawnWithStdin(cmd, args, prompt, env3) {
42737
- return new Promise((resolve) => {
43361
+ return new Promise((resolve2) => {
42738
43362
  const proc = spawn(cmd, args, {
42739
43363
  stdio: ["pipe", "pipe", "pipe"],
42740
43364
  env: env3 ?? process.env
@@ -42743,7 +43367,7 @@ function spawnWithStdin(cmd, args, prompt, env3) {
42743
43367
  let stderr = "";
42744
43368
  const timer = setTimeout(() => {
42745
43369
  proc.kill();
42746
- resolve({ text: "", error: `${cmd} CLI timed out after 60s` });
43370
+ resolve2({ text: "", error: `${cmd} CLI timed out after 60s` });
42747
43371
  }, 6e4);
42748
43372
  proc.stdout.on("data", (chunk) => {
42749
43373
  stdout += chunk.toString();
@@ -42754,14 +43378,14 @@ function spawnWithStdin(cmd, args, prompt, env3) {
42754
43378
  proc.on("close", (code) => {
42755
43379
  clearTimeout(timer);
42756
43380
  if (code !== 0) {
42757
- resolve({ text: "", error: `${cmd} CLI failed (exit ${code}): ${stderr.substring(0, 200)}` });
43381
+ resolve2({ text: "", error: `${cmd} CLI failed (exit ${code}): ${stderr.substring(0, 200)}` });
42758
43382
  } else {
42759
- resolve({ text: stdout.trim() });
43383
+ resolve2({ text: stdout.trim() });
42760
43384
  }
42761
43385
  });
42762
43386
  proc.on("error", (err) => {
42763
43387
  clearTimeout(timer);
42764
- resolve({ text: "", error: `${cmd} CLI failed: ${err.message}` });
43388
+ resolve2({ text: "", error: `${cmd} CLI failed: ${err.message}` });
42765
43389
  });
42766
43390
  proc.stdin.write(prompt);
42767
43391
  proc.stdin.end();
@@ -48943,8 +49567,8 @@ var require_react_reconciler_production = __commonJS({
48943
49567
  currentEntangledActionThenable = {
48944
49568
  status: "pending",
48945
49569
  value: void 0,
48946
- then: function(resolve) {
48947
- entangledListeners.push(resolve);
49570
+ then: function(resolve2) {
49571
+ entangledListeners.push(resolve2);
48948
49572
  }
48949
49573
  };
48950
49574
  }
@@ -48967,8 +49591,8 @@ var require_react_reconciler_production = __commonJS({
48967
49591
  status: "pending",
48968
49592
  value: null,
48969
49593
  reason: null,
48970
- then: function(resolve) {
48971
- listeners.push(resolve);
49594
+ then: function(resolve2) {
49595
+ listeners.push(resolve2);
48972
49596
  }
48973
49597
  };
48974
49598
  thenable.then(
@@ -55332,14 +55956,14 @@ var require_react_reconciler_production = __commonJS({
55332
55956
  }
55333
55957
  var exports2 = {};
55334
55958
  "use strict";
55335
- var React17 = require_react(), Scheduler2 = require_scheduler(), assign = Object.assign, REACT_LEGACY_ELEMENT_TYPE = Symbol.for("react.element"), REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy");
55959
+ var React18 = require_react(), Scheduler2 = require_scheduler(), assign = Object.assign, REACT_LEGACY_ELEMENT_TYPE = Symbol.for("react.element"), REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy");
55336
55960
  Symbol.for("react.scope");
55337
55961
  var REACT_ACTIVITY_TYPE = Symbol.for("react.activity");
55338
55962
  Symbol.for("react.legacy_hidden");
55339
55963
  Symbol.for("react.tracing_marker");
55340
55964
  var REACT_MEMO_CACHE_SENTINEL = Symbol.for("react.memo_cache_sentinel");
55341
55965
  Symbol.for("react.view_transition");
55342
- var MAYBE_ITERATOR_SYMBOL = Symbol.iterator, REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), isArrayImpl = Array.isArray, ReactSharedInternals = React17.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, rendererVersion = $$$config.rendererVersion, rendererPackageName = $$$config.rendererPackageName, extraDevToolsConfig = $$$config.extraDevToolsConfig, getPublicInstance = $$$config.getPublicInstance, getRootHostContext = $$$config.getRootHostContext, getChildHostContext = $$$config.getChildHostContext, prepareForCommit = $$$config.prepareForCommit, resetAfterCommit = $$$config.resetAfterCommit, createInstance = $$$config.createInstance;
55966
+ var MAYBE_ITERATOR_SYMBOL = Symbol.iterator, REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), isArrayImpl = Array.isArray, ReactSharedInternals = React18.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, rendererVersion = $$$config.rendererVersion, rendererPackageName = $$$config.rendererPackageName, extraDevToolsConfig = $$$config.extraDevToolsConfig, getPublicInstance = $$$config.getPublicInstance, getRootHostContext = $$$config.getRootHostContext, getChildHostContext = $$$config.getChildHostContext, prepareForCommit = $$$config.prepareForCommit, resetAfterCommit = $$$config.resetAfterCommit, createInstance = $$$config.createInstance;
55343
55967
  $$$config.cloneMutableInstance;
55344
55968
  var appendInitialChild = $$$config.appendInitialChild, finalizeInitialChildren = $$$config.finalizeInitialChildren, shouldSetTextContent = $$$config.shouldSetTextContent, createTextInstance = $$$config.createTextInstance;
55345
55969
  $$$config.cloneMutableTextInstance;
@@ -58567,8 +59191,8 @@ var require_react_reconciler_development = __commonJS({
58567
59191
  currentEntangledActionThenable = {
58568
59192
  status: "pending",
58569
59193
  value: void 0,
58570
- then: function(resolve) {
58571
- entangledListeners.push(resolve);
59194
+ then: function(resolve2) {
59195
+ entangledListeners.push(resolve2);
58572
59196
  }
58573
59197
  };
58574
59198
  }
@@ -58591,8 +59215,8 @@ var require_react_reconciler_development = __commonJS({
58591
59215
  status: "pending",
58592
59216
  value: null,
58593
59217
  reason: null,
58594
- then: function(resolve) {
58595
- listeners.push(resolve);
59218
+ then: function(resolve2) {
59219
+ listeners.push(resolve2);
58596
59220
  }
58597
59221
  };
58598
59222
  thenable.then(
@@ -67932,14 +68556,14 @@ var require_react_reconciler_development = __commonJS({
67932
68556
  }
67933
68557
  var exports2 = {};
67934
68558
  "use strict";
67935
- var React17 = require_react(), Scheduler2 = require_scheduler(), assign = Object.assign, REACT_LEGACY_ELEMENT_TYPE = Symbol.for("react.element"), REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy");
68559
+ var React18 = require_react(), Scheduler2 = require_scheduler(), assign = Object.assign, REACT_LEGACY_ELEMENT_TYPE = Symbol.for("react.element"), REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy");
67936
68560
  Symbol.for("react.scope");
67937
68561
  var REACT_ACTIVITY_TYPE = Symbol.for("react.activity");
67938
68562
  Symbol.for("react.legacy_hidden");
67939
68563
  Symbol.for("react.tracing_marker");
67940
68564
  var REACT_MEMO_CACHE_SENTINEL = Symbol.for("react.memo_cache_sentinel");
67941
68565
  Symbol.for("react.view_transition");
67942
- var MAYBE_ITERATOR_SYMBOL = Symbol.iterator, REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), isArrayImpl = Array.isArray, ReactSharedInternals = React17.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, rendererVersion = $$$config.rendererVersion, rendererPackageName = $$$config.rendererPackageName, extraDevToolsConfig = $$$config.extraDevToolsConfig, getPublicInstance = $$$config.getPublicInstance, getRootHostContext = $$$config.getRootHostContext, getChildHostContext = $$$config.getChildHostContext, prepareForCommit = $$$config.prepareForCommit, resetAfterCommit = $$$config.resetAfterCommit, createInstance = $$$config.createInstance;
68566
+ var MAYBE_ITERATOR_SYMBOL = Symbol.iterator, REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), isArrayImpl = Array.isArray, ReactSharedInternals = React18.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, rendererVersion = $$$config.rendererVersion, rendererPackageName = $$$config.rendererPackageName, extraDevToolsConfig = $$$config.extraDevToolsConfig, getPublicInstance = $$$config.getPublicInstance, getRootHostContext = $$$config.getRootHostContext, getChildHostContext = $$$config.getChildHostContext, prepareForCommit = $$$config.prepareForCommit, resetAfterCommit = $$$config.resetAfterCommit, createInstance = $$$config.createInstance;
67943
68567
  $$$config.cloneMutableInstance;
67944
68568
  var appendInitialChild = $$$config.appendInitialChild, finalizeInitialChildren = $$$config.finalizeInitialChildren, shouldSetTextContent = $$$config.shouldSetTextContent, createTextInstance = $$$config.createTextInstance;
67945
68569
  $$$config.cloneMutableTextInstance;
@@ -80273,8 +80897,8 @@ var init_ink = __esm({
80273
80897
  }
80274
80898
  }
80275
80899
  async waitUntilExit() {
80276
- this.exitPromise ||= new Promise((resolve, reject) => {
80277
- this.resolveExitPromise = resolve;
80900
+ this.exitPromise ||= new Promise((resolve2, reject) => {
80901
+ this.resolveExitPromise = resolve2;
80278
80902
  this.rejectExitPromise = reject;
80279
80903
  });
80280
80904
  if (!this.beforeExitHandler) {
@@ -81617,18 +82241,18 @@ var require_react_jsx_runtime_development = __commonJS({
81617
82241
  function isValidElement(object) {
81618
82242
  return "object" === typeof object && null !== object && object.$$typeof === REACT_ELEMENT_TYPE;
81619
82243
  }
81620
- var React17 = require_react(), REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_ACTIVITY_TYPE = Symbol.for("react.activity"), REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), ReactSharedInternals = React17.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, hasOwnProperty = Object.prototype.hasOwnProperty, isArrayImpl = Array.isArray, createTask = console.createTask ? console.createTask : function() {
82244
+ var React18 = require_react(), REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_ACTIVITY_TYPE = Symbol.for("react.activity"), REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), ReactSharedInternals = React18.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, hasOwnProperty = Object.prototype.hasOwnProperty, isArrayImpl = Array.isArray, createTask = console.createTask ? console.createTask : function() {
81621
82245
  return null;
81622
82246
  };
81623
- React17 = {
82247
+ React18 = {
81624
82248
  react_stack_bottom_frame: function(callStackForError) {
81625
82249
  return callStackForError();
81626
82250
  }
81627
82251
  };
81628
82252
  var specialPropKeyWarningShown;
81629
82253
  var didWarnAboutElementRef = {};
81630
- var unknownOwnerDebugStack = React17.react_stack_bottom_frame.bind(
81631
- React17,
82254
+ var unknownOwnerDebugStack = React18.react_stack_bottom_frame.bind(
82255
+ React18,
81632
82256
  UnknownOwner
81633
82257
  )();
81634
82258
  var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));
@@ -81929,7 +82553,7 @@ async function showSessionPicker(provider, workspacePath, additionalProviders) {
81929
82553
  items = collectSessionItems(sessions, provider);
81930
82554
  }
81931
82555
  if (items.length === 0) return { sessionPath: null };
81932
- return new Promise((resolve, reject) => {
82556
+ return new Promise((resolve2, reject) => {
81933
82557
  const instance = render2(
81934
82558
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
81935
82559
  SessionPickerInk,
@@ -81938,7 +82562,7 @@ async function showSessionPicker(provider, workspacePath, additionalProviders) {
81938
82562
  onSelect: (selectedPath) => {
81939
82563
  instance.unmount();
81940
82564
  const selected = items.find((it) => it.sessionPath === selectedPath);
81941
- resolve({ sessionPath: selectedPath, providerId: selected?.providerId });
82565
+ resolve2({ sessionPath: selectedPath, providerId: selected?.providerId });
81942
82566
  }
81943
82567
  }
81944
82568
  )
@@ -82362,7 +82986,7 @@ function StatusBar({
82362
82986
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: parseBlessedTags(BRAND_INLINE) }),
82363
82987
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { dimColor: true, children: [
82364
82988
  " v",
82365
- "0.19.3"
82989
+ "0.20.0"
82366
82990
  ] }),
82367
82991
  updateInfo && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { color: "yellow", children: [
82368
82992
  " (v",
@@ -82752,7 +83376,7 @@ function ChangelogOverlay({ entries, scrollOffset }) {
82752
83376
  " ",
82753
83377
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { bold: true, color: "cyan", children: [
82754
83378
  "Terminal Dashboard v",
82755
- "0.19.3"
83379
+ "0.20.0"
82756
83380
  ] }),
82757
83381
  latestDate ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { color: "gray", children: [
82758
83382
  " \u2014 ",
@@ -83074,7 +83698,7 @@ var init_mouse = __esm({
83074
83698
  var CHANGELOG_default;
83075
83699
  var init_CHANGELOG = __esm({
83076
83700
  "CHANGELOG.md"() {
83077
- 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.19.3] - 2026-06-17\n\n### Changed\n\n- **Bundled `sidekick-shared` projection contract**: The shared assistant-turn projection now exposes a v2 `timeline` array for interleaved reasoning, narration, and tool groups. This is a shared-library contract update for downstream consumers and does not change CLI behavior by itself\n\n## [0.19.2] - 2026-06-15\n\n### Changed\n\n- **Bundled `sidekick-shared` 0.19.2**: The shared library gains a browser-safe assistant-turn projection module (`segmentAssistantTurn()`, `assistantTurnEventsFromSessionEvents()`, and mirrored Zod schemas) that segments an assistant turn into a compact Process + Answer shape, with Claude `Task` subagent refs surfaced without leaking prompt text \u2014 internal additions that don\'t change CLI behavior\n\n## [0.19.1] - 2026-06-09\n\n### Changed\n\n- **Bundled `sidekick-shared` 0.19.1**: Model-ID pricing and context-window lookups (behind the dashboard\'s cost and context gauges) now tolerate padded or mixed-case IDs. The shared library also gains Zod boundary schemas, an `extractSessionEvents()` progress-unwrapping helper, and a `/schemas` subpath export for downstream consumers \u2014 internal additions that don\'t change CLI behavior\n\n## [0.19.0] - 2026-06-09\n\n### Added\n\n- **Claude Opus 4.8 & Fable 5 support**: The dashboard\'s context-window gauge and cost estimates recognize `claude-opus-4-8` and `claude-fable-5` (both 1M-token context; Opus 4.8: $5/$25 per MTok, Fable 5: $10/$50 per MTok) via the shared model catalog\n\n### Changed\n\n- **Codex account switching now swaps `~/.codex/auth.json`**: `sidekick account --provider codex --switch-to <id>` (and `--add`) activates the account by atomically swapping its backed-up credentials into the system `~/.codex/` home, mirroring the Claude switch pattern \u2014 codex terminals outside Sidekick pick up the switch. Profile directories become pure credential backups, with a one-time startup migration for installs created under the old `CODEX_HOME`-redirection model. The command surfaces swap warnings on add, switch, and remove: a running codex process that needs restarting, stale credentials, or OS-keyring credential storage that Sidekick cannot swap\n\n### Fixed\n\n- **Opus 4.6/4.7 cost over-estimation**: Dashed model IDs fell back to the Opus 4.0 pricing tier ($15/$75 instead of $5/$25), inflating estimated costs 3\xD7\n- **Haiku 4.5 unpriced under dashed IDs**: Costs for `claude-haiku-4-5-*` sessions could render as "\u2014" because no dashed static pricing key existed\n\n## [0.18.5] - 2026-06-04\n\n### Changed\n\n- **Consistent Codex transcripts**: `sidekick dashboard` and `sidekick report` now parse Codex sessions via `parseTranscriptFromEvents()`, matching the canonical `SessionEvent` pipeline used by the other providers\n- **Bundled `sidekick-shared` 0.18.5**: Picks up the new session context evidence snapshot API (`buildSessionContextSnapshot`, `readSessionContextSnapshot`, `SessionContextSnapshot` and related types) and the Codex session evidence gap closures \u2014 `system` audit events, normalized `token_count` rate limits, per-file `apply_patch` expansion, tool-emission dedupe, MCP server attribution, and the new `ProviderReaderSessionWatcher`\n\n## [0.18.4] - 2026-05-27\n\n### Added\n\n- **`sidekick peak --provider <id>`**: New flag gates peak-hours output on the session provider. When the resolved provider is not `claude-code`, the command prints a "not applicable" message instead of calling the upstream endpoint\n\n### Changed\n\n- **Bundled `sidekick-shared` 0.18.4**: Picks up `scopePeakHoursToSessionProvider()`, `isClaudeCodeSessionProvider()`, `createPeakHoursNotApplicableState()` for peak-hours scoping, the improved Codex quota snapshot selection logic (`isPreferredQuotaHit`, `findAccountRolloutFiles`, `shouldKeepExistingSnapshot`), and the `notApplicable` field on `PeakHoursState`\n\n## [0.18.3] - 2026-05-19\n\n### Added\n\n- **`sidekick quota history`**: New subcommand that renders a 13-week GitHub-contributions-style heatmap of quota utilization for the current workspace. Flags: `--weeks <n>` (1-26, default 13), `--provider claude|codex` (default both), `--workspace <path>` (default cwd). Bucketed glyphs (`\xB7 \u2591 \u2592 \u2593 \u2588`) are color-coded by utilization band (\u22640 / <25 / <50 / <75 / \u226575), with per-provider rows and a peak / avg / unavailable-days / samples footer. Days that hit `available: false` render as a red `\xD7`. With `--json`, emits a `{ workspaceId, weeks, providers: { claude?, codex? }, generatedAt }` payload \u2014 the same shape consumed by the VS Code dashboard\n\n### Changed\n\n- **Bundled `sidekick-shared` 0.18.3**: Picks up the new per-workspace quota history surface (`appendQuotaHistorySample`, `readQuotaHistoryRange`, `readQuotaHistoryDailyBuckets`, `pruneQuotaHistory`, `getWorkspaceIdFromPath`) and the optional `workspaceId` / `appendHistorySample` hooks on `CodexQuotaWatcher`\n\n## [0.18.2] - 2026-05-19\n\n### Added\n\n- **`sidekick quota --refresh`**: New flag on the `quota` command that, for Codex, explicitly refreshes from the ChatGPT usage API before falling back to local rollout data and cached snapshots. Without the flag, the Codex quota path stays fully local and makes no upstream network call\n\n### Changed\n\n- **Codex quota is local-only by default**: `sidekick quota --provider codex` now delegates to the new `resolveCodexQuota` orchestrator in `sidekick-shared`. It checks the current workspace\'s most recent rollout, then recent account-level rollouts under `CODEX_HOME/sessions`, then the active account\'s cached snapshot \u2014 no upstream network call unless `--refresh` is passed. Failure output continues to include structured `failureKind` / `httpStatus` / `retryAfterMs` fields under `--json`\n- **Bundled `sidekick-shared` 0.18.2**: Picks up the new Codex quota orchestrator (`resolveCodexQuota`, `resolveCodexQuotaFromLocalSources`, `readLatestCodexQuotaFromRollouts`, `fetchCodexQuotaFromApi`), the relaxed `CodexRateLimits` shape (nullable `resets_at` / `window_minutes`), the rate-limit-only `token_count` event emission in `JsonlSessionWatcher`, and `state_N.sqlite` discovery in `CodexDatabase` + provider auto-detect\n\n## [0.18.1] - 2026-05-08\n\n### Changed\n\n- **Shared dashboard formatting**: terminal dashboard `fmtNum()` and `formatDuration()` now delegate to `formatTokenCount()` and `formatDurationMs()` from `sidekick-shared`, keeping the existing CLI surface (uppercase `K`/`M` suffix, compact `1m5s` style) while removing forked rounding logic\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';
83701
+ 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.20.0] - 2026-06-17\n\n### Added\n\n- **`sidekick extract`**: New one-shot command for pulling actionable assets out of recent Claude Code and Codex chats for exactly the current cwd. It extracts URLs, filesystem-validated file paths, commands the agent suggested for the user to run, and plan-mode plans. Output is grouped and colored by default, labels each item with its source agent, validates invalid `--type` and `--limit` values, preserves `inChat` and per-item provenance in `--json`, and offers `-i/--interactive` for a picker that opens URLs or copies selected paths, commands, and plans. `--provider claude-code` and `--provider codex` scope extraction to one agent; OpenCode is reported as unsupported for now\n\nThanks to [@B33pBeeps](https://github.com/B33pBeeps) (Juan Fourie) for contributing this feature in [#17](https://github.com/cesarandreslopez/sidekick-agent-hub/pull/17), adapted from his MIT-licensed [`trawl`](https://github.com/B33pBeeps/trawl) project.\n\n## [0.19.3] - 2026-06-17\n\n### Changed\n\n- **Bundled `sidekick-shared` projection contract**: The shared assistant-turn projection now exposes a v2 `timeline` array for interleaved reasoning, narration, and tool groups. This is a shared-library contract update for downstream consumers and does not change CLI behavior by itself\n\n## [0.19.2] - 2026-06-15\n\n### Changed\n\n- **Bundled `sidekick-shared` 0.19.2**: The shared library gains a browser-safe assistant-turn projection module (`segmentAssistantTurn()`, `assistantTurnEventsFromSessionEvents()`, and mirrored Zod schemas) that segments an assistant turn into a compact Process + Answer shape, with Claude `Task` subagent refs surfaced without leaking prompt text \u2014 internal additions that don\'t change CLI behavior\n\n## [0.19.1] - 2026-06-09\n\n### Changed\n\n- **Bundled `sidekick-shared` 0.19.1**: Model-ID pricing and context-window lookups (behind the dashboard\'s cost and context gauges) now tolerate padded or mixed-case IDs. The shared library also gains Zod boundary schemas, an `extractSessionEvents()` progress-unwrapping helper, and a `/schemas` subpath export for downstream consumers \u2014 internal additions that don\'t change CLI behavior\n\n## [0.19.0] - 2026-06-09\n\n### Added\n\n- **Claude Opus 4.8 & Fable 5 support**: The dashboard\'s context-window gauge and cost estimates recognize `claude-opus-4-8` and `claude-fable-5` (both 1M-token context; Opus 4.8: $5/$25 per MTok, Fable 5: $10/$50 per MTok) via the shared model catalog\n\n### Changed\n\n- **Codex account switching now swaps `~/.codex/auth.json`**: `sidekick account --provider codex --switch-to <id>` (and `--add`) activates the account by atomically swapping its backed-up credentials into the system `~/.codex/` home, mirroring the Claude switch pattern \u2014 codex terminals outside Sidekick pick up the switch. Profile directories become pure credential backups, with a one-time startup migration for installs created under the old `CODEX_HOME`-redirection model. The command surfaces swap warnings on add, switch, and remove: a running codex process that needs restarting, stale credentials, or OS-keyring credential storage that Sidekick cannot swap\n\n### Fixed\n\n- **Opus 4.6/4.7 cost over-estimation**: Dashed model IDs fell back to the Opus 4.0 pricing tier ($15/$75 instead of $5/$25), inflating estimated costs 3\xD7\n- **Haiku 4.5 unpriced under dashed IDs**: Costs for `claude-haiku-4-5-*` sessions could render as "\u2014" because no dashed static pricing key existed\n\n## [0.18.5] - 2026-06-04\n\n### Changed\n\n- **Consistent Codex transcripts**: `sidekick dashboard` and `sidekick report` now parse Codex sessions via `parseTranscriptFromEvents()`, matching the canonical `SessionEvent` pipeline used by the other providers\n- **Bundled `sidekick-shared` 0.18.5**: Picks up the new session context evidence snapshot API (`buildSessionContextSnapshot`, `readSessionContextSnapshot`, `SessionContextSnapshot` and related types) and the Codex session evidence gap closures \u2014 `system` audit events, normalized `token_count` rate limits, per-file `apply_patch` expansion, tool-emission dedupe, MCP server attribution, and the new `ProviderReaderSessionWatcher`\n\n## [0.18.4] - 2026-05-27\n\n### Added\n\n- **`sidekick peak --provider <id>`**: New flag gates peak-hours output on the session provider. When the resolved provider is not `claude-code`, the command prints a "not applicable" message instead of calling the upstream endpoint\n\n### Changed\n\n- **Bundled `sidekick-shared` 0.18.4**: Picks up `scopePeakHoursToSessionProvider()`, `isClaudeCodeSessionProvider()`, `createPeakHoursNotApplicableState()` for peak-hours scoping, the improved Codex quota snapshot selection logic (`isPreferredQuotaHit`, `findAccountRolloutFiles`, `shouldKeepExistingSnapshot`), and the `notApplicable` field on `PeakHoursState`\n\n## [0.18.3] - 2026-05-19\n\n### Added\n\n- **`sidekick quota history`**: New subcommand that renders a 13-week GitHub-contributions-style heatmap of quota utilization for the current workspace. Flags: `--weeks <n>` (1-26, default 13), `--provider claude|codex` (default both), `--workspace <path>` (default cwd). Bucketed glyphs (`\xB7 \u2591 \u2592 \u2593 \u2588`) are color-coded by utilization band (\u22640 / <25 / <50 / <75 / \u226575), with per-provider rows and a peak / avg / unavailable-days / samples footer. Days that hit `available: false` render as a red `\xD7`. With `--json`, emits a `{ workspaceId, weeks, providers: { claude?, codex? }, generatedAt }` payload \u2014 the same shape consumed by the VS Code dashboard\n\n### Changed\n\n- **Bundled `sidekick-shared` 0.18.3**: Picks up the new per-workspace quota history surface (`appendQuotaHistorySample`, `readQuotaHistoryRange`, `readQuotaHistoryDailyBuckets`, `pruneQuotaHistory`, `getWorkspaceIdFromPath`) and the optional `workspaceId` / `appendHistorySample` hooks on `CodexQuotaWatcher`\n\n## [0.18.2] - 2026-05-19\n\n### Added\n\n- **`sidekick quota --refresh`**: New flag on the `quota` command that, for Codex, explicitly refreshes from the ChatGPT usage API before falling back to local rollout data and cached snapshots. Without the flag, the Codex quota path stays fully local and makes no upstream network call\n\n### Changed\n\n- **Codex quota is local-only by default**: `sidekick quota --provider codex` now delegates to the new `resolveCodexQuota` orchestrator in `sidekick-shared`. It checks the current workspace\'s most recent rollout, then recent account-level rollouts under `CODEX_HOME/sessions`, then the active account\'s cached snapshot \u2014 no upstream network call unless `--refresh` is passed. Failure output continues to include structured `failureKind` / `httpStatus` / `retryAfterMs` fields under `--json`\n- **Bundled `sidekick-shared` 0.18.2**: Picks up the new Codex quota orchestrator (`resolveCodexQuota`, `resolveCodexQuotaFromLocalSources`, `readLatestCodexQuotaFromRollouts`, `fetchCodexQuotaFromApi`), the relaxed `CodexRateLimits` shape (nullable `resets_at` / `window_minutes`), the rate-limit-only `token_count` event emission in `JsonlSessionWatcher`, and `state_N.sqlite` discovery in `CodexDatabase` + provider auto-detect\n\n## [0.18.1] - 2026-05-08\n\n### Changed\n\n- **Shared dashboard formatting**: terminal dashboard `fmtNum()` and `formatDuration()` now delegate to `formatTokenCount()` and `formatDurationMs()` from `sidekick-shared`, keeping the existing CLI surface (uppercase `K`/`M` suffix, compact `1m5s` style) while removing forked rounding logic\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';
83078
83702
  }
83079
83703
  });
83080
83704
 
@@ -86014,6 +86638,417 @@ var init_account = __esm({
86014
86638
  }
86015
86639
  });
86016
86640
 
86641
+ // src/utils/clipboard.ts
86642
+ import { execSync as execSync4, spawnSync as spawnSync2 } from "node:child_process";
86643
+ import { closeSync, openSync, readFileSync as readFileSync4, writeSync } from "node:fs";
86644
+ import { platform as platform2 } from "node:os";
86645
+ function which(bin) {
86646
+ try {
86647
+ execSync4(process.platform === "win32" ? `where ${bin}` : `command -v ${bin}`, { stdio: "ignore" });
86648
+ return true;
86649
+ } catch {
86650
+ return false;
86651
+ }
86652
+ }
86653
+ function isWSL() {
86654
+ try {
86655
+ return /microsoft/i.test(readFileSync4("/proc/version", "utf8"));
86656
+ } catch {
86657
+ return false;
86658
+ }
86659
+ }
86660
+ function nativeCopy(text) {
86661
+ const attempts = [];
86662
+ if (platform2() === "darwin") attempts.push(["pbcopy", []]);
86663
+ else if (isWSL()) attempts.push(["clip.exe", []]);
86664
+ if (process.env.WAYLAND_DISPLAY) attempts.push(["wl-copy", []]);
86665
+ attempts.push(["xclip", ["-selection", "clipboard"]], ["xsel", ["-ib"]]);
86666
+ for (const [bin, args] of attempts) {
86667
+ if (!which(bin)) continue;
86668
+ const result = spawnSync2(bin, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
86669
+ if (result.status === 0) return true;
86670
+ }
86671
+ return false;
86672
+ }
86673
+ function osc52Copy(text) {
86674
+ const base64 = Buffer.from(text, "utf8").toString("base64");
86675
+ let sequence = `\x1B]52;c;${base64}\x07`;
86676
+ if (process.env.TMUX) {
86677
+ const esc = "\x1B";
86678
+ sequence = `${esc}Ptmux;${esc}${sequence.split(esc).join(esc + esc)}${esc}\\`;
86679
+ }
86680
+ try {
86681
+ const fd = openSync("/dev/tty", "w");
86682
+ writeSync(fd, sequence);
86683
+ closeSync(fd);
86684
+ return true;
86685
+ } catch {
86686
+ return false;
86687
+ }
86688
+ }
86689
+ function copyToClipboard2(text) {
86690
+ if (process.env.SIDEKICK_CLIP === "osc52") return osc52Copy(text) || nativeCopy(text);
86691
+ return nativeCopy(text) || osc52Copy(text);
86692
+ }
86693
+ var init_clipboard2 = __esm({
86694
+ "src/utils/clipboard.ts"() {
86695
+ "use strict";
86696
+ }
86697
+ });
86698
+
86699
+ // src/utils/openUrl.ts
86700
+ import { spawn as spawn2 } from "node:child_process";
86701
+ import { readFileSync as readFileSync5 } from "node:fs";
86702
+ import { platform as platform3 } from "node:os";
86703
+ function isWSL2() {
86704
+ try {
86705
+ return /microsoft/i.test(readFileSync5("/proc/version", "utf8"));
86706
+ } catch {
86707
+ return false;
86708
+ }
86709
+ }
86710
+ function opener() {
86711
+ if (platform3() === "darwin") return "open";
86712
+ if (isWSL2()) return "explorer.exe";
86713
+ return "xdg-open";
86714
+ }
86715
+ function openUrl(url) {
86716
+ try {
86717
+ const child = spawn2(opener(), [url], { detached: true, stdio: "ignore" });
86718
+ child.unref();
86719
+ return true;
86720
+ } catch {
86721
+ return false;
86722
+ }
86723
+ }
86724
+ var init_openUrl = __esm({
86725
+ "src/utils/openUrl.ts"() {
86726
+ "use strict";
86727
+ }
86728
+ });
86729
+
86730
+ // src/commands/AssetPickerInk.tsx
86731
+ var AssetPickerInk_exports = {};
86732
+ __export(AssetPickerInk_exports, {
86733
+ showAssetPicker: () => showAssetPicker
86734
+ });
86735
+ function AssetPickerInk({ items, onDone }) {
86736
+ const { exit } = use_app_default();
86737
+ const [query, setQuery] = (0, import_react38.useState)("");
86738
+ const [filterIdx, setFilterIdx] = (0, import_react38.useState)(0);
86739
+ const [selected, setSelected] = (0, import_react38.useState)(0);
86740
+ const typeFilter = FILTER_CYCLE[filterIdx];
86741
+ const visible = (0, import_react38.useMemo)(() => {
86742
+ const normalizedQuery = query.toLowerCase();
86743
+ return items.filter((item) => {
86744
+ if (typeFilter !== "all" && item.type !== typeFilter) return false;
86745
+ if (!normalizedQuery) return true;
86746
+ return `${item.display} ${item.text}`.toLowerCase().includes(normalizedQuery);
86747
+ });
86748
+ }, [items, query, typeFilter]);
86749
+ const selectedClamped = Math.min(selected, Math.max(0, visible.length - 1));
86750
+ use_input_default((input, key) => {
86751
+ if (key.escape || key.ctrl && input === "c") {
86752
+ onDone({ kind: "quit" });
86753
+ exit();
86754
+ return;
86755
+ }
86756
+ if (key.downArrow || key.ctrl && input === "n") {
86757
+ setSelected((prev) => Math.min(prev + 1, Math.max(0, visible.length - 1)));
86758
+ return;
86759
+ }
86760
+ if (key.upArrow || key.ctrl && input === "p") {
86761
+ setSelected((prev) => Math.max(prev - 1, 0));
86762
+ return;
86763
+ }
86764
+ if (key.tab) {
86765
+ setFilterIdx((prev) => (prev + 1) % FILTER_CYCLE.length);
86766
+ setSelected(0);
86767
+ return;
86768
+ }
86769
+ if (key.ctrl && input === "y") {
86770
+ const asset = visible[selectedClamped];
86771
+ if (asset) {
86772
+ onDone({ kind: "copy", asset });
86773
+ exit();
86774
+ }
86775
+ return;
86776
+ }
86777
+ if (key.return) {
86778
+ const asset = visible[selectedClamped];
86779
+ if (asset) {
86780
+ onDone({ kind: asset.type === "url" ? "open" : "copy", asset });
86781
+ exit();
86782
+ }
86783
+ return;
86784
+ }
86785
+ if (key.backspace || key.delete) {
86786
+ setQuery((prev) => prev.slice(0, -1));
86787
+ setSelected(0);
86788
+ return;
86789
+ }
86790
+ if (input && !key.ctrl && !key.meta) {
86791
+ setQuery((prev) => prev + input);
86792
+ setSelected(0);
86793
+ }
86794
+ });
86795
+ const rows = process.stdout.rows || 24;
86796
+ const viewportHeight = Math.max(5, rows - 8);
86797
+ const scrollOffset = Math.max(
86798
+ 0,
86799
+ Math.min(selectedClamped - Math.floor(viewportHeight / 2), Math.max(0, visible.length - viewportHeight))
86800
+ );
86801
+ const windowItems = visible.slice(scrollOffset, scrollOffset + viewportHeight);
86802
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Box_default, { flexDirection: "column", width: "100%", children: [
86803
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Box_default, { children: [
86804
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { color: "magenta", bold: true, children: "extract" }),
86805
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { children: " " }),
86806
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { dimColor: true, children: "filter:" }),
86807
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Text, { children: [
86808
+ " ",
86809
+ typeFilter
86810
+ ] }),
86811
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { dimColor: true, children: " query:" }),
86812
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Text, { children: [
86813
+ " ",
86814
+ query
86815
+ ] }),
86816
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { inverse: true, children: " " })
86817
+ ] }),
86818
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Box_default, { flexDirection: "column", borderStyle: "single", borderColor: "magenta", paddingX: 1, children: [
86819
+ visible.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { dimColor: true, children: "No matches." }),
86820
+ windowItems.map((item, index) => {
86821
+ const realIndex = scrollOffset + index;
86822
+ const isSelected = realIndex === selectedClamped;
86823
+ const meta = TYPE_META[item.type];
86824
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Text, { inverse: isSelected, children: [
86825
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Text, { color: meta.color, children: [
86826
+ "[",
86827
+ meta.tag,
86828
+ "]"
86829
+ ] }),
86830
+ " ",
86831
+ item.display
86832
+ ] }) }, `${item.type}-${realIndex}`);
86833
+ })
86834
+ ] }),
86835
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Box_default, { height: 1, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Text, { dimColor: true, children: [
86836
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { bold: true, children: "\u2191/\u2193" }),
86837
+ " nav ",
86838
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { bold: true, children: "Tab" }),
86839
+ " filter ",
86840
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { bold: true, children: "Enter" }),
86841
+ " open/copy ",
86842
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { bold: true, children: "^Y" }),
86843
+ " copy ",
86844
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { bold: true, children: "Esc" }),
86845
+ " quit (",
86846
+ visible.length,
86847
+ "/",
86848
+ items.length,
86849
+ ")"
86850
+ ] }) })
86851
+ ] });
86852
+ }
86853
+ async function showAssetPicker(items) {
86854
+ const { render: render2 } = await init_build2().then(() => build_exports);
86855
+ const action = await new Promise((resolve2) => {
86856
+ let settled = false;
86857
+ const finish = (nextAction) => {
86858
+ if (settled) return;
86859
+ settled = true;
86860
+ instance.unmount();
86861
+ resolve2(nextAction);
86862
+ };
86863
+ const instance = render2(/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(AssetPickerInk, { items, onDone: finish }));
86864
+ instance.waitUntilExit().then(() => finish({ kind: "quit" })).catch(() => finish({ kind: "quit" }));
86865
+ });
86866
+ if (action.kind === "quit" || !action.asset) return;
86867
+ if (action.kind === "open") {
86868
+ const opened = openUrl(action.asset.text);
86869
+ process.stdout.write(opened ? `Opened ${action.asset.text}
86870
+ ` : `Could not open ${action.asset.text}
86871
+ `);
86872
+ } else {
86873
+ const copied = copyToClipboard2(action.asset.text);
86874
+ process.stdout.write(copied ? "Copied to clipboard.\n" : "Could not copy to clipboard.\n");
86875
+ }
86876
+ }
86877
+ var import_react38, import_jsx_runtime17, TYPE_META, FILTER_CYCLE;
86878
+ var init_AssetPickerInk = __esm({
86879
+ async "src/commands/AssetPickerInk.tsx"() {
86880
+ "use strict";
86881
+ import_react38 = __toESM(require_react(), 1);
86882
+ await init_build2();
86883
+ init_clipboard2();
86884
+ init_openUrl();
86885
+ import_jsx_runtime17 = __toESM(require_jsx_runtime(), 1);
86886
+ TYPE_META = {
86887
+ url: { tag: "url", color: "cyan" },
86888
+ path: { tag: "path", color: "yellow" },
86889
+ command: { tag: "cmd", color: "green" },
86890
+ plan: { tag: "plan", color: "magenta" }
86891
+ };
86892
+ FILTER_CYCLE = ["all", "url", "path", "command", "plan"];
86893
+ }
86894
+ });
86895
+
86896
+ // src/commands/extract.ts
86897
+ var extract_exports = {};
86898
+ __export(extract_exports, {
86899
+ extractAction: () => extractAction,
86900
+ filterByTypes: () => filterByTypes,
86901
+ flattenAssets: () => flattenAssets,
86902
+ formatAssetsForText: () => formatAssetsForText,
86903
+ parseLimit: () => parseLimit,
86904
+ parseTypes: () => parseTypes,
86905
+ resolveAssetAgents: () => resolveAssetAgents
86906
+ });
86907
+ import { resolve } from "node:path";
86908
+ function parseTypes(raw) {
86909
+ if (!raw) return ALL_TYPES;
86910
+ const types = [];
86911
+ for (const token of raw.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean)) {
86912
+ const type = TYPE_ALIASES[token];
86913
+ if (!type) {
86914
+ throw new Error(`Invalid asset type '${token}'. Use one of: url, path, command, plan.`);
86915
+ }
86916
+ if (type && !types.includes(type)) types.push(type);
86917
+ }
86918
+ return types.length > 0 ? types : ALL_TYPES;
86919
+ }
86920
+ function parseLimit(raw) {
86921
+ if (raw === void 0) return void 0;
86922
+ if (!/^[1-9]\d*$/.test(raw)) {
86923
+ throw new Error("Limit must be a positive integer.");
86924
+ }
86925
+ return Number.parseInt(raw, 10);
86926
+ }
86927
+ function filterByTypes(assets, types) {
86928
+ const wanted = new Set(types);
86929
+ return {
86930
+ urls: wanted.has("url") ? assets.urls : [],
86931
+ paths: wanted.has("path") ? assets.paths : [],
86932
+ commands: wanted.has("command") ? assets.commands : [],
86933
+ plans: wanted.has("plan") ? assets.plans : [],
86934
+ ...typeof assets.inChat === "boolean" ? { inChat: assets.inChat } : {}
86935
+ };
86936
+ }
86937
+ function flattenAssets(assets) {
86938
+ return [...assets.urls, ...assets.paths, ...assets.commands, ...assets.plans];
86939
+ }
86940
+ function resolveAssetAgents(globalOpts) {
86941
+ if (globalOpts.provider === "claude-code") return { agents: ["claude"] };
86942
+ if (globalOpts.provider === "codex") return { agents: ["codex"] };
86943
+ if (globalOpts.provider === "opencode") return { agents: [], unsupportedProvider: "opencode" };
86944
+ return { agents: void 0 };
86945
+ }
86946
+ function listFor(assets, type) {
86947
+ switch (type) {
86948
+ case "url":
86949
+ return assets.urls;
86950
+ case "path":
86951
+ return assets.paths;
86952
+ case "command":
86953
+ return assets.commands;
86954
+ case "plan":
86955
+ return assets.plans;
86956
+ }
86957
+ }
86958
+ function formatAssetsForText(assets, types) {
86959
+ if (flattenAssets(assets).length === 0) {
86960
+ const message = assets.inChat === false ? "No recent Claude Code or Codex sessions found for this project." : "No extractable assets found in recent sessions.";
86961
+ return `${source_default.dim(message)}
86962
+ `;
86963
+ }
86964
+ let output = "";
86965
+ for (const type of types) {
86966
+ const items = listFor(assets, type);
86967
+ if (items.length === 0) continue;
86968
+ const meta = TYPE_META2[type];
86969
+ output += `${source_default.bold(meta.heading)} ${source_default.dim(`(${items.length})`)}
86970
+ `;
86971
+ for (const item of items) {
86972
+ const tag2 = item.agent ? `${item.agent} ${meta.tag}` : meta.tag;
86973
+ output += ` ${meta.color(`[${tag2}]`)} ${item.display}
86974
+ `;
86975
+ }
86976
+ output += "\n";
86977
+ }
86978
+ return output;
86979
+ }
86980
+ function printAssets(assets, types) {
86981
+ process.stdout.write(formatAssetsForText(assets, types));
86982
+ }
86983
+ async function extractAction(_opts, cmd) {
86984
+ const globalOpts = cmd.parent.opts();
86985
+ const opts = cmd.opts();
86986
+ const workspacePath = resolve(globalOpts.project || process.cwd());
86987
+ const jsonOutput = !!globalOpts.json;
86988
+ try {
86989
+ const types = parseTypes(opts.type);
86990
+ const limit = parseLimit(opts.limit);
86991
+ const { agents, unsupportedProvider } = resolveAssetAgents(globalOpts);
86992
+ if (unsupportedProvider) {
86993
+ process.stderr.write(`Error: sidekick extract supports Claude Code and Codex sessions; provider '${unsupportedProvider}' is not supported yet.
86994
+ `);
86995
+ process.exit(1);
86996
+ }
86997
+ const caps = limit && limit > 0 ? { url: limit, path: limit, command: limit, plan: limit } : void 0;
86998
+ const assets = (0, import_sidekick_shared34.gatherAssetsForCwd)({ cwd: workspacePath, agents, caps });
86999
+ const filtered = filterByTypes(assets, types);
87000
+ if (opts.interactive) {
87001
+ const items = flattenAssets(filtered);
87002
+ if (items.length === 0) {
87003
+ process.stderr.write("No extractable assets found in recent sessions.\n");
87004
+ return;
87005
+ }
87006
+ const { showAssetPicker: showAssetPicker2 } = await init_AssetPickerInk().then(() => AssetPickerInk_exports);
87007
+ await showAssetPicker2(items);
87008
+ return;
87009
+ }
87010
+ if (jsonOutput) {
87011
+ process.stdout.write(JSON.stringify(filtered, null, 2) + "\n");
87012
+ return;
87013
+ }
87014
+ printAssets(filtered, types);
87015
+ } catch (error) {
87016
+ const message = error instanceof Error ? error.message : String(error);
87017
+ process.stderr.write(`Error: ${message}
87018
+ `);
87019
+ process.exit(1);
87020
+ }
87021
+ }
87022
+ var import_sidekick_shared34, ALL_TYPES, TYPE_META2, TYPE_ALIASES;
87023
+ var init_extract = __esm({
87024
+ "src/commands/extract.ts"() {
87025
+ "use strict";
87026
+ init_source();
87027
+ import_sidekick_shared34 = __toESM(require_dist(), 1);
87028
+ ALL_TYPES = ["url", "path", "command", "plan"];
87029
+ TYPE_META2 = {
87030
+ url: { tag: "url", color: source_default.cyan, heading: "URLs" },
87031
+ path: { tag: "path", color: source_default.yellow, heading: "Paths" },
87032
+ command: { tag: "cmd", color: source_default.green, heading: "Commands" },
87033
+ plan: { tag: "plan", color: source_default.magenta, heading: "Plans" }
87034
+ };
87035
+ TYPE_ALIASES = {
87036
+ url: "url",
87037
+ urls: "url",
87038
+ path: "path",
87039
+ paths: "path",
87040
+ file: "path",
87041
+ files: "path",
87042
+ command: "command",
87043
+ commands: "command",
87044
+ cmd: "command",
87045
+ cmds: "command",
87046
+ plan: "plan",
87047
+ plans: "plan"
87048
+ };
87049
+ }
87050
+ });
87051
+
86017
87052
  // src/commands/handoff.ts
86018
87053
  var handoff_exports = {};
86019
87054
  __export(handoff_exports, {
@@ -86024,12 +87059,12 @@ async function handoffAction(_opts, cmd) {
86024
87059
  const workspacePath = globalOpts.project || process.cwd();
86025
87060
  const jsonOutput = !!globalOpts.json;
86026
87061
  try {
86027
- const rawSlug = (0, import_sidekick_shared34.getProjectSlugRaw)(workspacePath);
86028
- const resolvedSlug = (0, import_sidekick_shared34.getProjectSlug)(workspacePath);
87062
+ const rawSlug = (0, import_sidekick_shared35.getProjectSlugRaw)(workspacePath);
87063
+ const resolvedSlug = (0, import_sidekick_shared35.getProjectSlug)(workspacePath);
86029
87064
  const slugs = rawSlug !== resolvedSlug ? [rawSlug, resolvedSlug] : [rawSlug];
86030
87065
  let content = null;
86031
87066
  for (const slug of slugs) {
86032
- content = await (0, import_sidekick_shared34.readLatestHandoff)(slug);
87067
+ content = await (0, import_sidekick_shared35.readLatestHandoff)(slug);
86033
87068
  if (content) break;
86034
87069
  }
86035
87070
  if (!content) {
@@ -86054,12 +87089,12 @@ async function handoffAction(_opts, cmd) {
86054
87089
  process.exit(1);
86055
87090
  }
86056
87091
  }
86057
- var import_sidekick_shared34;
87092
+ var import_sidekick_shared35;
86058
87093
  var init_handoff = __esm({
86059
87094
  "src/commands/handoff.ts"() {
86060
87095
  "use strict";
86061
87096
  init_source();
86062
- import_sidekick_shared34 = __toESM(require_dist(), 1);
87097
+ import_sidekick_shared35 = __toESM(require_dist(), 1);
86063
87098
  }
86064
87099
  });
86065
87100
 
@@ -86073,35 +87108,35 @@ function resolveProviderId(opts, defaultProvider = "auto") {
86073
87108
  if (defaultProvider !== "auto") {
86074
87109
  return defaultProvider;
86075
87110
  }
86076
- return (0, import_sidekick_shared35.detectProvider)();
87111
+ return (0, import_sidekick_shared36.detectProvider)();
86077
87112
  }
86078
87113
  function resolveProvider(opts) {
86079
87114
  const id = resolveProviderId(opts);
86080
87115
  switch (id) {
86081
87116
  case "opencode":
86082
- return new import_sidekick_shared36.OpenCodeProvider();
87117
+ return new import_sidekick_shared37.OpenCodeProvider();
86083
87118
  case "codex":
86084
- return new import_sidekick_shared36.CodexProvider();
87119
+ return new import_sidekick_shared37.CodexProvider();
86085
87120
  case "claude-code":
86086
87121
  default:
86087
- return new import_sidekick_shared36.ClaudeCodeProvider();
87122
+ return new import_sidekick_shared37.ClaudeCodeProvider();
86088
87123
  }
86089
87124
  }
86090
- var import_sidekick_shared35, import_node, import_sidekick_shared36, defaultAccountsReady, program2, dashCmd, dumpCmd, ctxCmd, reportCmd, searchCmd, tasksCmd, decisionsCmd, notesCmd, statsCmd, quotaCmd, statusCmd, peakCmd, accountCmd, handoffCmd;
87125
+ var import_sidekick_shared36, import_node, import_sidekick_shared37, defaultAccountsReady, program2, dashCmd, dumpCmd, ctxCmd, reportCmd, searchCmd, tasksCmd, decisionsCmd, notesCmd, statsCmd, quotaCmd, statusCmd, peakCmd, accountCmd, extractCmd, handoffCmd;
86091
87126
  var init_cli = __esm({
86092
87127
  "src/cli.ts"() {
86093
87128
  init_esm();
86094
- import_sidekick_shared35 = __toESM(require_dist(), 1);
86095
- import_node = __toESM(require_node(), 1);
86096
87129
  import_sidekick_shared36 = __toESM(require_dist(), 1);
87130
+ import_node = __toESM(require_node(), 1);
87131
+ import_sidekick_shared37 = __toESM(require_dist(), 1);
86097
87132
  (0, import_node.hydratePricingCatalog)({
86098
87133
  cacheDir: path7.join(os5.homedir(), ".config", "sidekick")
86099
87134
  }).catch(() => {
86100
87135
  });
86101
- defaultAccountsReady = (0, import_sidekick_shared35.ensureDefaultAccounts)().catch(() => {
87136
+ defaultAccountsReady = (0, import_sidekick_shared36.ensureDefaultAccounts)().catch(() => {
86102
87137
  });
86103
87138
  program2 = new Command();
86104
- program2.name("sidekick").description("Query Sidekick project intelligence from the command line").version("0.19.3").option("--json", "Output as JSON").option("--project <path>", "Override project path (default: cwd)").option("--provider <id>", "Provider: claude-code, opencode, codex, auto (default: auto)");
87139
+ program2.name("sidekick").description("Query Sidekick project intelligence from the command line").version("0.20.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)");
86105
87140
  program2.hook("preAction", async () => {
86106
87141
  await defaultAccountsReady;
86107
87142
  });
@@ -86175,6 +87210,11 @@ var init_cli = __esm({
86175
87210
  return accountAction2(_opts, cmd);
86176
87211
  });
86177
87212
  program2.addCommand(accountCmd);
87213
+ extractCmd = new Command("extract").description("Extract URLs, file paths, commands, and plans from recent sessions").option("--type <types>", "Comma list: url, path, command, plan (default: all)").option("--limit <n>", "Maximum items per type").option("-i, --interactive", "Interactive picker with copy/open actions").action(async (_opts, cmd) => {
87214
+ const { extractAction: extractAction2 } = await Promise.resolve().then(() => (init_extract(), extract_exports));
87215
+ return extractAction2(_opts, cmd);
87216
+ });
87217
+ program2.addCommand(extractCmd);
86178
87218
  handoffCmd = new Command("handoff").description("Show the latest session handoff document for the current project").action(async (_opts, cmd) => {
86179
87219
  const { handoffAction: handoffAction2 } = await Promise.resolve().then(() => (init_handoff(), handoff_exports));
86180
87220
  return handoffAction2(_opts, cmd);