dev-cockpit 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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));
@@ -83869,8 +83869,40 @@ var v1ToV2 = (raw) => {
83869
83869
  if (watchers === void 0) return { ...rest, version: 2 };
83870
83870
  return { ...rest, processes: watchers, version: 2 };
83871
83871
  };
83872
+ var v2ToV3 = (raw) => {
83873
+ const existing = raw["keybindings"] ?? {};
83874
+ const merged = {};
83875
+ for (const [k, v] of Object.entries(existing)) {
83876
+ const ids = Array.isArray(v) ? v : [v];
83877
+ merged[k] = ids.filter((s) => typeof s === "string" && s.length > 0);
83878
+ }
83879
+ const health = Array.isArray(raw["health"]) ? raw["health"] : [];
83880
+ const migratedHealth = health.map((entry) => {
83881
+ if (!entry || typeof entry !== "object") return entry;
83882
+ const e = entry;
83883
+ const rem = e["remediation"];
83884
+ const id = e["id"];
83885
+ if (!rem || typeof rem.key !== "string" || !id) return entry;
83886
+ const key = rem.key;
83887
+ const bucket = merged[key] ?? [];
83888
+ if (!bucket.includes(id)) bucket.push(id);
83889
+ merged[key] = bucket;
83890
+ const remRest = {};
83891
+ for (const [k, v] of Object.entries(rem)) {
83892
+ if (k !== "key") remRest[k] = v;
83893
+ }
83894
+ return { ...e, remediation: remRest };
83895
+ });
83896
+ const result = { ...raw, version: 3 };
83897
+ result["health"] = migratedHealth;
83898
+ if (Object.keys(merged).length > 0) {
83899
+ result["keybindings"] = merged;
83900
+ }
83901
+ return result;
83902
+ };
83872
83903
  var MIGRATORS = {
83873
- 1: v1ToV2
83904
+ 1: v1ToV2,
83905
+ 2: v2ToV3
83874
83906
  };
83875
83907
  var MigrationError = class extends Error {
83876
83908
  constructor(fromVersion, toVersion, reason) {
@@ -83900,7 +83932,7 @@ function runMigrations(raw, fromVersion, toVersion) {
83900
83932
  }
83901
83933
 
83902
83934
  // src/core/config.ts
83903
- var CONFIG_VERSION = 2;
83935
+ var CONFIG_VERSION = 3;
83904
83936
  var NotifyOverrideSchema = external_exports.union([
83905
83937
  external_exports.literal(false),
83906
83938
  external_exports.object({
@@ -83935,15 +83967,6 @@ var HighlightSchema = external_exports.object({
83935
83967
  severity: external_exports.enum(["info", "warn", "error"]).optional().default("info")
83936
83968
  });
83937
83969
  var RemediationSchema = external_exports.object({
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(),
83947
83970
  label: external_exports.string(),
83948
83971
  command: external_exports.string(),
83949
83972
  cwd: external_exports.string().optional()
@@ -84007,6 +84030,7 @@ var ComposerSettingsSchema = external_exports.object({
84007
84030
  */
84008
84031
  packageDirs: external_exports.array(external_exports.string()).optional().default(["."])
84009
84032
  });
84033
+ var KeybindingsSchema = external_exports.record(external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]));
84010
84034
  var MountSettingsSchema = external_exports.object({
84011
84035
  /**
84012
84036
  * Override path for the generated docker-compose overlay. When unset
@@ -84040,6 +84064,7 @@ var BaseCockpitConfigSchema = external_exports.object({
84040
84064
  manifestFile: "mount.manifest.json"
84041
84065
  }),
84042
84066
  composer: ComposerSettingsSchema.optional().default({ packageDirs: ["."] }),
84067
+ keybindings: KeybindingsSchema.optional().default({}),
84043
84068
  /** Which pane the cockpit lands on when it boots. Profile may override. */
84044
84069
  defaultPane: external_exports.enum(["repos", "output", "health", "help"]).optional().default("repos"),
84045
84070
  /** Named keystroke-bound shell commands surfaced via the `:` palette. */
@@ -84051,6 +84076,16 @@ var BaseCockpitConfigSchema = external_exports.object({
84051
84076
  */
84052
84077
  profile: external_exports.record(external_exports.unknown()).optional().default({})
84053
84078
  });
84079
+ function normaliseKeybindings(raw) {
84080
+ const out = {};
84081
+ if (!raw) return out;
84082
+ for (const [key, value] of Object.entries(raw)) {
84083
+ const ids = Array.isArray(value) ? value : [value];
84084
+ const clean = ids.filter((s) => typeof s === "string" && s.length > 0);
84085
+ if (clean.length > 0) out[key] = clean;
84086
+ }
84087
+ return out;
84088
+ }
84054
84089
  var ConfigVersionError = class extends Error {
84055
84090
  constructor(filePath, found, supported) {
84056
84091
  super(
@@ -84166,7 +84201,7 @@ var {
84166
84201
  } = import_index.default;
84167
84202
 
84168
84203
  // src/cockpit/Cockpit.tsx
84169
- var import_react8 = __toESM(require_react(), 1);
84204
+ var import_react9 = __toESM(require_react(), 1);
84170
84205
 
84171
84206
  // src/cockpit/hooks/useCockpitStore.ts
84172
84207
  var import_react = __toESM(require_react(), 1);
@@ -84213,6 +84248,7 @@ var cockpitStore = createStore()((set, get2) => ({
84213
84248
  output: [],
84214
84249
  outputFilter: {},
84215
84250
  knownSources: [],
84251
+ keybindings: {},
84216
84252
  activeModal: null,
84217
84253
  actions: [],
84218
84254
  commandFilter: "",
@@ -84306,6 +84342,7 @@ var cockpitStore = createStore()((set, get2) => ({
84306
84342
  setActiveRemediation: (remediation) => set({ activeRemediation: remediation }),
84307
84343
  setHelpConfig: (config) => set({ helpConfig: config }),
84308
84344
  setFocus: (focus) => set({ focus }),
84345
+ setKeybindings: (bindings) => set({ keybindings: bindings }),
84309
84346
  setActions: (actions) => set({ actions }),
84310
84347
  setCommandFilter: (filter) => set((state) => {
84311
84348
  const filtered = filterActions(state.actions, filter);
@@ -84678,7 +84715,17 @@ function RecentErrorRow({ err }) {
84678
84715
  }
84679
84716
 
84680
84717
  // src/cockpit/panes/Health.tsx
84718
+ var import_react4 = __toESM(require_react(), 1);
84681
84719
  var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
84720
+ function buildReverseKeybindings(bindings) {
84721
+ const out = {};
84722
+ for (const [key, ids] of Object.entries(bindings)) {
84723
+ for (const id of ids) {
84724
+ if (!(id in out)) out[id] = key;
84725
+ }
84726
+ }
84727
+ return out;
84728
+ }
84682
84729
  var SEVERITY_GLYPH = { ok: "\u2713", warn: "\u26A0", error: "\u2717" };
84683
84730
  var SEVERITY_COLOR = {
84684
84731
  ok: "green",
@@ -84692,6 +84739,8 @@ function Health() {
84692
84739
  const focus = useCockpitStore((s) => s.focus);
84693
84740
  const notificationsEnabledSession = useCockpitStore((s) => s.notificationsEnabledSession);
84694
84741
  const activeRemediation = useCockpitStore((s) => s.activeRemediation);
84742
+ const keybindings = useCockpitStore((s) => s.keybindings);
84743
+ const idToKey = (0, import_react4.useMemo)(() => buildReverseKeybindings(keybindings), [keybindings]);
84695
84744
  const isFocused = focus === "health";
84696
84745
  if (health.length === 0) {
84697
84746
  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" }) });
@@ -84708,7 +84757,8 @@ function Health() {
84708
84757
  const isRunning = activeRemediation?.healthId === item.id;
84709
84758
  const glyph = isRunning ? "\u2026" : SEVERITY_GLYPH[item.severity] ?? "?";
84710
84759
  const color = isRunning ? "yellow" : SEVERITY_COLOR[item.severity] ?? "white";
84711
- const remKey = item.remediationKey ? ` [${item.remediationKey}]` : "";
84760
+ const displayKey = idToKey[item.id] ?? item.remediationKey ?? null;
84761
+ const remKey = displayKey ? ` [${displayKey}]` : "";
84712
84762
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { flexDirection: "column", children: [
84713
84763
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { children: [
84714
84764
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: isSelected ? "cyan" : void 0, children: isSelected ? "\u25B6 " : " " }),
@@ -84731,7 +84781,7 @@ function Health() {
84731
84781
  }
84732
84782
 
84733
84783
  // src/cockpit/panes/Help.tsx
84734
- var import_react4 = __toESM(require_react(), 1);
84784
+ var import_react5 = __toESM(require_react(), 1);
84735
84785
 
84736
84786
  // src/cockpit/help/loader.ts
84737
84787
  import fs5 from "node:fs";
@@ -87542,7 +87592,7 @@ function Help2() {
87542
87592
  const helpConfig = useCockpitStore((s) => s.helpConfig);
87543
87593
  const { stdout } = use_stdout_default();
87544
87594
  const isActive = focus === "help";
87545
- const initial = (0, import_react4.useMemo)(() => {
87595
+ const initial = (0, import_react5.useMemo)(() => {
87546
87596
  const result = loadHelpPages({
87547
87597
  sources: helpConfig.sources,
87548
87598
  defaultPage: helpConfig.defaultPage
@@ -87551,17 +87601,17 @@ function Help2() {
87551
87601
  return { pages: [FALLBACK_PAGE], defaultIndex: 0 };
87552
87602
  }, [helpConfig.sources, helpConfig.defaultPage]);
87553
87603
  const pages = initial.pages;
87554
- const [pageIndex, setPageIndex] = (0, import_react4.useState)(initial.defaultIndex);
87555
- const [scrollOffset, setScrollOffset] = (0, import_react4.useState)(0);
87556
- const lastPageIndexRef = (0, import_react4.useRef)(pageIndex);
87557
- (0, import_react4.useEffect)(() => {
87604
+ const [pageIndex, setPageIndex] = (0, import_react5.useState)(initial.defaultIndex);
87605
+ const [scrollOffset, setScrollOffset] = (0, import_react5.useState)(0);
87606
+ const lastPageIndexRef = (0, import_react5.useRef)(pageIndex);
87607
+ (0, import_react5.useEffect)(() => {
87558
87608
  if (lastPageIndexRef.current !== pageIndex) {
87559
87609
  lastPageIndexRef.current = pageIndex;
87560
87610
  setScrollOffset(0);
87561
87611
  }
87562
87612
  }, [pageIndex]);
87563
87613
  const activePage = pages[pageIndex] ?? pages[0];
87564
- const renderedLines = (0, import_react4.useMemo)(
87614
+ const renderedLines = (0, import_react5.useMemo)(
87565
87615
  () => renderMarkdown(activePage.body).split("\n"),
87566
87616
  [activePage.body]
87567
87617
  );
@@ -87689,20 +87739,20 @@ function Footer({ legends } = {}) {
87689
87739
  }
87690
87740
 
87691
87741
  // src/cockpit/panes/FilterModal.tsx
87692
- var import_react5 = __toESM(require_react(), 1);
87742
+ var import_react6 = __toESM(require_react(), 1);
87693
87743
  var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
87694
87744
  var SEVERITIES = [void 0, "info", "warn", "error"];
87695
87745
  function FilterModal() {
87696
87746
  const outputFilter = useCockpitStore((s) => s.outputFilter);
87697
87747
  const knownSources = useCockpitStore((s) => s.knownSources);
87698
- const [severityDraft, setSeverityDraft] = (0, import_react5.useState)(
87748
+ const [severityDraft, setSeverityDraft] = (0, import_react6.useState)(
87699
87749
  outputFilter.severity
87700
87750
  );
87701
87751
  const initialSelected = new Set(
87702
87752
  outputFilter.sources && outputFilter.sources.length > 0 ? outputFilter.sources : knownSources
87703
87753
  );
87704
- const [selectedSources, setSelectedSources] = (0, import_react5.useState)(initialSelected);
87705
- const [cursor, setCursor] = (0, import_react5.useState)(0);
87754
+ const [selectedSources, setSelectedSources] = (0, import_react6.useState)(initialSelected);
87755
+ const [cursor, setCursor] = (0, import_react6.useState)(0);
87706
87756
  const sources = knownSources;
87707
87757
  const hasSources = sources.length > 0;
87708
87758
  use_input_default((input, key) => {
@@ -87787,11 +87837,11 @@ function FilterModal() {
87787
87837
  }
87788
87838
 
87789
87839
  // src/cockpit/panes/SearchModal.tsx
87790
- var import_react6 = __toESM(require_react(), 1);
87840
+ var import_react7 = __toESM(require_react(), 1);
87791
87841
  var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
87792
87842
  function SearchModal() {
87793
87843
  const outputFilter = useCockpitStore((s) => s.outputFilter);
87794
- const [query, setQuery] = (0, import_react6.useState)(outputFilter.search ?? "");
87844
+ const [query, setQuery] = (0, import_react7.useState)(outputFilter.search ?? "");
87795
87845
  use_input_default((input, key) => {
87796
87846
  if (key.escape) {
87797
87847
  cockpitStore.getState().setActiveModal(null);
@@ -88026,7 +88076,7 @@ function useGlobalKeys(opts) {
88026
88076
  }
88027
88077
 
88028
88078
  // src/health/useHealth.ts
88029
- var import_react7 = __toESM(require_react(), 1);
88079
+ var import_react8 = __toESM(require_react(), 1);
88030
88080
 
88031
88081
  // src/health/scheduler.ts
88032
88082
  import path9 from "node:path";
@@ -88159,10 +88209,21 @@ var HealthScheduler = class {
88159
88209
  };
88160
88210
 
88161
88211
  // src/health/remediations.ts
88162
- async function runRemediation(key, checks, ctx, workspaceRoot) {
88163
- const check = checks.find((c3) => c3.remediation.key === key);
88164
- if (!check) return;
88165
- await dispatchRemediation(check.remediation, ctx, workspaceRoot, check.id);
88212
+ function findChecksByKey(key, checks, keybindings) {
88213
+ const ids = keybindings?.[key];
88214
+ if (ids && ids.length > 0) {
88215
+ return ids.map((id) => checks.find((c4) => c4.id === id)).filter((c4) => Boolean(c4));
88216
+ }
88217
+ const c3 = checks.find((c4) => c4.remediation.key === key);
88218
+ return c3 ? [c3] : [];
88219
+ }
88220
+ async function runRemediation(key, checks, ctx, workspaceRoot, keybindings) {
88221
+ const matched = findChecksByKey(key, checks, keybindings);
88222
+ await Promise.all(
88223
+ matched.map(
88224
+ (check) => dispatchRemediation(check.remediation, ctx, workspaceRoot, check.id)
88225
+ )
88226
+ );
88166
88227
  }
88167
88228
  function findRemediation(key, checks) {
88168
88229
  return checks.find((c3) => c3.remediation.key === key);
@@ -88449,23 +88510,23 @@ function buildHealthContext(workspaceRoot, appendOutput) {
88449
88510
 
88450
88511
  // src/health/useHealth.ts
88451
88512
  function useHealth(opts) {
88452
- const checks = (0, import_react7.useMemo)(() => {
88513
+ const checks = (0, import_react8.useMemo)(() => {
88453
88514
  if (!opts) return [];
88454
88515
  return buildHealthRegistry({
88455
88516
  profileChecks: opts.profileChecks,
88456
88517
  configEntries: opts.configEntries
88457
88518
  });
88458
88519
  }, [opts]);
88459
- const checksRef = (0, import_react7.useRef)(checks);
88460
- const ctxRef = (0, import_react7.useRef)(opts?.ctx);
88461
- const optsRef = (0, import_react7.useRef)(opts);
88462
- const schedulerRef = (0, import_react7.useRef)(null);
88463
- (0, import_react7.useEffect)(() => {
88520
+ const checksRef = (0, import_react8.useRef)(checks);
88521
+ const ctxRef = (0, import_react8.useRef)(opts?.ctx);
88522
+ const optsRef = (0, import_react8.useRef)(opts);
88523
+ const schedulerRef = (0, import_react8.useRef)(null);
88524
+ (0, import_react8.useEffect)(() => {
88464
88525
  checksRef.current = checks;
88465
88526
  ctxRef.current = opts?.ctx;
88466
88527
  optsRef.current = opts;
88467
88528
  }, [checks, opts]);
88468
- (0, import_react7.useEffect)(() => {
88529
+ (0, import_react8.useEffect)(() => {
88469
88530
  if (!opts) return;
88470
88531
  if (checks.length === 0) return;
88471
88532
  const scheduler3 = new HealthScheduler({
@@ -88504,21 +88565,25 @@ function useHealth(opts) {
88504
88565
  runRemediation: (key) => {
88505
88566
  const list3 = checksRef.current;
88506
88567
  const ctx = ctxRef.current ?? buildDefaultCtx(optsRef.current?.workspaceRoot ?? ".");
88507
- const check = list3.find((c3) => c3.remediation.key === key);
88508
- if (!check) return null;
88568
+ const keybindings = optsRef.current?.keybindings;
88569
+ const matched = findChecksByKey(key, list3, keybindings);
88570
+ if (matched.length === 0) return null;
88571
+ const first = matched[0];
88572
+ const banner = matched.length === 1 ? first.remediation.label : `${matched.length} remediations`;
88509
88573
  const promise = runRemediation(
88510
88574
  key,
88511
88575
  list3,
88512
88576
  ctx,
88513
- optsRef.current?.workspaceRoot ?? "."
88577
+ optsRef.current?.workspaceRoot ?? ".",
88578
+ keybindings
88514
88579
  ).finally(() => {
88515
88580
  void schedulerRef.current?.runAll();
88516
88581
  });
88517
88582
  return {
88518
- label: check.remediation.label,
88583
+ label: banner,
88519
88584
  promise,
88520
- healthId: check.id,
88521
- healthLabel: check.label
88585
+ healthId: first.id,
88586
+ healthLabel: first.label
88522
88587
  };
88523
88588
  }
88524
88589
  };
@@ -88551,8 +88616,8 @@ function Cockpit(props) {
88551
88616
  const { stdout } = use_stdout_default();
88552
88617
  const focus = useCockpitStore((s) => s.focus);
88553
88618
  const activeModal = useCockpitStore((s) => s.activeModal);
88554
- const [rows, setRows] = (0, import_react8.useState)(stdout.rows ?? 24);
88555
- (0, import_react8.useEffect)(() => {
88619
+ const [rows, setRows] = (0, import_react9.useState)(stdout.rows ?? 24);
88620
+ (0, import_react9.useEffect)(() => {
88556
88621
  const onResize = () => setRows(stdout.rows ?? 24);
88557
88622
  stdout.on("resize", onResize);
88558
88623
  return () => {
@@ -91386,7 +91451,7 @@ function hasLockfile(workspaceRoot) {
91386
91451
  }
91387
91452
 
91388
91453
  // src/runCockpit.ts
91389
- var import_react9 = __toESM(require_react(), 1);
91454
+ var import_react10 = __toESM(require_react(), 1);
91390
91455
  var ENTER_ALT_SCREEN = "\x1B[?1049h\x1B[H";
91391
91456
  var EXIT_ALT_SCREEN = "\x1B[?1049l";
91392
91457
  function runCockpit(opts = {}) {
@@ -91425,7 +91490,7 @@ function runCockpit(opts = {}) {
91425
91490
  throw err;
91426
91491
  });
91427
91492
  }
91428
- const ink = render_default(import_react9.default.createElement(Cockpit, cockpitProps), { exitOnCtrlC: true });
91493
+ const ink = render_default(import_react10.default.createElement(Cockpit, cockpitProps), { exitOnCtrlC: true });
91429
91494
  return {
91430
91495
  waitUntilExit: async () => {
91431
91496
  try {
@@ -91533,6 +91598,7 @@ async function devCommand(opts = {}) {
91533
91598
  const requestedHidden = requestedPane === "repos" && reposEmpty || requestedPane === "health" && healthEmpty;
91534
91599
  const initialFocus = requestedHidden ? "output" : requestedPane;
91535
91600
  cockpitStore.getState().setFocus(initialFocus);
91601
+ cockpitStore.getState().setKeybindings(normaliseKeybindings(config.keybindings));
91536
91602
  const builtinActions = buildBuiltinActions(config);
91537
91603
  const actions = buildActionRegistry(
91538
91604
  [...config.actions],
@@ -91664,7 +91730,8 @@ async function devCommand(opts = {}) {
91664
91730
  exclude: config.notifications.exclude
91665
91731
  },
91666
91732
  appName: config.appName,
91667
- subscribeFsEvents: bootResult.subscribeFsEvents
91733
+ subscribeFsEvents: bootResult.subscribeFsEvents,
91734
+ keybindings: normaliseKeybindings(config.keybindings)
91668
91735
  }
91669
91736
  });
91670
91737
  for (const proc of config.processes) {
@@ -91802,6 +91869,29 @@ function quote(value) {
91802
91869
  if (/^[A-Za-z0-9_./-]+$/.test(value)) return value;
91803
91870
  return `'${value.replace(/'/g, "''")}'`;
91804
91871
  }
91872
+ function renderKeybindings(checks) {
91873
+ const bindings = {};
91874
+ for (const c3 of checks) {
91875
+ const key = c3.remediation.key;
91876
+ if (!key) continue;
91877
+ if (!bindings[key]) bindings[key] = [];
91878
+ if (!bindings[key].includes(c3.id)) bindings[key].push(c3.id);
91879
+ }
91880
+ const entries = Object.entries(bindings);
91881
+ if (entries.length === 0) return [];
91882
+ const out = ["keybindings:"];
91883
+ for (const [key, ids] of entries) {
91884
+ if (ids.length === 1) {
91885
+ out.push(indent(`${quote(key)}: ${quote(ids[0])}`, 1));
91886
+ } else {
91887
+ out.push(indent(`${quote(key)}:`, 1));
91888
+ for (const id of ids) {
91889
+ out.push(indent(`- ${quote(id)}`, 2));
91890
+ }
91891
+ }
91892
+ }
91893
+ return out;
91894
+ }
91805
91895
  function renderHealth(checks) {
91806
91896
  if (checks.length === 0) return [];
91807
91897
  const out = ["health:"];
@@ -91829,9 +91919,6 @@ function renderHealth(checks) {
91829
91919
  break;
91830
91920
  }
91831
91921
  out.push(indent(` remediation:`, 1));
91832
- if (c3.remediation.key) {
91833
- out.push(indent(` key: ${quote(c3.remediation.key)}`, 1));
91834
- }
91835
91922
  out.push(indent(` label: ${quote(c3.remediation.label)}`, 1));
91836
91923
  out.push(indent(` command: ${quote(c3.remediation.command)}`, 1));
91837
91924
  }
@@ -91896,7 +91983,7 @@ function renderWizardYaml(result) {
91896
91983
  blocks.push([
91897
91984
  "# Generated by `dev-cockpit init-config --interactive`. Edit freely.",
91898
91985
  "",
91899
- "version: 2",
91986
+ "version: 3",
91900
91987
  `appName: ${quote(result.appName)}`
91901
91988
  ]);
91902
91989
  const processes = renderProcesses(result.processes);
@@ -91909,6 +91996,8 @@ function renderWizardYaml(result) {
91909
91996
  if (highlights.length > 0) blocks.push(highlights);
91910
91997
  const health = renderHealth(result.health);
91911
91998
  if (health.length > 0) blocks.push(health);
91999
+ const keybindings = renderKeybindings(result.health);
92000
+ if (keybindings.length > 0) blocks.push(keybindings);
91912
92001
  const actions = renderActions(result.actions);
91913
92002
  if (actions.length > 0) blocks.push(actions);
91914
92003
  blocks.push(["notifications:", indent(`enabled: ${result.notifications.enabled}`, 1)]);