sidekick-docker 0.2.1 → 0.2.3

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.
@@ -56086,7 +56086,7 @@ var require_ComposeClient = __commonJS({
56086
56086
  Object.defineProperty(exports2, "__esModule", { value: true });
56087
56087
  exports2.ComposeClient = void 0;
56088
56088
  var child_process_1 = __require("child_process");
56089
- var ComposeClient2 = class {
56089
+ var ComposeClient3 = class {
56090
56090
  async exec(args, cwd2) {
56091
56091
  return new Promise((resolve, reject) => {
56092
56092
  const proc = (0, child_process_1.spawn)("docker", ["compose", ...args], {
@@ -56224,7 +56224,7 @@ var require_ComposeClient = __commonJS({
56224
56224
  }
56225
56225
  }
56226
56226
  };
56227
- exports2.ComposeClient = ComposeClient2;
56227
+ exports2.ComposeClient = ComposeClient3;
56228
56228
  }
56229
56229
  });
56230
56230
 
@@ -56511,7 +56511,7 @@ var require_LogFilter = __commonJS({
56511
56511
  Object.defineProperty(exports2, "__esModule", { value: true });
56512
56512
  exports2.exactMatch = exactMatch;
56513
56513
  exports2.fuzzyMatch = fuzzyMatch;
56514
- exports2.filterLine = filterLine2;
56514
+ exports2.filterLine = filterLine4;
56515
56515
  function exactMatch(line, query) {
56516
56516
  if (!query)
56517
56517
  return { matched: true, matches: [] };
@@ -56546,7 +56546,7 @@ var require_LogFilter = __commonJS({
56546
56546
  matches.sort((a, b) => a.start - b.start);
56547
56547
  return { matched: true, matches };
56548
56548
  }
56549
- function filterLine2(line, query, mode) {
56549
+ function filterLine4(line, query, mode) {
56550
56550
  return mode === "exact" ? exactMatch(line, query) : fuzzyMatch(line, query);
56551
56551
  }
56552
56552
  }
@@ -94249,7 +94249,7 @@ var {
94249
94249
 
94250
94250
  // src/commands/dashboard.ts
94251
94251
  var import_react37 = __toESM(require_react(), 1);
94252
- var import_sidekick_docker_shared13 = __toESM(require_dist(), 1);
94252
+ var import_sidekick_docker_shared14 = __toESM(require_dist(), 1);
94253
94253
  import { spawnSync } from "child_process";
94254
94254
 
94255
94255
  // src/utils/clipboard.ts
@@ -94290,6 +94290,8 @@ var DockerState = class {
94290
94290
  composeProjects = [];
94291
94291
  selectedLogs = [];
94292
94292
  selectedComposeLogs = [];
94293
+ secondaryLogs = [];
94294
+ secondaryComposeLogs = [];
94293
94295
  inspectedEnv = /* @__PURE__ */ new Map();
94294
94296
  containerChanges = /* @__PURE__ */ new Map();
94295
94297
  imageLayers = /* @__PURE__ */ new Map();
@@ -94400,6 +94402,18 @@ var DockerState = class {
94400
94402
  clearComposeLogs() {
94401
94403
  this.selectedComposeLogs = [];
94402
94404
  }
94405
+ setSecondaryLogs(logs) {
94406
+ this.secondaryLogs = logs;
94407
+ }
94408
+ clearSecondaryLogs() {
94409
+ this.secondaryLogs = [];
94410
+ }
94411
+ setSecondaryComposeLogs(logs) {
94412
+ this.secondaryComposeLogs = logs;
94413
+ }
94414
+ clearSecondaryComposeLogs() {
94415
+ this.secondaryComposeLogs = [];
94416
+ }
94403
94417
  getStatsCollector() {
94404
94418
  return this.statsCollector;
94405
94419
  }
@@ -94434,13 +94448,17 @@ var DockerState = class {
94434
94448
  imageLayers: this.imageLayers,
94435
94449
  selectedContainerLogs: [...this.selectedLogs],
94436
94450
  selectedComposeLogs: [...this.selectedComposeLogs],
94451
+ secondaryContainerLogs: [...this.secondaryLogs],
94452
+ secondaryComposeLogs: [...this.secondaryComposeLogs],
94437
94453
  lastRefresh: this.lastRefresh,
94438
94454
  daemonConnected: this.daemonConnected,
94439
94455
  logFilterString: "",
94440
94456
  logFilterMode: "exact",
94441
94457
  logSeverityCounts: null,
94442
94458
  logSeverityTimeSeries: [],
94443
- logTemplates: []
94459
+ logTemplates: [],
94460
+ secondaryLogSeverityCounts: null,
94461
+ secondaryLogSeverityTimeSeries: []
94444
94462
  };
94445
94463
  }
94446
94464
  };
@@ -94661,6 +94679,64 @@ function colorizeNetworkContainer(name, id) {
94661
94679
  function stripCursorEscapes(text) {
94662
94680
  return text.replace(/\x1b\[\d*[ABCD]/g, "").replace(/\x1b\[\d*(?:;\d*)?[Hf]/g, "").replace(/\x1b\[\d*[JK]/g, "").replace(/\x1b\[\?\d+[hl]/g, "").replace(/\x1b\[[su]/g, "").replace(/\x1b[78]/g, "").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\r/g, "");
94663
94681
  }
94682
+ function renderLogLines(logs, filterString, filterMode, severityCounts) {
94683
+ const lines = [];
94684
+ if (severityCounts && severityCounts.total > 0) {
94685
+ const parts = [];
94686
+ if (severityCounts.error > 0) parts.push(`\x1B[31mE:${severityCounts.error}\x1B[39m`);
94687
+ if (severityCounts.warn > 0) parts.push(`\x1B[33mW:${severityCounts.warn}\x1B[39m`);
94688
+ if (severityCounts.info > 0) parts.push(`\x1B[38;2;43;76;126mI:${severityCounts.info}\x1B[39m`);
94689
+ if (severityCounts.debug > 0) parts.push(`\x1B[90mD:${severityCounts.debug}\x1B[39m`);
94690
+ if (parts.length > 0) lines.push(parts.join(" "));
94691
+ }
94692
+ if (filterString) {
94693
+ let matchCount = 0;
94694
+ for (const l of logs) {
94695
+ const result = (0, import_sidekick_docker_shared2.filterLine)(l.message, filterString, filterMode);
94696
+ if (result.matched) {
94697
+ matchCount++;
94698
+ lines.push(colorizeLogEntry(l, result.matches));
94699
+ }
94700
+ }
94701
+ if (lines.length <= (severityCounts ? 1 : 0)) {
94702
+ lines.push(`\x1B[90mNo logs matching "${filterString}"\x1B[39m`);
94703
+ } else {
94704
+ const headerIdx = severityCounts ? 1 : 0;
94705
+ lines.splice(headerIdx, 0, `\x1B[90m${matchCount} matches (f to filter, Tab to toggle mode)\x1B[39m`);
94706
+ }
94707
+ } else {
94708
+ for (const l of logs) {
94709
+ lines.push(colorizeLogEntry(l));
94710
+ }
94711
+ }
94712
+ return lines;
94713
+ }
94714
+ function clipAnsi(str, maxWidth) {
94715
+ let visible = 0;
94716
+ let i = 0;
94717
+ let result = "";
94718
+ while (i < str.length && visible < maxWidth) {
94719
+ if (str[i] === "\x1B") {
94720
+ const start = i;
94721
+ i++;
94722
+ if (i < str.length && str[i] === "[") {
94723
+ i++;
94724
+ while (i < str.length && str[i] !== "m" && !/[A-Za-z]/.test(str[i])) i++;
94725
+ if (i < str.length) i++;
94726
+ }
94727
+ result += str.slice(start, i);
94728
+ } else {
94729
+ result += str[i];
94730
+ visible++;
94731
+ i++;
94732
+ }
94733
+ }
94734
+ if (visible < maxWidth) {
94735
+ result += " ".repeat(maxWidth - visible);
94736
+ }
94737
+ result += "\x1B[0m";
94738
+ return result;
94739
+ }
94664
94740
 
94665
94741
  // src/dashboard/panels/ContainersPanel.ts
94666
94742
  var ContainersPanel = class {
@@ -94687,41 +94763,10 @@ var ContainersPanel = class {
94687
94763
  detailTabs = [
94688
94764
  {
94689
94765
  label: "Logs",
94690
- render: (item, metrics) => {
94766
+ render: (_item, metrics) => {
94691
94767
  const logs = metrics.selectedContainerLogs;
94692
94768
  if (logs.length === 0) return "No logs available. Select a container to view logs.";
94693
- const lines = [];
94694
- if (metrics.logSeverityCounts && metrics.logSeverityCounts.total > 0) {
94695
- const c = metrics.logSeverityCounts;
94696
- const parts = [];
94697
- if (c.error > 0) parts.push(`\x1B[31mE:${c.error}\x1B[39m`);
94698
- if (c.warn > 0) parts.push(`\x1B[33mW:${c.warn}\x1B[39m`);
94699
- if (c.info > 0) parts.push(`\x1B[38;2;43;76;126mI:${c.info}\x1B[39m`);
94700
- if (c.debug > 0) parts.push(`\x1B[90mD:${c.debug}\x1B[39m`);
94701
- if (parts.length > 0) lines.push(parts.join(" "));
94702
- }
94703
- const query = metrics.logFilterString;
94704
- const mode = metrics.logFilterMode;
94705
- if (query) {
94706
- let matchCount = 0;
94707
- for (const l of logs) {
94708
- const result = (0, import_sidekick_docker_shared4.filterLine)(l.message, query, mode);
94709
- if (result.matched) {
94710
- matchCount++;
94711
- lines.push(colorizeLogEntry(l, result.matches));
94712
- }
94713
- }
94714
- if (lines.length <= 1) {
94715
- lines.push(`\x1B[90mNo logs matching "${query}"\x1B[39m`);
94716
- } else {
94717
- lines.splice(1, 0, `\x1B[90m${matchCount} matches (f to filter, Tab to toggle mode)\x1B[39m`);
94718
- }
94719
- } else {
94720
- for (const l of logs) {
94721
- lines.push(colorizeLogEntry(l));
94722
- }
94723
- }
94724
- return lines.join("\n");
94769
+ return renderLogLines(logs, metrics.logFilterString, metrics.logFilterMode, metrics.logSeverityCounts).join("\n");
94725
94770
  },
94726
94771
  autoScrollBottom: true
94727
94772
  },
@@ -94965,6 +95010,7 @@ var ContainersPanel = class {
94965
95010
  };
94966
95011
 
94967
95012
  // src/dashboard/panels/ServicesPanel.ts
95013
+ var import_sidekick_docker_shared5 = __toESM(require_dist(), 1);
94968
95014
  function getProjectName(d) {
94969
95015
  return d.type === "project" ? d.project.name : d.service.projectName;
94970
95016
  }
@@ -94976,12 +95022,17 @@ var ServicesPanel = class {
94976
95022
  onAction;
94977
95023
  onError;
94978
95024
  cwd;
95025
+ onCopyLogs;
95026
+ lastMetrics = null;
94979
95027
  constructor(composeClient, onAction, cwd2, onError) {
94980
95028
  this.composeClient = composeClient;
94981
95029
  this.onAction = onAction;
94982
95030
  this.onError = onError ?? defaultOnError;
94983
95031
  this.cwd = cwd2;
94984
95032
  }
95033
+ setOnCopyLogs(handler) {
95034
+ this.onCopyLogs = handler;
95035
+ }
94985
95036
  detailTabs = [
94986
95037
  {
94987
95038
  label: "Info",
@@ -95015,12 +95066,13 @@ var ServicesPanel = class {
95015
95066
  render: (_item, metrics) => {
95016
95067
  const logs = metrics.selectedComposeLogs;
95017
95068
  if (logs.length === 0) return "No compose logs. Logs will appear when a service produces output.";
95018
- return logs.map((e) => colorizeLogEntry(e)).join("\n");
95069
+ return renderLogLines(logs, metrics.logFilterString, metrics.logFilterMode).join("\n");
95019
95070
  },
95020
95071
  autoScrollBottom: true
95021
95072
  }
95022
95073
  ];
95023
95074
  getItems(metrics) {
95075
+ this.lastMetrics = metrics;
95024
95076
  const items = [];
95025
95077
  let sortKey = 0;
95026
95078
  for (const project of metrics.composeProjects) {
@@ -95115,6 +95167,23 @@ var ServicesPanel = class {
95115
95167
  });
95116
95168
  },
95117
95169
  condition: (item) => item.data !== null
95170
+ },
95171
+ {
95172
+ key: "c",
95173
+ label: "Copy Logs",
95174
+ handler: () => {
95175
+ if (!this.lastMetrics || !this.onCopyLogs) return;
95176
+ const logs = this.lastMetrics.selectedComposeLogs;
95177
+ const query = this.lastMetrics.logFilterString;
95178
+ const mode = this.lastMetrics.logFilterMode;
95179
+ let lines;
95180
+ if (query) {
95181
+ lines = logs.filter((l) => (0, import_sidekick_docker_shared5.filterLine)(l.message, query, mode).matched).map((l) => l.message);
95182
+ } else {
95183
+ lines = logs.map((l) => l.message);
95184
+ }
95185
+ this.onCopyLogs(lines.join("\n"));
95186
+ }
95118
95187
  }
95119
95188
  ];
95120
95189
  }
@@ -95400,15 +95469,15 @@ var NetworksPanel = class {
95400
95469
  };
95401
95470
 
95402
95471
  // src/dashboard/LogStreamManager.ts
95403
- var import_sidekick_docker_shared6 = __toESM(require_dist(), 1);
95472
+ var import_sidekick_docker_shared7 = __toESM(require_dist(), 1);
95404
95473
 
95405
95474
  // src/dashboard/BaseStreamManager.ts
95406
- var import_sidekick_docker_shared5 = __toESM(require_dist(), 1);
95475
+ var import_sidekick_docker_shared6 = __toESM(require_dist(), 1);
95407
95476
  var BaseStreamManager = class {
95408
95477
  currentId;
95409
95478
  aborted = false;
95410
95479
  streamPromise = null;
95411
- reconnect = new import_sidekick_docker_shared5.ReconnectScheduler();
95480
+ reconnect = new import_sidekick_docker_shared6.ReconnectScheduler();
95412
95481
  onChange;
95413
95482
  constructor(onChange) {
95414
95483
  this.onChange = onChange;
@@ -95435,7 +95504,7 @@ var BaseStreamManager = class {
95435
95504
  }
95436
95505
  this.reconnect.reset();
95437
95506
  } catch (err) {
95438
- console.debug(`${this.streamLabel} stream error:`, (0, import_sidekick_docker_shared5.errorMessage)(err));
95507
+ console.debug(`${this.streamLabel} stream error:`, (0, import_sidekick_docker_shared6.errorMessage)(err));
95439
95508
  }
95440
95509
  if (!this.aborted && this.isSameId(id, this.currentId)) {
95441
95510
  const scheduled = this.reconnect.schedule(() => {
@@ -95472,9 +95541,9 @@ var BaseStreamManager = class {
95472
95541
  var LogStreamManager = class extends BaseStreamManager {
95473
95542
  client;
95474
95543
  logs = [];
95475
- analytics = new import_sidekick_docker_shared6.LogAnalytics();
95476
- timeSeries = new import_sidekick_docker_shared6.LogSeverityTimeSeries();
95477
- templateEngine = new import_sidekick_docker_shared6.LogTemplateEngine();
95544
+ analytics = new import_sidekick_docker_shared7.LogAnalytics();
95545
+ timeSeries = new import_sidekick_docker_shared7.LogSeverityTimeSeries();
95546
+ templateEngine = new import_sidekick_docker_shared7.LogTemplateEngine();
95478
95547
  streamLabel = "log";
95479
95548
  constructor(client, onChange) {
95480
95549
  super(onChange);
@@ -95500,7 +95569,7 @@ var LogStreamManager = class extends BaseStreamManager {
95500
95569
  const severity = this.analytics.push(entry.message);
95501
95570
  this.timeSeries.push(severity);
95502
95571
  this.templateEngine.push(entry.message);
95503
- if (this.logs.length > import_sidekick_docker_shared6.MAX_LOG_LINES) {
95572
+ if (this.logs.length > import_sidekick_docker_shared7.MAX_LOG_LINES) {
95504
95573
  this.logs.shift();
95505
95574
  }
95506
95575
  }
@@ -95569,7 +95638,7 @@ var StatsStreamManager = class extends BaseStreamManager {
95569
95638
  };
95570
95639
 
95571
95640
  // src/dashboard/ComposeLogStreamManager.ts
95572
- var import_sidekick_docker_shared7 = __toESM(require_dist(), 1);
95641
+ var import_sidekick_docker_shared8 = __toESM(require_dist(), 1);
95573
95642
  var ComposeLogStreamManager = class extends BaseStreamManager {
95574
95643
  composeClient;
95575
95644
  logs = [];
@@ -95595,7 +95664,7 @@ var ComposeLogStreamManager = class extends BaseStreamManager {
95595
95664
  }
95596
95665
  processItem(_id, entry) {
95597
95666
  this.logs.push(entry);
95598
- if (this.logs.length > import_sidekick_docker_shared7.MAX_LOG_LINES) {
95667
+ if (this.logs.length > import_sidekick_docker_shared8.MAX_LOG_LINES) {
95599
95668
  this.logs.shift();
95600
95669
  }
95601
95670
  }
@@ -95740,7 +95809,7 @@ function handleFilterInput(input, key, opts) {
95740
95809
  }
95741
95810
  function useKeyboardHandler(ctx) {
95742
95811
  const { exit } = use_app_default();
95743
- const { state, dispatch, panels, panel, selectedItem, contextActions, clampedSelection, currentItems, detailLines, detailViewportHeight, detailTabs, tabIdx, panelActions, sideScroll, addToast, removeToast, rotatePhrase } = ctx;
95812
+ const { state, dispatch, panels, panel, selectedItem, contextActions, clampedSelection, currentItems, detailLines, detailViewportHeight, detailTabs, tabIdx, panelActions, sideScroll, addToast, removeToast, rotatePhrase, secondaryDetailLineCount } = ctx;
95744
95813
  use_input_default((input, key) => {
95745
95814
  if (state.overlay === "exec") return;
95746
95815
  rotatePhrase();
@@ -95911,6 +95980,18 @@ function useKeyboardHandler(ctx) {
95911
95980
  addToast(`Sort: ${state.sortReversed ? "ascending" : "descending"}`, "info");
95912
95981
  return;
95913
95982
  }
95983
+ if (input === "m" && selectedItem && (panel.id === "containers" || panel.id === "services")) {
95984
+ const currentCompare = state.compareItemIds[panel.id] ?? null;
95985
+ if (currentCompare === selectedItem.id) {
95986
+ dispatch({ type: "PIN_COMPARE", panelId: panel.id, itemId: selectedItem.id });
95987
+ addToast("Unpinned comparison", "info");
95988
+ } else {
95989
+ dispatch({ type: "PIN_COMPARE", panelId: panel.id, itemId: selectedItem.id });
95990
+ const label = selectedItem.label.replace(/^[^\w]*/, "").trim();
95991
+ addToast(`Pinned ${label} for comparison`, "info");
95992
+ }
95993
+ return;
95994
+ }
95914
95995
  if (input === "x") {
95915
95996
  if (selectedItem && panelActions.length > 0) {
95916
95997
  dispatch({ type: "SET_OVERLAY", overlay: "context-menu" });
@@ -95956,6 +96037,14 @@ function useKeyboardHandler(ctx) {
95956
96037
  }
95957
96038
  }
95958
96039
  if (state.focusTarget === "detail") {
96040
+ if (input === "J" && secondaryDetailLineCount > 0) {
96041
+ dispatch({ type: "SCROLL_SECONDARY_DETAIL_DELTA", delta: 1, totalLines: secondaryDetailLineCount, viewportHeight: detailViewportHeight });
96042
+ return;
96043
+ }
96044
+ if (input === "K" && secondaryDetailLineCount > 0) {
96045
+ dispatch({ type: "SCROLL_SECONDARY_DETAIL_DELTA", delta: -1, totalLines: secondaryDetailLineCount, viewportHeight: detailViewportHeight });
96046
+ return;
96047
+ }
95959
96048
  if (input === "j" || key.downArrow) {
95960
96049
  dispatch({ type: "SCROLL_DETAIL_DELTA", delta: 1, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
95961
96050
  return;
@@ -96123,7 +96212,7 @@ var EMPTY_HINTS = {
96123
96212
  networks: ["Create a network to get started:", " docker network create my-net"],
96124
96213
  services: ["Start a compose project:", " docker compose up -d"]
96125
96214
  };
96126
- function SideList({ items, selectedIndex, scrollOffset, focused, width, viewportHeight, panelTitle, filterString, panelId, totalCount, runningCount }) {
96215
+ function SideList({ items, selectedIndex, scrollOffset, focused, width, viewportHeight, panelTitle, filterString, panelId, totalCount, runningCount, compareItemId }) {
96127
96216
  const hasScrollUp = scrollOffset > 0;
96128
96217
  const hasScrollDown = scrollOffset + viewportHeight < items.length;
96129
96218
  const aboveCount = scrollOffset;
@@ -96150,7 +96239,8 @@ function SideList({ items, selectedIndex, scrollOffset, focused, width, viewport
96150
96239
  const segments = [...trimmed];
96151
96240
  const icon = segments[0] || "";
96152
96241
  const rest = segments.slice(1).join("");
96153
- const rightLabel = item.rightLabel || "";
96242
+ const isPinned = compareItemId != null && item.id === compareItemId;
96243
+ const rightLabel = isPinned ? `\u{1F4CC}${item.rightLabel ? " " + item.rightLabel : ""}` : item.rightLabel || "";
96154
96244
  const rightLen = rightLabel.length;
96155
96245
  const leftWidth = innerWidth - rightLen - (rightLen ? 1 : 0);
96156
96246
  if (isSelected && focused) {
@@ -96229,11 +96319,67 @@ function DetailPane({ lines, scrollOffset, viewportHeight, focused }) {
96229
96319
  ] });
96230
96320
  }
96231
96321
 
96322
+ // src/dashboard/ink/CompareDetailPane.tsx
96323
+ await init_build2();
96324
+ var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
96325
+ function CompareDetailPane({
96326
+ primaryLines,
96327
+ secondaryLines,
96328
+ primaryScrollOffset,
96329
+ secondaryScrollOffset,
96330
+ viewportHeight,
96331
+ totalWidth,
96332
+ focused,
96333
+ primaryLabel,
96334
+ secondaryLabel
96335
+ }) {
96336
+ const colWidth = Math.max(10, Math.floor((totalWidth - 5) / 2));
96337
+ const headerLeft = clipAnsi(`\x1B[1m${primaryLabel}\x1B[22m`, colWidth);
96338
+ const headerRight = clipAnsi(`\x1B[1m${secondaryLabel}\x1B[22m`, colWidth);
96339
+ const leftVisible = primaryLines.slice(primaryScrollOffset, primaryScrollOffset + viewportHeight);
96340
+ const rightVisible = secondaryLines.slice(secondaryScrollOffset, secondaryScrollOffset + viewportHeight);
96341
+ const leftHasUp = primaryScrollOffset > 0;
96342
+ const leftHasDown = primaryScrollOffset + viewportHeight < primaryLines.length;
96343
+ const rightHasUp = secondaryScrollOffset > 0;
96344
+ const rightHasDown = secondaryScrollOffset + viewportHeight < secondaryLines.length;
96345
+ const maxRows = Math.max(leftVisible.length, rightVisible.length, viewportHeight);
96346
+ const rows = [];
96347
+ for (let i = 0; i < maxRows && i < viewportHeight; i++) {
96348
+ const left = leftVisible[i] ?? "";
96349
+ const right = rightVisible[i] ?? "";
96350
+ rows.push(
96351
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96352
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { wrap: "truncate", children: clipAnsi(left, colWidth) }),
96353
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: " \u2502 " }),
96354
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { wrap: "truncate", children: clipAnsi(right, colWidth) })
96355
+ ] }, i)
96356
+ );
96357
+ }
96358
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, borderStyle: focused ? "bold" : "single", borderColor: focused ? "#2B4C7E" : "gray", children: [
96359
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96360
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { wrap: "truncate", children: headerLeft }),
96361
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: " \u2502 " }),
96362
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { wrap: "truncate", children: headerRight })
96363
+ ] }),
96364
+ (leftHasUp || rightHasUp) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96365
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: leftHasUp ? clipAnsi(`\u25B2 (${primaryScrollOffset} more)`, colWidth) : " ".repeat(colWidth) }),
96366
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: " \u2502 " }),
96367
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: rightHasUp ? clipAnsi(`\u25B2 (${secondaryScrollOffset} more)`, colWidth) : " ".repeat(colWidth) })
96368
+ ] }),
96369
+ rows,
96370
+ (leftHasDown || rightHasDown) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96371
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: leftHasDown ? clipAnsi(`\u25BC (${Math.max(0, primaryLines.length - primaryScrollOffset - viewportHeight)} more)`, colWidth) : " ".repeat(colWidth) }),
96372
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: " \u2502 " }),
96373
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: rightHasDown ? clipAnsi(`\u25BC (${Math.max(0, secondaryLines.length - secondaryScrollOffset - viewportHeight)} more)`, colWidth) : " ".repeat(colWidth) })
96374
+ ] })
96375
+ ] });
96376
+ }
96377
+
96232
96378
  // src/dashboard/ink/StatusBar.tsx
96233
96379
  var import_react32 = __toESM(require_react(), 1);
96234
96380
  await init_build2();
96235
- var import_sidekick_docker_shared8 = __toESM(require_dist(), 1);
96236
- var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
96381
+ var import_sidekick_docker_shared9 = __toESM(require_dist(), 1);
96382
+ var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
96237
96383
  function formatAgo(date) {
96238
96384
  const secs = Math.floor((Date.now() - date.getTime()) / 1e3);
96239
96385
  if (secs < 5) return { text: "just now", stale: false };
@@ -96249,33 +96395,33 @@ function StatusBar({ daemonConnected, focusTarget, panelActionHints, filterStrin
96249
96395
  return () => clearInterval(timer);
96250
96396
  }, []);
96251
96397
  const ago = lastRefresh ? formatAgo(lastRefresh) : null;
96252
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
96253
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: true, color: "magenta", children: ` \u26A1 ${import_sidekick_docker_shared8.BRAND_INLINE}` }),
96254
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${import_sidekick_docker_shared8.BRAND_TAGLINE} v${version2}` }),
96255
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
96256
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: daemonConnected ? "green" : "red", children: daemonConnected ? `\u25CF ${runningCount ?? 0}/${containerCount ?? 0}` : "\u25CB disconnected" }),
96257
- ago && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: ago.stale ? "yellow" : "gray", dimColor: !ago.stale, children: ` \u21BB ${ago.text}` }),
96258
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
96259
- panelActionHints.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
96260
- panelActionHints.map((hint, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react32.default.Fragment, { children: [
96261
- i > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: " " }),
96262
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: hint.destructive ? "red" : "#2B4C7E", children: `${hint.key}:${hint.label}` })
96398
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96399
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "magenta", children: ` \u26A1 ${import_sidekick_docker_shared9.BRAND_INLINE}` }),
96400
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` ${import_sidekick_docker_shared9.BRAND_TAGLINE} v${version2}` }),
96401
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
96402
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: daemonConnected ? "green" : "red", children: daemonConnected ? `\u25CF ${runningCount ?? 0}/${containerCount ?? 0}` : "\u25CB disconnected" }),
96403
+ ago && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: ago.stale ? "yellow" : "gray", dimColor: !ago.stale, children: ` \u21BB ${ago.text}` }),
96404
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
96405
+ panelActionHints.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
96406
+ panelActionHints.map((hint, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react32.default.Fragment, { children: [
96407
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: " " }),
96408
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: hint.destructive ? "red" : "#2B4C7E", children: `${hint.key}:${hint.label}` })
96263
96409
  ] }, hint.key)),
96264
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` })
96410
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` })
96265
96411
  ] }),
96266
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: focusTarget === "side" ? "#2B4C7E" : "gray", bold: focusTarget === "side", children: "\u25C0" }),
96267
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: "/" }),
96268
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: focusTarget === "detail" ? "#2B4C7E" : "gray", bold: focusTarget === "detail", children: "\u25B6" }),
96269
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: " j/k Tab / ?" }),
96270
- contextHint ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "cyan", dimColor: true, children: ` ${contextHint}` }) : null,
96271
- filterString ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "yellow", bold: true, children: ` \u25C9 "${filterString}"${matchCount !== void 0 && totalCount !== void 0 ? ` ${matchCount}/${totalCount}` : ""}` }) : null
96412
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: focusTarget === "side" ? "#2B4C7E" : "gray", bold: focusTarget === "side", children: "\u25C0" }),
96413
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "/" }),
96414
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: focusTarget === "detail" ? "#2B4C7E" : "gray", bold: focusTarget === "detail", children: "\u25B6" }),
96415
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: " j/k Tab / ?" }),
96416
+ contextHint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "cyan", dimColor: true, children: ` ${contextHint}` }) : null,
96417
+ filterString ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "yellow", bold: true, children: ` \u25C9 "${filterString}"${matchCount !== void 0 && totalCount !== void 0 ? ` ${matchCount}/${totalCount}` : ""}` }) : null
96272
96418
  ] });
96273
96419
  }
96274
96420
 
96275
96421
  // src/dashboard/ink/HelpOverlay.tsx
96276
96422
  await init_build2();
96277
- var import_sidekick_docker_shared9 = __toESM(require_dist(), 1);
96278
- var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
96423
+ var import_sidekick_docker_shared10 = __toESM(require_dist(), 1);
96424
+ var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
96279
96425
  var GLOBAL_BINDINGS = [
96280
96426
  { key: "1-5", label: "Switch panel" },
96281
96427
  { key: "j/k", label: "Navigate / scroll" },
@@ -96288,66 +96434,69 @@ var GLOBAL_BINDINGS = [
96288
96434
  { key: "a", label: "Toggle all/running (Containers)" },
96289
96435
  { key: "o", label: "Sort menu (Containers)" },
96290
96436
  { key: "R", label: "Reverse sort (Containers)" },
96437
+ { key: "f", label: "Log filter (Logs tab)" },
96438
+ { key: "m", label: "Pin/unpin log comparison" },
96439
+ { key: "J/K", label: "Scroll compare pane" },
96291
96440
  { key: "V", label: "Version info" },
96292
96441
  { key: "?", label: "This help" },
96293
96442
  { key: "q", label: "Quit" }
96294
96443
  ];
96295
96444
  function KeyBadge({ k }) {
96296
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "white", backgroundColor: "#2B4C7E", bold: true, children: ` ${k} ` });
96445
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "white", backgroundColor: "#2B4C7E", bold: true, children: ` ${k} ` });
96297
96446
  }
96298
96447
  function HelpOverlay({ panels, activePanelIndex, version: version2 }) {
96299
96448
  const panel = panels[activePanelIndex];
96300
96449
  const actions = panel.getActions();
96301
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
96302
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96303
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared9.BRAND_INLINE} ${import_sidekick_docker_shared9.BRAND_TAGLINE}` }),
96304
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` v${version2}` })
96450
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
96451
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
96452
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared10.BRAND_INLINE} ${import_sidekick_docker_shared10.BRAND_TAGLINE}` }),
96453
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: ` v${version2}` })
96305
96454
  ] }),
96306
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
96307
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96308
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "yellow", children: "\u2500\u2500 Navigation " }),
96309
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(30) })
96455
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: "" }),
96456
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
96457
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { bold: true, color: "yellow", children: "\u2500\u2500 Navigation " }),
96458
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(30) })
96310
96459
  ] }),
96311
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
96312
- GLOBAL_BINDINGS.map((b) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96313
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { width: 10, justifyContent: "flex-end", marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyBadge, { k: b.key }) }),
96314
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", children: b.label })
96460
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: "" }),
96461
+ GLOBAL_BINDINGS.map((b) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
96462
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { width: 10, justifyContent: "flex-end", marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(KeyBadge, { k: b.key }) }),
96463
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", children: b.label })
96315
96464
  ] }, b.key)),
96316
- actions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
96317
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
96318
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96319
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "yellow", children: `\u2500\u2500 ${panel.title} Actions ` }),
96320
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(24) })
96465
+ actions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
96466
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: "" }),
96467
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
96468
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { bold: true, color: "yellow", children: `\u2500\u2500 ${panel.title} Actions ` }),
96469
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(24) })
96321
96470
  ] }),
96322
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
96323
- actions.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
96324
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Box_default, { width: 10, justifyContent: "flex-end", marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyBadge, { k: a.key }) }),
96325
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: a.confirm ? "red" : "gray", children: a.label }),
96326
- a.confirm && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "red", dimColor: true, children: " \u26A0" })
96471
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: "" }),
96472
+ actions.map((a) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
96473
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { width: 10, justifyContent: "flex-end", marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(KeyBadge, { k: a.key }) }),
96474
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: a.confirm ? "red" : "gray", children: a.label }),
96475
+ a.confirm && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "red", dimColor: true, children: " \u26A0" })
96327
96476
  ] }, a.key))
96328
96477
  ] }),
96329
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
96330
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "Press ? or Esc to close" })
96478
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: "" }),
96479
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: "Press ? or Esc to close" })
96331
96480
  ] });
96332
96481
  }
96333
96482
 
96334
96483
  // src/dashboard/ink/FilterOverlay.tsx
96335
96484
  await init_build2();
96336
- var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
96485
+ var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
96337
96486
  function FilterOverlay({ filterString, matchCount, totalCount, panelTitle }) {
96338
96487
  const countInfo = matchCount !== void 0 && totalCount !== void 0 && panelTitle ? ` ${matchCount} of ${totalCount} ${panelTitle.toLowerCase()}` : "";
96339
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { position: "absolute", marginTop: 1, marginLeft: 1, children: [
96340
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { backgroundColor: "#2B4C7E", color: "white", bold: true, children: ` \u2315 ${filterString}\u2588 ` }),
96341
- countInfo && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: countInfo }),
96342
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: " Enter: apply Esc: clear" })
96488
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { position: "absolute", marginTop: 1, marginLeft: 1, children: [
96489
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { backgroundColor: "#2B4C7E", color: "white", bold: true, children: ` \u2315 ${filterString}\u2588 ` }),
96490
+ countInfo && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "gray", dimColor: true, children: countInfo }),
96491
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "gray", dimColor: true, children: " Enter: apply Esc: clear" })
96343
96492
  ] });
96344
96493
  }
96345
96494
 
96346
96495
  // src/dashboard/ink/ContextMenuOverlay.tsx
96347
96496
  await init_build2();
96348
- var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
96497
+ var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
96349
96498
  function ContextMenuOverlay({ actions, selectedIndex }) {
96350
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
96499
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
96351
96500
  Box_default,
96352
96501
  {
96353
96502
  position: "absolute",
@@ -96358,13 +96507,13 @@ function ContextMenuOverlay({ actions, selectedIndex }) {
96358
96507
  borderColor: "#2B4C7E",
96359
96508
  paddingX: 1,
96360
96509
  children: [
96361
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { bold: true, color: "#2B4C7E", children: "\u2630 Actions" }),
96510
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { bold: true, color: "#2B4C7E", children: "\u2630 Actions" }),
96362
96511
  actions.map((action, i) => {
96363
96512
  const isSelected = i === selectedIndex;
96364
96513
  const isDanger = !!action.confirm;
96365
96514
  const color = isSelected ? isDanger ? "red" : "#2B4C7E" : isDanger ? "red" : "white";
96366
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { children: [
96367
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
96515
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
96516
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
96368
96517
  Text,
96369
96518
  {
96370
96519
  color,
@@ -96373,7 +96522,7 @@ function ContextMenuOverlay({ actions, selectedIndex }) {
96373
96522
  children: ` ${action.key} `
96374
96523
  }
96375
96524
  ),
96376
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
96525
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
96377
96526
  Text,
96378
96527
  {
96379
96528
  color,
@@ -96384,8 +96533,8 @@ function ContextMenuOverlay({ actions, selectedIndex }) {
96384
96533
  )
96385
96534
  ] }, action.key);
96386
96535
  }),
96387
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { children: "" }),
96388
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "gray", dimColor: true, children: "j/k select Enter run Esc close" })
96536
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "" }),
96537
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "gray", dimColor: true, children: "j/k select Enter run Esc close" })
96389
96538
  ]
96390
96539
  }
96391
96540
  );
@@ -96393,7 +96542,7 @@ function ContextMenuOverlay({ actions, selectedIndex }) {
96393
96542
 
96394
96543
  // src/dashboard/ink/ConfirmOverlay.tsx
96395
96544
  await init_build2();
96396
- var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
96545
+ var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
96397
96546
  var SEVERITY_CONFIG = {
96398
96547
  low: { borderColor: "yellow", icon: "\u26A0", title: "Confirm", warning: "" },
96399
96548
  high: { borderColor: "red", icon: "\u2717", title: "Destructive Action", warning: " This cannot be undone." },
@@ -96402,7 +96551,7 @@ var SEVERITY_CONFIG = {
96402
96551
  function ConfirmOverlay({ message, severity }) {
96403
96552
  const config = SEVERITY_CONFIG[severity];
96404
96553
  const color = config.borderColor;
96405
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
96554
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
96406
96555
  Box_default,
96407
96556
  {
96408
96557
  position: "absolute",
@@ -96414,19 +96563,19 @@ function ConfirmOverlay({ message, severity }) {
96414
96563
  paddingX: 2,
96415
96564
  paddingY: 1,
96416
96565
  children: [
96417
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
96418
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color, bold: true, children: `${config.icon} ` }),
96419
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { bold: true, color, children: config.title })
96566
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { children: [
96567
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color, bold: true, children: `${config.icon} ` }),
96568
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, color, children: config.title })
96420
96569
  ] }),
96421
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "" }),
96422
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { bold: true, children: ` ${message}` }),
96423
- config.warning ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "gray", dimColor: true, children: config.warning }) : null,
96424
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "" }),
96425
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
96426
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { backgroundColor: severity === "low" ? "yellow" : "green", color: severity === "low" ? "black" : "white", bold: true, children: " y Yes " }),
96427
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: " " }),
96428
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { backgroundColor: "red", color: "white", bold: true, children: " n No " }),
96429
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "gray", dimColor: true, children: " or Esc to cancel" })
96570
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }),
96571
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: ` ${message}` }),
96572
+ config.warning ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: "gray", dimColor: true, children: config.warning }) : null,
96573
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: "" }),
96574
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { children: [
96575
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { backgroundColor: severity === "low" ? "yellow" : "green", color: severity === "low" ? "black" : "white", bold: true, children: " y Yes " }),
96576
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: " " }),
96577
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { backgroundColor: "red", color: "white", bold: true, children: " n No " }),
96578
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: "gray", dimColor: true, children: " or Esc to cancel" })
96430
96579
  ] })
96431
96580
  ]
96432
96581
  }
@@ -96436,7 +96585,7 @@ function ConfirmOverlay({ message, severity }) {
96436
96585
  // src/dashboard/ink/ToastNotification.tsx
96437
96586
  var import_react33 = __toESM(require_react(), 1);
96438
96587
  await init_build2();
96439
- var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
96588
+ var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
96440
96589
  var SEVERITY_COLORS2 = {
96441
96590
  error: "red",
96442
96591
  warning: "yellow",
@@ -96464,43 +96613,43 @@ function ToastNotification({ toast }) {
96464
96613
  };
96465
96614
  const icon = SEVERITY_ICONS[toast.severity] || "";
96466
96615
  const textColor = toast.severity === "warning" ? "black" : "white";
96467
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { position: "absolute", marginTop: 0, justifyContent: "flex-end", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { backgroundColor: SEVERITY_COLORS2[toast.severity] || "white", color: textColor, bold: true, children: ` ${icon} ${toast.message} ` }) });
96616
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { position: "absolute", marginTop: 0, justifyContent: "flex-end", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { backgroundColor: SEVERITY_COLORS2[toast.severity] || "white", color: textColor, bold: true, children: ` ${icon} ${toast.message} ` }) });
96468
96617
  }
96469
96618
 
96470
96619
  // src/dashboard/ink/TooSmallOverlay.tsx
96471
96620
  await init_build2();
96472
- var import_sidekick_docker_shared10 = __toESM(require_dist(), 1);
96473
- var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
96621
+ var import_sidekick_docker_shared11 = __toESM(require_dist(), 1);
96622
+ var import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
96474
96623
  function TooSmallOverlay({ columns, rows }) {
96475
96624
  const needWidth = Math.max(0, 60 - columns);
96476
96625
  const needHeight = Math.max(0, 15 - rows);
96477
96626
  const hints = [];
96478
96627
  if (needWidth > 0) hints.push(`${needWidth} col${needWidth > 1 ? "s" : ""} wider`);
96479
96628
  if (needHeight > 0) hints.push(`${needHeight} row${needHeight > 1 ? "s" : ""} taller`);
96480
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", justifyContent: "center", alignItems: "center", height: rows, width: columns, children: [
96481
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared10.BRAND_INLINE}` }),
96482
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
96483
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "yellow", bold: true, children: "Terminal too small" }),
96484
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", children: `${columns}\xD7${rows} \u2192 need ${hints.join(" and ")}` }),
96485
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
96486
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", dimColor: true, children: "Resize to at least 60\xD715 to continue." })
96629
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", justifyContent: "center", alignItems: "center", height: rows, width: columns, children: [
96630
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared11.BRAND_INLINE}` }),
96631
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { children: "" }),
96632
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: "yellow", bold: true, children: "Terminal too small" }),
96633
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: "gray", children: `${columns}\xD7${rows} \u2192 need ${hints.join(" and ")}` }),
96634
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { children: "" }),
96635
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: "gray", dimColor: true, children: "Resize to at least 60\xD715 to continue." })
96487
96636
  ] });
96488
96637
  }
96489
96638
 
96490
96639
  // src/dashboard/ink/ExecOverlay.tsx
96491
96640
  await init_build2();
96492
- var import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
96641
+ var import_jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
96493
96642
  function ExecOverlay({ containerName, outputLines }) {
96494
96643
  const { rows } = useTerminalSize();
96495
96644
  const viewportHeight = Math.max(1, rows - 3);
96496
96645
  const scrollOffset = Math.max(0, outputLines.length - viewportHeight);
96497
96646
  const visibleLines = outputLines.slice(scrollOffset, scrollOffset + viewportHeight);
96498
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, children: [
96499
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { children: [
96500
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { bold: true, color: "#2B4C7E", children: ` Exec: ${containerName} ` }),
96501
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: "gray", children: " (Ctrl+] to detach)" })
96647
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, children: [
96648
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { children: [
96649
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { bold: true, color: "#2B4C7E", children: ` Exec: ${containerName} ` }),
96650
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: "gray", children: " (Ctrl+] to detach)" })
96502
96651
  ] }),
96503
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { flexDirection: "column", flexGrow: 1, children: visibleLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { children: line || " " }, scrollOffset + i)) })
96652
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Box_default, { flexDirection: "column", flexGrow: 1, children: visibleLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { children: line || " " }, scrollOffset + i)) })
96504
96653
  ] });
96505
96654
  }
96506
96655
 
@@ -96574,7 +96723,7 @@ function parseMouseEvent(data) {
96574
96723
  // src/dashboard/ink/mouse/MouseProvider.tsx
96575
96724
  var import_react34 = __toESM(require_react(), 1);
96576
96725
  await init_build2();
96577
- var import_jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
96726
+ var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
96578
96727
  function InputSink() {
96579
96728
  use_input_default(() => {
96580
96729
  });
@@ -96598,47 +96747,47 @@ function MouseProvider({ onMouse, children }) {
96598
96747
  disableMouse();
96599
96748
  };
96600
96749
  }, [onMouse]);
96601
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
96602
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(InputSink, {}),
96750
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
96751
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(InputSink, {}),
96603
96752
  children
96604
96753
  ] });
96605
96754
  }
96606
96755
 
96607
96756
  // src/dashboard/ink/LogFilterOverlay.tsx
96608
96757
  await init_build2();
96609
- var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
96758
+ var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
96610
96759
  function LogFilterOverlay({ filterString, filterMode }) {
96611
96760
  const modeLabel = filterMode === "exact" ? "exact" : "fuzzy";
96612
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Box_default, { position: "absolute", marginTop: 1, marginLeft: 1, children: [
96613
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { backgroundColor: "#2B4C7E", color: "white", bold: true, children: ` Log filter (${modeLabel}): ${filterString}\u2588 ` }),
96614
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: "gray", children: " Tab: toggle mode Enter: apply Esc: clear" })
96761
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { position: "absolute", marginTop: 1, marginLeft: 1, children: [
96762
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { backgroundColor: "#2B4C7E", color: "white", bold: true, children: ` Log filter (${modeLabel}): ${filterString}\u2588 ` }),
96763
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", children: " Tab: toggle mode Enter: apply Esc: clear" })
96615
96764
  ] });
96616
96765
  }
96617
96766
 
96618
96767
  // src/dashboard/ink/VersionOverlay.tsx
96619
96768
  var import_react35 = __toESM(require_react(), 1);
96620
96769
  await init_build2();
96621
- var import_sidekick_docker_shared11 = __toESM(require_dist(), 1);
96622
- var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
96770
+ var import_sidekick_docker_shared12 = __toESM(require_dist(), 1);
96771
+ var import_jsx_runtime16 = __toESM(require_jsx_runtime(), 1);
96623
96772
  function VersionOverlay({ version: version2 }) {
96624
- const phrase = import_react35.default.useMemo(() => (0, import_sidekick_docker_shared11.getRandomPhrase)(), []);
96625
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
96626
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared11.BRAND_INLINE}` }),
96627
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "#2B4C7E", bold: true, children: `${import_sidekick_docker_shared11.BRAND_TAGLINE} v${version2}` }),
96628
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
96629
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
96630
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
96631
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", italic: true, children: ` "${phrase}"` }),
96632
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
96633
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
96634
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
96635
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "Press V or Esc to close" })
96773
+ const phrase = import_react35.default.useMemo(() => (0, import_sidekick_docker_shared12.getRandomPhrase)(), []);
96774
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
96775
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared12.BRAND_INLINE}` }),
96776
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: "#2B4C7E", bold: true, children: `${import_sidekick_docker_shared12.BRAND_TAGLINE} v${version2}` }),
96777
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { children: "" }),
96778
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
96779
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { children: "" }),
96780
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: "gray", italic: true, children: ` "${phrase}"` }),
96781
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { children: "" }),
96782
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
96783
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { children: "" }),
96784
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: "gray", dimColor: true, children: "Press V or Esc to close" })
96636
96785
  ] });
96637
96786
  }
96638
96787
 
96639
96788
  // src/dashboard/ink/SortOverlay.tsx
96640
96789
  await init_build2();
96641
- var import_jsx_runtime16 = __toESM(require_jsx_runtime(), 1);
96790
+ var import_jsx_runtime17 = __toESM(require_jsx_runtime(), 1);
96642
96791
  var SORT_OPTIONS = [
96643
96792
  { field: "state", label: "State (running first)" },
96644
96793
  { field: "name", label: "Name" },
@@ -96649,7 +96798,7 @@ var SORT_OPTIONS = [
96649
96798
  { field: "pids", label: "PIDs" }
96650
96799
  ];
96651
96800
  function SortOverlay({ selectedIndex, currentField, reversed }) {
96652
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
96801
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
96653
96802
  Box_default,
96654
96803
  {
96655
96804
  position: "absolute",
@@ -96660,12 +96809,12 @@ function SortOverlay({ selectedIndex, currentField, reversed }) {
96660
96809
  borderColor: "#2B4C7E",
96661
96810
  paddingX: 1,
96662
96811
  children: [
96663
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { bold: true, color: "#2B4C7E", children: "\u2195 Sort by" }),
96812
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { bold: true, color: "#2B4C7E", children: "\u2195 Sort by" }),
96664
96813
  SORT_OPTIONS.map((opt, i) => {
96665
96814
  const isSelected = i === selectedIndex;
96666
96815
  const isCurrent = opt.field === currentField;
96667
96816
  const indicator = isCurrent ? reversed ? " \u25B2" : " \u25BC" : "";
96668
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
96817
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
96669
96818
  Text,
96670
96819
  {
96671
96820
  color: isSelected ? "#2B4C7E" : isCurrent ? "yellow" : "white",
@@ -96675,15 +96824,15 @@ function SortOverlay({ selectedIndex, currentField, reversed }) {
96675
96824
  }
96676
96825
  ) }, opt.field);
96677
96826
  }),
96678
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { children: "" }),
96679
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: "gray", dimColor: true, children: "j/k select Enter apply R reverse Esc close" })
96827
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { children: "" }),
96828
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Text, { color: "gray", dimColor: true, children: "j/k select Enter apply R reverse Esc close" })
96680
96829
  ]
96681
96830
  }
96682
96831
  );
96683
96832
  }
96684
96833
 
96685
96834
  // src/dashboard/ink/Dashboard.tsx
96686
- var import_sidekick_docker_shared12 = __toESM(require_dist(), 1);
96835
+ var import_sidekick_docker_shared13 = __toESM(require_dist(), 1);
96687
96836
 
96688
96837
  // src/dashboard/ExecManager.ts
96689
96838
  var ExecManager = class {
@@ -96740,7 +96889,7 @@ var ExecManager = class {
96740
96889
  };
96741
96890
 
96742
96891
  // src/dashboard/ink/Dashboard.tsx
96743
- var import_jsx_runtime17 = __toESM(require_jsx_runtime(), 1);
96892
+ var import_jsx_runtime18 = __toESM(require_jsx_runtime(), 1);
96744
96893
  var SORT_FIELDS2 = ["state", "name", "cpu", "mem", "net", "io", "pids"];
96745
96894
  var SIDE_PANEL_WIDTH = 28;
96746
96895
  var SIDE_PANEL_WIDTH_WIDE = 42;
@@ -96763,7 +96912,7 @@ function reducer(state, action) {
96763
96912
  detailScrollPerTab: {}
96764
96913
  };
96765
96914
  case "SELECT_ITEM":
96766
- return { ...state, selectedItemIndex: action.index, detailTabIndex: 0, detailScrollOffset: 0, detailScrollPerTab: {} };
96915
+ return { ...state, selectedItemIndex: action.index, detailTabIndex: 0, detailScrollOffset: 0, detailScrollPerTab: {}, secondaryDetailScrollOffset: 0 };
96767
96916
  case "SET_DETAIL_TAB": {
96768
96917
  const saved = { ...state.detailScrollPerTab, [state.detailTabIndex]: state.detailScrollOffset };
96769
96918
  return { ...state, detailTabIndex: action.index, detailScrollOffset: saved[action.index] ?? 0, detailScrollPerTab: saved };
@@ -96842,6 +96991,22 @@ function reducer(state, action) {
96842
96991
  const next = (state.sortMenuIndex + action.delta + SORT_FIELDS2.length) % SORT_FIELDS2.length;
96843
96992
  return { ...state, sortMenuIndex: next };
96844
96993
  }
96994
+ case "PIN_COMPARE": {
96995
+ const current = state.compareItemIds[action.panelId] ?? null;
96996
+ const newId = current === action.itemId ? null : action.itemId;
96997
+ return {
96998
+ ...state,
96999
+ compareItemIds: { ...state.compareItemIds, [action.panelId]: newId },
97000
+ secondaryDetailScrollOffset: 0
97001
+ };
97002
+ }
97003
+ case "SCROLL_SECONDARY_DETAIL":
97004
+ return { ...state, secondaryDetailScrollOffset: action.offset };
97005
+ case "SCROLL_SECONDARY_DETAIL_DELTA": {
97006
+ const maxOffset = Math.max(0, action.totalLines - action.viewportHeight);
97007
+ const next = Math.max(0, Math.min(state.secondaryDetailScrollOffset + action.delta, maxOffset));
97008
+ return { ...state, secondaryDetailScrollOffset: next };
97009
+ }
96845
97010
  default:
96846
97011
  return state;
96847
97012
  }
@@ -96869,18 +97034,20 @@ var initialState = {
96869
97034
  showAllContainers: true,
96870
97035
  sortField: "state",
96871
97036
  sortReversed: false,
96872
- sortMenuIndex: 0
97037
+ sortMenuIndex: 0,
97038
+ compareItemIds: {},
97039
+ secondaryDetailScrollOffset: 0
96873
97040
  };
96874
97041
  function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecFallback }) {
96875
97042
  const [state, dispatch] = (0, import_react36.useReducer)(reducer, initialState);
96876
97043
  const { columns, rows } = useTerminalSize();
96877
97044
  const toastIdRef = (0, import_react36.useRef)(0);
96878
97045
  const execManagerRef = (0, import_react36.useRef)(null);
96879
- const [phrase, setPhrase] = import_react36.default.useState(() => (0, import_sidekick_docker_shared12.getRandomPhrase)());
97046
+ const [phrase, setPhrase] = import_react36.default.useState(() => (0, import_sidekick_docker_shared13.getRandomPhrase)());
96880
97047
  const phraseTimerRef = (0, import_react36.useRef)(null);
96881
97048
  const rotatePhraseRef = (0, import_react36.useRef)(void 0);
96882
97049
  rotatePhraseRef.current = () => {
96883
- setPhrase((0, import_sidekick_docker_shared12.getRandomPhrase)());
97050
+ setPhrase((0, import_sidekick_docker_shared13.getRandomPhrase)());
96884
97051
  if (phraseTimerRef.current) clearTimeout(phraseTimerRef.current);
96885
97052
  phraseTimerRef.current = setTimeout(() => rotatePhraseRef.current?.(), 7e3);
96886
97053
  };
@@ -97035,14 +97202,21 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97035
97202
  const selectedItem = currentItems[clampedSelection];
97036
97203
  const detailTabs = panel.detailTabs;
97037
97204
  const tabIdx = Math.min(state.detailTabIndex, detailTabs.length - 1);
97205
+ const compareItemId = state.compareItemIds[panel.id] ?? null;
97206
+ (0, import_react36.useEffect)(() => {
97207
+ if (compareItemId && selectedItem?.id === compareItemId) {
97208
+ dispatch({ type: "PIN_COMPARE", panelId: panel.id, itemId: compareItemId });
97209
+ }
97210
+ }, [selectedItem?.id, compareItemId, panel.id]);
97038
97211
  (0, import_react36.useEffect)(() => {
97039
97212
  onViewStateChange?.({
97040
97213
  panelId: panel.id,
97041
97214
  itemId: selectedItem?.id ?? null,
97042
97215
  detailTabIndex: tabIdx,
97043
- sortField: state.sortField
97216
+ sortField: state.sortField,
97217
+ compareItemId
97044
97218
  });
97045
- }, [panel.id, selectedItem?.id, tabIdx, state.sortField]);
97219
+ }, [panel.id, selectedItem?.id, tabIdx, state.sortField, compareItemId]);
97046
97220
  const enrichedMetrics = {
97047
97221
  ...metrics,
97048
97222
  logFilterString: state.logFilterString,
@@ -97063,6 +97237,31 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97063
97237
  dispatch({ type: "SCROLL_DETAIL", offset: detailLines.length - detailViewportHeight });
97064
97238
  }
97065
97239
  }, [shouldAutoScroll, detailLines.length, detailViewportHeight]);
97240
+ const isCompareActive = compareItemId != null && shouldAutoScroll;
97241
+ const secondaryDetailLines = import_react36.default.useMemo(() => {
97242
+ if (!isCompareActive) return [];
97243
+ if (panel.id === "containers") {
97244
+ const logs = enrichedMetrics.secondaryContainerLogs;
97245
+ if (logs.length === 0) return ["Waiting for logs..."];
97246
+ return renderLogLines(logs, state.logFilterString, state.logFilterMode, enrichedMetrics.secondaryLogSeverityCounts);
97247
+ }
97248
+ if (panel.id === "services") {
97249
+ const logs = enrichedMetrics.secondaryComposeLogs;
97250
+ if (logs.length === 0) return ["Waiting for logs..."];
97251
+ return renderLogLines(logs, state.logFilterString, state.logFilterMode);
97252
+ }
97253
+ return [];
97254
+ }, [isCompareActive, panel.id, enrichedMetrics.secondaryContainerLogs, enrichedMetrics.secondaryComposeLogs, enrichedMetrics.secondaryLogSeverityCounts, state.logFilterString, state.logFilterMode]);
97255
+ (0, import_react36.useEffect)(() => {
97256
+ if (isCompareActive && secondaryDetailLines.length > detailViewportHeight) {
97257
+ dispatch({ type: "SCROLL_SECONDARY_DETAIL", offset: secondaryDetailLines.length - detailViewportHeight });
97258
+ }
97259
+ }, [isCompareActive, secondaryDetailLines.length, detailViewportHeight]);
97260
+ const compareItemLabel = import_react36.default.useMemo(() => {
97261
+ if (!compareItemId) return "";
97262
+ const item = currentItems.find((it) => it.id === compareItemId);
97263
+ return item ? item.label.replace(/^[^\w]*/, "").trim() : compareItemId.slice(0, 12);
97264
+ }, [compareItemId, currentItems]);
97066
97265
  const panelActions = panel.getActions();
97067
97266
  const applicableActions = selectedItem ? panelActions.filter((a) => !a.condition || a.condition(selectedItem)) : [];
97068
97267
  const contextActions = state.overlay === "context-menu" ? applicableActions : [];
@@ -97110,27 +97309,35 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97110
97309
  sideScroll,
97111
97310
  addToast,
97112
97311
  removeToast,
97113
- rotatePhrase
97312
+ rotatePhrase,
97313
+ secondaryDetailLineCount: secondaryDetailLines.length
97114
97314
  });
97115
97315
  if (tooSmall) {
97116
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TooSmallOverlay, { columns, rows });
97316
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TooSmallOverlay, { columns, rows });
97117
97317
  }
97118
97318
  const showNormalLayout = state.overlay !== "help" && state.overlay !== "exec" && state.overlay !== "version";
97119
97319
  const panelActionHints = applicableActions.length > 0 ? [{ key: "x", label: "Actions", destructive: false }] : [];
97120
97320
  const contextHint = (() => {
97121
97321
  if (panel.id === "containers") {
97122
97322
  const parts = [];
97123
- if (tabIdx === 0) parts.push("f:Filter", "c:Copy");
97323
+ if (tabIdx === 0) {
97324
+ parts.push("f:Filter", "c:Copy", "m:Compare");
97325
+ }
97124
97326
  parts.push(state.showAllContainers ? "a:All" : "a:Running");
97125
97327
  parts.push(`o:\u2195${state.sortField}`);
97126
97328
  return parts.join(" ");
97127
97329
  }
97330
+ if (panel.id === "services") {
97331
+ const parts = [];
97332
+ if (tabIdx === 1) parts.push("m:Compare");
97333
+ return parts.join(" ");
97334
+ }
97128
97335
  return "";
97129
97336
  })();
97130
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MouseProvider, { onMouse: handleMouse, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Box_default, { flexDirection: "column", height: rows, width: columns, children: [
97131
- showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TabBar, { panels, activeIndex: state.activePanelIndex, layoutMode: state.layoutMode, phrase, panelCounts }),
97132
- showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Box_default, { flexGrow: 1, flexDirection: "row", children: [
97133
- sideWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97337
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MouseProvider, { onMouse: handleMouse, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Box_default, { flexDirection: "column", height: rows, width: columns, children: [
97338
+ showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TabBar, { panels, activeIndex: state.activePanelIndex, layoutMode: state.layoutMode, phrase, panelCounts }),
97339
+ showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Box_default, { flexGrow: 1, flexDirection: "row", children: [
97340
+ sideWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97134
97341
  SideList,
97135
97342
  {
97136
97343
  items: currentItems,
@@ -97143,12 +97350,26 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97143
97350
  filterString: state.filterString || void 0,
97144
97351
  panelId: panel.id,
97145
97352
  totalCount: totalItemCount,
97146
- runningCount: panel.id === "containers" ? runningCount : void 0
97353
+ runningCount: panel.id === "containers" ? runningCount : void 0,
97354
+ compareItemId: compareItemId || void 0
97147
97355
  }
97148
97356
  ),
97149
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, children: [
97150
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DetailTabBar, { tabs: detailTabs, activeIndex: state.detailTabIndex }),
97151
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97357
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, children: [
97358
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DetailTabBar, { tabs: detailTabs, activeIndex: state.detailTabIndex }),
97359
+ isCompareActive ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97360
+ CompareDetailPane,
97361
+ {
97362
+ primaryLines: detailLines,
97363
+ secondaryLines: secondaryDetailLines,
97364
+ primaryScrollOffset: state.detailScrollOffset,
97365
+ secondaryScrollOffset: state.secondaryDetailScrollOffset,
97366
+ viewportHeight: detailViewportHeight,
97367
+ totalWidth: columns - sideWidth,
97368
+ focused: state.focusTarget === "detail",
97369
+ primaryLabel: selectedItem?.label.replace(/^[^\w]*/, "").trim() ?? "",
97370
+ secondaryLabel: compareItemLabel
97371
+ }
97372
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97152
97373
  DetailPane,
97153
97374
  {
97154
97375
  lines: detailLines,
@@ -97159,16 +97380,16 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97159
97380
  )
97160
97381
  ] })
97161
97382
  ] }),
97162
- state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.2.1" }),
97163
- state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VersionOverlay, { version: "0.2.1" }),
97164
- state.overlay === "exec" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97383
+ state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.2.3" }),
97384
+ state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VersionOverlay, { version: "0.2.3" }),
97385
+ state.overlay === "exec" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97165
97386
  ExecOverlay,
97166
97387
  {
97167
97388
  containerName: state.execContainerName,
97168
97389
  outputLines: state.execOutputLines
97169
97390
  }
97170
97391
  ),
97171
- state.overlay !== "exec" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97392
+ state.overlay !== "exec" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97172
97393
  StatusBar,
97173
97394
  {
97174
97395
  daemonConnected: metrics.daemonConnected,
@@ -97177,15 +97398,15 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97177
97398
  filterString: state.filterString,
97178
97399
  containerCount: metrics.containers.length,
97179
97400
  runningCount,
97180
- version: "0.2.1",
97401
+ version: "0.2.3",
97181
97402
  matchCount: state.filterString ? currentItems.length : void 0,
97182
97403
  totalCount: state.filterString ? totalItemCount : void 0,
97183
97404
  lastRefresh: metrics.lastRefresh,
97184
97405
  contextHint
97185
97406
  }
97186
97407
  ),
97187
- state.overlay === "context-menu" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ContextMenuOverlay, { actions: contextActions, selectedIndex: state.contextMenuIndex }),
97188
- state.overlay === "filter" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97408
+ state.overlay === "context-menu" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ContextMenuOverlay, { actions: contextActions, selectedIndex: state.contextMenuIndex }),
97409
+ state.overlay === "filter" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97189
97410
  FilterOverlay,
97190
97411
  {
97191
97412
  filterString: state.filterString,
@@ -97194,15 +97415,15 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97194
97415
  panelTitle: panel.title
97195
97416
  }
97196
97417
  ),
97197
- state.overlay === "log-filter" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97418
+ state.overlay === "log-filter" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97198
97419
  LogFilterOverlay,
97199
97420
  {
97200
97421
  filterString: state.logFilterString,
97201
97422
  filterMode: state.logFilterMode
97202
97423
  }
97203
97424
  ),
97204
- state.overlay === "sort" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SortOverlay, { selectedIndex: state.sortMenuIndex, currentField: state.sortField, reversed: state.sortReversed }),
97205
- state.overlay === "confirm" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
97425
+ state.overlay === "sort" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SortOverlay, { selectedIndex: state.sortMenuIndex, currentField: state.sortField, reversed: state.sortReversed }),
97426
+ state.overlay === "confirm" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
97206
97427
  ConfirmOverlay,
97207
97428
  {
97208
97429
  message: state.confirmMessage,
@@ -97214,7 +97435,7 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97214
97435
  onCancel: () => dispatch({ type: "SET_CONFIRM", action: null, message: "" })
97215
97436
  }
97216
97437
  ),
97217
- state.toasts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ToastNotification, { toast: state.toasts[state.toasts.length - 1] })
97438
+ state.toasts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ToastNotification, { toast: state.toasts[state.toasts.length - 1] })
97218
97439
  ] }) });
97219
97440
  }
97220
97441
 
@@ -97222,7 +97443,7 @@ function Dashboard({ panels, metrics, onViewStateChange, execTriggerRef, onExecF
97222
97443
  async function dashboardAction(_opts, cmd) {
97223
97444
  const globalOpts = cmd.parent?.opts() ?? cmd.opts();
97224
97445
  const socketPath = globalOpts.socket;
97225
- const client = new import_sidekick_docker_shared13.DockerClient(socketPath ? { socketPath } : void 0);
97446
+ const client = new import_sidekick_docker_shared14.DockerClient(socketPath ? { socketPath } : void 0);
97226
97447
  const ok = await client.ping();
97227
97448
  if (!ok) {
97228
97449
  console.error("Error: Cannot connect to Docker daemon. Is Docker running?");
@@ -97231,7 +97452,7 @@ async function dashboardAction(_opts, cmd) {
97231
97452
  const cwd2 = process.cwd();
97232
97453
  const state = new DockerState(client, cwd2);
97233
97454
  await state.refresh();
97234
- const composeClient = new import_sidekick_docker_shared13.ComposeClient();
97455
+ const composeClient = new import_sidekick_docker_shared14.ComposeClient();
97235
97456
  const onAction = () => {
97236
97457
  state.refresh().then(() => scheduleRender()).catch((e) => console.debug("refresh failed:", e));
97237
97458
  };
@@ -97240,6 +97461,8 @@ async function dashboardAction(_opts, cmd) {
97240
97461
  };
97241
97462
  let logFlushTimer = null;
97242
97463
  let composeLogFlushTimer = null;
97464
+ let secondaryLogFlushTimer = null;
97465
+ let secondaryComposeLogFlushTimer = null;
97243
97466
  const logManager = new LogStreamManager(client, () => {
97244
97467
  if (logFlushTimer) return;
97245
97468
  logFlushTimer = setTimeout(() => {
@@ -97259,6 +97482,25 @@ async function dashboardAction(_opts, cmd) {
97259
97482
  scheduleRender();
97260
97483
  };
97261
97484
  let logSeverityCounts = logManager.getSeverityCounts();
97485
+ const secondaryLogManager = new LogStreamManager(client, () => {
97486
+ if (secondaryLogFlushTimer) return;
97487
+ secondaryLogFlushTimer = setTimeout(() => {
97488
+ secondaryLogFlushTimer = null;
97489
+ state.setSecondaryLogs(secondaryLogManager.getLogs());
97490
+ secondaryLogSeverityCounts = secondaryLogManager.getSeverityCounts();
97491
+ scheduleRender();
97492
+ }, 100);
97493
+ });
97494
+ const flushSecondaryLogsNow = () => {
97495
+ if (secondaryLogFlushTimer) {
97496
+ clearTimeout(secondaryLogFlushTimer);
97497
+ secondaryLogFlushTimer = null;
97498
+ }
97499
+ state.setSecondaryLogs(secondaryLogManager.getLogs());
97500
+ secondaryLogSeverityCounts = secondaryLogManager.getSeverityCounts();
97501
+ scheduleRender();
97502
+ };
97503
+ let secondaryLogSeverityCounts = secondaryLogManager.getSeverityCounts();
97262
97504
  const statsManager = new StatsStreamManager(client, state.getStatsCollector(), () => {
97263
97505
  scheduleRender();
97264
97506
  });
@@ -97284,15 +97526,35 @@ async function dashboardAction(_opts, cmd) {
97284
97526
  }
97285
97527
  scheduleRender();
97286
97528
  };
97529
+ const secondaryComposeLogManager = new ComposeLogStreamManager(composeClient, () => {
97530
+ if (secondaryComposeLogFlushTimer) return;
97531
+ secondaryComposeLogFlushTimer = setTimeout(() => {
97532
+ secondaryComposeLogFlushTimer = null;
97533
+ state.setSecondaryComposeLogs(secondaryComposeLogManager.getLogs());
97534
+ scheduleRender();
97535
+ }, 100);
97536
+ });
97537
+ const flushSecondaryComposeLogsNow = () => {
97538
+ if (secondaryComposeLogFlushTimer) {
97539
+ clearTimeout(secondaryComposeLogFlushTimer);
97540
+ secondaryComposeLogFlushTimer = null;
97541
+ }
97542
+ state.setSecondaryComposeLogs(secondaryComposeLogManager.getLogs());
97543
+ scheduleRender();
97544
+ };
97287
97545
  const onViewStateChange = (viewState) => {
97288
- const { panelId, itemId, detailTabIndex, sortField } = viewState;
97546
+ const { panelId, itemId, detailTabIndex, sortField, compareItemId } = viewState;
97289
97547
  const wantsLiveStats = sortField === "cpu" || sortField === "mem" || sortField === "net" || sortField === "io" || sortField === "pids";
97290
97548
  if (panelId === "containers") {
97291
97549
  void logManager.select(detailTabIndex === 0 ? itemId : null);
97292
97550
  void statsManager.select(itemId && (detailTabIndex === 1 || wantsLiveStats) ? itemId : null);
97293
97551
  void composeLogManager.selectCompose(null, null);
97552
+ void secondaryLogManager.select(detailTabIndex === 0 && compareItemId ? compareItemId : null);
97553
+ void secondaryComposeLogManager.selectCompose(null, null);
97294
97554
  flushLogsNow();
97295
97555
  flushComposeLogsNow();
97556
+ flushSecondaryLogsNow();
97557
+ flushSecondaryComposeLogsNow();
97296
97558
  if (itemId && !state.getInspectedEnv(itemId)) {
97297
97559
  client.getContainerEnv(itemId).then((env3) => {
97298
97560
  state.setInspectedEnv(itemId, env3);
@@ -97308,6 +97570,7 @@ async function dashboardAction(_opts, cmd) {
97308
97570
  } else if (panelId === "services" && itemId) {
97309
97571
  void logManager.select(null);
97310
97572
  void statsManager.select(null);
97573
+ void secondaryLogManager.select(null);
97311
97574
  if (detailTabIndex === 1) {
97312
97575
  const parts = itemId.split(":");
97313
97576
  if (parts[0] === "project") {
@@ -97315,17 +97578,34 @@ async function dashboardAction(_opts, cmd) {
97315
97578
  } else if (parts[0] === "service") {
97316
97579
  void composeLogManager.selectCompose(parts[1], parts.slice(2).join(":"));
97317
97580
  }
97581
+ if (compareItemId) {
97582
+ const cParts = compareItemId.split(":");
97583
+ if (cParts[0] === "project") {
97584
+ void secondaryComposeLogManager.selectCompose(cParts.slice(1).join(":"), null);
97585
+ } else if (cParts[0] === "service") {
97586
+ void secondaryComposeLogManager.selectCompose(cParts[1], cParts.slice(2).join(":"));
97587
+ }
97588
+ } else {
97589
+ void secondaryComposeLogManager.selectCompose(null, null);
97590
+ }
97318
97591
  } else {
97319
97592
  void composeLogManager.selectCompose(null, null);
97593
+ void secondaryComposeLogManager.selectCompose(null, null);
97320
97594
  }
97321
97595
  flushLogsNow();
97322
97596
  flushComposeLogsNow();
97597
+ flushSecondaryLogsNow();
97598
+ flushSecondaryComposeLogsNow();
97323
97599
  } else if (panelId === "images") {
97324
97600
  void logManager.select(null);
97325
97601
  void statsManager.select(null);
97326
97602
  void composeLogManager.selectCompose(null, null);
97603
+ void secondaryLogManager.select(null);
97604
+ void secondaryComposeLogManager.selectCompose(null, null);
97327
97605
  flushLogsNow();
97328
97606
  flushComposeLogsNow();
97607
+ flushSecondaryLogsNow();
97608
+ flushSecondaryComposeLogsNow();
97329
97609
  if (itemId && !state.getImageLayers(itemId)) {
97330
97610
  client.getImageHistory(itemId).then((layers) => {
97331
97611
  state.setImageLayers(itemId, layers);
@@ -97336,8 +97616,12 @@ async function dashboardAction(_opts, cmd) {
97336
97616
  void logManager.select(null);
97337
97617
  void statsManager.select(null);
97338
97618
  void composeLogManager.selectCompose(null, null);
97619
+ void secondaryLogManager.select(null);
97620
+ void secondaryComposeLogManager.selectCompose(null, null);
97339
97621
  flushLogsNow();
97340
97622
  flushComposeLogsNow();
97623
+ flushSecondaryLogsNow();
97624
+ flushSecondaryComposeLogsNow();
97341
97625
  }
97342
97626
  };
97343
97627
  const panels = [
@@ -97347,7 +97631,7 @@ async function dashboardAction(_opts, cmd) {
97347
97631
  new VolumesPanel(client, onAction, onError),
97348
97632
  new NetworksPanel(client, onAction, onError)
97349
97633
  ];
97350
- const watcher = new import_sidekick_docker_shared13.EventWatcher(client, {
97634
+ const watcher = new import_sidekick_docker_shared14.EventWatcher(client, {
97351
97635
  onEvent: (event) => {
97352
97636
  state.processEvent(event);
97353
97637
  scheduleRender();
@@ -97392,7 +97676,7 @@ async function dashboardAction(_opts, cmd) {
97392
97676
  const containersPanel = panels[0];
97393
97677
  containersPanel.setOnExec((containerId) => {
97394
97678
  const container = state.getMetrics().containers.find((c) => c.id === containerId);
97395
- const name = container?.name ?? (0, import_sidekick_docker_shared13.shortId)(containerId);
97679
+ const name = container?.name ?? (0, import_sidekick_docker_shared14.shortId)(containerId);
97396
97680
  if (execTriggerRef.current) {
97397
97681
  execTriggerRef.current(containerId, name);
97398
97682
  } else {
@@ -97402,12 +97686,18 @@ async function dashboardAction(_opts, cmd) {
97402
97686
  containersPanel.setOnCopyLogs((text) => {
97403
97687
  copyToClipboard(text);
97404
97688
  });
97689
+ const servicesPanel = panels[1];
97690
+ servicesPanel.setOnCopyLogs((text) => {
97691
+ copyToClipboard(text);
97692
+ });
97405
97693
  let renderTimer = null;
97406
97694
  function getEnrichedMetrics() {
97407
97695
  const m = state.getMetrics();
97408
97696
  m.logSeverityCounts = logSeverityCounts;
97409
97697
  m.logSeverityTimeSeries = logManager.getSeverityTimeSeries();
97410
97698
  m.logTemplates = logManager.getTemplates();
97699
+ m.secondaryLogSeverityCounts = secondaryLogSeverityCounts;
97700
+ m.secondaryLogSeverityTimeSeries = secondaryLogManager.getSeverityTimeSeries();
97411
97701
  return m;
97412
97702
  }
97413
97703
  function scheduleRender() {
@@ -97433,6 +97723,10 @@ async function dashboardAction(_opts, cmd) {
97433
97723
  logManager.dispose();
97434
97724
  } catch {
97435
97725
  }
97726
+ try {
97727
+ secondaryLogManager.dispose();
97728
+ } catch {
97729
+ }
97436
97730
  try {
97437
97731
  statsManager.dispose();
97438
97732
  } catch {
@@ -97441,14 +97735,26 @@ async function dashboardAction(_opts, cmd) {
97441
97735
  composeLogManager.dispose();
97442
97736
  } catch {
97443
97737
  }
97738
+ try {
97739
+ secondaryComposeLogManager.dispose();
97740
+ } catch {
97741
+ }
97444
97742
  try {
97445
97743
  if (logFlushTimer) clearTimeout(logFlushTimer);
97446
97744
  } catch {
97447
97745
  }
97746
+ try {
97747
+ if (secondaryLogFlushTimer) clearTimeout(secondaryLogFlushTimer);
97748
+ } catch {
97749
+ }
97448
97750
  try {
97449
97751
  if (composeLogFlushTimer) clearTimeout(composeLogFlushTimer);
97450
97752
  } catch {
97451
97753
  }
97754
+ try {
97755
+ if (secondaryComposeLogFlushTimer) clearTimeout(secondaryComposeLogFlushTimer);
97756
+ } catch {
97757
+ }
97452
97758
  try {
97453
97759
  clearInterval(refreshInterval);
97454
97760
  } catch {
@@ -97473,9 +97779,9 @@ async function dashboardAction(_opts, cmd) {
97473
97779
  }
97474
97780
 
97475
97781
  // src/commands/ps.ts
97476
- var import_sidekick_docker_shared14 = __toESM(require_dist(), 1);
97782
+ var import_sidekick_docker_shared15 = __toESM(require_dist(), 1);
97477
97783
  async function psAction(opts) {
97478
- const client = new import_sidekick_docker_shared14.DockerClient();
97784
+ const client = new import_sidekick_docker_shared15.DockerClient();
97479
97785
  const ok = await client.ping();
97480
97786
  if (!ok) {
97481
97787
  console.error("Error: Cannot connect to Docker daemon. Is Docker running?");
@@ -97491,7 +97797,7 @@ async function psAction(opts) {
97491
97797
  console.log("-".repeat(100));
97492
97798
  for (const c of containers) {
97493
97799
  const row = [
97494
- (0, import_sidekick_docker_shared14.shortId)(c.id).padEnd(20),
97800
+ (0, import_sidekick_docker_shared15.shortId)(c.id).padEnd(20),
97495
97801
  `${(0, import_sidekick_docker_shared3.stateIcon)(c.state)} ${c.name}`.padEnd(20),
97496
97802
  c.image.padEnd(20),
97497
97803
  formatUptime(c.status).padEnd(20),
@@ -97502,9 +97808,9 @@ async function psAction(opts) {
97502
97808
  }
97503
97809
 
97504
97810
  // src/commands/logs.ts
97505
- var import_sidekick_docker_shared15 = __toESM(require_dist(), 1);
97811
+ var import_sidekick_docker_shared16 = __toESM(require_dist(), 1);
97506
97812
  async function logsAction(container, opts) {
97507
- const client = new import_sidekick_docker_shared15.DockerClient();
97813
+ const client = new import_sidekick_docker_shared16.DockerClient();
97508
97814
  const ok = await client.ping();
97509
97815
  if (!ok) {
97510
97816
  console.error("Error: Cannot connect to Docker daemon. Is Docker running?");
@@ -97515,13 +97821,13 @@ async function logsAction(container, opts) {
97515
97821
  follow: opts.follow ?? true,
97516
97822
  tail: parseInt(opts.tail || "100", 10)
97517
97823
  })) {
97518
- const ts = entry.timestamp ? (0, import_sidekick_docker_shared15.formatTimestampTime)(entry.timestamp) : "";
97824
+ const ts = entry.timestamp ? (0, import_sidekick_docker_shared16.formatTimestampTime)(entry.timestamp) : "";
97519
97825
  const prefix = entry.stream === "stderr" ? "\x1B[31m" : "";
97520
97826
  const reset = entry.stream === "stderr" ? "\x1B[0m" : "";
97521
97827
  console.log(`${prefix}${ts} ${entry.message}${reset}`);
97522
97828
  }
97523
97829
  } catch (err) {
97524
- const msg = (0, import_sidekick_docker_shared15.errorMessage)(err);
97830
+ const msg = (0, import_sidekick_docker_shared16.errorMessage)(err);
97525
97831
  if (msg.includes("no such container") || msg.includes("No such container")) {
97526
97832
  console.error(`Error: Container "${container}" not found.`);
97527
97833
  } else {
@@ -97533,7 +97839,7 @@ async function logsAction(container, opts) {
97533
97839
 
97534
97840
  // src/cli.ts
97535
97841
  var program2 = new Command();
97536
- program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.2.1").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
97842
+ program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.2.3").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
97537
97843
  await dashboardAction(_opts, cmd);
97538
97844
  });
97539
97845
  program2.command("ps").description("List containers").option("-a, --all", "Show all containers (default: running only)", false).action(async (opts) => {