dev-cockpit 0.3.2 → 0.3.4

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.
package/dist/index.js CHANGED
@@ -15378,18 +15378,18 @@ var require_react_jsx_runtime_development = __commonJS({
15378
15378
  function isValidElement(object) {
15379
15379
  return "object" === typeof object && null !== object && object.$$typeof === REACT_ELEMENT_TYPE;
15380
15380
  }
15381
- var React7 = require_react(), REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_ACTIVITY_TYPE = Symbol.for("react.activity"), REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), ReactSharedInternals = React7.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, hasOwnProperty = Object.prototype.hasOwnProperty, isArrayImpl = Array.isArray, createTask = console.createTask ? console.createTask : function() {
15381
+ var React8 = require_react(), REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_ACTIVITY_TYPE = Symbol.for("react.activity"), REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), ReactSharedInternals = React8.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, hasOwnProperty = Object.prototype.hasOwnProperty, isArrayImpl = Array.isArray, createTask = console.createTask ? console.createTask : function() {
15382
15382
  return null;
15383
15383
  };
15384
- React7 = {
15384
+ React8 = {
15385
15385
  react_stack_bottom_frame: function(callStackForError) {
15386
15386
  return callStackForError();
15387
15387
  }
15388
15388
  };
15389
15389
  var specialPropKeyWarningShown;
15390
15390
  var didWarnAboutElementRef = {};
15391
- var unknownOwnerDebugStack = React7.react_stack_bottom_frame.bind(
15392
- React7,
15391
+ var unknownOwnerDebugStack = React8.react_stack_bottom_frame.bind(
15392
+ React8,
15393
15393
  UnknownOwner
15394
15394
  )();
15395
15395
  var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));
@@ -83935,7 +83935,15 @@ var HighlightSchema = external_exports.object({
83935
83935
  severity: external_exports.enum(["info", "warn", "error"]).optional().default("info")
83936
83936
  });
83937
83937
  var RemediationSchema = external_exports.object({
83938
- key: external_exports.string(),
83938
+ /**
83939
+ * Optional keypress binding on the Health pane. When omitted, the
83940
+ * remediation is documentation-only — the row shows the label but pressing
83941
+ * any key does nothing for this entry. Useful when several health entries
83942
+ * share the same shell command and you only want ONE row to be the
83943
+ * trigger — see `docker compose up -d` covering all four container-running
83944
+ * checks in the awc wrapper config.
83945
+ */
83946
+ key: external_exports.string().optional(),
83939
83947
  label: external_exports.string(),
83940
83948
  command: external_exports.string(),
83941
83949
  cwd: external_exports.string().optional()
@@ -83999,6 +84007,7 @@ var ComposerSettingsSchema = external_exports.object({
83999
84007
  */
84000
84008
  packageDirs: external_exports.array(external_exports.string()).optional().default(["."])
84001
84009
  });
84010
+ var KeybindingsSchema = external_exports.record(external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]));
84002
84011
  var MountSettingsSchema = external_exports.object({
84003
84012
  /**
84004
84013
  * Override path for the generated docker-compose overlay. When unset
@@ -84032,6 +84041,7 @@ var BaseCockpitConfigSchema = external_exports.object({
84032
84041
  manifestFile: "mount.manifest.json"
84033
84042
  }),
84034
84043
  composer: ComposerSettingsSchema.optional().default({ packageDirs: ["."] }),
84044
+ keybindings: KeybindingsSchema.optional().default({}),
84035
84045
  /** Which pane the cockpit lands on when it boots. Profile may override. */
84036
84046
  defaultPane: external_exports.enum(["repos", "output", "health", "help"]).optional().default("repos"),
84037
84047
  /** Named keystroke-bound shell commands surfaced via the `:` palette. */
@@ -84043,6 +84053,16 @@ var BaseCockpitConfigSchema = external_exports.object({
84043
84053
  */
84044
84054
  profile: external_exports.record(external_exports.unknown()).optional().default({})
84045
84055
  });
84056
+ function normaliseKeybindings(raw) {
84057
+ const out = {};
84058
+ if (!raw) return out;
84059
+ for (const [key, value] of Object.entries(raw)) {
84060
+ const ids = Array.isArray(value) ? value : [value];
84061
+ const clean = ids.filter((s) => typeof s === "string" && s.length > 0);
84062
+ if (clean.length > 0) out[key] = clean;
84063
+ }
84064
+ return out;
84065
+ }
84046
84066
  var ConfigVersionError = class extends Error {
84047
84067
  constructor(filePath, found, supported) {
84048
84068
  super(
@@ -84158,7 +84178,7 @@ var {
84158
84178
  } = import_index.default;
84159
84179
 
84160
84180
  // src/cockpit/Cockpit.tsx
84161
- var import_react8 = __toESM(require_react(), 1);
84181
+ var import_react9 = __toESM(require_react(), 1);
84162
84182
 
84163
84183
  // src/cockpit/hooks/useCockpitStore.ts
84164
84184
  var import_react = __toESM(require_react(), 1);
@@ -84204,6 +84224,8 @@ var cockpitStore = createStore()((set, get2) => ({
84204
84224
  selectedRepoIndex: 0,
84205
84225
  output: [],
84206
84226
  outputFilter: {},
84227
+ knownSources: [],
84228
+ keybindings: {},
84207
84229
  activeModal: null,
84208
84230
  actions: [],
84209
84231
  commandFilter: "",
@@ -84257,7 +84279,8 @@ var cockpitStore = createStore()((set, get2) => ({
84257
84279
  appendOutput: (line2) => set((state) => {
84258
84280
  const next = [...state.output, line2];
84259
84281
  const capped = next.length > MAX_OUTPUT_LINES ? next.slice(next.length - MAX_OUTPUT_LINES) : next;
84260
- return { output: capped };
84282
+ const knownSources = state.knownSources.includes(line2.source) ? state.knownSources : [...state.knownSources, line2.source];
84283
+ return { output: capped, knownSources };
84261
84284
  }),
84262
84285
  clearOutput: () => set({ output: [] }),
84263
84286
  setFilter: (filter) => set({ outputFilter: filter }),
@@ -84296,6 +84319,7 @@ var cockpitStore = createStore()((set, get2) => ({
84296
84319
  setActiveRemediation: (remediation) => set({ activeRemediation: remediation }),
84297
84320
  setHelpConfig: (config) => set({ helpConfig: config }),
84298
84321
  setFocus: (focus) => set({ focus }),
84322
+ setKeybindings: (bindings) => set({ keybindings: bindings }),
84299
84323
  setActions: (actions) => set({ actions }),
84300
84324
  setCommandFilter: (filter) => set((state) => {
84301
84325
  const filtered = filterActions(state.actions, filter);
@@ -84668,7 +84692,17 @@ function RecentErrorRow({ err }) {
84668
84692
  }
84669
84693
 
84670
84694
  // src/cockpit/panes/Health.tsx
84695
+ var import_react4 = __toESM(require_react(), 1);
84671
84696
  var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
84697
+ function buildReverseKeybindings(bindings) {
84698
+ const out = {};
84699
+ for (const [key, ids] of Object.entries(bindings)) {
84700
+ for (const id of ids) {
84701
+ if (!(id in out)) out[id] = key;
84702
+ }
84703
+ }
84704
+ return out;
84705
+ }
84672
84706
  var SEVERITY_GLYPH = { ok: "\u2713", warn: "\u26A0", error: "\u2717" };
84673
84707
  var SEVERITY_COLOR = {
84674
84708
  ok: "green",
@@ -84682,6 +84716,8 @@ function Health() {
84682
84716
  const focus = useCockpitStore((s) => s.focus);
84683
84717
  const notificationsEnabledSession = useCockpitStore((s) => s.notificationsEnabledSession);
84684
84718
  const activeRemediation = useCockpitStore((s) => s.activeRemediation);
84719
+ const keybindings = useCockpitStore((s) => s.keybindings);
84720
+ const idToKey = (0, import_react4.useMemo)(() => buildReverseKeybindings(keybindings), [keybindings]);
84685
84721
  const isFocused = focus === "health";
84686
84722
  if (health.length === 0) {
84687
84723
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { flexDirection: "column", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "running health checks\u2026" }) });
@@ -84698,7 +84734,8 @@ function Health() {
84698
84734
  const isRunning = activeRemediation?.healthId === item.id;
84699
84735
  const glyph = isRunning ? "\u2026" : SEVERITY_GLYPH[item.severity] ?? "?";
84700
84736
  const color = isRunning ? "yellow" : SEVERITY_COLOR[item.severity] ?? "white";
84701
- const remKey = item.remediationKey ? ` [${item.remediationKey}]` : "";
84737
+ const displayKey = idToKey[item.id] ?? item.remediationKey ?? null;
84738
+ const remKey = displayKey ? ` [${displayKey}]` : "";
84702
84739
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", children: [
84703
84740
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
84704
84741
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: isSelected ? "cyan" : void 0, children: isSelected ? "\u25B6 " : " " }),
@@ -84721,7 +84758,7 @@ function Health() {
84721
84758
  }
84722
84759
 
84723
84760
  // src/cockpit/panes/Help.tsx
84724
- var import_react4 = __toESM(require_react(), 1);
84761
+ var import_react5 = __toESM(require_react(), 1);
84725
84762
 
84726
84763
  // src/cockpit/help/loader.ts
84727
84764
  import fs5 from "node:fs";
@@ -87532,7 +87569,7 @@ function Help2() {
87532
87569
  const helpConfig = useCockpitStore((s) => s.helpConfig);
87533
87570
  const { stdout } = use_stdout_default();
87534
87571
  const isActive = focus === "help";
87535
- const initial = (0, import_react4.useMemo)(() => {
87572
+ const initial = (0, import_react5.useMemo)(() => {
87536
87573
  const result = loadHelpPages({
87537
87574
  sources: helpConfig.sources,
87538
87575
  defaultPage: helpConfig.defaultPage
@@ -87541,17 +87578,17 @@ function Help2() {
87541
87578
  return { pages: [FALLBACK_PAGE], defaultIndex: 0 };
87542
87579
  }, [helpConfig.sources, helpConfig.defaultPage]);
87543
87580
  const pages = initial.pages;
87544
- const [pageIndex, setPageIndex] = (0, import_react4.useState)(initial.defaultIndex);
87545
- const [scrollOffset, setScrollOffset] = (0, import_react4.useState)(0);
87546
- const lastPageIndexRef = (0, import_react4.useRef)(pageIndex);
87547
- (0, import_react4.useEffect)(() => {
87581
+ const [pageIndex, setPageIndex] = (0, import_react5.useState)(initial.defaultIndex);
87582
+ const [scrollOffset, setScrollOffset] = (0, import_react5.useState)(0);
87583
+ const lastPageIndexRef = (0, import_react5.useRef)(pageIndex);
87584
+ (0, import_react5.useEffect)(() => {
87548
87585
  if (lastPageIndexRef.current !== pageIndex) {
87549
87586
  lastPageIndexRef.current = pageIndex;
87550
87587
  setScrollOffset(0);
87551
87588
  }
87552
87589
  }, [pageIndex]);
87553
87590
  const activePage = pages[pageIndex] ?? pages[0];
87554
- const renderedLines = (0, import_react4.useMemo)(
87591
+ const renderedLines = (0, import_react5.useMemo)(
87555
87592
  () => renderMarkdown(activePage.body).split("\n"),
87556
87593
  [activePage.body]
87557
87594
  );
@@ -87679,22 +87716,70 @@ function Footer({ legends } = {}) {
87679
87716
  }
87680
87717
 
87681
87718
  // src/cockpit/panes/FilterModal.tsx
87682
- var import_react5 = __toESM(require_react(), 1);
87719
+ var import_react6 = __toESM(require_react(), 1);
87683
87720
  var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
87721
+ var SEVERITIES = [void 0, "info", "warn", "error"];
87684
87722
  function FilterModal() {
87685
87723
  const outputFilter = useCockpitStore((s) => s.outputFilter);
87686
- const [severityDraft, setSeverityDraft] = (0, import_react5.useState)(
87724
+ const knownSources = useCockpitStore((s) => s.knownSources);
87725
+ const [severityDraft, setSeverityDraft] = (0, import_react6.useState)(
87687
87726
  outputFilter.severity
87688
87727
  );
87689
- const SEVERITIES = [void 0, "info", "warn", "error"];
87728
+ const initialSelected = new Set(
87729
+ outputFilter.sources && outputFilter.sources.length > 0 ? outputFilter.sources : knownSources
87730
+ );
87731
+ const [selectedSources, setSelectedSources] = (0, import_react6.useState)(initialSelected);
87732
+ const [cursor, setCursor] = (0, import_react6.useState)(0);
87733
+ const sources = knownSources;
87734
+ const hasSources = sources.length > 0;
87690
87735
  use_input_default((input, key) => {
87691
87736
  if (input === "s") {
87692
87737
  const idx = SEVERITIES.indexOf(severityDraft);
87693
- const next = SEVERITIES[(idx + 1) % SEVERITIES.length];
87694
- setSeverityDraft(next);
87738
+ setSeverityDraft(SEVERITIES[(idx + 1) % SEVERITIES.length]);
87739
+ return;
87740
+ }
87741
+ if (hasSources) {
87742
+ if (key.upArrow) {
87743
+ setCursor((c3) => Math.max(0, c3 - 1));
87744
+ return;
87745
+ }
87746
+ if (key.downArrow) {
87747
+ setCursor((c3) => Math.min(sources.length - 1, c3 + 1));
87748
+ return;
87749
+ }
87750
+ if (input === " ") {
87751
+ const src = sources[cursor];
87752
+ if (src) {
87753
+ setSelectedSources((prev) => {
87754
+ const next = new Set(prev);
87755
+ if (next.has(src)) next.delete(src);
87756
+ else next.add(src);
87757
+ return next;
87758
+ });
87759
+ }
87760
+ return;
87761
+ }
87762
+ if (input === "a") {
87763
+ setSelectedSources(new Set(sources));
87764
+ return;
87765
+ }
87766
+ if (input === "n") {
87767
+ setSelectedSources(/* @__PURE__ */ new Set());
87768
+ return;
87769
+ }
87770
+ }
87771
+ if (key.escape) {
87772
+ cockpitStore.getState().setActiveModal(null);
87773
+ return;
87695
87774
  }
87696
- if (key.return || key.escape) {
87697
- cockpitStore.getState().setFilter({ ...outputFilter, severity: severityDraft });
87775
+ if (key.return) {
87776
+ const allSelected = selectedSources.size === sources.length;
87777
+ const next = {
87778
+ ...outputFilter,
87779
+ severity: severityDraft,
87780
+ sources: allSelected ? void 0 : Array.from(selectedSources)
87781
+ };
87782
+ cockpitStore.getState().setFilter(next);
87698
87783
  cockpitStore.getState().setActiveModal(null);
87699
87784
  }
87700
87785
  });
@@ -87702,20 +87787,38 @@ function FilterModal() {
87702
87787
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { bold: true, children: " Filter Output " }),
87703
87788
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: " " }),
87704
87789
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
87705
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: "Severity (s to cycle): " }),
87790
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: "Severity (s): " }),
87706
87791
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: "cyan", children: severityDraft ?? "all" })
87707
87792
  ] }),
87708
87793
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: " " }),
87794
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { bold: true, children: "Sources" }),
87795
+ hasSources ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { flexDirection: "column", children: [
87796
+ sources.map((src, idx) => {
87797
+ const checked = selectedSources.has(src);
87798
+ const isCursor = idx === cursor;
87799
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { children: [
87800
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: isCursor ? "cyan" : void 0, children: isCursor ? "\u25B6 " : " " }),
87801
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { children: [
87802
+ "[",
87803
+ checked ? "x" : " ",
87804
+ "] "
87805
+ ] }),
87806
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: isCursor ? "cyan" : void 0, children: src })
87807
+ ] }, src);
87808
+ }),
87809
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { dimColor: true, children: " \u2191\u2193 move \xB7 space toggle \xB7 a all \xB7 n none" })
87810
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { dimColor: true, children: " (none yet \u2014 output sources appear as they emit)" }),
87811
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: " " }),
87709
87812
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { dimColor: true, children: "Enter = apply \xB7 Esc = cancel" })
87710
87813
  ] }) });
87711
87814
  }
87712
87815
 
87713
87816
  // src/cockpit/panes/SearchModal.tsx
87714
- var import_react6 = __toESM(require_react(), 1);
87817
+ var import_react7 = __toESM(require_react(), 1);
87715
87818
  var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
87716
87819
  function SearchModal() {
87717
87820
  const outputFilter = useCockpitStore((s) => s.outputFilter);
87718
- const [query, setQuery] = (0, import_react6.useState)(outputFilter.search ?? "");
87821
+ const [query, setQuery] = (0, import_react7.useState)(outputFilter.search ?? "");
87719
87822
  use_input_default((input, key) => {
87720
87823
  if (key.escape) {
87721
87824
  cockpitStore.getState().setActiveModal(null);
@@ -87950,7 +88053,7 @@ function useGlobalKeys(opts) {
87950
88053
  }
87951
88054
 
87952
88055
  // src/health/useHealth.ts
87953
- var import_react7 = __toESM(require_react(), 1);
88056
+ var import_react8 = __toESM(require_react(), 1);
87954
88057
 
87955
88058
  // src/health/scheduler.ts
87956
88059
  import path9 from "node:path";
@@ -87974,7 +88077,7 @@ async function runChecks(checks, triggerSet, ctx) {
87974
88077
  label: check.label,
87975
88078
  severity: "warn",
87976
88079
  detail: `check failed with error: ${String(err)}`,
87977
- remediationKey: check.remediation.key
88080
+ remediationKey: check.remediation.key ?? null
87978
88081
  };
87979
88082
  }
87980
88083
  })
@@ -88044,7 +88147,7 @@ var HealthScheduler = class {
88044
88147
  label: check.label,
88045
88148
  severity: "warn",
88046
88149
  detail: `check failed with error: ${String(err)}`,
88047
- remediationKey: check.remediation.key
88150
+ remediationKey: check.remediation.key ?? null
88048
88151
  };
88049
88152
  }
88050
88153
  })
@@ -88083,10 +88186,21 @@ var HealthScheduler = class {
88083
88186
  };
88084
88187
 
88085
88188
  // src/health/remediations.ts
88086
- async function runRemediation(key, checks, ctx, workspaceRoot) {
88087
- const check = checks.find((c3) => c3.remediation.key === key);
88088
- if (!check) return;
88089
- await dispatchRemediation(check.remediation, ctx, workspaceRoot, check.id);
88189
+ function findChecksByKey(key, checks, keybindings) {
88190
+ const ids = keybindings?.[key];
88191
+ if (ids && ids.length > 0) {
88192
+ return ids.map((id) => checks.find((c4) => c4.id === id)).filter((c4) => Boolean(c4));
88193
+ }
88194
+ const c3 = checks.find((c4) => c4.remediation.key === key);
88195
+ return c3 ? [c3] : [];
88196
+ }
88197
+ async function runRemediation(key, checks, ctx, workspaceRoot, keybindings) {
88198
+ const matched = findChecksByKey(key, checks, keybindings);
88199
+ await Promise.all(
88200
+ matched.map(
88201
+ (check) => dispatchRemediation(check.remediation, ctx, workspaceRoot, check.id)
88202
+ )
88203
+ );
88090
88204
  }
88091
88205
  function findRemediation(key, checks) {
88092
88206
  return checks.find((c3) => c3.remediation.key === key);
@@ -88158,7 +88272,7 @@ var containerRunning = (entry) => {
88158
88272
  label: entry.label,
88159
88273
  severity: running ? "ok" : entry.severity ?? "error",
88160
88274
  detail: running ? `container matching '${container}' is running` : states.length > 0 ? `container matching '${container}' exists but is in state: ${states.join(", ")}` : `no container matching '${container}' is running`,
88161
- remediationKey: entry.remediation.key
88275
+ remediationKey: entry.remediation.key ?? null
88162
88276
  };
88163
88277
  };
88164
88278
  };
@@ -88186,7 +88300,7 @@ var portOpen = (entry) => {
88186
88300
  label: entry.label,
88187
88301
  severity: open2 ? "ok" : entry.severity ?? "error",
88188
88302
  detail: open2 ? `${host}:${port} is reachable` : `${host}:${port} is not reachable`,
88189
- remediationKey: entry.remediation.key
88303
+ remediationKey: entry.remediation.key ?? null
88190
88304
  };
88191
88305
  };
88192
88306
  };
@@ -88214,7 +88328,7 @@ var httpOk = (entry) => {
88214
88328
  label: entry.label,
88215
88329
  severity: ok ? "ok" : entry.severity ?? "error",
88216
88330
  detail: ok ? `${url} returned ${status4}` : detail || `${url} returned ${status4} (expected ${expect})`,
88217
- remediationKey: entry.remediation.key
88331
+ remediationKey: entry.remediation.key ?? null
88218
88332
  };
88219
88333
  };
88220
88334
  };
@@ -88231,7 +88345,7 @@ var fileExists = (entry) => {
88231
88345
  label: entry.label,
88232
88346
  severity: exists ? "ok" : entry.severity ?? "error",
88233
88347
  detail: exists ? `${resolved} exists` : `${resolved} is missing`,
88234
- remediationKey: entry.remediation.key
88348
+ remediationKey: entry.remediation.key ?? null
88235
88349
  };
88236
88350
  };
88237
88351
  };
@@ -88250,7 +88364,7 @@ var execZero = (entry) => {
88250
88364
  label: entry.label,
88251
88365
  severity: ok ? "ok" : entry.severity ?? "error",
88252
88366
  detail: ok ? `${command} ${args.join(" ")} exited 0` : `${command} ${args.join(" ")} exited ${exitCode}`,
88253
- remediationKey: entry.remediation.key
88367
+ remediationKey: entry.remediation.key ?? null
88254
88368
  };
88255
88369
  };
88256
88370
  };
@@ -88373,23 +88487,23 @@ function buildHealthContext(workspaceRoot, appendOutput) {
88373
88487
 
88374
88488
  // src/health/useHealth.ts
88375
88489
  function useHealth(opts) {
88376
- const checks = (0, import_react7.useMemo)(() => {
88490
+ const checks = (0, import_react8.useMemo)(() => {
88377
88491
  if (!opts) return [];
88378
88492
  return buildHealthRegistry({
88379
88493
  profileChecks: opts.profileChecks,
88380
88494
  configEntries: opts.configEntries
88381
88495
  });
88382
88496
  }, [opts]);
88383
- const checksRef = (0, import_react7.useRef)(checks);
88384
- const ctxRef = (0, import_react7.useRef)(opts?.ctx);
88385
- const optsRef = (0, import_react7.useRef)(opts);
88386
- const schedulerRef = (0, import_react7.useRef)(null);
88387
- (0, import_react7.useEffect)(() => {
88497
+ const checksRef = (0, import_react8.useRef)(checks);
88498
+ const ctxRef = (0, import_react8.useRef)(opts?.ctx);
88499
+ const optsRef = (0, import_react8.useRef)(opts);
88500
+ const schedulerRef = (0, import_react8.useRef)(null);
88501
+ (0, import_react8.useEffect)(() => {
88388
88502
  checksRef.current = checks;
88389
88503
  ctxRef.current = opts?.ctx;
88390
88504
  optsRef.current = opts;
88391
88505
  }, [checks, opts]);
88392
- (0, import_react7.useEffect)(() => {
88506
+ (0, import_react8.useEffect)(() => {
88393
88507
  if (!opts) return;
88394
88508
  if (checks.length === 0) return;
88395
88509
  const scheduler3 = new HealthScheduler({
@@ -88428,21 +88542,25 @@ function useHealth(opts) {
88428
88542
  runRemediation: (key) => {
88429
88543
  const list3 = checksRef.current;
88430
88544
  const ctx = ctxRef.current ?? buildDefaultCtx(optsRef.current?.workspaceRoot ?? ".");
88431
- const check = list3.find((c3) => c3.remediation.key === key);
88432
- if (!check) return null;
88545
+ const keybindings = optsRef.current?.keybindings;
88546
+ const matched = findChecksByKey(key, list3, keybindings);
88547
+ if (matched.length === 0) return null;
88548
+ const first = matched[0];
88549
+ const banner = matched.length === 1 ? first.remediation.label : `${matched.length} remediations`;
88433
88550
  const promise = runRemediation(
88434
88551
  key,
88435
88552
  list3,
88436
88553
  ctx,
88437
- optsRef.current?.workspaceRoot ?? "."
88554
+ optsRef.current?.workspaceRoot ?? ".",
88555
+ keybindings
88438
88556
  ).finally(() => {
88439
88557
  void schedulerRef.current?.runAll();
88440
88558
  });
88441
88559
  return {
88442
- label: check.remediation.label,
88560
+ label: banner,
88443
88561
  promise,
88444
- healthId: check.id,
88445
- healthLabel: check.label
88562
+ healthId: first.id,
88563
+ healthLabel: first.label
88446
88564
  };
88447
88565
  }
88448
88566
  };
@@ -88475,8 +88593,8 @@ function Cockpit(props) {
88475
88593
  const { stdout } = use_stdout_default();
88476
88594
  const focus = useCockpitStore((s) => s.focus);
88477
88595
  const activeModal = useCockpitStore((s) => s.activeModal);
88478
- const [rows, setRows] = (0, import_react8.useState)(stdout.rows ?? 24);
88479
- (0, import_react8.useEffect)(() => {
88596
+ const [rows, setRows] = (0, import_react9.useState)(stdout.rows ?? 24);
88597
+ (0, import_react9.useEffect)(() => {
88480
88598
  const onResize = () => setRows(stdout.rows ?? 24);
88481
88599
  stdout.on("resize", onResize);
88482
88600
  return () => {
@@ -91219,7 +91337,7 @@ function createMountBrokenSymlinkCheck(opts) {
91219
91337
  const severity = opts.severity ?? "error";
91220
91338
  const triggers = opts.triggers ?? ["startup", "fsevent"];
91221
91339
  const remediation = opts.remediation ?? DEFAULT_REMEDIATION;
91222
- const remediationKey = remediation.key;
91340
+ const remediationKey = remediation.key ?? null;
91223
91341
  const predicate = async (ctx) => {
91224
91342
  const { workspaceRoot } = ctx;
91225
91343
  let manifestPath2;
@@ -91310,7 +91428,7 @@ function hasLockfile(workspaceRoot) {
91310
91428
  }
91311
91429
 
91312
91430
  // src/runCockpit.ts
91313
- var import_react9 = __toESM(require_react(), 1);
91431
+ var import_react10 = __toESM(require_react(), 1);
91314
91432
  var ENTER_ALT_SCREEN = "\x1B[?1049h\x1B[H";
91315
91433
  var EXIT_ALT_SCREEN = "\x1B[?1049l";
91316
91434
  function runCockpit(opts = {}) {
@@ -91349,7 +91467,7 @@ function runCockpit(opts = {}) {
91349
91467
  throw err;
91350
91468
  });
91351
91469
  }
91352
- const ink = render_default(import_react9.default.createElement(Cockpit, cockpitProps), { exitOnCtrlC: true });
91470
+ const ink = render_default(import_react10.default.createElement(Cockpit, cockpitProps), { exitOnCtrlC: true });
91353
91471
  return {
91354
91472
  waitUntilExit: async () => {
91355
91473
  try {
@@ -91457,6 +91575,7 @@ async function devCommand(opts = {}) {
91457
91575
  const requestedHidden = requestedPane === "repos" && reposEmpty || requestedPane === "health" && healthEmpty;
91458
91576
  const initialFocus = requestedHidden ? "output" : requestedPane;
91459
91577
  cockpitStore.getState().setFocus(initialFocus);
91578
+ cockpitStore.getState().setKeybindings(normaliseKeybindings(config.keybindings));
91460
91579
  const builtinActions = buildBuiltinActions(config);
91461
91580
  const actions = buildActionRegistry(
91462
91581
  [...config.actions],
@@ -91588,7 +91707,8 @@ async function devCommand(opts = {}) {
91588
91707
  exclude: config.notifications.exclude
91589
91708
  },
91590
91709
  appName: config.appName,
91591
- subscribeFsEvents: bootResult.subscribeFsEvents
91710
+ subscribeFsEvents: bootResult.subscribeFsEvents,
91711
+ keybindings: normaliseKeybindings(config.keybindings)
91592
91712
  }
91593
91713
  });
91594
91714
  for (const proc of config.processes) {
@@ -91753,7 +91873,9 @@ function renderHealth(checks) {
91753
91873
  break;
91754
91874
  }
91755
91875
  out.push(indent(` remediation:`, 1));
91756
- out.push(indent(` key: ${quote(c3.remediation.key)}`, 1));
91876
+ if (c3.remediation.key) {
91877
+ out.push(indent(` key: ${quote(c3.remediation.key)}`, 1));
91878
+ }
91757
91879
  out.push(indent(` label: ${quote(c3.remediation.label)}`, 1));
91758
91880
  out.push(indent(` command: ${quote(c3.remediation.command)}`, 1));
91759
91881
  }