sidekick-docker 0.1.1 → 0.1.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.
@@ -15141,7 +15141,7 @@ var require_load_balancer_child_handler = __commonJS({
15141
15141
  createSubchannel(subchannelAddress, subchannelArgs) {
15142
15142
  return this.parent.channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs);
15143
15143
  }
15144
- updateState(connectivityState, picker, errorMessage) {
15144
+ updateState(connectivityState, picker, errorMessage5) {
15145
15145
  var _a;
15146
15146
  if (this.calledByPendingChild()) {
15147
15147
  if (connectivityState === connectivity_state_1.ConnectivityState.CONNECTING) {
@@ -15153,7 +15153,7 @@ var require_load_balancer_child_handler = __commonJS({
15153
15153
  } else if (!this.calledByCurrentChild()) {
15154
15154
  return;
15155
15155
  }
15156
- this.parent.channelControlHelper.updateState(connectivityState, picker, errorMessage);
15156
+ this.parent.channelControlHelper.updateState(connectivityState, picker, errorMessage5);
15157
15157
  }
15158
15158
  requestReresolution() {
15159
15159
  var _a;
@@ -15379,11 +15379,11 @@ var require_resolving_load_balancer = __commonJS({
15379
15379
  this.updateResolution();
15380
15380
  }
15381
15381
  },
15382
- updateState: (newState, picker, errorMessage) => {
15382
+ updateState: (newState, picker, errorMessage5) => {
15383
15383
  this.latestChildState = newState;
15384
15384
  this.latestChildPicker = picker;
15385
- this.latestChildErrorMessage = errorMessage;
15386
- this.updateState(newState, picker, errorMessage);
15385
+ this.latestChildErrorMessage = errorMessage5;
15386
+ this.updateState(newState, picker, errorMessage5);
15387
15387
  },
15388
15388
  addChannelzChild: channelControlHelper.addChannelzChild.bind(channelControlHelper),
15389
15389
  removeChannelzChild: channelControlHelper.removeChannelzChild.bind(channelControlHelper)
@@ -15447,13 +15447,13 @@ var require_resolving_load_balancer = __commonJS({
15447
15447
  }
15448
15448
  this.backoffTimeout.runOnce();
15449
15449
  }
15450
- updateState(connectivityState, picker, errorMessage) {
15450
+ updateState(connectivityState, picker, errorMessage5) {
15451
15451
  trace((0, uri_parser_1.uriToString)(this.target) + " " + connectivity_state_1.ConnectivityState[this.currentState] + " -> " + connectivity_state_1.ConnectivityState[connectivityState]);
15452
15452
  if (connectivityState === connectivity_state_1.ConnectivityState.IDLE) {
15453
15453
  picker = new picker_1.QueuePicker(this, picker);
15454
15454
  }
15455
15455
  this.currentState = connectivityState;
15456
- this.channelControlHelper.updateState(connectivityState, picker, errorMessage);
15456
+ this.channelControlHelper.updateState(connectivityState, picker, errorMessage5);
15457
15457
  }
15458
15458
  handleResolutionFailure(error) {
15459
15459
  if (this.latestChildState === connectivity_state_1.ConnectivityState.IDLE) {
@@ -28353,13 +28353,13 @@ var require_subchannel = __commonJS({
28353
28353
  * @param newState The state to transition to
28354
28354
  * @returns True if the state changed, false otherwise
28355
28355
  */
28356
- transitionToState(oldStates, newState, errorMessage) {
28356
+ transitionToState(oldStates, newState, errorMessage5) {
28357
28357
  var _a, _b;
28358
28358
  if (oldStates.indexOf(this.connectivityState) === -1) {
28359
28359
  return false;
28360
28360
  }
28361
- if (errorMessage) {
28362
- this.trace(connectivity_state_1.ConnectivityState[this.connectivityState] + " -> " + connectivity_state_1.ConnectivityState[newState] + ' with error "' + errorMessage + '"');
28361
+ if (errorMessage5) {
28362
+ this.trace(connectivity_state_1.ConnectivityState[this.connectivityState] + " -> " + connectivity_state_1.ConnectivityState[newState] + ' with error "' + errorMessage5 + '"');
28363
28363
  } else {
28364
28364
  this.trace(connectivity_state_1.ConnectivityState[this.connectivityState] + " -> " + connectivity_state_1.ConnectivityState[newState]);
28365
28365
  }
@@ -28400,7 +28400,7 @@ var require_subchannel = __commonJS({
28400
28400
  throw new Error(`Invalid state: unknown ConnectivityState ${newState}`);
28401
28401
  }
28402
28402
  for (const listener of this.stateListeners) {
28403
- listener(this, previousState, newState, this.keepaliveTime, errorMessage);
28403
+ listener(this, previousState, newState, this.keepaliveTime, errorMessage5);
28404
28404
  }
28405
28405
  return true;
28406
28406
  }
@@ -29969,18 +29969,18 @@ var require_transport = __commonJS({
29969
29969
  setImmediate(() => {
29970
29970
  if (!reportedError) {
29971
29971
  reportedError = true;
29972
- reject(`${errorMessage.trim()} (${(/* @__PURE__ */ new Date()).toISOString()})`);
29972
+ reject(`${errorMessage5.trim()} (${(/* @__PURE__ */ new Date()).toISOString()})`);
29973
29973
  }
29974
29974
  });
29975
29975
  };
29976
29976
  const errorHandler = (error) => {
29977
29977
  var _a2;
29978
29978
  (_a2 = this.session) === null || _a2 === void 0 ? void 0 : _a2.destroy();
29979
- errorMessage = error.message;
29980
- this.trace("connection failed with error " + errorMessage);
29979
+ errorMessage5 = error.message;
29980
+ this.trace("connection failed with error " + errorMessage5);
29981
29981
  if (!reportedError) {
29982
29982
  reportedError = true;
29983
- reject(`${errorMessage} (${(/* @__PURE__ */ new Date()).toISOString()})`);
29983
+ reject(`${errorMessage5} (${(/* @__PURE__ */ new Date()).toISOString()})`);
29984
29984
  }
29985
29985
  };
29986
29986
  const sessionOptions = {
@@ -30001,7 +30001,7 @@ var require_transport = __commonJS({
30001
30001
  const defaultWin = (_h = (_g = (_f = http2.getDefaultSettings) === null || _f === void 0 ? void 0 : _f.call(http2)) === null || _g === void 0 ? void 0 : _g.initialWindowSize) !== null && _h !== void 0 ? _h : 65535;
30002
30002
  const connWin = options["grpc-node.flow_control_window"];
30003
30003
  this.session = session;
30004
- let errorMessage = "Failed to connect";
30004
+ let errorMessage5 = "Failed to connect";
30005
30005
  let reportedError = false;
30006
30006
  session.unref();
30007
30007
  session.once("remoteSettings", () => {
@@ -35224,8 +35224,8 @@ var require_load_balancer_pick_first = __commonJS({
35224
35224
  this.currentState = connectivity_state_1.ConnectivityState.IDLE;
35225
35225
  this.currentSubchannelIndex = 0;
35226
35226
  this.currentPick = null;
35227
- this.subchannelStateListener = (subchannel, previousState, newState, keepaliveTime, errorMessage) => {
35228
- this.onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage);
35227
+ this.subchannelStateListener = (subchannel, previousState, newState, keepaliveTime, errorMessage5) => {
35228
+ this.onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage5);
35229
35229
  };
35230
35230
  this.pickedSubchannelHealthListener = () => this.calculateAndReportNewState();
35231
35231
  this.stickyTransientFailureMode = false;
@@ -35248,26 +35248,26 @@ var require_load_balancer_pick_first = __commonJS({
35248
35248
  var _a;
35249
35249
  if (this.currentPick) {
35250
35250
  if (this.reportHealthStatus && !this.currentPick.isHealthy()) {
35251
- const errorMessage = `Picked subchannel ${this.currentPick.getAddress()} is unhealthy`;
35251
+ const errorMessage5 = `Picked subchannel ${this.currentPick.getAddress()} is unhealthy`;
35252
35252
  this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
35253
- details: errorMessage
35254
- }), errorMessage);
35253
+ details: errorMessage5
35254
+ }), errorMessage5);
35255
35255
  } else {
35256
35256
  this.updateState(connectivity_state_1.ConnectivityState.READY, new PickFirstPicker(this.currentPick), null);
35257
35257
  }
35258
35258
  } else if (((_a = this.latestAddressList) === null || _a === void 0 ? void 0 : _a.length) === 0) {
35259
- const errorMessage = `No connection established. Last error: ${this.lastError}. Resolution note: ${this.latestResolutionNote}`;
35259
+ const errorMessage5 = `No connection established. Last error: ${this.lastError}. Resolution note: ${this.latestResolutionNote}`;
35260
35260
  this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
35261
- details: errorMessage
35262
- }), errorMessage);
35261
+ details: errorMessage5
35262
+ }), errorMessage5);
35263
35263
  } else if (this.children.length === 0) {
35264
35264
  this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this), null);
35265
35265
  } else {
35266
35266
  if (this.stickyTransientFailureMode) {
35267
- const errorMessage = `No connection established. Last error: ${this.lastError}. Resolution note: ${this.latestResolutionNote}`;
35267
+ const errorMessage5 = `No connection established. Last error: ${this.lastError}. Resolution note: ${this.latestResolutionNote}`;
35268
35268
  this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
35269
- details: errorMessage
35270
- }), errorMessage);
35269
+ details: errorMessage5
35270
+ }), errorMessage5);
35271
35271
  } else {
35272
35272
  this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, new picker_1.QueuePicker(this), null);
35273
35273
  }
@@ -35301,7 +35301,7 @@ var require_load_balancer_pick_first = __commonJS({
35301
35301
  this.currentPick = null;
35302
35302
  }
35303
35303
  }
35304
- onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage) {
35304
+ onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage5) {
35305
35305
  var _a;
35306
35306
  if ((_a = this.currentPick) === null || _a === void 0 ? void 0 : _a.realSubchannelEquals(subchannel)) {
35307
35307
  if (newState !== connectivity_state_1.ConnectivityState.READY) {
@@ -35317,8 +35317,8 @@ var require_load_balancer_pick_first = __commonJS({
35317
35317
  }
35318
35318
  if (newState === connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) {
35319
35319
  child.hasReportedTransientFailure = true;
35320
- if (errorMessage) {
35321
- this.lastError = errorMessage;
35320
+ if (errorMessage5) {
35321
+ this.lastError = errorMessage5;
35322
35322
  }
35323
35323
  this.maybeEnterStickyTransientFailureMode();
35324
35324
  if (index === this.currentSubchannelIndex) {
@@ -35383,10 +35383,10 @@ var require_load_balancer_pick_first = __commonJS({
35383
35383
  clearTimeout(this.connectionDelayTimeout);
35384
35384
  this.calculateAndReportNewState();
35385
35385
  }
35386
- updateState(newState, picker, errorMessage) {
35386
+ updateState(newState, picker, errorMessage5) {
35387
35387
  trace(connectivity_state_1.ConnectivityState[this.currentState] + " -> " + connectivity_state_1.ConnectivityState[newState]);
35388
35388
  this.currentState = newState;
35389
- this.channelControlHelper.updateState(newState, picker, errorMessage);
35389
+ this.channelControlHelper.updateState(newState, picker, errorMessage5);
35390
35390
  }
35391
35391
  resetSubchannelList() {
35392
35392
  for (const child of this.children) {
@@ -35479,10 +35479,10 @@ var require_load_balancer_pick_first = __commonJS({
35479
35479
  this.resolutionNote = resolutionNote;
35480
35480
  this.latestState = connectivity_state_1.ConnectivityState.IDLE;
35481
35481
  const childChannelControlHelper = (0, load_balancer_1.createChildChannelControlHelper)(channelControlHelper, {
35482
- updateState: (connectivityState, picker, errorMessage) => {
35482
+ updateState: (connectivityState, picker, errorMessage5) => {
35483
35483
  this.latestState = connectivityState;
35484
35484
  this.latestPicker = picker;
35485
- channelControlHelper.updateState(connectivityState, picker, errorMessage);
35485
+ channelControlHelper.updateState(connectivityState, picker, errorMessage5);
35486
35486
  }
35487
35487
  });
35488
35488
  this.pickFirstBalancer = new PickFirstLoadBalancer(childChannelControlHelper);
@@ -35981,12 +35981,12 @@ var require_load_balancer_round_robin = __commonJS({
35981
35981
  this.updatesPaused = false;
35982
35982
  this.lastError = null;
35983
35983
  this.childChannelControlHelper = (0, load_balancer_1.createChildChannelControlHelper)(channelControlHelper, {
35984
- updateState: (connectivityState, picker, errorMessage) => {
35984
+ updateState: (connectivityState, picker, errorMessage5) => {
35985
35985
  if (this.currentState === connectivity_state_1.ConnectivityState.READY && connectivityState !== connectivity_state_1.ConnectivityState.READY) {
35986
35986
  this.channelControlHelper.requestReresolution();
35987
35987
  }
35988
- if (errorMessage) {
35989
- this.lastError = errorMessage;
35988
+ if (errorMessage5) {
35989
+ this.lastError = errorMessage5;
35990
35990
  }
35991
35991
  this.calculateAndUpdateState();
35992
35992
  }
@@ -36016,10 +36016,10 @@ var require_load_balancer_round_robin = __commonJS({
36016
36016
  } else if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.CONNECTING) > 0) {
36017
36017
  this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, new picker_1.QueuePicker(this), null);
36018
36018
  } else if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) > 0) {
36019
- const errorMessage = `round_robin: No connection established. Last error: ${this.lastError}`;
36019
+ const errorMessage5 = `round_robin: No connection established. Last error: ${this.lastError}`;
36020
36020
  this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
36021
- details: errorMessage
36022
- }), errorMessage);
36021
+ details: errorMessage5
36022
+ }), errorMessage5);
36023
36023
  } else {
36024
36024
  this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this), null);
36025
36025
  }
@@ -36029,7 +36029,7 @@ var require_load_balancer_round_robin = __commonJS({
36029
36029
  }
36030
36030
  }
36031
36031
  }
36032
- updateState(newState, picker, errorMessage) {
36032
+ updateState(newState, picker, errorMessage5) {
36033
36033
  trace(connectivity_state_1.ConnectivityState[this.currentState] + " -> " + connectivity_state_1.ConnectivityState[newState]);
36034
36034
  if (newState === connectivity_state_1.ConnectivityState.READY) {
36035
36035
  this.currentReadyPicker = picker;
@@ -36037,7 +36037,7 @@ var require_load_balancer_round_robin = __commonJS({
36037
36037
  this.currentReadyPicker = null;
36038
36038
  }
36039
36039
  this.currentState = newState;
36040
- this.channelControlHelper.updateState(newState, picker, errorMessage);
36040
+ this.channelControlHelper.updateState(newState, picker, errorMessage5);
36041
36041
  }
36042
36042
  resetSubchannelList() {
36043
36043
  for (const child of this.children) {
@@ -36059,8 +36059,8 @@ var require_load_balancer_round_robin = __commonJS({
36059
36059
  const endpointList = rotateArray(maybeEndpointList.value, startIndex);
36060
36060
  this.resetSubchannelList();
36061
36061
  if (endpointList.length === 0) {
36062
- const errorMessage = `No addresses resolved. Resolution note: ${resolutionNote}`;
36063
- this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({ details: errorMessage }), errorMessage);
36062
+ const errorMessage5 = `No addresses resolved. Resolution note: ${resolutionNote}`;
36063
+ this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({ details: errorMessage5 }), errorMessage5);
36064
36064
  }
36065
36065
  trace("Connect to endpoint list " + endpointList.map(subchannel_address_1.endpointToString));
36066
36066
  this.updatesPaused = true;
@@ -36347,11 +36347,11 @@ var require_load_balancer_outlier_detection = __commonJS({
36347
36347
  mapEntry === null || mapEntry === void 0 ? void 0 : mapEntry.subchannelWrappers.push(subchannelWrapper);
36348
36348
  return subchannelWrapper;
36349
36349
  },
36350
- updateState: (connectivityState, picker, errorMessage) => {
36350
+ updateState: (connectivityState, picker, errorMessage5) => {
36351
36351
  if (connectivityState === connectivity_state_1.ConnectivityState.READY) {
36352
- channelControlHelper.updateState(connectivityState, new OutlierDetectionPicker(picker, this.isCountingEnabled()), errorMessage);
36352
+ channelControlHelper.updateState(connectivityState, new OutlierDetectionPicker(picker, this.isCountingEnabled()), errorMessage5);
36353
36353
  } else {
36354
- channelControlHelper.updateState(connectivityState, picker, errorMessage);
36354
+ channelControlHelper.updateState(connectivityState, picker, errorMessage5);
36355
36355
  }
36356
36356
  }
36357
36357
  }));
@@ -36918,10 +36918,10 @@ var require_load_balancer_weighted_round_robin = __commonJS({
36918
36918
  } else if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.CONNECTING) > 0) {
36919
36919
  this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, new picker_1.QueuePicker(this), null);
36920
36920
  } else if (this.countChildrenWithState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) > 0) {
36921
- const errorMessage = `weighted_round_robin: No connection established. Last error: ${this.lastError}`;
36921
+ const errorMessage5 = `weighted_round_robin: No connection established. Last error: ${this.lastError}`;
36922
36922
  this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({
36923
- details: errorMessage
36924
- }), errorMessage);
36923
+ details: errorMessage5
36924
+ }), errorMessage5);
36925
36925
  } else {
36926
36926
  this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this), null);
36927
36927
  }
@@ -36931,10 +36931,10 @@ var require_load_balancer_weighted_round_robin = __commonJS({
36931
36931
  }
36932
36932
  }
36933
36933
  }
36934
- updateState(newState, picker, errorMessage) {
36934
+ updateState(newState, picker, errorMessage5) {
36935
36935
  trace(connectivity_state_1.ConnectivityState[this.currentState] + " -> " + connectivity_state_1.ConnectivityState[newState]);
36936
36936
  this.currentState = newState;
36937
- this.channelControlHelper.updateState(newState, picker, errorMessage);
36937
+ this.channelControlHelper.updateState(newState, picker, errorMessage5);
36938
36938
  }
36939
36939
  updateAddressList(maybeEndpointList, lbConfig, options, resolutionNote) {
36940
36940
  var _a, _b;
@@ -36948,8 +36948,8 @@ var require_load_balancer_weighted_round_robin = __commonJS({
36948
36948
  return true;
36949
36949
  }
36950
36950
  if (maybeEndpointList.value.length === 0) {
36951
- const errorMessage = `No addresses resolved. Resolution note: ${resolutionNote}`;
36952
- this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({ details: errorMessage }), errorMessage);
36951
+ const errorMessage5 = `No addresses resolved. Resolution note: ${resolutionNote}`;
36952
+ this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker({ details: errorMessage5 }), errorMessage5);
36953
36953
  return false;
36954
36954
  }
36955
36955
  trace("Connect to endpoint list " + maybeEndpointList.value.map(subchannel_address_1.endpointToString));
@@ -36964,15 +36964,15 @@ var require_load_balancer_weighted_round_robin = __commonJS({
36964
36964
  if (!entry) {
36965
36965
  entry = {
36966
36966
  child: new load_balancer_pick_first_1.LeafLoadBalancer(endpoint, (0, load_balancer_1.createChildChannelControlHelper)(this.channelControlHelper, {
36967
- updateState: (connectivityState, picker, errorMessage) => {
36967
+ updateState: (connectivityState, picker, errorMessage5) => {
36968
36968
  if (this.currentState === connectivity_state_1.ConnectivityState.READY && connectivityState !== connectivity_state_1.ConnectivityState.READY) {
36969
36969
  this.channelControlHelper.requestReresolution();
36970
36970
  }
36971
36971
  if (connectivityState === connectivity_state_1.ConnectivityState.READY) {
36972
36972
  entry.nonEmptySince = null;
36973
36973
  }
36974
- if (errorMessage) {
36975
- this.lastError = errorMessage;
36974
+ if (errorMessage5) {
36975
+ this.lastError = errorMessage5;
36976
36976
  }
36977
36977
  this.calculateAndUpdateState();
36978
36978
  },
@@ -39503,7 +39503,7 @@ var require_DockerClient = __commonJS({
39503
39503
  Object.defineProperty(exports2, "__esModule", { value: true });
39504
39504
  exports2.DockerClient = void 0;
39505
39505
  var dockerode_1 = __importDefault(require_docker());
39506
- var DockerClient5 = class {
39506
+ var DockerClient6 = class {
39507
39507
  docker;
39508
39508
  constructor(opts) {
39509
39509
  this.docker = new dockerode_1.default(opts);
@@ -39740,9 +39740,15 @@ var require_DockerClient = __commonJS({
39740
39740
  const result = await this.docker.pruneNetworks();
39741
39741
  return { networksDeleted: result.NetworksDeleted || [] };
39742
39742
  }
39743
- async *streamEvents(filters) {
39743
+ async *streamEvents(filters, signal) {
39744
39744
  const stream = await this.docker.getEvents({ filters });
39745
+ if (signal) {
39746
+ const onAbort = () => stream.destroy?.();
39747
+ signal.addEventListener("abort", onAbort, { once: true });
39748
+ }
39745
39749
  for await (const chunk of stream) {
39750
+ if (signal?.aborted)
39751
+ break;
39746
39752
  const lines = chunk.toString("utf8").split("\n").filter(Boolean);
39747
39753
  for (const line of lines) {
39748
39754
  let raw;
@@ -39767,7 +39773,7 @@ var require_DockerClient = __commonJS({
39767
39773
  dispose() {
39768
39774
  }
39769
39775
  };
39770
- exports2.DockerClient = DockerClient5;
39776
+ exports2.DockerClient = DockerClient6;
39771
39777
  function mapResourceType(type) {
39772
39778
  switch (type) {
39773
39779
  case "container":
@@ -40087,7 +40093,7 @@ var require_StatsCollector = __commonJS({
40087
40093
  Object.defineProperty(exports2, "__esModule", { value: true });
40088
40094
  exports2.StatsCollector = void 0;
40089
40095
  var DEFAULT_MAX_SAMPLES = 60;
40090
- var StatsCollector2 = class {
40096
+ var StatsCollector3 = class {
40091
40097
  histories = /* @__PURE__ */ new Map();
40092
40098
  maxSamples;
40093
40099
  constructor(maxSamples = DEFAULT_MAX_SAMPLES) {
@@ -40132,7 +40138,520 @@ var require_StatsCollector = __commonJS({
40132
40138
  this.histories.clear();
40133
40139
  }
40134
40140
  };
40135
- exports2.StatsCollector = StatsCollector2;
40141
+ exports2.StatsCollector = StatsCollector3;
40142
+ }
40143
+ });
40144
+
40145
+ // ../sidekick-docker-shared/dist/log/LogTokenizer.js
40146
+ var require_LogTokenizer = __commonJS({
40147
+ "../sidekick-docker-shared/dist/log/LogTokenizer.js"(exports2) {
40148
+ "use strict";
40149
+ Object.defineProperty(exports2, "__esModule", { value: true });
40150
+ exports2.tokenizeLogLine = tokenizeLogLine2;
40151
+ var TOKEN_PATTERN = new RegExp([
40152
+ // HTTP status codes (3-digit, standalone)
40153
+ "(?<status>\\b[2345]\\d{2}\\b)",
40154
+ // HTTP methods
40155
+ "(?<method>\\b(?:GET|HEAD|OPTIONS|TRACE|PUT|POST|PATCH|DELETE)\\b)",
40156
+ // Severity keywords
40157
+ "(?<severity>\\b(?:FATAL|PANIC|ERROR|ERR|WARN|WARNING|INFO|DEBUG|TRACE)\\b)",
40158
+ // URLs (http/https)
40159
+ `(?<url>https?://[^\\s"'\\]}>)]+)`,
40160
+ // IP addresses (v4)
40161
+ "(?<ip>\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(?::\\d+)?\\b)",
40162
+ // ISO timestamps or common log timestamps
40163
+ "(?<timestamp>\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:?\\d{2})?)",
40164
+ // JSON keys: "key":
40165
+ '(?<jsonkey>"[^"]{1,40}"(?=\\s*:))',
40166
+ // State keywords
40167
+ "(?<stateok>\\b(?:success|succeeded|healthy|active|enabled|connected|ready|complete|completed|started|up|online|resolved|passed|ok|done|alive)\\b)",
40168
+ "(?<statefail>\\b(?:fail|failed|failure|unhealthy|inactive|disabled|disconnected|timeout|refused|rejected|crashed|killed|stopped|unreachable|blocked|denied|broken)\\b)",
40169
+ // Unix-style paths
40170
+ "(?<path>(?:^|\\s)/[\\w./-]{2,})"
40171
+ ].join("|"), "gi");
40172
+ function classifyStatus(code) {
40173
+ const n = parseInt(code, 10);
40174
+ if (n >= 200 && n < 300)
40175
+ return "http-status-2xx";
40176
+ if (n >= 300 && n < 400)
40177
+ return "http-status-3xx";
40178
+ if (n >= 400 && n < 500)
40179
+ return "http-status-4xx";
40180
+ return "http-status-5xx";
40181
+ }
40182
+ function classifySeverity(word) {
40183
+ const u = word.toUpperCase();
40184
+ if (u === "FATAL" || u === "PANIC" || u === "ERROR" || u === "ERR")
40185
+ return "severity-error";
40186
+ if (u === "WARN" || u === "WARNING")
40187
+ return "severity-warn";
40188
+ if (u === "INFO")
40189
+ return "severity-info";
40190
+ return "severity-debug";
40191
+ }
40192
+ function classifyMethod(method) {
40193
+ const u = method.toUpperCase();
40194
+ if (u === "GET" || u === "HEAD" || u === "OPTIONS" || u === "TRACE")
40195
+ return "http-method-safe";
40196
+ return "http-method-unsafe";
40197
+ }
40198
+ function tokenizeLogLine2(line) {
40199
+ if (!line)
40200
+ return [{ text: "", type: "plain" }];
40201
+ const tokens = [];
40202
+ let lastIndex = 0;
40203
+ TOKEN_PATTERN.lastIndex = 0;
40204
+ let match;
40205
+ while ((match = TOKEN_PATTERN.exec(line)) !== null) {
40206
+ if (match.index > lastIndex) {
40207
+ tokens.push({ text: line.slice(lastIndex, match.index), type: "plain" });
40208
+ }
40209
+ const groups = match.groups;
40210
+ let type = "plain";
40211
+ if (groups.status !== void 0)
40212
+ type = classifyStatus(groups.status);
40213
+ else if (groups.method !== void 0)
40214
+ type = classifyMethod(groups.method);
40215
+ else if (groups.severity !== void 0)
40216
+ type = classifySeverity(groups.severity);
40217
+ else if (groups.url !== void 0)
40218
+ type = "url";
40219
+ else if (groups.ip !== void 0)
40220
+ type = "ip-address";
40221
+ else if (groups.timestamp !== void 0)
40222
+ type = "timestamp";
40223
+ else if (groups.jsonkey !== void 0)
40224
+ type = "json-key";
40225
+ else if (groups.stateok !== void 0)
40226
+ type = "state-ok";
40227
+ else if (groups.statefail !== void 0)
40228
+ type = "state-fail";
40229
+ else if (groups.path !== void 0)
40230
+ type = "path";
40231
+ tokens.push({ text: match[0], type });
40232
+ lastIndex = match.index + match[0].length;
40233
+ }
40234
+ if (lastIndex < line.length) {
40235
+ tokens.push({ text: line.slice(lastIndex), type: "plain" });
40236
+ }
40237
+ return tokens;
40238
+ }
40239
+ }
40240
+ });
40241
+
40242
+ // ../sidekick-docker-shared/dist/log/LogFilter.js
40243
+ var require_LogFilter = __commonJS({
40244
+ "../sidekick-docker-shared/dist/log/LogFilter.js"(exports2) {
40245
+ "use strict";
40246
+ Object.defineProperty(exports2, "__esModule", { value: true });
40247
+ exports2.exactMatch = exactMatch;
40248
+ exports2.fuzzyMatch = fuzzyMatch;
40249
+ exports2.filterLine = filterLine2;
40250
+ function exactMatch(line, query) {
40251
+ if (!query)
40252
+ return { matched: true, matches: [] };
40253
+ const lower = line.toLowerCase();
40254
+ const queryLower = query.toLowerCase();
40255
+ const matches = [];
40256
+ let pos = 0;
40257
+ while (pos < lower.length) {
40258
+ const idx = lower.indexOf(queryLower, pos);
40259
+ if (idx === -1)
40260
+ break;
40261
+ matches.push({ start: idx, length: queryLower.length });
40262
+ pos = idx + queryLower.length;
40263
+ }
40264
+ return { matched: matches.length > 0, matches };
40265
+ }
40266
+ function fuzzyMatch(line, query) {
40267
+ if (!query)
40268
+ return { matched: true, matches: [] };
40269
+ const words = query.split(/\s+/).filter((w) => w.length > 0);
40270
+ if (words.length === 0)
40271
+ return { matched: true, matches: [] };
40272
+ const lower = line.toLowerCase();
40273
+ const matches = [];
40274
+ for (const word of words) {
40275
+ const wordLower = word.toLowerCase();
40276
+ const idx = lower.indexOf(wordLower);
40277
+ if (idx === -1)
40278
+ return { matched: false, matches: [] };
40279
+ matches.push({ start: idx, length: wordLower.length });
40280
+ }
40281
+ matches.sort((a, b) => a.start - b.start);
40282
+ return { matched: true, matches };
40283
+ }
40284
+ function filterLine2(line, query, mode) {
40285
+ return mode === "exact" ? exactMatch(line, query) : fuzzyMatch(line, query);
40286
+ }
40287
+ }
40288
+ });
40289
+
40290
+ // ../sidekick-docker-shared/dist/log/LogAnalytics.js
40291
+ var require_LogAnalytics = __commonJS({
40292
+ "../sidekick-docker-shared/dist/log/LogAnalytics.js"(exports2) {
40293
+ "use strict";
40294
+ Object.defineProperty(exports2, "__esModule", { value: true });
40295
+ exports2.LogAnalytics = void 0;
40296
+ exports2.detectSeverity = detectSeverity;
40297
+ var SEVERITY_PATTERN = /\b(FATAL|PANIC|ERROR|ERR|WARN|WARNING|INFO|DEBUG|TRACE)\b/i;
40298
+ function detectSeverity(message) {
40299
+ const match = message.substring(0, 200).match(SEVERITY_PATTERN);
40300
+ if (!match)
40301
+ return "other";
40302
+ const keyword = match[1].toUpperCase();
40303
+ if (keyword === "FATAL" || keyword === "PANIC" || keyword === "ERROR" || keyword === "ERR")
40304
+ return "error";
40305
+ if (keyword === "WARN" || keyword === "WARNING")
40306
+ return "warn";
40307
+ if (keyword === "INFO")
40308
+ return "info";
40309
+ return "debug";
40310
+ }
40311
+ var LogAnalytics2 = class {
40312
+ counts = { error: 0, warn: 0, info: 0, debug: 0, other: 0, total: 0 };
40313
+ /**
40314
+ * Classify a log message and update counts.
40315
+ */
40316
+ push(message) {
40317
+ const severity = detectSeverity(message);
40318
+ this.counts[severity]++;
40319
+ this.counts.total++;
40320
+ return severity;
40321
+ }
40322
+ getCounts() {
40323
+ return { ...this.counts };
40324
+ }
40325
+ reset() {
40326
+ this.counts = { error: 0, warn: 0, info: 0, debug: 0, other: 0, total: 0 };
40327
+ }
40328
+ };
40329
+ exports2.LogAnalytics = LogAnalytics2;
40330
+ }
40331
+ });
40332
+
40333
+ // ../sidekick-docker-shared/dist/log/LogParser.js
40334
+ var require_LogParser = __commonJS({
40335
+ "../sidekick-docker-shared/dist/log/LogParser.js"(exports2) {
40336
+ "use strict";
40337
+ Object.defineProperty(exports2, "__esModule", { value: true });
40338
+ exports2.detectFormat = detectFormat;
40339
+ exports2.parseLine = parseLine;
40340
+ var LogAnalytics_1 = require_LogAnalytics();
40341
+ var LEVEL_KEYS = ["level", "severity", "lvl", "log_level", "loglevel"];
40342
+ var MESSAGE_KEYS = ["msg", "message", "text", "body"];
40343
+ var TIMESTAMP_KEYS = ["time", "timestamp", "ts", "datetime", "date", "@timestamp", "t"];
40344
+ function normalizeLevel(value) {
40345
+ const upper = value.toUpperCase().trim();
40346
+ if (["FATAL", "PANIC", "ERROR", "ERR", "CRITICAL", "ALERT", "EMERGENCY", "EMERG"].includes(upper))
40347
+ return "error";
40348
+ if (["WARN", "WARNING"].includes(upper))
40349
+ return "warn";
40350
+ if (["INFO", "INFORMATION", "NOTICE"].includes(upper))
40351
+ return "info";
40352
+ if (["DEBUG", "TRACE", "VERBOSE"].includes(upper))
40353
+ return "debug";
40354
+ return null;
40355
+ }
40356
+ function findField(obj, keys) {
40357
+ for (const key of keys) {
40358
+ if (key in obj && obj[key] != null)
40359
+ return String(obj[key]);
40360
+ }
40361
+ return null;
40362
+ }
40363
+ function parseJson(line) {
40364
+ const trimmed = line.trim();
40365
+ if (!trimmed.startsWith("{"))
40366
+ return null;
40367
+ try {
40368
+ const obj = JSON.parse(trimmed);
40369
+ const levelValue = findField(obj, LEVEL_KEYS);
40370
+ const level = levelValue ? normalizeLevel(levelValue) : null;
40371
+ const message = findField(obj, MESSAGE_KEYS) ?? "";
40372
+ const timestamp = findField(obj, TIMESTAMP_KEYS);
40373
+ const extracted = /* @__PURE__ */ new Set([...LEVEL_KEYS, ...MESSAGE_KEYS, ...TIMESTAMP_KEYS]);
40374
+ const fields = {};
40375
+ for (const [k, v] of Object.entries(obj)) {
40376
+ if (!extracted.has(k) && v != null) {
40377
+ fields[k] = typeof v === "string" ? v : JSON.stringify(v);
40378
+ }
40379
+ }
40380
+ return { format: "json", level, message, timestamp, fields, raw: line };
40381
+ } catch {
40382
+ return null;
40383
+ }
40384
+ }
40385
+ var LOGFMT_PAIR = /([a-zA-Z_][\w.-]*)=(?:"([^"]*)"|(\S*))/g;
40386
+ function parseLogfmt(line) {
40387
+ const pairs = [];
40388
+ let match;
40389
+ LOGFMT_PAIR.lastIndex = 0;
40390
+ while ((match = LOGFMT_PAIR.exec(line)) !== null) {
40391
+ pairs.push([match[1], match[2] ?? match[3]]);
40392
+ }
40393
+ if (pairs.length < 2)
40394
+ return null;
40395
+ const obj = {};
40396
+ for (const [k, v] of pairs)
40397
+ obj[k] = v;
40398
+ const levelValue = findField(obj, LEVEL_KEYS);
40399
+ const level = levelValue ? normalizeLevel(levelValue) : null;
40400
+ const message = findField(obj, MESSAGE_KEYS) ?? "";
40401
+ const timestamp = findField(obj, TIMESTAMP_KEYS);
40402
+ const extracted = /* @__PURE__ */ new Set([...LEVEL_KEYS, ...MESSAGE_KEYS, ...TIMESTAMP_KEYS]);
40403
+ const fields = {};
40404
+ for (const [k, v] of Object.entries(obj)) {
40405
+ if (!extracted.has(k))
40406
+ fields[k] = v;
40407
+ }
40408
+ return { format: "logfmt", level, message, timestamp, fields, raw: line };
40409
+ }
40410
+ function detectFormat(line) {
40411
+ const trimmed = line.trim();
40412
+ if (trimmed.startsWith("{") && trimmed.endsWith("}"))
40413
+ return "json";
40414
+ LOGFMT_PAIR.lastIndex = 0;
40415
+ let pairCount = 0;
40416
+ while (LOGFMT_PAIR.exec(trimmed) !== null) {
40417
+ pairCount++;
40418
+ if (pairCount >= 2)
40419
+ return "logfmt";
40420
+ }
40421
+ return "plain";
40422
+ }
40423
+ function parseLine(line) {
40424
+ const jsonResult = parseJson(line);
40425
+ if (jsonResult)
40426
+ return jsonResult;
40427
+ const logfmtResult = parseLogfmt(line);
40428
+ if (logfmtResult)
40429
+ return logfmtResult;
40430
+ return {
40431
+ format: "plain",
40432
+ level: (0, LogAnalytics_1.detectSeverity)(line) !== "other" ? (0, LogAnalytics_1.detectSeverity)(line) : null,
40433
+ message: line,
40434
+ timestamp: null,
40435
+ fields: {},
40436
+ raw: line
40437
+ };
40438
+ }
40439
+ }
40440
+ });
40441
+
40442
+ // ../sidekick-docker-shared/dist/log/LogSeverityTimeSeries.js
40443
+ var require_LogSeverityTimeSeries = __commonJS({
40444
+ "../sidekick-docker-shared/dist/log/LogSeverityTimeSeries.js"(exports2) {
40445
+ "use strict";
40446
+ Object.defineProperty(exports2, "__esModule", { value: true });
40447
+ exports2.LogSeverityTimeSeries = void 0;
40448
+ var DEFAULT_BUCKET_COUNT = 60;
40449
+ var BUCKET_DURATION_MS = 6e4;
40450
+ var LogSeverityTimeSeries2 = class {
40451
+ buckets = [];
40452
+ maxBuckets;
40453
+ bucketDuration;
40454
+ constructor(maxBuckets = DEFAULT_BUCKET_COUNT, bucketDuration = BUCKET_DURATION_MS) {
40455
+ this.maxBuckets = maxBuckets;
40456
+ this.bucketDuration = bucketDuration;
40457
+ }
40458
+ /**
40459
+ * Record a severity event at the current time.
40460
+ */
40461
+ push(severity, now = Date.now()) {
40462
+ const bucketTs = Math.floor(now / this.bucketDuration) * this.bucketDuration;
40463
+ let bucket = this.buckets.length > 0 ? this.buckets[this.buckets.length - 1] : null;
40464
+ if (!bucket || bucket.timestamp !== bucketTs) {
40465
+ bucket = { timestamp: bucketTs, error: 0, warn: 0, info: 0, debug: 0, other: 0 };
40466
+ this.buckets.push(bucket);
40467
+ if (this.buckets.length > this.maxBuckets) {
40468
+ this.buckets.shift();
40469
+ }
40470
+ }
40471
+ bucket[severity]++;
40472
+ }
40473
+ /**
40474
+ * Get all buckets in chronological order.
40475
+ */
40476
+ getBuckets() {
40477
+ return [...this.buckets];
40478
+ }
40479
+ /**
40480
+ * Get the dominant severity for each bucket (for coloring sparklines).
40481
+ */
40482
+ getDominantSeries() {
40483
+ return this.buckets.map((b) => {
40484
+ const counts = [
40485
+ ["error", b.error],
40486
+ ["warn", b.warn],
40487
+ ["info", b.info],
40488
+ ["debug", b.debug],
40489
+ ["other", b.other]
40490
+ ];
40491
+ const total = counts.reduce((sum, [, c]) => sum + c, 0);
40492
+ const dominant = counts.reduce((best, curr) => curr[1] > best[1] ? curr : best);
40493
+ return { severity: dominant[0], total };
40494
+ });
40495
+ }
40496
+ /**
40497
+ * Get total counts per bucket (for sparkline height).
40498
+ */
40499
+ getTotalSeries() {
40500
+ return this.buckets.map((b) => b.error + b.warn + b.info + b.debug + b.other);
40501
+ }
40502
+ reset() {
40503
+ this.buckets = [];
40504
+ }
40505
+ };
40506
+ exports2.LogSeverityTimeSeries = LogSeverityTimeSeries2;
40507
+ }
40508
+ });
40509
+
40510
+ // ../sidekick-docker-shared/dist/log/LogTemplateEngine.js
40511
+ var require_LogTemplateEngine = __commonJS({
40512
+ "../sidekick-docker-shared/dist/log/LogTemplateEngine.js"(exports2) {
40513
+ "use strict";
40514
+ Object.defineProperty(exports2, "__esModule", { value: true });
40515
+ exports2.LogTemplateEngine = void 0;
40516
+ var VARIABLE_PATTERNS = [
40517
+ /^[0-9a-f]{8,}$/i,
40518
+ // hex strings (hashes, IDs)
40519
+ /^[0-9a-f]{8}-[0-9a-f]{4}-/i,
40520
+ // UUIDs
40521
+ /^\d+\.\d+\.\d+\.\d+/,
40522
+ // IP addresses
40523
+ /^\d{4}-\d{2}-\d{2}/,
40524
+ // dates
40525
+ /^\d+$/,
40526
+ // pure numbers
40527
+ /^\/[\w/.-]+$/,
40528
+ // paths
40529
+ /^https?:\/\//,
40530
+ // URLs
40531
+ /^"[^"]*"$/,
40532
+ // quoted strings
40533
+ /^'[^']*'$/
40534
+ // single-quoted strings
40535
+ ];
40536
+ function isVariable(token) {
40537
+ return VARIABLE_PATTERNS.some((p) => p.test(token));
40538
+ }
40539
+ var LogTemplateEngine2 = class {
40540
+ // Group templates by token count for efficient lookup
40541
+ groups = /* @__PURE__ */ new Map();
40542
+ totalLines = 0;
40543
+ /**
40544
+ * Process a log line and update templates.
40545
+ */
40546
+ push(line) {
40547
+ this.totalLines++;
40548
+ const tokens = tokenize3(line);
40549
+ if (tokens.length === 0)
40550
+ return;
40551
+ const groups = this.groups.get(tokens.length);
40552
+ if (!groups) {
40553
+ this.groups.set(tokens.length, [{ tokens: tokens.map((t) => isVariable(t) ? "<*>" : t), count: 1 }]);
40554
+ return;
40555
+ }
40556
+ let bestMatch = null;
40557
+ let bestScore = 0;
40558
+ for (const group of groups) {
40559
+ let matching = 0;
40560
+ for (let i = 0; i < tokens.length; i++) {
40561
+ if (group.tokens[i] === "<*>" || group.tokens[i] === tokens[i]) {
40562
+ matching++;
40563
+ }
40564
+ }
40565
+ const score = matching / tokens.length;
40566
+ if (score > bestScore && score >= 0.5) {
40567
+ bestScore = score;
40568
+ bestMatch = group;
40569
+ }
40570
+ }
40571
+ if (bestMatch) {
40572
+ bestMatch.count++;
40573
+ for (let i = 0; i < tokens.length; i++) {
40574
+ if (bestMatch.tokens[i] !== "<*>" && bestMatch.tokens[i] !== tokens[i]) {
40575
+ bestMatch.tokens[i] = "<*>";
40576
+ }
40577
+ }
40578
+ } else {
40579
+ groups.push({ tokens: tokens.map((t) => isVariable(t) ? "<*>" : t), count: 1 });
40580
+ }
40581
+ }
40582
+ /**
40583
+ * Get all templates sorted by frequency (most common first).
40584
+ */
40585
+ getTemplates(limit = 20) {
40586
+ const all = [];
40587
+ for (const [tokenCount, groups] of this.groups) {
40588
+ for (const group of groups) {
40589
+ all.push({
40590
+ pattern: group.tokens.join(" "),
40591
+ count: group.count,
40592
+ tokenCount
40593
+ });
40594
+ }
40595
+ }
40596
+ all.sort((a, b) => b.count - a.count);
40597
+ return all.slice(0, limit);
40598
+ }
40599
+ getTotalLines() {
40600
+ return this.totalLines;
40601
+ }
40602
+ reset() {
40603
+ this.groups.clear();
40604
+ this.totalLines = 0;
40605
+ }
40606
+ };
40607
+ exports2.LogTemplateEngine = LogTemplateEngine2;
40608
+ function tokenize3(line) {
40609
+ return line.split(/\s+/).filter((t) => t.length > 0);
40610
+ }
40611
+ }
40612
+ });
40613
+
40614
+ // ../sidekick-docker-shared/dist/reconnect.js
40615
+ var require_reconnect = __commonJS({
40616
+ "../sidekick-docker-shared/dist/reconnect.js"(exports2) {
40617
+ "use strict";
40618
+ Object.defineProperty(exports2, "__esModule", { value: true });
40619
+ exports2.ReconnectScheduler = exports2.MAX_RECONNECT_ATTEMPTS = exports2.MAX_RECONNECT_DELAY = exports2.INITIAL_RECONNECT_DELAY = void 0;
40620
+ exports2.INITIAL_RECONNECT_DELAY = 2e3;
40621
+ exports2.MAX_RECONNECT_DELAY = 3e4;
40622
+ exports2.MAX_RECONNECT_ATTEMPTS = 10;
40623
+ var ReconnectScheduler4 = class {
40624
+ timer = null;
40625
+ attempts = 0;
40626
+ delay = exports2.INITIAL_RECONNECT_DELAY;
40627
+ /** Schedule a reconnect callback with exponential backoff. Returns false if max attempts reached. */
40628
+ schedule(callback) {
40629
+ if (this.attempts >= exports2.MAX_RECONNECT_ATTEMPTS) {
40630
+ return false;
40631
+ }
40632
+ this.clear();
40633
+ this.timer = setTimeout(() => {
40634
+ this.timer = null;
40635
+ this.attempts++;
40636
+ this.delay = Math.min(this.delay * 2, exports2.MAX_RECONNECT_DELAY);
40637
+ callback();
40638
+ }, this.delay);
40639
+ return true;
40640
+ }
40641
+ /** Clear any pending reconnect timer. */
40642
+ clear() {
40643
+ if (this.timer) {
40644
+ clearTimeout(this.timer);
40645
+ this.timer = null;
40646
+ }
40647
+ }
40648
+ /** Reset backoff state (call after a successful stream). */
40649
+ reset() {
40650
+ this.attempts = 0;
40651
+ this.delay = exports2.INITIAL_RECONNECT_DELAY;
40652
+ }
40653
+ };
40654
+ exports2.ReconnectScheduler = ReconnectScheduler4;
40136
40655
  }
40137
40656
  });
40138
40657
 
@@ -40142,13 +40661,13 @@ var require_EventWatcher = __commonJS({
40142
40661
  "use strict";
40143
40662
  Object.defineProperty(exports2, "__esModule", { value: true });
40144
40663
  exports2.EventWatcher = void 0;
40664
+ var reconnect_1 = require_reconnect();
40145
40665
  var EventWatcher2 = class {
40146
40666
  client;
40147
40667
  callbacks;
40148
40668
  running = false;
40149
40669
  abortController = null;
40150
- reconnectDelay = 1e3;
40151
- maxReconnectDelay = 3e4;
40670
+ reconnectDelay = reconnect_1.INITIAL_RECONNECT_DELAY;
40152
40671
  constructor(client, callbacks) {
40153
40672
  this.client = client;
40154
40673
  this.callbacks = callbacks;
@@ -40157,7 +40676,7 @@ var require_EventWatcher = __commonJS({
40157
40676
  if (this.running)
40158
40677
  return;
40159
40678
  this.running = true;
40160
- this.reconnectDelay = 1e3;
40679
+ this.reconnectDelay = reconnect_1.INITIAL_RECONNECT_DELAY;
40161
40680
  this.watch();
40162
40681
  }
40163
40682
  stop() {
@@ -40172,12 +40691,12 @@ var require_EventWatcher = __commonJS({
40172
40691
  while (this.running) {
40173
40692
  try {
40174
40693
  this.abortController = new AbortController();
40175
- for await (const event of this.client.streamEvents()) {
40694
+ for await (const event of this.client.streamEvents(void 0, this.abortController.signal)) {
40176
40695
  if (!this.running)
40177
40696
  break;
40178
40697
  this.callbacks.onEvent(event);
40179
40698
  }
40180
- this.reconnectDelay = 1e3;
40699
+ this.reconnectDelay = reconnect_1.INITIAL_RECONNECT_DELAY;
40181
40700
  } catch (err) {
40182
40701
  if (!this.running)
40183
40702
  break;
@@ -40186,15 +40705,22 @@ var require_EventWatcher = __commonJS({
40186
40705
  if (!this.running)
40187
40706
  break;
40188
40707
  this.callbacks.onReconnect?.();
40189
- await sleep(this.reconnectDelay);
40190
- this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
40191
- }
40708
+ await this.cancellableSleep(this.reconnectDelay);
40709
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, reconnect_1.MAX_RECONNECT_DELAY);
40710
+ }
40711
+ }
40712
+ /** Sleep that resolves early when stop() is called. */
40713
+ cancellableSleep(ms) {
40714
+ return new Promise((resolve) => {
40715
+ const timer = setTimeout(resolve, ms);
40716
+ this.abortController?.signal.addEventListener("abort", () => {
40717
+ clearTimeout(timer);
40718
+ resolve();
40719
+ }, { once: true });
40720
+ });
40192
40721
  }
40193
40722
  };
40194
40723
  exports2.EventWatcher = EventWatcher2;
40195
- function sleep(ms) {
40196
- return new Promise((resolve) => setTimeout(resolve, ms));
40197
- }
40198
40724
  }
40199
40725
  });
40200
40726
 
@@ -40279,6 +40805,18 @@ var require_formatters = __commonJS({
40279
40805
  }
40280
40806
  });
40281
40807
 
40808
+ // ../sidekick-docker-shared/dist/errors.js
40809
+ var require_errors2 = __commonJS({
40810
+ "../sidekick-docker-shared/dist/errors.js"(exports2) {
40811
+ "use strict";
40812
+ Object.defineProperty(exports2, "__esModule", { value: true });
40813
+ exports2.errorMessage = errorMessage5;
40814
+ function errorMessage5(err) {
40815
+ return err instanceof Error ? err.message : String(err);
40816
+ }
40817
+ }
40818
+ });
40819
+
40282
40820
  // ../sidekick-docker-shared/dist/branding.js
40283
40821
  var require_branding = __commonJS({
40284
40822
  "../sidekick-docker-shared/dist/branding.js"(exports2) {
@@ -41003,7 +41541,7 @@ var require_dist = __commonJS({
41003
41541
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
41004
41542
  };
41005
41543
  Object.defineProperty(exports2, "__esModule", { value: true });
41006
- exports2.getRandomPhrase = exports2.BRAND_COLOR_ANSI_RESET = exports2.BRAND_COLOR_ANSI = exports2.BRAND_COLOR_HEX = exports2.BRAND_TAGLINE = exports2.BRAND_INLINE = exports2.stateColor = exports2.truncate = exports2.stateIcon = exports2.formatPorts = exports2.formatMemory = exports2.formatCpu = exports2.formatBytes = exports2.EventWatcher = exports2.StatsCollector = exports2.ComposeFileReader = exports2.ComposeClient = exports2.ComposeDetector = exports2.DockerClient = void 0;
41544
+ exports2.getRandomPhrase = exports2.BRAND_COLOR_ANSI_RESET = exports2.BRAND_COLOR_ANSI = exports2.BRAND_COLOR_HEX = exports2.BRAND_TAGLINE = exports2.BRAND_INLINE = exports2.MAX_LOG_LINES = exports2.errorMessage = exports2.MAX_RECONNECT_ATTEMPTS = exports2.MAX_RECONNECT_DELAY = exports2.INITIAL_RECONNECT_DELAY = exports2.ReconnectScheduler = exports2.stateColor = exports2.truncate = exports2.stateIcon = exports2.formatPorts = exports2.formatMemory = exports2.formatCpu = exports2.formatBytes = exports2.EventWatcher = exports2.LogTemplateEngine = exports2.LogSeverityTimeSeries = exports2.parseLine = exports2.detectFormat = exports2.detectSeverity = exports2.LogAnalytics = exports2.filterLine = exports2.fuzzyMatch = exports2.exactMatch = exports2.tokenizeLogLine = exports2.StatsCollector = exports2.ComposeFileReader = exports2.ComposeClient = exports2.ComposeDetector = exports2.DockerClient = void 0;
41007
41545
  __exportStar(require_types(), exports2);
41008
41546
  var DockerClient_1 = require_DockerClient();
41009
41547
  Object.defineProperty(exports2, "DockerClient", { enumerable: true, get: function() {
@@ -41025,6 +41563,42 @@ var require_dist = __commonJS({
41025
41563
  Object.defineProperty(exports2, "StatsCollector", { enumerable: true, get: function() {
41026
41564
  return StatsCollector_1.StatsCollector;
41027
41565
  } });
41566
+ var LogTokenizer_1 = require_LogTokenizer();
41567
+ Object.defineProperty(exports2, "tokenizeLogLine", { enumerable: true, get: function() {
41568
+ return LogTokenizer_1.tokenizeLogLine;
41569
+ } });
41570
+ var LogFilter_1 = require_LogFilter();
41571
+ Object.defineProperty(exports2, "exactMatch", { enumerable: true, get: function() {
41572
+ return LogFilter_1.exactMatch;
41573
+ } });
41574
+ Object.defineProperty(exports2, "fuzzyMatch", { enumerable: true, get: function() {
41575
+ return LogFilter_1.fuzzyMatch;
41576
+ } });
41577
+ Object.defineProperty(exports2, "filterLine", { enumerable: true, get: function() {
41578
+ return LogFilter_1.filterLine;
41579
+ } });
41580
+ var LogAnalytics_1 = require_LogAnalytics();
41581
+ Object.defineProperty(exports2, "LogAnalytics", { enumerable: true, get: function() {
41582
+ return LogAnalytics_1.LogAnalytics;
41583
+ } });
41584
+ Object.defineProperty(exports2, "detectSeverity", { enumerable: true, get: function() {
41585
+ return LogAnalytics_1.detectSeverity;
41586
+ } });
41587
+ var LogParser_1 = require_LogParser();
41588
+ Object.defineProperty(exports2, "detectFormat", { enumerable: true, get: function() {
41589
+ return LogParser_1.detectFormat;
41590
+ } });
41591
+ Object.defineProperty(exports2, "parseLine", { enumerable: true, get: function() {
41592
+ return LogParser_1.parseLine;
41593
+ } });
41594
+ var LogSeverityTimeSeries_1 = require_LogSeverityTimeSeries();
41595
+ Object.defineProperty(exports2, "LogSeverityTimeSeries", { enumerable: true, get: function() {
41596
+ return LogSeverityTimeSeries_1.LogSeverityTimeSeries;
41597
+ } });
41598
+ var LogTemplateEngine_1 = require_LogTemplateEngine();
41599
+ Object.defineProperty(exports2, "LogTemplateEngine", { enumerable: true, get: function() {
41600
+ return LogTemplateEngine_1.LogTemplateEngine;
41601
+ } });
41028
41602
  var EventWatcher_1 = require_EventWatcher();
41029
41603
  Object.defineProperty(exports2, "EventWatcher", { enumerable: true, get: function() {
41030
41604
  return EventWatcher_1.EventWatcher;
@@ -41051,6 +41625,24 @@ var require_dist = __commonJS({
41051
41625
  Object.defineProperty(exports2, "stateColor", { enumerable: true, get: function() {
41052
41626
  return formatters_1.stateColor;
41053
41627
  } });
41628
+ var reconnect_1 = require_reconnect();
41629
+ Object.defineProperty(exports2, "ReconnectScheduler", { enumerable: true, get: function() {
41630
+ return reconnect_1.ReconnectScheduler;
41631
+ } });
41632
+ Object.defineProperty(exports2, "INITIAL_RECONNECT_DELAY", { enumerable: true, get: function() {
41633
+ return reconnect_1.INITIAL_RECONNECT_DELAY;
41634
+ } });
41635
+ Object.defineProperty(exports2, "MAX_RECONNECT_DELAY", { enumerable: true, get: function() {
41636
+ return reconnect_1.MAX_RECONNECT_DELAY;
41637
+ } });
41638
+ Object.defineProperty(exports2, "MAX_RECONNECT_ATTEMPTS", { enumerable: true, get: function() {
41639
+ return reconnect_1.MAX_RECONNECT_ATTEMPTS;
41640
+ } });
41641
+ var errors_1 = require_errors2();
41642
+ Object.defineProperty(exports2, "errorMessage", { enumerable: true, get: function() {
41643
+ return errors_1.errorMessage;
41644
+ } });
41645
+ exports2.MAX_LOG_LINES = 1e3;
41054
41646
  var branding_1 = require_branding();
41055
41647
  Object.defineProperty(exports2, "BRAND_INLINE", { enumerable: true, get: function() {
41056
41648
  return branding_1.BRAND_INLINE;
@@ -77369,8 +77961,8 @@ var {
77369
77961
  } = import_index.default;
77370
77962
 
77371
77963
  // src/commands/dashboard.ts
77372
- var import_react36 = __toESM(require_react(), 1);
77373
- var import_sidekick_docker_shared7 = __toESM(require_dist(), 1);
77964
+ var import_react37 = __toESM(require_react(), 1);
77965
+ var import_sidekick_docker_shared13 = __toESM(require_dist(), 1);
77374
77966
  import { spawnSync } from "child_process";
77375
77967
 
77376
77968
  // src/dashboard/DockerState.ts
@@ -77426,8 +78018,7 @@ var DockerState = class {
77426
78018
  case "image":
77427
78019
  case "volume":
77428
78020
  case "network":
77429
- this.refresh().catch(() => {
77430
- });
78021
+ this.refresh().catch((e) => console.debug("refresh failed:", e));
77431
78022
  break;
77432
78023
  }
77433
78024
  }
@@ -77439,11 +78030,10 @@ var DockerState = class {
77439
78030
  case "unpause": {
77440
78031
  const existing = this.containers.find((c) => c.id === resourceId);
77441
78032
  if (existing) {
77442
- existing.state = type === "unpause" ? "running" : "running";
78033
+ existing.state = "running";
77443
78034
  existing.status = "Up just now";
77444
78035
  }
77445
- this.refresh().catch(() => {
77446
- });
78036
+ this.refresh().catch((e) => console.debug("refresh failed:", e));
77447
78037
  break;
77448
78038
  }
77449
78039
  case "stop":
@@ -77467,13 +78057,11 @@ var DockerState = class {
77467
78057
  this.statsCollector.remove(resourceId);
77468
78058
  break;
77469
78059
  case "create":
77470
- this.refresh().catch(() => {
77471
- });
78060
+ this.refresh().catch((e) => console.debug("refresh failed:", e));
77472
78061
  break;
77473
78062
  default:
77474
78063
  if (name) {
77475
- this.refresh().catch(() => {
77476
- });
78064
+ this.refresh().catch((e) => console.debug("refresh failed:", e));
77477
78065
  }
77478
78066
  break;
77479
78067
  }
@@ -77484,7 +78072,7 @@ var DockerState = class {
77484
78072
  }
77485
78073
  appendLog(entry) {
77486
78074
  this.selectedLogs.push(entry);
77487
- if (this.selectedLogs.length > 1e3) {
78075
+ if (this.selectedLogs.length > import_sidekick_docker_shared.MAX_LOG_LINES) {
77488
78076
  this.selectedLogs.shift();
77489
78077
  }
77490
78078
  }
@@ -77493,7 +78081,7 @@ var DockerState = class {
77493
78081
  }
77494
78082
  appendComposeLog(entry) {
77495
78083
  this.selectedComposeLogs.push(entry);
77496
- if (this.selectedComposeLogs.length > 1e3) {
78084
+ if (this.selectedComposeLogs.length > import_sidekick_docker_shared.MAX_LOG_LINES) {
77497
78085
  this.selectedComposeLogs.shift();
77498
78086
  }
77499
78087
  }
@@ -77521,13 +78109,27 @@ var DockerState = class {
77521
78109
  selectedContainerLogs: [...this.selectedLogs],
77522
78110
  selectedComposeLogs: [...this.selectedComposeLogs],
77523
78111
  lastRefresh: this.lastRefresh,
77524
- daemonConnected: this.daemonConnected
78112
+ daemonConnected: this.daemonConnected,
78113
+ logFilterString: "",
78114
+ logFilterMode: "exact",
78115
+ logSeverityCounts: null,
78116
+ logSeverityTimeSeries: [],
78117
+ logTemplates: []
77525
78118
  };
77526
78119
  }
77527
78120
  };
77528
78121
 
78122
+ // src/dashboard/panels/ContainersPanel.ts
78123
+ var import_sidekick_docker_shared4 = __toESM(require_dist(), 1);
78124
+
78125
+ // src/dashboard/panels/types.ts
78126
+ var defaultOnError = (msg) => {
78127
+ console.debug(msg);
78128
+ };
78129
+
77529
78130
  // src/formatters.ts
77530
78131
  var import_sidekick_docker_shared2 = __toESM(require_dist(), 1);
78132
+ var import_sidekick_docker_shared3 = __toESM(require_dist(), 1);
77531
78133
  function formatUptime(status) {
77532
78134
  return status;
77533
78135
  }
@@ -77574,6 +78176,23 @@ function sparkline(values, width = 40) {
77574
78176
  return bars[idx];
77575
78177
  }).join("");
77576
78178
  }
78179
+ var SEVERITY_COLORS = {
78180
+ error: (s) => `\x1B[31m${s}\x1B[39m`,
78181
+ warn: (s) => `\x1B[33m${s}\x1B[39m`,
78182
+ info: (s) => `\x1B[38;2;43;76;126m${s}\x1B[39m`,
78183
+ debug: (s) => `\x1B[90m${s}\x1B[39m`,
78184
+ other: (s) => `\x1B[90m${s}\x1B[39m`
78185
+ };
78186
+ function severitySparkline(series, width = 40) {
78187
+ if (series.length === 0) return "";
78188
+ const recent = series.slice(-width);
78189
+ const max = Math.max(...recent.map((s) => s.total), 1);
78190
+ const bars = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
78191
+ return recent.map((s) => {
78192
+ const idx = Math.min(Math.floor(s.total / max * (bars.length - 1)), bars.length - 1);
78193
+ return SEVERITY_COLORS[s.severity](bars[idx]);
78194
+ }).join("");
78195
+ }
77577
78196
  var ansi = {
77578
78197
  gray: (s) => `\x1B[90m${s}\x1B[39m`,
77579
78198
  red: (s) => `\x1B[31m${s}\x1B[39m`,
@@ -77583,26 +78202,64 @@ var ansi = {
77583
78202
  dim: (s) => `\x1B[2m${s}\x1B[22m`,
77584
78203
  bold: (s) => `\x1B[1m${s}\x1B[22m`
77585
78204
  };
77586
- function detectLogLevel(msg) {
77587
- const upper = msg.substring(0, 200).toUpperCase();
77588
- if (/\b(FATAL|PANIC)\b/.test(upper)) return ansi.red;
77589
- if (/\b(ERROR|ERR)\b/.test(upper)) return ansi.red;
77590
- if (/\b(WARN|WARNING)\b/.test(upper)) return ansi.yellow;
77591
- if (/\b(INFO)\b/.test(upper)) return ansi.brand;
77592
- if (/\b(DEBUG|TRACE)\b/.test(upper)) return ansi.gray;
77593
- return null;
78205
+ var TOKEN_COLORS = {
78206
+ "severity-error": ansi.red,
78207
+ "severity-warn": ansi.yellow,
78208
+ "severity-info": ansi.brand,
78209
+ "severity-debug": ansi.gray,
78210
+ "http-method-safe": ansi.green,
78211
+ "http-method-unsafe": ansi.yellow,
78212
+ "http-status-2xx": ansi.green,
78213
+ "http-status-3xx": ansi.brand,
78214
+ "http-status-4xx": ansi.yellow,
78215
+ "http-status-5xx": ansi.red,
78216
+ "url": ansi.brand,
78217
+ "ip-address": ansi.dim,
78218
+ "timestamp": (s) => ansi.dim(ansi.gray(s)),
78219
+ "json-key": ansi.brand,
78220
+ "state-ok": ansi.green,
78221
+ "state-fail": ansi.red,
78222
+ "path": ansi.dim,
78223
+ "number": null,
78224
+ "plain": null
78225
+ };
78226
+ function colorizeTokens(message) {
78227
+ const tokens = (0, import_sidekick_docker_shared2.tokenizeLogLine)(message);
78228
+ return tokens.map((t) => {
78229
+ const colorFn = TOKEN_COLORS[t.type];
78230
+ return colorFn ? colorFn(t.text) : t.text;
78231
+ }).join("");
77594
78232
  }
77595
- function colorizeLogEntry(entry) {
78233
+ function colorizeLogEntry(entry, filterMatches) {
77596
78234
  const ts = entry.timestamp ? entry.timestamp.toISOString().substring(11, 23) : "";
77597
78235
  const tsColored = ts ? ansi.dim(ansi.gray(ts)) + " " : "";
77598
78236
  if (entry.stream === "stderr") {
77599
- return tsColored + ansi.red(entry.message);
78237
+ const msg = filterMatches ? highlightMatches(entry.message, filterMatches, ansi.red) : ansi.red(entry.message);
78238
+ return tsColored + msg;
77600
78239
  }
77601
- const levelColor = detectLogLevel(entry.message);
77602
- if (levelColor) {
77603
- return tsColored + levelColor(entry.message);
78240
+ if (filterMatches && filterMatches.length > 0) {
78241
+ return tsColored + highlightMatches(entry.message, filterMatches);
77604
78242
  }
77605
- return tsColored + entry.message;
78243
+ return tsColored + colorizeTokens(entry.message);
78244
+ }
78245
+ function highlightMatches(text, matches, baseFn) {
78246
+ if (matches.length === 0) return baseFn ? baseFn(text) : colorizeTokens(text);
78247
+ const sorted = [...matches].sort((a, b) => a.start - b.start);
78248
+ const parts = [];
78249
+ let pos = 0;
78250
+ for (const m of sorted) {
78251
+ if (m.start > pos) {
78252
+ const segment = text.slice(pos, m.start);
78253
+ parts.push(baseFn ? baseFn(segment) : segment);
78254
+ }
78255
+ parts.push(`\x1B[44;37m${text.slice(m.start, m.start + m.length)}\x1B[49;39m`);
78256
+ pos = m.start + m.length;
78257
+ }
78258
+ if (pos < text.length) {
78259
+ const segment = text.slice(pos);
78260
+ parts.push(baseFn ? baseFn(segment) : segment);
78261
+ }
78262
+ return parts.join("");
77606
78263
  }
77607
78264
  function colorizeEnvLine(line) {
77608
78265
  const eqIdx = line.indexOf("=");
@@ -77657,10 +78314,12 @@ var ContainersPanel = class {
77657
78314
  shortcutKey = 1;
77658
78315
  client;
77659
78316
  onAction;
78317
+ onError;
77660
78318
  onExec;
77661
- constructor(client, onAction) {
78319
+ constructor(client, onAction, onError) {
77662
78320
  this.client = client;
77663
78321
  this.onAction = onAction;
78322
+ this.onError = onError ?? defaultOnError;
77664
78323
  }
77665
78324
  setOnExec(handler) {
77666
78325
  this.onExec = handler;
@@ -77671,7 +78330,38 @@ var ContainersPanel = class {
77671
78330
  render: (item, metrics) => {
77672
78331
  const logs = metrics.selectedContainerLogs;
77673
78332
  if (logs.length === 0) return "No logs available. Select a container to view logs.";
77674
- return logs.map((l) => colorizeLogEntry(l)).join("\n");
78333
+ const lines = [];
78334
+ if (metrics.logSeverityCounts && metrics.logSeverityCounts.total > 0) {
78335
+ const c = metrics.logSeverityCounts;
78336
+ const parts = [];
78337
+ if (c.error > 0) parts.push(`\x1B[31mE:${c.error}\x1B[39m`);
78338
+ if (c.warn > 0) parts.push(`\x1B[33mW:${c.warn}\x1B[39m`);
78339
+ if (c.info > 0) parts.push(`\x1B[38;2;43;76;126mI:${c.info}\x1B[39m`);
78340
+ if (c.debug > 0) parts.push(`\x1B[90mD:${c.debug}\x1B[39m`);
78341
+ if (parts.length > 0) lines.push(parts.join(" "));
78342
+ }
78343
+ const query = metrics.logFilterString;
78344
+ const mode = metrics.logFilterMode;
78345
+ if (query) {
78346
+ let matchCount = 0;
78347
+ for (const l of logs) {
78348
+ const result = (0, import_sidekick_docker_shared4.filterLine)(l.message, query, mode);
78349
+ if (result.matched) {
78350
+ matchCount++;
78351
+ lines.push(colorizeLogEntry(l, result.matches));
78352
+ }
78353
+ }
78354
+ if (lines.length <= 1) {
78355
+ lines.push(`\x1B[90mNo logs matching "${query}"\x1B[39m`);
78356
+ } else {
78357
+ lines.splice(1, 0, `\x1B[90m${matchCount} matches (f to filter, Tab to toggle mode)\x1B[39m`);
78358
+ }
78359
+ } else {
78360
+ for (const l of logs) {
78361
+ lines.push(colorizeLogEntry(l));
78362
+ }
78363
+ }
78364
+ return lines.join("\n");
77675
78365
  },
77676
78366
  autoScrollBottom: true
77677
78367
  },
@@ -77694,14 +78384,19 @@ var ContainersPanel = class {
77694
78384
  if (cpuSeries.length > 1) {
77695
78385
  lines.push(` ${coloredSparkline(cpuSeries, "cpu")}`);
77696
78386
  }
77697
- lines.push(colorizeDetailKey(`Memory: ${(0, import_sidekick_docker_shared2.formatMemory)(latest.memoryUsage, latest.memoryLimit)} (${colorizePercent(latest.memoryPercent)})`));
78387
+ lines.push(colorizeDetailKey(`Memory: ${(0, import_sidekick_docker_shared3.formatMemory)(latest.memoryUsage, latest.memoryLimit)} (${colorizePercent(latest.memoryPercent)})`));
77698
78388
  if (memSeries.length > 1) {
77699
78389
  lines.push(` ${coloredSparkline(memSeries, "memory")}`);
77700
78390
  }
77701
78391
  lines.push(
77702
- colorizeDetailKey(`Net: \u25BC ${(0, import_sidekick_docker_shared2.formatBytes)(latest.networkRx)} \u25B2 ${(0, import_sidekick_docker_shared2.formatBytes)(latest.networkTx)}`),
78392
+ colorizeDetailKey(`Net: \u25BC ${(0, import_sidekick_docker_shared3.formatBytes)(latest.networkRx)} \u25B2 ${(0, import_sidekick_docker_shared3.formatBytes)(latest.networkTx)}`),
77703
78393
  colorizeDetailKey(`PIDs: ${latest.pids}`)
77704
78394
  );
78395
+ if (metrics.logSeverityTimeSeries.length > 1) {
78396
+ lines.push("");
78397
+ lines.push(sectionHeader("Log Activity"));
78398
+ lines.push(` ${severitySparkline(metrics.logSeverityTimeSeries)}`);
78399
+ }
77705
78400
  return lines.join("\n");
77706
78401
  }
77707
78402
  },
@@ -77731,7 +78426,7 @@ var ContainersPanel = class {
77731
78426
  colorizeDetailKey(` Created: ${c.created.toLocaleString()}`),
77732
78427
  "",
77733
78428
  sectionHeader("Network"),
77734
- colorizeDetailKey(` Ports: ${(0, import_sidekick_docker_shared2.formatPorts)(c.ports)}`)
78429
+ colorizeDetailKey(` Ports: ${(0, import_sidekick_docker_shared3.formatPorts)(c.ports)}`)
77735
78430
  ];
77736
78431
  if (c.composeProject) {
77737
78432
  lines.push("", sectionHeader("Compose"));
@@ -77740,19 +78435,34 @@ var ContainersPanel = class {
77740
78435
  }
77741
78436
  return lines.join("\n");
77742
78437
  }
78438
+ },
78439
+ {
78440
+ label: "Patterns",
78441
+ render: (_item, metrics) => {
78442
+ const templates = metrics.logTemplates;
78443
+ if (templates.length === 0) return "No log patterns detected yet. Patterns will appear as logs stream in.";
78444
+ const lines = [sectionHeader("Top Log Patterns"), ""];
78445
+ for (let i = 0; i < templates.length; i++) {
78446
+ const t = templates[i];
78447
+ const count = `\x1B[33m${String(t.count).padStart(5)}\x1B[39m`;
78448
+ const pattern = t.pattern.replace(/<\*>/g, "\x1B[90m<*>\x1B[39m");
78449
+ lines.push(`${count} ${pattern}`);
78450
+ }
78451
+ return lines.join("\n");
78452
+ }
77743
78453
  }
77744
78454
  ];
77745
78455
  getItems(metrics) {
77746
78456
  return metrics.containers.map((c) => {
77747
78457
  const uptime = compactUptime(c.status);
77748
78458
  const portHint = c.state === "running" && c.ports.length > 0 ? `:${c.ports[0].hostPort || c.ports[0].containerPort}` : "";
77749
- const namePart = portHint ? `${(0, import_sidekick_docker_shared2.truncate)(c.name, 16)} ${portHint}` : (0, import_sidekick_docker_shared2.truncate)(c.name, 20);
78459
+ const namePart = portHint ? `${(0, import_sidekick_docker_shared3.truncate)(c.name, 16)} ${portHint}` : (0, import_sidekick_docker_shared3.truncate)(c.name, 20);
77750
78460
  return {
77751
78461
  id: c.id,
77752
- label: `${(0, import_sidekick_docker_shared2.stateIcon)(c.state)} ${namePart}`,
78462
+ label: `${(0, import_sidekick_docker_shared3.stateIcon)(c.state)} ${namePart}`,
77753
78463
  sortKey: c.state === "running" ? 0 : 1,
77754
78464
  data: c,
77755
- iconColor: (0, import_sidekick_docker_shared2.stateColor)(c.state),
78465
+ iconColor: (0, import_sidekick_docker_shared3.stateColor)(c.state),
77756
78466
  rightLabel: uptime,
77757
78467
  rightColor: c.state === "running" ? "green" : "gray"
77758
78468
  };
@@ -77765,8 +78475,7 @@ var ContainersPanel = class {
77765
78475
  label: "Start",
77766
78476
  handler: (item) => {
77767
78477
  const c = item.data;
77768
- this.client.startContainer(c.id).then(() => this.onAction()).catch(() => {
77769
- });
78478
+ this.client.startContainer(c.id).then(() => this.onAction()).catch((e) => this.onError(String(e)));
77770
78479
  },
77771
78480
  condition: (item) => item.data.state !== "running"
77772
78481
  },
@@ -77775,8 +78484,7 @@ var ContainersPanel = class {
77775
78484
  label: "Stop",
77776
78485
  handler: (item) => {
77777
78486
  const c = item.data;
77778
- this.client.stopContainer(c.id).then(() => this.onAction()).catch(() => {
77779
- });
78487
+ this.client.stopContainer(c.id).then(() => this.onAction()).catch((e) => this.onError(String(e)));
77780
78488
  },
77781
78489
  condition: (item) => item.data.state === "running"
77782
78490
  },
@@ -77785,8 +78493,7 @@ var ContainersPanel = class {
77785
78493
  label: "Restart",
77786
78494
  handler: (item) => {
77787
78495
  const c = item.data;
77788
- this.client.restartContainer(c.id).then(() => this.onAction()).catch(() => {
77789
- });
78496
+ this.client.restartContainer(c.id).then(() => this.onAction()).catch((e) => this.onError(String(e)));
77790
78497
  },
77791
78498
  condition: (item) => item.data.state === "running"
77792
78499
  },
@@ -77797,8 +78504,7 @@ var ContainersPanel = class {
77797
78504
  confirmMessage: "Remove this container?",
77798
78505
  handler: (item) => {
77799
78506
  const c = item.data;
77800
- this.client.removeContainer(c.id, true).then(() => this.onAction()).catch(() => {
77801
- });
78507
+ this.client.removeContainer(c.id, true).then(() => this.onAction()).catch((e) => this.onError(String(e)));
77802
78508
  }
77803
78509
  },
77804
78510
  {
@@ -77819,16 +78525,21 @@ var ContainersPanel = class {
77819
78525
  };
77820
78526
 
77821
78527
  // src/dashboard/panels/ServicesPanel.ts
78528
+ function getProjectName(d) {
78529
+ return d.type === "project" ? d.project.name : d.service.projectName;
78530
+ }
77822
78531
  var ServicesPanel = class {
77823
78532
  id = "services";
77824
78533
  title = "Services";
77825
78534
  shortcutKey = 2;
77826
78535
  composeClient;
77827
78536
  onAction;
78537
+ onError;
77828
78538
  cwd;
77829
- constructor(composeClient, onAction, cwd2) {
78539
+ constructor(composeClient, onAction, cwd2, onError) {
77830
78540
  this.composeClient = composeClient;
77831
78541
  this.onAction = onAction;
78542
+ this.onError = onError ?? defaultOnError;
77832
78543
  this.cwd = cwd2;
77833
78544
  }
77834
78545
  detailTabs = [
@@ -77844,7 +78555,7 @@ var ServicesPanel = class {
77844
78555
  colorizeDetailKey(`Status: ${colorizeState(p.status)}`),
77845
78556
  colorizeDetailKey(`Services: ${p.services.length}`),
77846
78557
  "",
77847
- ...p.services.map((s2) => ` ${(0, import_sidekick_docker_shared2.stateIcon)(s2.state)} ${s2.name} (${s2.image})`)
78558
+ ...p.services.map((s2) => ` ${(0, import_sidekick_docker_shared3.stateIcon)(s2.state)} ${s2.name} (${s2.image})`)
77848
78559
  ];
77849
78560
  return lines.join("\n");
77850
78561
  }
@@ -77886,13 +78597,13 @@ var ServicesPanel = class {
77886
78597
  rightColor: projectIconColor
77887
78598
  });
77888
78599
  for (const service of project.services) {
77889
- const icon = (0, import_sidekick_docker_shared2.stateIcon)(service.state);
78600
+ const icon = (0, import_sidekick_docker_shared3.stateIcon)(service.state);
77890
78601
  items.push({
77891
78602
  id: `service:${project.name}:${service.name}`,
77892
- label: ` ${icon} ${(0, import_sidekick_docker_shared2.truncate)(service.name, 18)}`,
78603
+ label: ` ${icon} ${(0, import_sidekick_docker_shared3.truncate)(service.name, 18)}`,
77893
78604
  sortKey: sortKey++,
77894
78605
  data: { type: "service", service },
77895
- iconColor: (0, import_sidekick_docker_shared2.stateColor)(service.state)
78606
+ iconColor: (0, import_sidekick_docker_shared3.stateColor)(service.state)
77896
78607
  });
77897
78608
  }
77898
78609
  }
@@ -77913,9 +78624,7 @@ var ServicesPanel = class {
77913
78624
  label: "Up",
77914
78625
  handler: (item) => {
77915
78626
  const d = item.data;
77916
- const projectName = d.type === "project" ? d.project.name : d.service.projectName;
77917
- this.composeClient.up(projectName, this.cwd).then(() => this.onAction()).catch(() => {
77918
- });
78627
+ this.composeClient.up(getProjectName(d), this.cwd).then(() => this.onAction()).catch((e) => this.onError(String(e)));
77919
78628
  },
77920
78629
  condition: (item) => item.data !== null
77921
78630
  },
@@ -77926,9 +78635,7 @@ var ServicesPanel = class {
77926
78635
  confirmMessage: "Bring down this compose project?",
77927
78636
  handler: (item) => {
77928
78637
  const d = item.data;
77929
- const projectName = d.type === "project" ? d.project.name : d.service.projectName;
77930
- this.composeClient.down(projectName, this.cwd).then(() => this.onAction()).catch(() => {
77931
- });
78638
+ this.composeClient.down(getProjectName(d), this.cwd).then(() => this.onAction()).catch((e) => this.onError(String(e)));
77932
78639
  },
77933
78640
  condition: (item) => item.data !== null
77934
78641
  },
@@ -77938,11 +78645,9 @@ var ServicesPanel = class {
77938
78645
  handler: (item) => {
77939
78646
  const d = item.data;
77940
78647
  if (d.type === "service") {
77941
- this.composeClient.restart(d.service.projectName, d.service.name, this.cwd).then(() => this.onAction()).catch(() => {
77942
- });
78648
+ this.composeClient.restart(d.service.projectName, d.service.name, this.cwd).then(() => this.onAction()).catch((e) => this.onError(String(e)));
77943
78649
  } else {
77944
- this.composeClient.restart(d.project.name, void 0, this.cwd).then(() => this.onAction()).catch(() => {
77945
- });
78650
+ this.composeClient.restart(d.project.name, void 0, this.cwd).then(() => this.onAction()).catch((e) => this.onError(String(e)));
77946
78651
  }
77947
78652
  },
77948
78653
  condition: (item) => item.data !== null
@@ -77953,11 +78658,9 @@ var ServicesPanel = class {
77953
78658
  handler: (item) => {
77954
78659
  const d = item.data;
77955
78660
  if (d.type === "service") {
77956
- this.composeClient.stop(d.service.projectName, d.service.name, this.cwd).then(() => this.onAction()).catch(() => {
77957
- });
78661
+ this.composeClient.stop(d.service.projectName, d.service.name, this.cwd).then(() => this.onAction()).catch((e) => this.onError(String(e)));
77958
78662
  } else {
77959
- this.composeClient.stop(d.project.name, void 0, this.cwd).then(() => this.onAction()).catch(() => {
77960
- });
78663
+ this.composeClient.stop(d.project.name, void 0, this.cwd).then(() => this.onAction()).catch((e) => this.onError(String(e)));
77961
78664
  }
77962
78665
  },
77963
78666
  condition: (item) => item.data !== null
@@ -77979,9 +78682,11 @@ var ImagesPanel = class {
77979
78682
  shortcutKey = 3;
77980
78683
  client;
77981
78684
  onAction;
77982
- constructor(client, onAction) {
78685
+ onError;
78686
+ constructor(client, onAction, onError) {
77983
78687
  this.client = client;
77984
78688
  this.onAction = onAction;
78689
+ this.onError = onError ?? defaultOnError;
77985
78690
  }
77986
78691
  detailTabs = [
77987
78692
  {
@@ -77991,7 +78696,7 @@ var ImagesPanel = class {
77991
78696
  return [
77992
78697
  colorizeDetailKey(`ID: ${colorizeId(img.id)}`),
77993
78698
  colorizeDetailKey(`Tags: ${img.repoTags.join(", ")}`),
77994
- colorizeDetailKey(`Size: ${(0, import_sidekick_docker_shared2.formatBytes)(img.size)}`),
78699
+ colorizeDetailKey(`Size: ${(0, import_sidekick_docker_shared3.formatBytes)(img.size)}`),
77995
78700
  colorizeDetailKey(`Created: ${img.created.toLocaleString()}`),
77996
78701
  colorizeDetailKey(`Dangling: ${colorizeBool(img.isDangling)}`)
77997
78702
  ].join("\n");
@@ -78004,11 +78709,11 @@ var ImagesPanel = class {
78004
78709
  const icon = img.isDangling ? "\u25CB" : "\u25CF";
78005
78710
  return {
78006
78711
  id: img.id,
78007
- label: `${icon} ${(0, import_sidekick_docker_shared2.truncate)(tag, 20)}`,
78712
+ label: `${icon} ${(0, import_sidekick_docker_shared3.truncate)(tag, 20)}`,
78008
78713
  sortKey: img.isDangling ? 1 : 0,
78009
78714
  data: img,
78010
78715
  iconColor: img.isDangling ? "gray" : "#2B4C7E",
78011
- rightLabel: (0, import_sidekick_docker_shared2.formatBytes)(img.size),
78716
+ rightLabel: (0, import_sidekick_docker_shared3.formatBytes)(img.size),
78012
78717
  rightColor: "gray"
78013
78718
  };
78014
78719
  });
@@ -78022,8 +78727,7 @@ var ImagesPanel = class {
78022
78727
  confirmMessage: "Remove this image?",
78023
78728
  handler: (item) => {
78024
78729
  const img = item.data;
78025
- this.client.removeImage(img.id).then(() => this.onAction()).catch(() => {
78026
- });
78730
+ this.client.removeImage(img.id).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78027
78731
  }
78028
78732
  },
78029
78733
  {
@@ -78032,8 +78736,7 @@ var ImagesPanel = class {
78032
78736
  confirm: true,
78033
78737
  confirmMessage: "Prune all dangling images?",
78034
78738
  handler: () => {
78035
- this.client.pruneImages().then(() => this.onAction()).catch(() => {
78036
- });
78739
+ this.client.pruneImages().then(() => this.onAction()).catch((e) => this.onError(String(e)));
78037
78740
  }
78038
78741
  }
78039
78742
  ];
@@ -78051,9 +78754,11 @@ var VolumesPanel = class {
78051
78754
  shortcutKey = 4;
78052
78755
  client;
78053
78756
  onAction;
78054
- constructor(client, onAction) {
78757
+ onError;
78758
+ constructor(client, onAction, onError) {
78055
78759
  this.client = client;
78056
78760
  this.onAction = onAction;
78761
+ this.onError = onError ?? defaultOnError;
78057
78762
  }
78058
78763
  detailTabs = [
78059
78764
  {
@@ -78075,7 +78780,7 @@ var VolumesPanel = class {
78075
78780
  const icon = vol.isInUse ? "\u25CF" : "\u25CB";
78076
78781
  return {
78077
78782
  id: vol.name,
78078
- label: `${icon} ${(0, import_sidekick_docker_shared2.truncate)(vol.name, 20)}`,
78783
+ label: `${icon} ${(0, import_sidekick_docker_shared3.truncate)(vol.name, 20)}`,
78079
78784
  sortKey: vol.isInUse ? 0 : 1,
78080
78785
  data: vol,
78081
78786
  iconColor: vol.isInUse ? "green" : "gray",
@@ -78093,8 +78798,7 @@ var VolumesPanel = class {
78093
78798
  confirmMessage: "Remove this volume?",
78094
78799
  handler: (item) => {
78095
78800
  const vol = item.data;
78096
- this.client.removeVolume(vol.name).then(() => this.onAction()).catch(() => {
78097
- });
78801
+ this.client.removeVolume(vol.name).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78098
78802
  },
78099
78803
  condition: (item) => !item.data.isInUse
78100
78804
  },
@@ -78104,8 +78808,7 @@ var VolumesPanel = class {
78104
78808
  confirm: true,
78105
78809
  confirmMessage: "Prune all unused volumes?",
78106
78810
  handler: () => {
78107
- this.client.pruneVolumes().then(() => this.onAction()).catch(() => {
78108
- });
78811
+ this.client.pruneVolumes().then(() => this.onAction()).catch((e) => this.onError(String(e)));
78109
78812
  }
78110
78813
  }
78111
78814
  ];
@@ -78123,9 +78826,11 @@ var NetworksPanel = class {
78123
78826
  shortcutKey = 5;
78124
78827
  client;
78125
78828
  onAction;
78126
- constructor(client, onAction) {
78829
+ onError;
78830
+ constructor(client, onAction, onError) {
78127
78831
  this.client = client;
78128
78832
  this.onAction = onAction;
78833
+ this.onError = onError ?? defaultOnError;
78129
78834
  }
78130
78835
  detailTabs = [
78131
78836
  {
@@ -78157,7 +78862,7 @@ var NetworksPanel = class {
78157
78862
  const countLabel = net.containers.length > 0 ? `${net.containers.length}` : "";
78158
78863
  return {
78159
78864
  id: net.id,
78160
- label: `${icon} ${(0, import_sidekick_docker_shared2.truncate)(net.name, 20)}`,
78865
+ label: `${icon} ${(0, import_sidekick_docker_shared3.truncate)(net.name, 20)}`,
78161
78866
  sortKey: net.isDefault ? 0 : 1,
78162
78867
  data: net,
78163
78868
  iconColor: net.isDefault ? "#2B4C7E" : "gray",
@@ -78175,8 +78880,7 @@ var NetworksPanel = class {
78175
78880
  confirmMessage: "Remove this network?",
78176
78881
  handler: (item) => {
78177
78882
  const net = item.data;
78178
- this.client.removeNetwork(net.id).then(() => this.onAction()).catch(() => {
78179
- });
78883
+ this.client.removeNetwork(net.id).then(() => this.onAction()).catch((e) => this.onError(String(e)));
78180
78884
  },
78181
78885
  condition: (item) => {
78182
78886
  const net = item.data;
@@ -78189,8 +78893,7 @@ var NetworksPanel = class {
78189
78893
  confirm: true,
78190
78894
  confirmMessage: "Prune all unused networks?",
78191
78895
  handler: () => {
78192
- this.client.pruneNetworks().then(() => this.onAction()).catch(() => {
78193
- });
78896
+ this.client.pruneNetworks().then(() => this.onAction()).catch((e) => this.onError(String(e)));
78194
78897
  }
78195
78898
  }
78196
78899
  ];
@@ -78202,14 +78905,18 @@ var NetworksPanel = class {
78202
78905
  };
78203
78906
 
78204
78907
  // src/dashboard/LogStreamManager.ts
78205
- var MAX_LOG_LINES = 1e3;
78908
+ var import_sidekick_docker_shared5 = __toESM(require_dist(), 1);
78206
78909
  var LogStreamManager = class {
78207
78910
  client;
78208
78911
  currentContainerId = null;
78209
78912
  logs = [];
78210
78913
  aborted = false;
78211
78914
  streamPromise = null;
78915
+ reconnect = new import_sidekick_docker_shared5.ReconnectScheduler();
78212
78916
  onChange;
78917
+ analytics = new import_sidekick_docker_shared5.LogAnalytics();
78918
+ timeSeries = new import_sidekick_docker_shared5.LogSeverityTimeSeries();
78919
+ templateEngine = new import_sidekick_docker_shared5.LogTemplateEngine();
78213
78920
  constructor(client, onChange) {
78214
78921
  this.client = client;
78215
78922
  this.onChange = onChange;
@@ -78220,8 +78927,12 @@ var LogStreamManager = class {
78220
78927
  this.stop();
78221
78928
  this.currentContainerId = containerId;
78222
78929
  this.logs = [];
78930
+ this.analytics.reset();
78931
+ this.timeSeries.reset();
78932
+ this.templateEngine.reset();
78223
78933
  if (!containerId) return;
78224
78934
  this.aborted = false;
78935
+ this.reconnect.reset();
78225
78936
  this.streamPromise = this.streamLogs(containerId);
78226
78937
  }
78227
78938
  async streamLogs(containerId) {
@@ -78232,22 +78943,47 @@ var LogStreamManager = class {
78232
78943
  })) {
78233
78944
  if (this.aborted || this.currentContainerId !== containerId) break;
78234
78945
  this.logs.push(entry);
78235
- if (this.logs.length > MAX_LOG_LINES) {
78946
+ const severity = this.analytics.push(entry.message);
78947
+ this.timeSeries.push(severity);
78948
+ this.templateEngine.push(entry.message);
78949
+ if (this.logs.length > import_sidekick_docker_shared5.MAX_LOG_LINES) {
78236
78950
  this.logs.shift();
78237
78951
  }
78238
78952
  this.onChange();
78239
78953
  }
78240
- } catch {
78954
+ this.reconnect.reset();
78955
+ } catch (err) {
78956
+ console.debug("log stream error:", (0, import_sidekick_docker_shared5.errorMessage)(err));
78957
+ }
78958
+ if (!this.aborted && this.currentContainerId === containerId) {
78959
+ const scheduled = this.reconnect.schedule(() => {
78960
+ if (!this.aborted && this.currentContainerId === containerId) {
78961
+ this.streamPromise = this.streamLogs(containerId);
78962
+ }
78963
+ });
78964
+ if (!scheduled) {
78965
+ console.debug(`log stream: gave up reconnecting for ${containerId}`);
78966
+ }
78241
78967
  }
78242
78968
  }
78243
78969
  stop() {
78244
78970
  this.aborted = true;
78245
78971
  this.currentContainerId = null;
78246
78972
  this.streamPromise = null;
78973
+ this.reconnect.clear();
78247
78974
  }
78248
78975
  getLogs() {
78249
78976
  return this.logs;
78250
78977
  }
78978
+ getSeverityCounts() {
78979
+ return this.analytics.getCounts();
78980
+ }
78981
+ getSeverityTimeSeries() {
78982
+ return this.timeSeries.getDominantSeries();
78983
+ }
78984
+ getTemplates(limit = 20) {
78985
+ return this.templateEngine.getTemplates(limit);
78986
+ }
78251
78987
  getCurrentContainerId() {
78252
78988
  return this.currentContainerId;
78253
78989
  }
@@ -78257,12 +78993,14 @@ var LogStreamManager = class {
78257
78993
  };
78258
78994
 
78259
78995
  // src/dashboard/StatsStreamManager.ts
78996
+ var import_sidekick_docker_shared6 = __toESM(require_dist(), 1);
78260
78997
  var StatsStreamManager = class {
78261
78998
  client;
78262
78999
  collector;
78263
79000
  currentContainerId = null;
78264
79001
  aborted = false;
78265
79002
  streamPromise = null;
79003
+ reconnect = new import_sidekick_docker_shared6.ReconnectScheduler();
78266
79004
  onChange;
78267
79005
  loadingInterval = null;
78268
79006
  constructor(client, collector, onChange) {
@@ -78277,6 +79015,7 @@ var StatsStreamManager = class {
78277
79015
  this.currentContainerId = containerId;
78278
79016
  if (!containerId) return;
78279
79017
  this.aborted = false;
79018
+ this.reconnect.reset();
78280
79019
  this.loadingInterval = setInterval(() => this.onChange(), 200);
78281
79020
  this.streamPromise = this.streamStats(containerId);
78282
79021
  }
@@ -78294,12 +79033,26 @@ var StatsStreamManager = class {
78294
79033
  this.clearLoadingInterval();
78295
79034
  this.onChange();
78296
79035
  }
78297
- } catch {
79036
+ this.reconnect.reset();
79037
+ } catch (err) {
79038
+ console.debug("stats stream error:", (0, import_sidekick_docker_shared6.errorMessage)(err));
79039
+ }
79040
+ if (!this.aborted && this.currentContainerId === containerId) {
79041
+ const scheduled = this.reconnect.schedule(() => {
79042
+ if (!this.aborted && this.currentContainerId === containerId) {
79043
+ this.loadingInterval = setInterval(() => this.onChange(), 200);
79044
+ this.streamPromise = this.streamStats(containerId);
79045
+ }
79046
+ });
79047
+ if (!scheduled) {
79048
+ console.debug(`stats stream: gave up reconnecting for ${containerId}`);
79049
+ }
78298
79050
  }
78299
79051
  }
78300
79052
  stop() {
78301
79053
  this.aborted = true;
78302
79054
  this.clearLoadingInterval();
79055
+ this.reconnect.clear();
78303
79056
  this.currentContainerId = null;
78304
79057
  this.streamPromise = null;
78305
79058
  }
@@ -78311,18 +79064,18 @@ var StatsStreamManager = class {
78311
79064
  }
78312
79065
  dispose() {
78313
79066
  this.stop();
78314
- this.clearLoadingInterval();
78315
79067
  }
78316
79068
  };
78317
79069
 
78318
79070
  // src/dashboard/ComposeLogStreamManager.ts
78319
- var MAX_LOG_LINES2 = 1e3;
79071
+ var import_sidekick_docker_shared7 = __toESM(require_dist(), 1);
78320
79072
  var ComposeLogStreamManager = class {
78321
79073
  composeClient;
78322
79074
  currentProject = null;
78323
79075
  currentService = null;
78324
79076
  logs = [];
78325
79077
  aborted = false;
79078
+ reconnect = new import_sidekick_docker_shared7.ReconnectScheduler();
78326
79079
  onChange;
78327
79080
  constructor(composeClient, onChange) {
78328
79081
  this.composeClient = composeClient;
@@ -78336,6 +79089,7 @@ var ComposeLogStreamManager = class {
78336
79089
  this.logs = [];
78337
79090
  if (!project) return;
78338
79091
  this.aborted = false;
79092
+ this.reconnect.reset();
78339
79093
  this.streamLogs(project, service);
78340
79094
  }
78341
79095
  async streamLogs(project, service) {
@@ -78343,18 +79097,31 @@ var ComposeLogStreamManager = class {
78343
79097
  for await (const entry of this.composeClient.streamLogs(project, service ?? void 0)) {
78344
79098
  if (this.aborted || this.currentProject !== project) break;
78345
79099
  this.logs.push(entry);
78346
- if (this.logs.length > MAX_LOG_LINES2) {
79100
+ if (this.logs.length > import_sidekick_docker_shared7.MAX_LOG_LINES) {
78347
79101
  this.logs.shift();
78348
79102
  }
78349
79103
  this.onChange();
78350
79104
  }
78351
- } catch {
79105
+ this.reconnect.reset();
79106
+ } catch (err) {
79107
+ console.debug("compose log stream error:", (0, import_sidekick_docker_shared7.errorMessage)(err));
79108
+ }
79109
+ if (!this.aborted && this.currentProject === project) {
79110
+ const scheduled = this.reconnect.schedule(() => {
79111
+ if (!this.aborted && this.currentProject === project) {
79112
+ this.streamLogs(project, service);
79113
+ }
79114
+ });
79115
+ if (!scheduled) {
79116
+ console.debug(`compose log stream: gave up reconnecting for ${project}`);
79117
+ }
78352
79118
  }
78353
79119
  }
78354
79120
  stop() {
78355
79121
  this.aborted = true;
78356
79122
  this.currentProject = null;
78357
79123
  this.currentService = null;
79124
+ this.reconnect.clear();
78358
79125
  }
78359
79126
  getLogs() {
78360
79127
  return this.logs;
@@ -78365,7 +79132,7 @@ var ComposeLogStreamManager = class {
78365
79132
  };
78366
79133
 
78367
79134
  // src/dashboard/ink/Dashboard.tsx
78368
- var import_react35 = __toESM(require_react(), 1);
79135
+ var import_react36 = __toESM(require_react(), 1);
78369
79136
  await init_build2();
78370
79137
 
78371
79138
  // src/dashboard/ink/useTerminalSize.ts
@@ -78438,6 +79205,324 @@ function useWindowedScroll({ totalItems, viewportHeight }) {
78438
79205
  };
78439
79206
  }
78440
79207
 
79208
+ // src/dashboard/ink/useKeyboardHandler.ts
79209
+ await init_build2();
79210
+ function executeAction(action, item, dispatch, addToast) {
79211
+ if (action.confirm) {
79212
+ dispatch({ type: "SET_CONFIRM", action: () => {
79213
+ action.handler(item);
79214
+ addToast(action.label, "info");
79215
+ }, message: action.confirmMessage || "Are you sure?" });
79216
+ } else {
79217
+ action.handler(item);
79218
+ addToast(action.label, "info");
79219
+ }
79220
+ }
79221
+ function handleFilterInput(input, key, opts) {
79222
+ const { currentValue, setAction, clearToast, dispatch, addToast, onTab } = opts;
79223
+ if (key.escape) {
79224
+ if (currentValue && clearToast) addToast(clearToast, "info");
79225
+ dispatch({ type: setAction, value: "" });
79226
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79227
+ return;
79228
+ }
79229
+ if (key.return) {
79230
+ if (setAction === "SET_FILTER" && currentValue) addToast(`Filter: "${currentValue}"`, "info");
79231
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79232
+ return;
79233
+ }
79234
+ if (key.tab && onTab) {
79235
+ onTab();
79236
+ return;
79237
+ }
79238
+ if (key.backspace || key.delete) {
79239
+ dispatch({ type: setAction, value: currentValue.slice(0, -1) });
79240
+ return;
79241
+ }
79242
+ if (input && !key.ctrl && !key.meta) {
79243
+ dispatch({ type: setAction, value: currentValue + input });
79244
+ }
79245
+ }
79246
+ function useKeyboardHandler(ctx) {
79247
+ const { exit } = use_app_default();
79248
+ const { state, dispatch, panels, panel, selectedItem, contextActions, clampedSelection, currentItems, detailLines, detailViewportHeight, detailTabs, tabIdx, panelActions, sideScroll, addToast, rotatePhrase } = ctx;
79249
+ use_input_default((input, key) => {
79250
+ if (state.overlay === "exec") return;
79251
+ rotatePhrase();
79252
+ if (input === "q" || key.ctrl && input === "c") {
79253
+ if (state.overlay) {
79254
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79255
+ return;
79256
+ }
79257
+ exit();
79258
+ return;
79259
+ }
79260
+ if (state.overlay === "confirm") {
79261
+ if (input === "y" || input === "Y") {
79262
+ state.confirmAction?.();
79263
+ dispatch({ type: "SET_CONFIRM", action: null, message: "" });
79264
+ return;
79265
+ }
79266
+ if (input === "n" || input === "N" || key.escape) {
79267
+ dispatch({ type: "SET_CONFIRM", action: null, message: "" });
79268
+ return;
79269
+ }
79270
+ return;
79271
+ }
79272
+ if (state.overlay === "filter") {
79273
+ handleFilterInput(input, key, {
79274
+ currentValue: state.filterString,
79275
+ setAction: "SET_FILTER",
79276
+ clearToast: "Filter cleared",
79277
+ dispatch,
79278
+ addToast
79279
+ });
79280
+ return;
79281
+ }
79282
+ if (state.overlay === "log-filter") {
79283
+ handleFilterInput(input, key, {
79284
+ currentValue: state.logFilterString,
79285
+ setAction: "SET_LOG_FILTER",
79286
+ clearToast: "Log filter cleared",
79287
+ dispatch,
79288
+ addToast,
79289
+ onTab: () => dispatch({ type: "TOGGLE_LOG_FILTER_MODE" })
79290
+ });
79291
+ return;
79292
+ }
79293
+ if (state.overlay === "context-menu") {
79294
+ if (key.escape) {
79295
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79296
+ return;
79297
+ }
79298
+ if (input === "j" || key.downArrow) {
79299
+ dispatch({ type: "CONTEXT_MENU_NAV", delta: 1, itemCount: contextActions.length });
79300
+ return;
79301
+ }
79302
+ if (input === "k" || key.upArrow) {
79303
+ dispatch({ type: "CONTEXT_MENU_NAV", delta: -1, itemCount: contextActions.length });
79304
+ return;
79305
+ }
79306
+ if (key.return) {
79307
+ const action = contextActions[state.contextMenuIndex];
79308
+ if (action && selectedItem) {
79309
+ executeAction(action, selectedItem, dispatch, addToast);
79310
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79311
+ }
79312
+ return;
79313
+ }
79314
+ const match = contextActions.find((a) => a.key === input);
79315
+ if (match && selectedItem) {
79316
+ executeAction(match, selectedItem, dispatch, addToast);
79317
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79318
+ }
79319
+ return;
79320
+ }
79321
+ if (state.overlay === "help") {
79322
+ if (key.escape || input === "?") {
79323
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79324
+ }
79325
+ return;
79326
+ }
79327
+ if (state.overlay === "version") {
79328
+ if (key.escape || input === "V") {
79329
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79330
+ }
79331
+ return;
79332
+ }
79333
+ if (key.escape) {
79334
+ if (state.filterString) {
79335
+ dispatch({ type: "SET_FILTER", value: "" });
79336
+ return;
79337
+ }
79338
+ if (state.focusTarget === "detail") {
79339
+ dispatch({ type: "SET_FOCUS", target: "side" });
79340
+ return;
79341
+ }
79342
+ return;
79343
+ }
79344
+ if (input === "?") {
79345
+ dispatch({ type: "SET_OVERLAY", overlay: "help" });
79346
+ return;
79347
+ }
79348
+ if (input === "V") {
79349
+ dispatch({ type: "SET_OVERLAY", overlay: "version" });
79350
+ return;
79351
+ }
79352
+ const num = parseInt(input, 10);
79353
+ if (num >= 1 && num <= panels.length) {
79354
+ panels[state.activePanelIndex]?.onDeactivate?.();
79355
+ dispatch({ type: "SWITCH_PANEL", index: num - 1 });
79356
+ panels[num - 1]?.onActivate?.();
79357
+ return;
79358
+ }
79359
+ if (key.tab) {
79360
+ dispatch({ type: "TOGGLE_FOCUS" });
79361
+ return;
79362
+ }
79363
+ if (input === "z") {
79364
+ dispatch({ type: "CYCLE_LAYOUT" });
79365
+ addToast(`Layout: ${state.layoutMode === "normal" ? "Expanded" : "Normal"}`, "info");
79366
+ return;
79367
+ }
79368
+ if (input === "/") {
79369
+ dispatch({ type: "SET_OVERLAY", overlay: "filter" });
79370
+ return;
79371
+ }
79372
+ if (input === "f") {
79373
+ if (panel.id === "containers" && tabIdx === 0) {
79374
+ dispatch({ type: "SET_OVERLAY", overlay: "log-filter" });
79375
+ return;
79376
+ }
79377
+ }
79378
+ if (input === "x") {
79379
+ if (selectedItem && panelActions.length > 0) {
79380
+ dispatch({ type: "SET_OVERLAY", overlay: "context-menu" });
79381
+ }
79382
+ return;
79383
+ }
79384
+ if (input === "[") {
79385
+ dispatch({ type: "CYCLE_DETAIL_TAB", direction: -1, tabCount: detailTabs.length });
79386
+ return;
79387
+ }
79388
+ if (input === "]") {
79389
+ dispatch({ type: "CYCLE_DETAIL_TAB", direction: 1, tabCount: detailTabs.length });
79390
+ return;
79391
+ }
79392
+ if (state.focusTarget === "side") {
79393
+ if (input === "j" || key.downArrow) {
79394
+ if (clampedSelection < currentItems.length - 1) {
79395
+ dispatch({ type: "SELECT_ITEM", index: clampedSelection + 1 });
79396
+ sideScroll.selectNext();
79397
+ }
79398
+ return;
79399
+ }
79400
+ if (input === "k" || key.upArrow) {
79401
+ if (clampedSelection > 0) {
79402
+ dispatch({ type: "SELECT_ITEM", index: clampedSelection - 1 });
79403
+ sideScroll.selectPrev();
79404
+ }
79405
+ return;
79406
+ }
79407
+ if (input === "g") {
79408
+ dispatch({ type: "SELECT_ITEM", index: 0 });
79409
+ sideScroll.selectFirst();
79410
+ return;
79411
+ }
79412
+ if (input === "G") {
79413
+ dispatch({ type: "SELECT_ITEM", index: Math.max(0, currentItems.length - 1) });
79414
+ sideScroll.selectLast();
79415
+ return;
79416
+ }
79417
+ if (key.return) {
79418
+ dispatch({ type: "SET_FOCUS", target: "detail" });
79419
+ return;
79420
+ }
79421
+ }
79422
+ if (state.focusTarget === "detail") {
79423
+ if (input === "j" || key.downArrow) {
79424
+ dispatch({ type: "SCROLL_DETAIL_DELTA", delta: 1, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
79425
+ return;
79426
+ }
79427
+ if (input === "k" || key.upArrow) {
79428
+ dispatch({ type: "SCROLL_DETAIL_DELTA", delta: -1, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
79429
+ return;
79430
+ }
79431
+ if (input === "h" || key.leftArrow) {
79432
+ dispatch({ type: "SET_FOCUS", target: "side" });
79433
+ return;
79434
+ }
79435
+ if (input === "g") {
79436
+ dispatch({ type: "SCROLL_DETAIL", offset: 0 });
79437
+ return;
79438
+ }
79439
+ if (input === "G") {
79440
+ dispatch({ type: "SCROLL_DETAIL", offset: Math.max(0, detailLines.length - detailViewportHeight) });
79441
+ return;
79442
+ }
79443
+ }
79444
+ if (selectedItem) {
79445
+ const actionMatch = panelActions.find((a) => a.key === input && (!a.condition || a.condition(selectedItem)));
79446
+ if (actionMatch) {
79447
+ executeAction(actionMatch, selectedItem, dispatch, addToast);
79448
+ }
79449
+ }
79450
+ });
79451
+ }
79452
+
79453
+ // src/dashboard/ink/useMouseHandler.ts
79454
+ var import_react31 = __toESM(require_react(), 1);
79455
+ function useMouseHandler(ctx) {
79456
+ const { state, dispatch, panels, panelCounts, currentItems, clampedSelection, sideWidth, sideScroll, detailLines, detailViewportHeight, detailTabs, rows, rotatePhrase } = ctx;
79457
+ return (0, import_react31.useCallback)((event) => {
79458
+ rotatePhrase();
79459
+ if (state.overlay === "filter") return;
79460
+ if (state.overlay) {
79461
+ if (event.type === "click") {
79462
+ dispatch({ type: "SET_OVERLAY", overlay: null });
79463
+ }
79464
+ return;
79465
+ }
79466
+ const { x, y } = event;
79467
+ if (event.type === "scroll") {
79468
+ if (x < sideWidth && sideWidth > 0) {
79469
+ const delta = event.scrollDirection === "down" ? 3 : -3;
79470
+ dispatch({ type: "SCROLL_SIDE", delta, itemCount: currentItems.length });
79471
+ const newIdx = Math.max(0, Math.min(clampedSelection + delta, currentItems.length - 1));
79472
+ sideScroll.setSelected(newIdx);
79473
+ } else {
79474
+ const delta = event.scrollDirection === "down" ? 3 : -3;
79475
+ dispatch({ type: "SCROLL_DETAIL_DELTA", delta, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
79476
+ }
79477
+ return;
79478
+ }
79479
+ if (event.type !== "click" || event.button !== "left") return;
79480
+ if (y === 0) {
79481
+ let col = 0;
79482
+ for (let i = 0; i < panels.length; i++) {
79483
+ const count = panelCounts[i];
79484
+ let countLen = 0;
79485
+ if (count) {
79486
+ countLen = count.running !== void 0 ? ` ${count.running}/${count.total}`.length : ` ${count.total}`.length;
79487
+ }
79488
+ const tabWidth = String(panels[i].shortcutKey).length + panels[i].title.length + 3 + countLen + 1;
79489
+ if (x >= col && x < col + tabWidth) {
79490
+ panels[state.activePanelIndex]?.onDeactivate?.();
79491
+ dispatch({ type: "SWITCH_PANEL", index: i });
79492
+ panels[i]?.onActivate?.();
79493
+ return;
79494
+ }
79495
+ col += tabWidth;
79496
+ }
79497
+ return;
79498
+ }
79499
+ if (y >= rows - 1) return;
79500
+ if (x < sideWidth && sideWidth > 0) {
79501
+ dispatch({ type: "SET_FOCUS", target: "side" });
79502
+ const hasScrollUp = sideScroll.scrollOffset > 0;
79503
+ const itemRow = y - 2 - (hasScrollUp ? 1 : 0);
79504
+ const itemIndex = sideScroll.scrollOffset + itemRow;
79505
+ if (itemIndex >= 0 && itemIndex < currentItems.length) {
79506
+ dispatch({ type: "SELECT_ITEM", index: itemIndex });
79507
+ sideScroll.setSelected(itemIndex);
79508
+ }
79509
+ } else {
79510
+ dispatch({ type: "SET_FOCUS", target: "detail" });
79511
+ if (y === 1 && detailTabs.length > 1) {
79512
+ let col = sideWidth;
79513
+ for (let i = 0; i < detailTabs.length; i++) {
79514
+ const tabWidth = detailTabs[i].label.length + 3;
79515
+ if (x >= col && x < col + tabWidth) {
79516
+ dispatch({ type: "SET_DETAIL_TAB", index: i });
79517
+ return;
79518
+ }
79519
+ col += tabWidth;
79520
+ }
79521
+ }
79522
+ }
79523
+ }, [state.overlay, state.activePanelIndex, sideWidth, currentItems.length, clampedSelection, sideScroll, detailLines.length, detailViewportHeight, panels, panelCounts, detailTabs, rows, rotatePhrase, dispatch]);
79524
+ }
79525
+
78441
79526
  // src/dashboard/ink/TabBar.tsx
78442
79527
  await init_build2();
78443
79528
  var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
@@ -78447,8 +79532,14 @@ function TabBar({ panels, activeIndex, layoutMode, phrase, panelCounts }) {
78447
79532
  const isActive = i === activeIndex;
78448
79533
  const count = panelCounts?.[i];
78449
79534
  let countStr = "";
79535
+ let countColor;
78450
79536
  if (count) {
78451
79537
  countStr = count.running !== void 0 ? ` ${count.running}/${count.total}` : ` ${count.total}`;
79538
+ if (count.running !== void 0) {
79539
+ if (count.running === count.total && count.total > 0) countColor = "green";
79540
+ else if (count.running > 0) countColor = "yellow";
79541
+ else countColor = void 0;
79542
+ }
78452
79543
  }
78453
79544
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { marginRight: 1, children: [
78454
79545
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -78463,10 +79554,10 @@ function TabBar({ panels, activeIndex, layoutMode, phrase, panelCounts }) {
78463
79554
  countStr && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
78464
79555
  Text,
78465
79556
  {
78466
- color: isActive ? "#2B4C7E" : "gray",
79557
+ color: isActive ? countColor || "#2B4C7E" : countColor || "gray",
78467
79558
  bold: isActive,
78468
79559
  inverse: isActive,
78469
- dimColor: !isActive,
79560
+ dimColor: !isActive && !countColor,
78470
79561
  children: `${countStr} `
78471
79562
  }
78472
79563
  ),
@@ -78526,11 +79617,15 @@ function SideList({ items, selectedIndex, scrollOffset, focused, width, viewport
78526
79617
  const rightLabel = item.rightLabel || "";
78527
79618
  const rightLen = rightLabel.length;
78528
79619
  const leftWidth = innerWidth - rightLen - (rightLen ? 1 : 0);
78529
- const leftText = `${prefix}${icon}${rest}`;
78530
- const paddedLeft = leftText.length < leftWidth ? leftText + " ".repeat(leftWidth - leftText.length) : leftText.substring(0, leftWidth);
78531
79620
  if (isSelected && focused) {
78532
- const fullLine = rightLen ? ` ${paddedLeft} ${rightLabel}` : ` ${paddedLeft}`;
78533
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "cyan", bold: true, inverse: true, wrap: "truncate", children: fullLine.padEnd(innerWidth) }) }, item.id);
79621
+ const restText = rest.length < leftWidth - prefix.length - 1 ? rest + " ".repeat(leftWidth - prefix.length - 1 - rest.length) : rest.substring(0, leftWidth - prefix.length - 1);
79622
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { children: [
79623
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#2B4C7E", bold: true, inverse: true, wrap: "truncate", children: ` ${prefix}` }),
79624
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: item.iconColor || "#2B4C7E", bold: true, inverse: true, children: icon }),
79625
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#2B4C7E", bold: true, inverse: true, wrap: "truncate", children: restText }),
79626
+ rightLen > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#2B4C7E", bold: true, inverse: true, children: ` ${rightLabel}` }),
79627
+ !rightLen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#2B4C7E", bold: true, inverse: true, children: " " })
79628
+ ] }, item.id);
78534
79629
  }
78535
79630
  const iconColor = item.iconColor || (isSelected ? "#2B4C7E" : "white");
78536
79631
  const textColor = isSelected ? "#2B4C7E" : "white";
@@ -78542,13 +79637,17 @@ function SideList({ items, selectedIndex, scrollOffset, focused, width, viewport
78542
79637
  ] }, item.id);
78543
79638
  }),
78544
79639
  hasScrollDown && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", children: ` \u25BC (${belowCount} more)` }),
78545
- items.length === 0 && filterString && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", children: [
78546
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", children: ` No matches for "${filterString}"` }),
78547
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", children: " Press Esc to clear" })
79640
+ items.length === 0 && filterString && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingTop: 1, children: [
79641
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "yellow", children: ` \u2717 No matches for "${filterString}"` }),
79642
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", dimColor: true, children: " Press Esc to clear filter" })
78548
79643
  ] }),
78549
- items.length === 0 && !filterString && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", children: [
78550
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", children: ` No ${panelTitle.toLowerCase()} found` }),
78551
- panelId && EMPTY_HINTS[panelId]?.map((hint, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", children: ` ${hint}` }, i))
79644
+ items.length === 0 && !filterString && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingTop: 1, children: [
79645
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", children: ` \u2500 No ${panelTitle.toLowerCase()} found` }),
79646
+ panelId && EMPTY_HINTS[panelId] && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
79647
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: "" }),
79648
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "gray", dimColor: true, children: ` ${EMPTY_HINTS[panelId][0]}` }),
79649
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "#2B4C7E", bold: true, children: ` ${EMPTY_HINTS[panelId][1]}` })
79650
+ ] })
78552
79651
  ] })
78553
79652
  ] });
78554
79653
  }
@@ -78558,7 +79657,7 @@ await init_build2();
78558
79657
  var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
78559
79658
  function DetailTabBar({ tabs, activeIndex }) {
78560
79659
  if (tabs.length <= 1) {
78561
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, {});
79660
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { children: tabs.length === 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: "gray", dimColor: true, children: ` ${tabs[0].label}` }) });
78562
79661
  }
78563
79662
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
78564
79663
  tabs.map((tab2, i) => {
@@ -78574,7 +79673,7 @@ function DetailTabBar({ tabs, activeIndex }) {
78574
79673
  ) }, tab2.label);
78575
79674
  }),
78576
79675
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { flexGrow: 1 }),
78577
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: "gray", children: "[ ] switch" })
79676
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: "gray", dimColor: true, children: "[/] cycle tabs" })
78578
79677
  ] });
78579
79678
  }
78580
79679
 
@@ -78596,9 +79695,9 @@ function DetailPane({ content, scrollOffset, viewportHeight, focused }) {
78596
79695
  }
78597
79696
 
78598
79697
  // src/dashboard/ink/StatusBar.tsx
78599
- var import_react31 = __toESM(require_react(), 1);
79698
+ var import_react32 = __toESM(require_react(), 1);
78600
79699
  await init_build2();
78601
- var import_sidekick_docker_shared3 = __toESM(require_dist(), 1);
79700
+ var import_sidekick_docker_shared8 = __toESM(require_dist(), 1);
78602
79701
  var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
78603
79702
  function formatAgo(date) {
78604
79703
  const secs = Math.floor((Date.now() - date.getTime()) / 1e3);
@@ -78607,72 +79706,89 @@ function formatAgo(date) {
78607
79706
  const mins = Math.floor(secs / 60);
78608
79707
  return { text: `${mins}m ago`, stale: mins >= 1 };
78609
79708
  }
78610
- function StatusBar({ daemonConnected, focusTarget, panelHints, panelActionHints, filterString, containerCount, runningCount, version: version2, matchCount, totalCount, lastRefresh }) {
78611
- const [, setTick] = (0, import_react31.useState)(0);
78612
- (0, import_react31.useEffect)(() => {
79709
+ var SEP2 = "\u2502";
79710
+ function StatusBar({ daemonConnected, focusTarget, panelActionHints, filterString, containerCount, runningCount, version: version2, matchCount, totalCount, lastRefresh }) {
79711
+ const [, setTick] = (0, import_react32.useState)(0);
79712
+ (0, import_react32.useEffect)(() => {
78613
79713
  const timer = setInterval(() => setTick((t) => t + 1), 5e3);
78614
79714
  return () => clearInterval(timer);
78615
79715
  }, []);
78616
79716
  const ago = lastRefresh ? formatAgo(lastRefresh) : null;
78617
79717
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { children: [
78618
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: true, color: "magenta", children: ` ${import_sidekick_docker_shared3.BRAND_INLINE}` }),
78619
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: ` ${import_sidekick_docker_shared3.BRAND_TAGLINE} v${version2}` }),
78620
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: " " }),
79718
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: true, color: "magenta", children: ` \u26A1 ${import_sidekick_docker_shared8.BRAND_INLINE}` }),
79719
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${import_sidekick_docker_shared8.BRAND_TAGLINE} v${version2}` }),
79720
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
78621
79721
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: daemonConnected ? "green" : "red", children: daemonConnected ? `\u25CF ${runningCount ?? 0}/${containerCount ?? 0}` : "\u25CB disconnected" }),
78622
- ago && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: ago.stale ? "yellow" : "gray", children: ` \u21BB ${ago.text}` }),
78623
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: " " }),
78624
- panelActionHints ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: `${panelActionHints} ` }) : null,
78625
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "gray", children: [
78626
- panelHints,
78627
- focusTarget === "side" ? "j/k nav Tab focus " : "j/k scroll Tab focus ",
78628
- "/ filter ? help q quit"
79722
+ ago && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: ago.stale ? "yellow" : "gray", dimColor: !ago.stale, children: ` \u21BB ${ago.text}` }),
79723
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` }),
79724
+ panelActionHints.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
79725
+ panelActionHints.map((hint, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react32.default.Fragment, { children: [
79726
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: " " }),
79727
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: hint.destructive ? "red" : "#2B4C7E", children: `${hint.key}:${hint.label}` })
79728
+ ] }, hint.key)),
79729
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: ` ${SEP2} ` })
78629
79730
  ] }),
78630
- filterString ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "yellow", children: ` Filter: "${filterString}"${matchCount !== void 0 && totalCount !== void 0 ? ` (${matchCount} of ${totalCount})` : ""}` }) : null
79731
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: focusTarget === "side" ? "#2B4C7E" : "gray", bold: focusTarget === "side", children: "\u25C0" }),
79732
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: "/" }),
79733
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: focusTarget === "detail" ? "#2B4C7E" : "gray", bold: focusTarget === "detail", children: "\u25B6" }),
79734
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", dimColor: true, children: " j/k Tab / ? q" }),
79735
+ filterString ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "yellow", bold: true, children: ` \u25C9 "${filterString}"${matchCount !== void 0 && totalCount !== void 0 ? ` ${matchCount}/${totalCount}` : ""}` }) : null
78631
79736
  ] });
78632
79737
  }
78633
79738
 
78634
79739
  // src/dashboard/ink/HelpOverlay.tsx
78635
79740
  await init_build2();
78636
- var import_sidekick_docker_shared4 = __toESM(require_dist(), 1);
79741
+ var import_sidekick_docker_shared9 = __toESM(require_dist(), 1);
78637
79742
  var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
78638
79743
  var GLOBAL_BINDINGS = [
78639
79744
  { key: "1-5", label: "Switch panel" },
78640
79745
  { key: "j/k", label: "Navigate / scroll" },
78641
- { key: "g/G", label: "First / last item" },
78642
- { key: "Tab", label: "Toggle focus (side/detail)" },
78643
- { key: "[/]", label: "Prev / next detail tab" },
78644
- { key: "z", label: "Cycle layout mode" },
79746
+ { key: "g/G", label: "Jump to first / last" },
79747
+ { key: "Tab", label: "Toggle focus" },
79748
+ { key: "[/]", label: "Cycle detail tabs" },
79749
+ { key: "z", label: "Toggle expanded layout" },
78645
79750
  { key: "/", label: "Filter items" },
78646
- { key: "x", label: "Context menu (actions)" },
79751
+ { key: "x", label: "Actions menu" },
78647
79752
  { key: "V", label: "Version info" },
78648
- { key: "?", label: "Toggle help" },
79753
+ { key: "?", label: "This help" },
78649
79754
  { key: "q", label: "Quit" }
78650
79755
  ];
79756
+ function KeyBadge({ k }) {
79757
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "white", backgroundColor: "#2B4C7E", bold: true, children: ` ${k} ` });
79758
+ }
78651
79759
  function HelpOverlay({ panels, activePanelIndex, version: version2 }) {
78652
79760
  const panel = panels[activePanelIndex];
78653
79761
  const actions = panel.getActions();
78654
79762
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
78655
79763
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
78656
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "magenta", children: `${import_sidekick_docker_shared4.BRAND_INLINE} ${import_sidekick_docker_shared4.BRAND_TAGLINE}` }),
78657
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", children: ` v${version2}` })
79764
+ /* @__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}` }),
79765
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: ` v${version2}` })
78658
79766
  ] }),
78659
79767
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
78660
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "yellow", children: "Navigation" }),
78661
- GLOBAL_BINDINGS.map((b) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { children: [
78662
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "#2B4C7E", children: ` ${b.key.padEnd(8)}` }),
78663
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: b.label })
79768
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
79769
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "yellow", children: "\u2500\u2500 Navigation " }),
79770
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(30) })
79771
+ ] }),
79772
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
79773
+ GLOBAL_BINDINGS.map((b) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
79774
+ /* @__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 }) }),
79775
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", children: b.label })
78664
79776
  ] }, b.key)),
78665
79777
  actions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
78666
79778
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
78667
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "yellow", children: `${panel.title} Actions` }),
78668
- actions.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { children: [
78669
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "#2B4C7E", children: ` ${a.key.padEnd(8)}` }),
78670
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: a.label }),
78671
- a.confirm && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "red", children: " (requires confirmation)" })
79779
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
79780
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "yellow", children: `\u2500\u2500 ${panel.title} Actions ` }),
79781
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(24) })
79782
+ ] }),
79783
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
79784
+ actions.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Box_default, { children: [
79785
+ /* @__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 }) }),
79786
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: a.confirm ? "red" : "gray", children: a.label }),
79787
+ a.confirm && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "red", dimColor: true, children: " \u26A0" })
78672
79788
  ] }, a.key))
78673
79789
  ] }),
78674
79790
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: "" }),
78675
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", children: "Press ? or Esc to close" })
79791
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "gray", dimColor: true, children: "Press ? or Esc to close" })
78676
79792
  ] });
78677
79793
  }
78678
79794
 
@@ -78682,8 +79798,9 @@ var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
78682
79798
  function FilterOverlay({ filterString, matchCount, totalCount, panelTitle }) {
78683
79799
  const countInfo = matchCount !== void 0 && totalCount !== void 0 && panelTitle ? ` ${matchCount} of ${totalCount} ${panelTitle.toLowerCase()}` : "";
78684
79800
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { position: "absolute", marginTop: 1, marginLeft: 1, children: [
78685
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { backgroundColor: "gray", color: "white", children: ` / ${filterString}\u2588 ` }),
78686
- countInfo && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", children: countInfo })
79801
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { backgroundColor: "#2B4C7E", color: "white", bold: true, children: ` \u2315 ${filterString}\u2588 ` }),
79802
+ countInfo && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: countInfo }),
79803
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "gray", dimColor: true, children: " Enter: apply Esc: clear" })
78687
79804
  ] });
78688
79805
  }
78689
79806
 
@@ -78699,23 +79816,37 @@ function ContextMenuOverlay({ actions, selectedIndex }) {
78699
79816
  marginLeft: 2,
78700
79817
  flexDirection: "column",
78701
79818
  borderStyle: "single",
78702
- borderColor: "cyan",
79819
+ borderColor: "#2B4C7E",
78703
79820
  paddingX: 1,
78704
79821
  children: [
78705
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { bold: true, color: "cyan", children: "Actions" }),
79822
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { bold: true, color: "#2B4C7E", children: "\u2630 Actions" }),
78706
79823
  actions.map((action, i) => {
78707
79824
  const isSelected = i === selectedIndex;
78708
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
78709
- Text,
78710
- {
78711
- color: isSelected ? "#2B4C7E" : "white",
78712
- bold: isSelected,
78713
- inverse: isSelected,
78714
- children: ` ${action.key} ${action.label} `
78715
- }
78716
- ) }, action.key);
79825
+ const isDanger = !!action.confirm;
79826
+ const color = isSelected ? isDanger ? "red" : "#2B4C7E" : isDanger ? "red" : "white";
79827
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { children: [
79828
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
79829
+ Text,
79830
+ {
79831
+ color,
79832
+ bold: isSelected,
79833
+ inverse: isSelected,
79834
+ children: ` ${action.key} `
79835
+ }
79836
+ ),
79837
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
79838
+ Text,
79839
+ {
79840
+ color,
79841
+ bold: isSelected,
79842
+ inverse: isSelected,
79843
+ children: `${action.label}${isDanger ? " \u26A0" : ""} `
79844
+ }
79845
+ )
79846
+ ] }, action.key);
78717
79847
  }),
78718
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "gray", children: "Esc to close" })
79848
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { children: "" }),
79849
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "gray", dimColor: true, children: "j/k select Enter run Esc close" })
78719
79850
  ]
78720
79851
  }
78721
79852
  );
@@ -78734,16 +79865,22 @@ function ConfirmOverlay({ message }) {
78734
79865
  flexDirection: "column",
78735
79866
  borderStyle: "double",
78736
79867
  borderColor: "red",
78737
- paddingX: 1,
79868
+ paddingX: 2,
79869
+ paddingY: 1,
78738
79870
  children: [
78739
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { bold: true, color: "red", children: " Confirm " }),
79871
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
79872
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "red", bold: true, children: "\u26A0 " }),
79873
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { bold: true, color: "red", children: "Confirm Action" })
79874
+ ] }),
79875
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "" }),
78740
79876
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: ` ${message}` }),
79877
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "gray", dimColor: true, children: " This cannot be undone." }),
78741
79878
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "" }),
78742
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
78743
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "green", children: " y " }),
78744
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "Yes " }),
78745
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "red", children: " n " }),
78746
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: "No" })
79879
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
79880
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { backgroundColor: "green", color: "white", bold: true, children: " y Yes " }),
79881
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { children: " " }),
79882
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { backgroundColor: "red", color: "white", bold: true, children: " n No " }),
79883
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: "gray", dimColor: true, children: " or Esc to cancel" })
78747
79884
  ] })
78748
79885
  ]
78749
79886
  }
@@ -78751,37 +79888,53 @@ function ConfirmOverlay({ message }) {
78751
79888
  }
78752
79889
 
78753
79890
  // src/dashboard/ink/ToastNotification.tsx
78754
- var import_react32 = __toESM(require_react(), 1);
79891
+ var import_react33 = __toESM(require_react(), 1);
78755
79892
  await init_build2();
78756
79893
  var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
78757
- var SEVERITY_COLORS = {
79894
+ var SEVERITY_COLORS2 = {
78758
79895
  error: "red",
78759
79896
  warning: "yellow",
78760
79897
  info: "#2B4C7E"
78761
79898
  };
78762
79899
  var SPINNER_FRAMES = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827";
78763
79900
  function ToastNotification({ toast }) {
78764
- const [frame, setFrame] = (0, import_react32.useState)(0);
78765
- (0, import_react32.useEffect)(() => {
79901
+ const [frame, setFrame] = (0, import_react33.useState)(0);
79902
+ (0, import_react33.useEffect)(() => {
78766
79903
  if (toast.severity !== "info") return;
78767
79904
  const timer = setInterval(() => {
78768
79905
  setFrame((f) => (f + 1) % SPINNER_FRAMES.length);
78769
79906
  }, 100);
78770
79907
  return () => clearInterval(timer);
78771
79908
  }, [toast.id, toast.severity]);
78772
- const spinner = toast.severity === "info" ? `${SPINNER_FRAMES[frame]} ` : "";
78773
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { position: "absolute", marginTop: 0, justifyContent: "flex-end", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: SEVERITY_COLORS[toast.severity] || "white", children: ` ${spinner}${toast.message} ` }) });
79909
+ const SEVERITY_ICONS = {
79910
+ error: "\u2717",
79911
+ // ✗
79912
+ warning: "\u26A0",
79913
+ // ⚠
79914
+ info: SPINNER_FRAMES[frame]
79915
+ };
79916
+ const icon = SEVERITY_ICONS[toast.severity] || "";
79917
+ const textColor = toast.severity === "warning" ? "black" : "white";
79918
+ 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} ` }) });
78774
79919
  }
78775
79920
 
78776
79921
  // src/dashboard/ink/TooSmallOverlay.tsx
78777
79922
  await init_build2();
79923
+ var import_sidekick_docker_shared10 = __toESM(require_dist(), 1);
78778
79924
  var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
78779
79925
  function TooSmallOverlay({ columns, rows }) {
79926
+ const needWidth = Math.max(0, 60 - columns);
79927
+ const needHeight = Math.max(0, 15 - rows);
79928
+ const hints = [];
79929
+ if (needWidth > 0) hints.push(`${needWidth} col${needWidth > 1 ? "s" : ""} wider`);
79930
+ if (needHeight > 0) hints.push(`${needHeight} row${needHeight > 1 ? "s" : ""} taller`);
78780
79931
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", justifyContent: "center", alignItems: "center", height: rows, width: columns, children: [
79932
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared10.BRAND_INLINE}` }),
79933
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
78781
79934
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "yellow", bold: true, children: "Terminal too small" }),
78782
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", children: `Current: ${columns}x${rows}` }),
78783
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", children: "Minimum: 60x15" }),
78784
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", children: "Please resize your terminal." })
79935
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", children: `${columns}\xD7${rows} \u2192 need ${hints.join(" and ")}` }),
79936
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
79937
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "gray", dimColor: true, children: "Resize to at least 60\xD715 to continue." })
78785
79938
  ] });
78786
79939
  }
78787
79940
 
@@ -78870,7 +80023,7 @@ function parseMouseEvent(data) {
78870
80023
  }
78871
80024
 
78872
80025
  // src/dashboard/ink/mouse/MouseProvider.tsx
78873
- var import_react33 = __toESM(require_react(), 1);
80026
+ var import_react34 = __toESM(require_react(), 1);
78874
80027
  await init_build2();
78875
80028
  var import_jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
78876
80029
  function InputSink() {
@@ -78879,7 +80032,7 @@ function InputSink() {
78879
80032
  return null;
78880
80033
  }
78881
80034
  function MouseProvider({ onMouse, children }) {
78882
- (0, import_react33.useEffect)(() => {
80035
+ (0, import_react34.useEffect)(() => {
78883
80036
  enableMouse();
78884
80037
  const handler = (data) => {
78885
80038
  const event = parseMouseEvent(data);
@@ -78902,29 +80055,40 @@ function MouseProvider({ onMouse, children }) {
78902
80055
  ] });
78903
80056
  }
78904
80057
 
78905
- // src/dashboard/ink/VersionOverlay.tsx
78906
- var import_react34 = __toESM(require_react(), 1);
80058
+ // src/dashboard/ink/LogFilterOverlay.tsx
78907
80059
  await init_build2();
78908
- var import_sidekick_docker_shared5 = __toESM(require_dist(), 1);
78909
80060
  var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
80061
+ function LogFilterOverlay({ filterString, filterMode }) {
80062
+ const modeLabel = filterMode === "exact" ? "exact" : "fuzzy";
80063
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Box_default, { position: "absolute", marginTop: 1, marginLeft: 1, children: [
80064
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { backgroundColor: "#2B4C7E", color: "white", bold: true, children: ` Log filter (${modeLabel}): ${filterString}\u2588 ` }),
80065
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: "gray", children: " Tab: toggle mode Enter: apply Esc: clear" })
80066
+ ] });
80067
+ }
80068
+
80069
+ // src/dashboard/ink/VersionOverlay.tsx
80070
+ var import_react35 = __toESM(require_react(), 1);
80071
+ await init_build2();
80072
+ var import_sidekick_docker_shared11 = __toESM(require_dist(), 1);
80073
+ var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
78910
80074
  function VersionOverlay({ version: version2 }) {
78911
- const phrase = import_react34.default.useMemo(() => (0, import_sidekick_docker_shared5.getRandomPhrase)(), []);
78912
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
78913
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { bold: true, color: "magenta", children: import_sidekick_docker_shared5.BRAND_INLINE }),
78914
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { color: "gray", children: [
78915
- import_sidekick_docker_shared5.BRAND_TAGLINE,
78916
- " v",
78917
- version2
78918
- ] }),
78919
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { children: "" }),
78920
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: "gray", italic: true, children: `"${phrase}"` }),
78921
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { children: "" }),
78922
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: "gray", children: "Press V or Esc to close" })
80075
+ const phrase = import_react35.default.useMemo(() => (0, import_sidekick_docker_shared11.getRandomPhrase)(), []);
80076
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, padding: 1, children: [
80077
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { bold: true, color: "magenta", children: `\u26A1 ${import_sidekick_docker_shared11.BRAND_INLINE}` }),
80078
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "#2B4C7E", bold: true, children: `${import_sidekick_docker_shared11.BRAND_TAGLINE} v${version2}` }),
80079
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
80080
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
80081
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
80082
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", italic: true, children: ` "${phrase}"` }),
80083
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
80084
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "\u2500".repeat(40) }),
80085
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: "" }),
80086
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "gray", dimColor: true, children: "Press V or Esc to close" })
78923
80087
  ] });
78924
80088
  }
78925
80089
 
78926
80090
  // src/dashboard/ink/Dashboard.tsx
78927
- var import_sidekick_docker_shared6 = __toESM(require_dist(), 1);
80091
+ var import_sidekick_docker_shared12 = __toESM(require_dist(), 1);
78928
80092
 
78929
80093
  // src/dashboard/ExecManager.ts
78930
80094
  var ExecManager = class {
@@ -78981,10 +80145,12 @@ var ExecManager = class {
78981
80145
  };
78982
80146
 
78983
80147
  // src/dashboard/ink/Dashboard.tsx
78984
- var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
80148
+ var import_jsx_runtime16 = __toESM(require_jsx_runtime(), 1);
78985
80149
  var SIDE_PANEL_WIDTH = 28;
78986
80150
  var MIN_SCREEN_WIDTH = 60;
78987
80151
  var MIN_SCREEN_HEIGHT = 15;
80152
+ var RESERVED_UI_ROWS = 5;
80153
+ var TOAST_DURATIONS = { error: 4e3, warning: 3e3, info: 2e3 };
78988
80154
  function reducer(state, action) {
78989
80155
  switch (action.type) {
78990
80156
  case "SWITCH_PANEL":
@@ -79061,6 +80227,10 @@ function reducer(state, action) {
79061
80227
  }
79062
80228
  case "EXEC_END":
79063
80229
  return { ...state, overlay: null, execContainerId: null, execContainerName: "", execOutputLines: [] };
80230
+ case "SET_LOG_FILTER":
80231
+ return { ...state, logFilterString: action.value };
80232
+ case "TOGGLE_LOG_FILTER_MODE":
80233
+ return { ...state, logFilterMode: state.logFilterMode === "exact" ? "fuzzy" : "exact" };
79064
80234
  default:
79065
80235
  return state;
79066
80236
  }
@@ -79080,28 +80250,29 @@ var initialState = {
79080
80250
  confirmMessage: "",
79081
80251
  execOutputLines: [],
79082
80252
  execContainerId: null,
79083
- execContainerName: ""
80253
+ execContainerName: "",
80254
+ logFilterString: "",
80255
+ logFilterMode: "exact"
79084
80256
  };
79085
80257
  function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecFallback }) {
79086
- const [state, dispatch] = (0, import_react35.useReducer)(reducer, initialState);
79087
- const { exit } = use_app_default();
80258
+ const [state, dispatch] = (0, import_react36.useReducer)(reducer, initialState);
79088
80259
  const { columns, rows } = useTerminalSize();
79089
- const toastIdRef = (0, import_react35.useRef)(0);
79090
- const execManagerRef = (0, import_react35.useRef)(null);
79091
- const [phrase, setPhrase] = import_react35.default.useState(() => (0, import_sidekick_docker_shared6.getRandomPhrase)());
79092
- const phraseTimerRef = (0, import_react35.useRef)(null);
79093
- const rotatePhrase = (0, import_react35.useCallback)(() => {
79094
- setPhrase((0, import_sidekick_docker_shared6.getRandomPhrase)());
80260
+ const toastIdRef = (0, import_react36.useRef)(0);
80261
+ const execManagerRef = (0, import_react36.useRef)(null);
80262
+ const [phrase, setPhrase] = import_react36.default.useState(() => (0, import_sidekick_docker_shared12.getRandomPhrase)());
80263
+ const phraseTimerRef = (0, import_react36.useRef)(null);
80264
+ const rotatePhrase = (0, import_react36.useCallback)(() => {
80265
+ setPhrase((0, import_sidekick_docker_shared12.getRandomPhrase)());
79095
80266
  if (phraseTimerRef.current) clearTimeout(phraseTimerRef.current);
79096
80267
  phraseTimerRef.current = setTimeout(rotatePhrase, 7e3);
79097
80268
  }, []);
79098
- (0, import_react35.useEffect)(() => {
80269
+ (0, import_react36.useEffect)(() => {
79099
80270
  phraseTimerRef.current = setTimeout(rotatePhrase, 7e3);
79100
80271
  return () => {
79101
80272
  if (phraseTimerRef.current) clearTimeout(phraseTimerRef.current);
79102
80273
  };
79103
80274
  }, [rotatePhrase]);
79104
- (0, import_react35.useEffect)(() => {
80275
+ (0, import_react36.useEffect)(() => {
79105
80276
  if (!execTriggerRef) return;
79106
80277
  execTriggerRef.current = (containerId, containerName) => {
79107
80278
  const manager = new ExecManager();
@@ -79136,7 +80307,7 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79136
80307
  execTriggerRef.current = null;
79137
80308
  };
79138
80309
  }, [execTriggerRef, onExecFallback, columns, rows]);
79139
- (0, import_react35.useEffect)(() => {
80310
+ (0, import_react36.useEffect)(() => {
79140
80311
  if (state.overlay !== "exec") return;
79141
80312
  const handler = (data) => {
79142
80313
  if (data.length === 1 && data[0] === 29) {
@@ -79153,22 +80324,23 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79153
80324
  process.stdin.removeListener("data", handler);
79154
80325
  };
79155
80326
  }, [state.overlay]);
79156
- (0, import_react35.useEffect)(() => {
80327
+ (0, import_react36.useEffect)(() => {
79157
80328
  if (state.overlay === "exec" && execManagerRef.current) {
79158
80329
  execManagerRef.current.resize(columns, rows);
79159
80330
  }
79160
80331
  }, [columns, rows, state.overlay]);
79161
- const addToast = (0, import_react35.useCallback)((message, severity) => {
79162
- const durations = { error: 4e3, warning: 3e3, info: 2e3 };
80332
+ const addToast = (0, import_react36.useCallback)((message, severity) => {
79163
80333
  const id = ++toastIdRef.current;
79164
- dispatch({ type: "ADD_TOAST", toast: { id, message, severity, expiresAt: Date.now() + durations[severity] } });
79165
- setTimeout(() => dispatch({ type: "REMOVE_TOAST", id }), durations[severity]);
80334
+ dispatch({ type: "ADD_TOAST", toast: { id, message, severity, expiresAt: Date.now() + TOAST_DURATIONS[severity] } });
80335
+ setTimeout(() => dispatch({ type: "REMOVE_TOAST", id }), TOAST_DURATIONS[severity]);
79166
80336
  }, []);
79167
80337
  const panel = panels[state.activePanelIndex];
79168
80338
  const tooSmall = columns < MIN_SCREEN_WIDTH || rows < MIN_SCREEN_HEIGHT;
79169
80339
  const sideWidth = state.layoutMode === "expanded" ? 0 : SIDE_PANEL_WIDTH;
79170
- const getFilteredItems = (0, import_react35.useCallback)(() => {
79171
- let items = panel.getItems(metrics);
80340
+ const allItems = panel.getItems(metrics);
80341
+ const totalItemCount = allItems.length;
80342
+ const currentItems = (() => {
80343
+ let items = allItems;
79172
80344
  if (state.filterString) {
79173
80345
  const f = state.filterString.toLowerCase();
79174
80346
  items = items.filter((it) => {
@@ -79178,361 +80350,101 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79178
80350
  }
79179
80351
  items.sort((a, b) => a.sortKey - b.sortKey);
79180
80352
  return items;
79181
- }, [panel, metrics, state.filterString]);
79182
- const currentItems = getFilteredItems();
80353
+ })();
79183
80354
  const clampedSelection = Math.min(state.selectedItemIndex, Math.max(0, currentItems.length - 1));
79184
80355
  if (clampedSelection !== state.selectedItemIndex && currentItems.length > 0) {
79185
80356
  dispatch({ type: "SELECT_ITEM", index: clampedSelection });
79186
80357
  }
79187
- const sideViewportHeight = Math.max(1, rows - 5);
80358
+ const sideViewportHeight = Math.max(1, rows - RESERVED_UI_ROWS);
79188
80359
  const sideScroll = useWindowedScroll({ totalItems: currentItems.length, viewportHeight: sideViewportHeight });
79189
- (0, import_react35.useEffect)(() => {
80360
+ (0, import_react36.useEffect)(() => {
79190
80361
  if (sideScroll.selectedIndex !== state.selectedItemIndex) {
79191
80362
  sideScroll.setSelected(state.selectedItemIndex);
79192
80363
  }
79193
80364
  }, [state.selectedItemIndex]);
79194
80365
  const selectedItem = currentItems[clampedSelection];
79195
- (0, import_react35.useEffect)(() => {
80366
+ (0, import_react36.useEffect)(() => {
79196
80367
  onSelectionChange?.(panel.id, selectedItem?.id ?? null);
79197
80368
  }, [panel.id, selectedItem?.id]);
79198
80369
  const detailTabs = panel.detailTabs;
79199
80370
  const tabIdx = Math.min(state.detailTabIndex, detailTabs.length - 1);
80371
+ const enrichedMetrics = {
80372
+ ...metrics,
80373
+ logFilterString: state.logFilterString,
80374
+ logFilterMode: state.logFilterMode
80375
+ };
79200
80376
  let detailContent = "";
79201
80377
  if (selectedItem && detailTabs.length > 0 && tabIdx >= 0) {
79202
- detailContent = detailTabs[tabIdx].render(selectedItem, metrics);
80378
+ detailContent = detailTabs[tabIdx].render(selectedItem, enrichedMetrics);
79203
80379
  } else if (!selectedItem) {
79204
80380
  detailContent = "(no item selected)";
79205
80381
  }
79206
80382
  const detailLines = detailContent.split("\n");
79207
- const detailViewportHeight = Math.max(1, rows - 5);
80383
+ const detailViewportHeight = Math.max(1, rows - RESERVED_UI_ROWS);
79208
80384
  const activeTab = detailTabs[tabIdx];
79209
80385
  const shouldAutoScroll = activeTab?.autoScrollBottom ?? false;
79210
- (0, import_react35.useEffect)(() => {
80386
+ (0, import_react36.useEffect)(() => {
79211
80387
  if (shouldAutoScroll && detailLines.length > detailViewportHeight) {
79212
80388
  dispatch({ type: "SCROLL_DETAIL", offset: detailLines.length - detailViewportHeight });
79213
80389
  }
79214
80390
  }, [shouldAutoScroll, detailLines.length, detailViewportHeight]);
79215
- const getContextActions = (0, import_react35.useCallback)(() => {
79216
- if (!selectedItem) return [];
79217
- return panel.getActions().filter((a) => !a.condition || a.condition(selectedItem));
79218
- }, [panel, selectedItem]);
79219
- const contextActions = state.overlay === "context-menu" ? getContextActions() : [];
79220
- const handleMouse = (0, import_react35.useCallback)((event) => {
79221
- rotatePhrase();
79222
- if (state.overlay === "filter") return;
79223
- if (state.overlay) {
79224
- if (event.type === "click") {
79225
- dispatch({ type: "SET_OVERLAY", overlay: null });
79226
- }
79227
- return;
79228
- }
79229
- const { x, y } = event;
79230
- if (event.type === "scroll") {
79231
- if (x < sideWidth && sideWidth > 0) {
79232
- const delta = event.scrollDirection === "down" ? 3 : -3;
79233
- dispatch({ type: "SCROLL_SIDE", delta, itemCount: currentItems.length });
79234
- const newIdx = Math.max(0, Math.min(clampedSelection + delta, currentItems.length - 1));
79235
- sideScroll.setSelected(newIdx);
79236
- } else {
79237
- const delta = event.scrollDirection === "down" ? 3 : -3;
79238
- dispatch({ type: "SCROLL_DETAIL_DELTA", delta, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
79239
- }
79240
- return;
79241
- }
79242
- if (event.type !== "click" || event.button !== "left") return;
79243
- if (y === 0) {
79244
- let col = 0;
79245
- for (let i = 0; i < panels.length; i++) {
79246
- const count = panelCounts[i];
79247
- let countLen = 0;
79248
- if (count) {
79249
- countLen = count.running !== void 0 ? ` ${count.running}/${count.total}`.length : ` ${count.total}`.length;
79250
- }
79251
- const tabWidth = String(panels[i].shortcutKey).length + panels[i].title.length + 3 + countLen + 1;
79252
- if (x >= col && x < col + tabWidth) {
79253
- panels[state.activePanelIndex]?.onDeactivate?.();
79254
- dispatch({ type: "SWITCH_PANEL", index: i });
79255
- panels[i]?.onActivate?.();
79256
- return;
79257
- }
79258
- col += tabWidth;
79259
- }
79260
- return;
79261
- }
79262
- if (y >= rows - 1) return;
79263
- if (x < sideWidth && sideWidth > 0) {
79264
- dispatch({ type: "SET_FOCUS", target: "side" });
79265
- const hasScrollUp = sideScroll.scrollOffset > 0;
79266
- const itemRow = y - 2 - (hasScrollUp ? 1 : 0);
79267
- const itemIndex = sideScroll.scrollOffset + itemRow;
79268
- if (itemIndex >= 0 && itemIndex < currentItems.length) {
79269
- dispatch({ type: "SELECT_ITEM", index: itemIndex });
79270
- sideScroll.setSelected(itemIndex);
79271
- }
79272
- } else {
79273
- dispatch({ type: "SET_FOCUS", target: "detail" });
79274
- if (y === 1 && detailTabs.length > 1) {
79275
- let col = sideWidth;
79276
- for (let i = 0; i < detailTabs.length; i++) {
79277
- const tabWidth = detailTabs[i].label.length + 3;
79278
- if (x >= col && x < col + tabWidth) {
79279
- dispatch({ type: "SET_DETAIL_TAB", index: i });
79280
- return;
79281
- }
79282
- col += tabWidth;
79283
- }
79284
- }
79285
- }
79286
- }, [state.overlay, state.activePanelIndex, sideWidth, currentItems.length, clampedSelection, sideScroll, detailLines.length, detailViewportHeight, panels, detailTabs, rows, rotatePhrase]);
79287
- use_input_default((input, key) => {
79288
- if (state.overlay === "exec") return;
79289
- rotatePhrase();
79290
- if (input === "q" || key.ctrl && input === "c") {
79291
- if (state.overlay) {
79292
- dispatch({ type: "SET_OVERLAY", overlay: null });
79293
- return;
79294
- }
79295
- exit();
79296
- return;
79297
- }
79298
- if (state.overlay === "confirm") {
79299
- if (input === "y" || input === "Y") {
79300
- state.confirmAction?.();
79301
- dispatch({ type: "SET_CONFIRM", action: null, message: "" });
79302
- return;
79303
- }
79304
- if (input === "n" || input === "N" || key.escape) {
79305
- dispatch({ type: "SET_CONFIRM", action: null, message: "" });
79306
- return;
79307
- }
79308
- return;
79309
- }
79310
- if (state.overlay === "filter") {
79311
- if (key.escape) {
79312
- if (state.filterString) addToast("Filter cleared", "info");
79313
- dispatch({ type: "SET_FILTER", value: "" });
79314
- dispatch({ type: "SET_OVERLAY", overlay: null });
79315
- return;
79316
- }
79317
- if (key.return) {
79318
- if (state.filterString) addToast(`Filter: "${state.filterString}"`, "info");
79319
- dispatch({ type: "SET_OVERLAY", overlay: null });
79320
- return;
79321
- }
79322
- if (key.backspace || key.delete) {
79323
- dispatch({ type: "SET_FILTER", value: state.filterString.slice(0, -1) });
79324
- return;
79325
- }
79326
- if (input && !key.ctrl && !key.meta) {
79327
- dispatch({ type: "SET_FILTER", value: state.filterString + input });
79328
- return;
79329
- }
79330
- return;
79331
- }
79332
- if (state.overlay === "context-menu") {
79333
- if (key.escape) {
79334
- dispatch({ type: "SET_OVERLAY", overlay: null });
79335
- return;
79336
- }
79337
- if (input === "j" || key.downArrow) {
79338
- dispatch({ type: "CONTEXT_MENU_NAV", delta: 1, itemCount: contextActions.length });
79339
- return;
79340
- }
79341
- if (input === "k" || key.upArrow) {
79342
- dispatch({ type: "CONTEXT_MENU_NAV", delta: -1, itemCount: contextActions.length });
79343
- return;
79344
- }
79345
- if (key.return) {
79346
- const action = contextActions[state.contextMenuIndex];
79347
- if (action && selectedItem) {
79348
- if (action.confirm) {
79349
- dispatch({ type: "SET_CONFIRM", action: () => {
79350
- action.handler(selectedItem);
79351
- addToast(action.label, "info");
79352
- }, message: action.confirmMessage || "Are you sure?" });
79353
- } else {
79354
- action.handler(selectedItem);
79355
- addToast(action.label, "info");
79356
- }
79357
- dispatch({ type: "SET_OVERLAY", overlay: null });
79358
- }
79359
- return;
79360
- }
79361
- const match = contextActions.find((a) => a.key === input);
79362
- if (match && selectedItem) {
79363
- if (match.confirm) {
79364
- dispatch({ type: "SET_CONFIRM", action: () => {
79365
- match.handler(selectedItem);
79366
- addToast(match.label, "info");
79367
- }, message: match.confirmMessage || "Are you sure?" });
79368
- } else {
79369
- match.handler(selectedItem);
79370
- addToast(match.label, "info");
79371
- }
79372
- dispatch({ type: "SET_OVERLAY", overlay: null });
79373
- }
79374
- return;
79375
- }
79376
- if (state.overlay === "help") {
79377
- if (key.escape || input === "?") {
79378
- dispatch({ type: "SET_OVERLAY", overlay: null });
79379
- }
79380
- return;
79381
- }
79382
- if (state.overlay === "version") {
79383
- if (key.escape || input === "V") {
79384
- dispatch({ type: "SET_OVERLAY", overlay: null });
79385
- }
79386
- return;
79387
- }
79388
- if (key.escape) {
79389
- if (state.filterString) {
79390
- dispatch({ type: "SET_FILTER", value: "" });
79391
- return;
79392
- }
79393
- if (state.focusTarget === "detail") {
79394
- dispatch({ type: "SET_FOCUS", target: "side" });
79395
- return;
79396
- }
79397
- return;
79398
- }
79399
- if (input === "?") {
79400
- dispatch({ type: "SET_OVERLAY", overlay: "help" });
79401
- return;
79402
- }
79403
- if (input === "V") {
79404
- dispatch({ type: "SET_OVERLAY", overlay: "version" });
79405
- return;
79406
- }
79407
- const num = parseInt(input, 10);
79408
- if (num >= 1 && num <= panels.length) {
79409
- panels[state.activePanelIndex]?.onDeactivate?.();
79410
- dispatch({ type: "SWITCH_PANEL", index: num - 1 });
79411
- panels[num - 1]?.onActivate?.();
79412
- return;
79413
- }
79414
- if (key.tab) {
79415
- dispatch({ type: "TOGGLE_FOCUS" });
79416
- return;
79417
- }
79418
- if (input === "z") {
79419
- dispatch({ type: "CYCLE_LAYOUT" });
79420
- addToast(`Layout: ${state.layoutMode === "normal" ? "Expanded" : "Normal"}`, "info");
79421
- return;
79422
- }
79423
- if (input === "/") {
79424
- dispatch({ type: "SET_OVERLAY", overlay: "filter" });
79425
- return;
79426
- }
79427
- if (input === "x") {
79428
- if (selectedItem && panel.getActions().length > 0) {
79429
- dispatch({ type: "SET_OVERLAY", overlay: "context-menu" });
79430
- }
79431
- return;
79432
- }
79433
- if (input === "[") {
79434
- dispatch({ type: "CYCLE_DETAIL_TAB", direction: -1, tabCount: detailTabs.length });
79435
- return;
79436
- }
79437
- if (input === "]") {
79438
- dispatch({ type: "CYCLE_DETAIL_TAB", direction: 1, tabCount: detailTabs.length });
79439
- return;
79440
- }
79441
- if (state.focusTarget === "side") {
79442
- if (input === "j" || key.downArrow) {
79443
- if (clampedSelection < currentItems.length - 1) {
79444
- dispatch({ type: "SELECT_ITEM", index: clampedSelection + 1 });
79445
- sideScroll.selectNext();
79446
- }
79447
- return;
79448
- }
79449
- if (input === "k" || key.upArrow) {
79450
- if (clampedSelection > 0) {
79451
- dispatch({ type: "SELECT_ITEM", index: clampedSelection - 1 });
79452
- sideScroll.selectPrev();
79453
- }
79454
- return;
79455
- }
79456
- if (input === "g") {
79457
- dispatch({ type: "SELECT_ITEM", index: 0 });
79458
- sideScroll.selectFirst();
79459
- return;
79460
- }
79461
- if (input === "G") {
79462
- dispatch({ type: "SELECT_ITEM", index: Math.max(0, currentItems.length - 1) });
79463
- sideScroll.selectLast();
79464
- return;
79465
- }
79466
- if (key.return) {
79467
- dispatch({ type: "SET_FOCUS", target: "detail" });
79468
- return;
79469
- }
79470
- }
79471
- if (state.focusTarget === "detail") {
79472
- if (input === "j" || key.downArrow) {
79473
- dispatch({ type: "SCROLL_DETAIL_DELTA", delta: 1, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
79474
- return;
79475
- }
79476
- if (input === "k" || key.upArrow) {
79477
- dispatch({ type: "SCROLL_DETAIL_DELTA", delta: -1, totalLines: detailLines.length, viewportHeight: detailViewportHeight });
79478
- return;
79479
- }
79480
- if (input === "h" || key.leftArrow) {
79481
- dispatch({ type: "SET_FOCUS", target: "side" });
79482
- return;
79483
- }
79484
- if (input === "g") {
79485
- dispatch({ type: "SCROLL_DETAIL", offset: 0 });
79486
- return;
79487
- }
79488
- if (input === "G") {
79489
- dispatch({ type: "SCROLL_DETAIL", offset: Math.max(0, detailLines.length - detailViewportHeight) });
79490
- return;
79491
- }
79492
- }
79493
- if (selectedItem) {
79494
- const actions = panel.getActions();
79495
- const actionMatch = actions.find((a) => a.key === input && (!a.condition || a.condition(selectedItem)));
79496
- if (actionMatch) {
79497
- if (actionMatch.confirm) {
79498
- dispatch({ type: "SET_CONFIRM", action: () => {
79499
- actionMatch.handler(selectedItem);
79500
- addToast(actionMatch.label, "info");
79501
- }, message: actionMatch.confirmMessage || "Are you sure?" });
79502
- } else {
79503
- actionMatch.handler(selectedItem);
79504
- addToast(actionMatch.label, "info");
79505
- }
79506
- }
79507
- }
79508
- });
79509
- if (tooSmall) {
79510
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TooSmallOverlay, { columns, rows });
79511
- }
80391
+ const panelActions = panel.getActions();
80392
+ const applicableActions = selectedItem ? panelActions.filter((a) => !a.condition || a.condition(selectedItem)) : [];
80393
+ const contextActions = state.overlay === "context-menu" ? applicableActions : [];
79512
80394
  const runningCount = metrics.containers.filter((c) => c.state === "running").length;
79513
80395
  const panelCounts = panels.map((p) => {
79514
- const items = p.getItems(metrics);
79515
80396
  if (p.id === "containers") {
79516
- const running = items.filter((it) => {
79517
- const d = it.data;
79518
- return d?.state === "running";
79519
- }).length;
79520
- return { total: items.length, running };
80397
+ return { total: metrics.containers.length, running: runningCount };
79521
80398
  }
79522
80399
  if (p.id === "services") {
79523
- const meaningful = items.filter((it) => it.data !== null).length;
80400
+ const meaningful = metrics.composeProjects.reduce((sum, proj) => sum + proj.services.length, 0);
79524
80401
  return { total: meaningful };
79525
80402
  }
80403
+ const items = p === panel ? allItems : p.getItems(metrics);
79526
80404
  return { total: items.length };
79527
80405
  });
80406
+ const handleMouse = useMouseHandler({
80407
+ state,
80408
+ dispatch,
80409
+ panels,
80410
+ panelCounts,
80411
+ currentItems,
80412
+ clampedSelection,
80413
+ sideWidth,
80414
+ sideScroll,
80415
+ detailLines,
80416
+ detailViewportHeight,
80417
+ detailTabs,
80418
+ rows,
80419
+ rotatePhrase
80420
+ });
80421
+ useKeyboardHandler({
80422
+ state,
80423
+ dispatch,
80424
+ panels,
80425
+ panel,
80426
+ selectedItem,
80427
+ contextActions,
80428
+ clampedSelection,
80429
+ currentItems,
80430
+ detailLines,
80431
+ detailViewportHeight,
80432
+ detailTabs,
80433
+ tabIdx,
80434
+ panelActions,
80435
+ sideScroll,
80436
+ addToast,
80437
+ rotatePhrase
80438
+ });
80439
+ if (tooSmall) {
80440
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TooSmallOverlay, { columns, rows });
80441
+ }
79528
80442
  const showNormalLayout = state.overlay !== "help" && state.overlay !== "exec" && state.overlay !== "version";
79529
- const allItems = panel.getItems(metrics);
79530
- const totalItemCount = allItems.length;
79531
- const panelActionHints = selectedItem ? panel.getActions().filter((a) => !a.condition || a.condition(selectedItem)).map((a) => `${a.key}:${a.label}`).join(" ") : "";
79532
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MouseProvider, { onMouse: handleMouse, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", height: rows, width: columns, children: [
79533
- showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TabBar, { panels, activeIndex: state.activePanelIndex, layoutMode: state.layoutMode, phrase, panelCounts }),
79534
- showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexGrow: 1, flexDirection: "row", children: [
79535
- sideWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
80443
+ const panelActionHints = applicableActions.map((a) => ({ key: a.key, label: a.label, destructive: !!a.confirm }));
80444
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MouseProvider, { onMouse: handleMouse, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Box_default, { flexDirection: "column", height: rows, width: columns, children: [
80445
+ showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TabBar, { panels, activeIndex: state.activePanelIndex, layoutMode: state.layoutMode, phrase, panelCounts }),
80446
+ showNormalLayout && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Box_default, { flexGrow: 1, flexDirection: "row", children: [
80447
+ sideWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
79536
80448
  SideList,
79537
80449
  {
79538
80450
  items: currentItems,
@@ -79548,9 +80460,9 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79548
80460
  runningCount: panel.id === "containers" ? runningCount : void 0
79549
80461
  }
79550
80462
  ),
79551
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, children: [
79552
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DetailTabBar, { tabs: detailTabs, activeIndex: state.detailTabIndex }),
79553
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
80463
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Box_default, { flexDirection: "column", flexGrow: 1, children: [
80464
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DetailTabBar, { tabs: detailTabs, activeIndex: state.detailTabIndex }),
80465
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
79554
80466
  DetailPane,
79555
80467
  {
79556
80468
  content: detailContent,
@@ -79561,33 +80473,32 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79561
80473
  )
79562
80474
  ] })
79563
80475
  ] }),
79564
- state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.1.1" }),
79565
- state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VersionOverlay, { version: "0.1.1" }),
79566
- state.overlay === "exec" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
80476
+ state.overlay === "help" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(HelpOverlay, { panels, activePanelIndex: state.activePanelIndex, version: "0.1.3" }),
80477
+ state.overlay === "version" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VersionOverlay, { version: "0.1.3" }),
80478
+ state.overlay === "exec" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
79567
80479
  ExecOverlay,
79568
80480
  {
79569
80481
  containerName: state.execContainerName,
79570
80482
  outputLines: state.execOutputLines
79571
80483
  }
79572
80484
  ),
79573
- state.overlay !== "exec" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
80485
+ state.overlay !== "exec" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
79574
80486
  StatusBar,
79575
80487
  {
79576
80488
  daemonConnected: metrics.daemonConnected,
79577
80489
  focusTarget: state.focusTarget,
79578
- panelHints: "",
79579
80490
  panelActionHints,
79580
80491
  filterString: state.filterString,
79581
80492
  containerCount: metrics.containers.length,
79582
80493
  runningCount,
79583
- version: "0.1.1",
80494
+ version: "0.1.3",
79584
80495
  matchCount: state.filterString ? currentItems.length : void 0,
79585
80496
  totalCount: state.filterString ? totalItemCount : void 0,
79586
80497
  lastRefresh: metrics.lastRefresh
79587
80498
  }
79588
80499
  ),
79589
- state.overlay === "context-menu" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ContextMenuOverlay, { actions: contextActions, selectedIndex: state.contextMenuIndex }),
79590
- state.overlay === "filter" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
80500
+ state.overlay === "context-menu" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ContextMenuOverlay, { actions: contextActions, selectedIndex: state.contextMenuIndex }),
80501
+ state.overlay === "filter" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
79591
80502
  FilterOverlay,
79592
80503
  {
79593
80504
  filterString: state.filterString,
@@ -79596,7 +80507,14 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79596
80507
  panelTitle: panel.title
79597
80508
  }
79598
80509
  ),
79599
- state.overlay === "confirm" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
80510
+ state.overlay === "log-filter" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
80511
+ LogFilterOverlay,
80512
+ {
80513
+ filterString: state.logFilterString,
80514
+ filterMode: state.logFilterMode
80515
+ }
80516
+ ),
80517
+ state.overlay === "confirm" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
79600
80518
  ConfirmOverlay,
79601
80519
  {
79602
80520
  message: state.confirmMessage,
@@ -79607,7 +80525,7 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79607
80525
  onCancel: () => dispatch({ type: "SET_CONFIRM", action: null, message: "" })
79608
80526
  }
79609
80527
  ),
79610
- state.toasts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ToastNotification, { toast: state.toasts[state.toasts.length - 1] })
80528
+ state.toasts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ToastNotification, { toast: state.toasts[state.toasts.length - 1] })
79611
80529
  ] }) });
79612
80530
  }
79613
80531
 
@@ -79615,7 +80533,7 @@ function Dashboard({ panels, metrics, onSelectionChange, execTriggerRef, onExecF
79615
80533
  async function dashboardAction(_opts, cmd) {
79616
80534
  const globalOpts = cmd.parent?.opts() ?? cmd.opts();
79617
80535
  const socketPath = globalOpts.socket;
79618
- const client = new import_sidekick_docker_shared7.DockerClient(socketPath ? { socketPath } : void 0);
80536
+ const client = new import_sidekick_docker_shared13.DockerClient(socketPath ? { socketPath } : void 0);
79619
80537
  const ok = await client.ping();
79620
80538
  if (!ok) {
79621
80539
  console.error("Error: Cannot connect to Docker daemon. Is Docker running?");
@@ -79624,15 +80542,19 @@ async function dashboardAction(_opts, cmd) {
79624
80542
  const cwd2 = process.cwd();
79625
80543
  const state = new DockerState(client, cwd2);
79626
80544
  await state.refresh();
79627
- const composeClient = new import_sidekick_docker_shared7.ComposeClient();
80545
+ const composeClient = new import_sidekick_docker_shared13.ComposeClient();
79628
80546
  const onAction = () => {
79629
- state.refresh().then(() => scheduleRender()).catch(() => {
79630
- });
80547
+ state.refresh().then(() => scheduleRender()).catch((e) => console.debug("refresh failed:", e));
80548
+ };
80549
+ const onError = (msg) => {
80550
+ console.debug("panel action failed:", msg);
79631
80551
  };
79632
80552
  const logManager = new LogStreamManager(client, () => {
79633
80553
  state.setSelectedLogs(logManager.getLogs());
80554
+ logSeverityCounts = logManager.getSeverityCounts();
79634
80555
  scheduleRender();
79635
80556
  });
80557
+ let logSeverityCounts = logManager.getSeverityCounts();
79636
80558
  const statsManager = new StatsStreamManager(client, state.getStatsCollector(), () => {
79637
80559
  scheduleRender();
79638
80560
  });
@@ -79652,8 +80574,7 @@ async function dashboardAction(_opts, cmd) {
79652
80574
  client.inspectContainer(itemId).then((info) => {
79653
80575
  state.setInspectedEnv(itemId, info.Config.Env || []);
79654
80576
  scheduleRender();
79655
- }).catch(() => {
79656
- });
80577
+ }).catch((e) => console.debug("inspectContainer failed:", e));
79657
80578
  }
79658
80579
  } else if (panelId === "services" && itemId) {
79659
80580
  logManager.select(null);
@@ -79671,24 +80592,24 @@ async function dashboardAction(_opts, cmd) {
79671
80592
  }
79672
80593
  };
79673
80594
  const panels = [
79674
- new ContainersPanel(client, onAction),
79675
- new ServicesPanel(composeClient, onAction, cwd2),
79676
- new ImagesPanel(client, onAction),
79677
- new VolumesPanel(client, onAction),
79678
- new NetworksPanel(client, onAction)
80595
+ new ContainersPanel(client, onAction, onError),
80596
+ new ServicesPanel(composeClient, onAction, cwd2, onError),
80597
+ new ImagesPanel(client, onAction, onError),
80598
+ new VolumesPanel(client, onAction, onError),
80599
+ new NetworksPanel(client, onAction, onError)
79679
80600
  ];
79680
- const watcher = new import_sidekick_docker_shared7.EventWatcher(client, {
80601
+ const watcher = new import_sidekick_docker_shared13.EventWatcher(client, {
79681
80602
  onEvent: (event) => {
79682
80603
  state.processEvent(event);
79683
80604
  scheduleRender();
79684
80605
  },
79685
- onError: () => {
80606
+ onError: (err) => {
80607
+ console.debug("event watcher error:", err.message);
79686
80608
  }
79687
80609
  });
79688
80610
  watcher.start();
79689
80611
  const refreshInterval = setInterval(() => {
79690
- state.refresh().then(() => scheduleRender()).catch(() => {
79691
- });
80612
+ state.refresh().then(() => scheduleRender()).catch((e) => console.debug("periodic refresh failed:", e));
79692
80613
  }, 3e4);
79693
80614
  const execTriggerRef = { current: null };
79694
80615
  const onExecFallback = (containerId) => {
@@ -79699,9 +80620,9 @@ async function dashboardAction(_opts, cmd) {
79699
80620
  stdio: "inherit"
79700
80621
  });
79701
80622
  instance = render2(
79702
- import_react36.default.createElement(Dashboard, {
80623
+ import_react37.default.createElement(Dashboard, {
79703
80624
  panels,
79704
- metrics: state.getMetrics(),
80625
+ metrics: getEnrichedMetrics(),
79705
80626
  onSelectionChange,
79706
80627
  execTriggerRef,
79707
80628
  onExecFallback
@@ -79711,9 +80632,9 @@ async function dashboardAction(_opts, cmd) {
79711
80632
  };
79712
80633
  const { render: render2 } = await init_build2().then(() => build_exports);
79713
80634
  let instance = render2(
79714
- import_react36.default.createElement(Dashboard, {
80635
+ import_react37.default.createElement(Dashboard, {
79715
80636
  panels,
79716
- metrics: state.getMetrics(),
80637
+ metrics: getEnrichedMetrics(),
79717
80638
  onSelectionChange,
79718
80639
  execTriggerRef,
79719
80640
  onExecFallback
@@ -79730,14 +80651,21 @@ async function dashboardAction(_opts, cmd) {
79730
80651
  }
79731
80652
  });
79732
80653
  let renderTimer = null;
80654
+ function getEnrichedMetrics() {
80655
+ const m = state.getMetrics();
80656
+ m.logSeverityCounts = logSeverityCounts;
80657
+ m.logSeverityTimeSeries = logManager.getSeverityTimeSeries();
80658
+ m.logTemplates = logManager.getTemplates();
80659
+ return m;
80660
+ }
79733
80661
  function scheduleRender() {
79734
80662
  if (renderTimer) return;
79735
80663
  renderTimer = setTimeout(() => {
79736
80664
  renderTimer = null;
79737
80665
  instance.rerender(
79738
- import_react36.default.createElement(Dashboard, {
80666
+ import_react37.default.createElement(Dashboard, {
79739
80667
  panels,
79740
- metrics: state.getMetrics(),
80668
+ metrics: getEnrichedMetrics(),
79741
80669
  onSelectionChange,
79742
80670
  execTriggerRef,
79743
80671
  onExecFallback
@@ -79785,9 +80713,9 @@ async function dashboardAction(_opts, cmd) {
79785
80713
  }
79786
80714
 
79787
80715
  // src/commands/ps.ts
79788
- var import_sidekick_docker_shared8 = __toESM(require_dist(), 1);
80716
+ var import_sidekick_docker_shared14 = __toESM(require_dist(), 1);
79789
80717
  async function psAction(opts) {
79790
- const client = new import_sidekick_docker_shared8.DockerClient();
80718
+ const client = new import_sidekick_docker_shared14.DockerClient();
79791
80719
  const ok = await client.ping();
79792
80720
  if (!ok) {
79793
80721
  console.error("Error: Cannot connect to Docker daemon. Is Docker running?");
@@ -79804,19 +80732,19 @@ async function psAction(opts) {
79804
80732
  for (const c of containers) {
79805
80733
  const row = [
79806
80734
  c.id.substring(0, 12).padEnd(20),
79807
- `${(0, import_sidekick_docker_shared2.stateIcon)(c.state)} ${c.name}`.padEnd(20),
80735
+ `${(0, import_sidekick_docker_shared3.stateIcon)(c.state)} ${c.name}`.padEnd(20),
79808
80736
  c.image.padEnd(20),
79809
80737
  formatUptime(c.status).padEnd(20),
79810
- (0, import_sidekick_docker_shared2.formatPorts)(c.ports)
80738
+ (0, import_sidekick_docker_shared3.formatPorts)(c.ports)
79811
80739
  ].join("");
79812
80740
  console.log(row);
79813
80741
  }
79814
80742
  }
79815
80743
 
79816
80744
  // src/commands/logs.ts
79817
- var import_sidekick_docker_shared9 = __toESM(require_dist(), 1);
80745
+ var import_sidekick_docker_shared15 = __toESM(require_dist(), 1);
79818
80746
  async function logsAction(container, opts) {
79819
- const client = new import_sidekick_docker_shared9.DockerClient();
80747
+ const client = new import_sidekick_docker_shared15.DockerClient();
79820
80748
  const ok = await client.ping();
79821
80749
  if (!ok) {
79822
80750
  console.error("Error: Cannot connect to Docker daemon. Is Docker running?");
@@ -79833,7 +80761,7 @@ async function logsAction(container, opts) {
79833
80761
  console.log(`${prefix}${ts} ${entry.message}${reset}`);
79834
80762
  }
79835
80763
  } catch (err) {
79836
- const msg = err instanceof Error ? err.message : String(err);
80764
+ const msg = (0, import_sidekick_docker_shared15.errorMessage)(err);
79837
80765
  if (msg.includes("no such container") || msg.includes("No such container")) {
79838
80766
  console.error(`Error: Container "${container}" not found.`);
79839
80767
  } else {
@@ -79845,7 +80773,7 @@ async function logsAction(container, opts) {
79845
80773
 
79846
80774
  // src/cli.ts
79847
80775
  var program2 = new Command();
79848
- program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.1.1").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
80776
+ program2.name("sidekick-docker").description("Docker management TUI dashboard").version("0.1.3").option("--socket <path>", "Docker socket path").action(async (_opts, cmd) => {
79849
80777
  await dashboardAction(_opts, cmd);
79850
80778
  });
79851
80779
  program2.command("ps").description("List containers").option("-a, --all", "Show all containers (default: running only)", false).action(async (opts) => {