@kud/gh-cockpit 0.5.6 → 0.6.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.
@@ -113,7 +113,8 @@ var AiLauncher = ({
113
113
  item,
114
114
  login,
115
115
  prompt,
116
- onBack
116
+ onBack,
117
+ peelRef
117
118
  }) => {
118
119
  const [agents, setAgents] = useState(null);
119
120
  const [step, setStep] = useState("agent");
@@ -155,15 +156,19 @@ var AiLauncher = ({
155
156
  setNote(`\u2717 ${e.message}`);
156
157
  }
157
158
  };
159
+ useEffect(() => {
160
+ if (!peelRef) return;
161
+ peelRef.current = () => {
162
+ if (step !== "place") return false;
163
+ setStep("agent");
164
+ setCursor(0);
165
+ return true;
166
+ };
167
+ return () => {
168
+ peelRef.current = null;
169
+ };
170
+ });
158
171
  useInput((input, key) => {
159
- if (key.escape || input === "q") {
160
- if (step === "place") {
161
- setStep("agent");
162
- setCursor(0);
163
- return;
164
- }
165
- return onBack();
166
- }
167
172
  if (key.return) {
168
173
  if (step === "agent") {
169
174
  setAgent((agents ?? [])[safeCursor] ?? null);
@@ -182,7 +187,7 @@ var AiLauncher = ({
182
187
  hints: [
183
188
  ["\u2191\u2193", "nav"],
184
189
  ["\u21B5", step === "agent" ? "choose" : "launch"],
185
- ["q/esc", step === "place" ? "back" : "close"]
190
+ ["esc", step === "place" ? "back" : "close"]
186
191
  ],
187
192
  children: !agents ? /* @__PURE__ */ jsx(Text, { color: colors.info, children: "Detecting agents\u2026" }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
188
193
  list.map((row, i) => /* @__PURE__ */ jsxs(Box, { children: [
@@ -1,4 +1,4 @@
1
- import { AiLauncher, seedPromptFor, CopyPromptNotice } from '../chunk-6FVWJ3OY.js';
1
+ import { AiLauncher, seedPromptFor, CopyPromptNotice } from '../chunk-6DVOSLNG.js';
2
2
  import { useEffect } from 'react';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
package/dist/index.d.ts CHANGED
@@ -147,7 +147,7 @@ declare const DrillView: ({ title, subtitle, hints, children, }: {
147
147
  }) => React.JSX.Element;
148
148
 
149
149
  type Tab = "health" | "conversation";
150
- declare const PrView: ({ item, login, onBack, defaultTab, onRefresh, onRemove, onMerged, }: {
150
+ declare const PrView: ({ item, login, onBack, defaultTab, onRefresh, onRemove, onMerged, registerPeel, onTyping, }: {
151
151
  item: GHItem;
152
152
  login: string;
153
153
  onBack: () => void;
@@ -155,9 +155,11 @@ declare const PrView: ({ item, login, onBack, defaultTab, onRefresh, onRemove, o
155
155
  onRefresh?: () => void;
156
156
  onRemove?: (item: GHItem) => void;
157
157
  onMerged?: (item: GHItem) => void;
158
+ registerPeel?: (peel: (() => boolean) | null) => void;
159
+ onTyping?: (typing: boolean) => void;
158
160
  }) => React.JSX.Element;
159
161
 
160
- declare const IssueView: ({ item, login, onBack, }: {
162
+ declare const IssueView: ({ item, login, onBack, registerPeel, onTyping, }: {
161
163
  item: {
162
164
  number: number;
163
165
  repo: string;
@@ -167,6 +169,8 @@ declare const IssueView: ({ item, login, onBack, }: {
167
169
  };
168
170
  login: string;
169
171
  onBack: () => void;
172
+ registerPeel?: (peel: (() => boolean) | null) => void;
173
+ onTyping?: (typing: boolean) => void;
170
174
  }) => React.JSX.Element;
171
175
 
172
176
  export { type CheckDrill, type CheckDrillContext, type CockpitArgs, type CockpitConfig, DrillView, IssueView, PrView, type PromptContext, type PromptForms, type TabSearch, type TabSpec, checkDrillFor, computeHealth, defineCockpit, detailFor, hasPatterns, parseArgs, registerCheckDrills, registerPrompts, signalPath, withRetry };
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { useActionMenu, buildActions, AiLauncher, seedPromptFor, CopyPromptNotice, DrillView, ActionMenu, resolveRepoPath, openInTab } from './chunk-6FVWJ3OY.js';
2
- export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, DrillView, HealthPanel, MIN_SIDEBAR_COLS, PIN_MARK, SIDEBAR_COLS, SidePanel, bandOf, budgetNotice, buildActions, buildCheckoutCmd, buildInboxQueries, buildInboxQuery, checkoutDirs, clipboard, computeHealth, configureInbox, counts, drillCmd, explainGhAction, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, impliedLabels, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, labelPriority, layoutGHItems, matchesFilter, maxViewStart, mergeInboxData, moveCursor, openInTab, parsePatterns, profileOf, railCapacity, railWidth, readCache, registerPrompts, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signalPath, signatureOf, sortByRecency, sortItems, toCiStatusState, toGHItem, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withRetry, withoutItem, workCount, writeCache } from './chunk-6FVWJ3OY.js';
1
+ import { useActionMenu, buildActions, AiLauncher, seedPromptFor, CopyPromptNotice, DrillView, ActionMenu, resolveRepoPath, openInTab } from './chunk-6DVOSLNG.js';
2
+ export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, DrillView, HealthPanel, MIN_SIDEBAR_COLS, PIN_MARK, SIDEBAR_COLS, SidePanel, bandOf, budgetNotice, buildActions, buildCheckoutCmd, buildInboxQueries, buildInboxQuery, checkoutDirs, clipboard, computeHealth, configureInbox, counts, drillCmd, explainGhAction, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, impliedLabels, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, labelPriority, layoutGHItems, matchesFilter, maxViewStart, mergeInboxData, moveCursor, openInTab, parsePatterns, profileOf, railCapacity, railWidth, readCache, registerPrompts, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signalPath, signatureOf, sortByRecency, sortItems, toCiStatusState, toGHItem, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withRetry, withoutItem, workCount, writeCache } from './chunk-6DVOSLNG.js';
3
3
  import { parsePatterns, HealthPanel, CommentsPanel, inboxConfig } from '@kud/gh-ink';
4
4
  import { $ } from 'zx';
5
- import { useState, useEffect } from 'react';
5
+ import React3, { useState, useRef, useEffect } from 'react';
6
6
  import { useInput, Box, Text } from 'ink';
7
7
  import { useTabs, colors, Tabs, useListCursor, ScrollView } from '@kud/ink-ui';
8
- import { fetchHealth, fetchPrComments } from '@kud/gh';
8
+ import { fetchHealth, fetchPrComments, fetchDefaultBranch } from '@kud/gh';
9
9
  import { sizePartsOf, sizeOf, filesOf } from '@kud/gh-workflow';
10
10
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
11
11
  import { readFileSync, mkdirSync, writeFileSync } from 'fs';
@@ -99,23 +99,53 @@ var fetchBody = async (kind, repo, number) => {
99
99
  }
100
100
  };
101
101
  var DIVIDER = " \xB7 ";
102
- var summaryOf = (item, data, cols) => {
102
+ var SUMMARY_KEYS = [
103
+ "size",
104
+ "files",
105
+ "draft",
106
+ "head",
107
+ "base",
108
+ "author",
109
+ "opened"
110
+ ];
111
+ var separatorBefore = (key, previous) => key === "base" && previous === "head" ? " " : DIVIDER;
112
+ var joinCells = (cells) => {
113
+ let line = "";
114
+ let previous = null;
115
+ cells.forEach((cell, i) => {
116
+ if (!cell) return;
117
+ const key = SUMMARY_KEYS[i];
118
+ line = line ? line + separatorBefore(key, previous) + cell : cell;
119
+ previous = key;
120
+ });
121
+ return line;
122
+ };
123
+ var summaryOf = (item, data, cols, defaultBranch) => {
103
124
  const size = data ? sizeOf(data) : null;
104
125
  const files = data ? filesOf(data) : null;
105
- const base = data?.baseRefName;
126
+ const baseRef = data?.baseRefName;
106
127
  const author = item.author ? item.author : null;
107
128
  const opened = item.age ? `opened ${item.age} ago` : null;
108
- const branches = base ? item.branch ? `${item.branch} \u2192 ${base}` : `\u2192 ${base}` : item.branch ?? null;
109
- const parts = (branch) => [branch, author, opened].filter((p) => !!p);
110
- const full = parts(branches);
111
- const line = (ps) => ps.join(DIVIDER);
112
- const width = (ps) => (size ? size.length + DIVIDER.length : 0) + (files ? files.length + DIVIDER.length : 0) + line(ps).length;
113
- const draft = item.health === "draft";
114
- const prefix = draft ? `~ draft${DIVIDER}` : "";
115
- if (width(full) + prefix.length <= cols)
116
- return { size, files, rest: prefix + line(full) };
117
- const shortened = parts(base ? `\u2192 ${base}` : null);
118
- return { size, files, rest: prefix + line(shortened) };
129
+ const showBase = baseRef !== void 0 && baseRef !== defaultBranch;
130
+ const base = showBase ? `\u2192 ${baseRef}` : null;
131
+ const draft = item.health === "draft" ? `~ draft` : null;
132
+ const widthOf = (s) => joinCells([s.size, s.files, s.draft, s.head, s.base, s.author, s.opened]).length;
133
+ const full = {
134
+ size,
135
+ files,
136
+ draft,
137
+ head: item.branch ?? null,
138
+ base,
139
+ author,
140
+ opened
141
+ };
142
+ const rungs = ["author", "opened", "head"];
143
+ const line = { ...full };
144
+ for (const cell of rungs) {
145
+ if (widthOf(line) <= cols) return line;
146
+ line[cell] = null;
147
+ }
148
+ return line;
119
149
  };
120
150
  var jobIdOf = (url) => url?.match(/\/job\/(\d+)/)?.[1] ?? null;
121
151
  var stripTimestamp = (line) => line.replace(/^\d{4}-\d\d-\d\dT[\d:.]+Z\s?/, "").replace(/\s+$/, "");
@@ -175,8 +205,7 @@ var CheckLogView = ({
175
205
  live = false;
176
206
  };
177
207
  }, [repo, jobId]);
178
- useInput((input, key) => {
179
- if (key.escape || input === "q") return onBack();
208
+ useInput((input) => {
180
209
  if (input === "o" && url) $`open ${url}`.catch(() => {
181
210
  });
182
211
  });
@@ -196,7 +225,7 @@ var CheckLogView = ({
196
225
  ["\u2191\u2193/space", "scroll"],
197
226
  ["g/G", "top/tail"],
198
227
  ["o", "open"],
199
- ["q/esc", "back"]
228
+ ["esc", "back"]
200
229
  ],
201
230
  children: /* @__PURE__ */ jsx(ScrollView, { lines, initialStart: jumpTo })
202
231
  }
@@ -264,8 +293,7 @@ var FilePicker = ({
264
293
  const ed = await openInEditor(repoPath, f);
265
294
  setNote(`\u2197 ${f.path}${f.line ? `:${f.line}` : ""} \u2014 ${ed}`);
266
295
  };
267
- useInput((input, key) => {
268
- if (key.escape || input === "q") return onBack();
296
+ useInput((_input, key) => {
269
297
  if (key.return && list[safeCursor]) void open(list[safeCursor]);
270
298
  });
271
299
  return /* @__PURE__ */ jsxs(
@@ -276,7 +304,7 @@ var FilePicker = ({
276
304
  hints: [
277
305
  ["\u2191\u2193", "nav"],
278
306
  ["\u21B5", "open in editor"],
279
- ["q/esc", "back"]
307
+ ["esc", "back"]
280
308
  ],
281
309
  children: [
282
310
  !files ? /* @__PURE__ */ jsx(Text, { color: colors.info, children: "Fetching files\u2026" }) : list.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No files referenced in review comments." }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: list.map((f, i) => /* @__PURE__ */ jsxs(Box, { children: [
@@ -338,7 +366,9 @@ var PrView = ({
338
366
  defaultTab,
339
367
  onRefresh,
340
368
  onRemove,
341
- onMerged
369
+ onMerged,
370
+ registerPeel,
371
+ onTyping
342
372
  }) => {
343
373
  const [log, setLog] = useState(null);
344
374
  const [ai, setAi] = useState(false);
@@ -369,8 +399,43 @@ var PrView = ({
369
399
  `pr-health-${item.repo}-${item.number}`,
370
400
  () => fetchHealth(item.repo, item.number)
371
401
  );
372
- const summary = summaryOf(item, health.data, process.stdout.columns ?? 80);
402
+ const defaultBranch = useCachedResource(
403
+ `repo-default-branch-${item.repo}`,
404
+ () => fetchDefaultBranch(item.repo)
405
+ );
406
+ const summary = summaryOf(
407
+ item,
408
+ health.data,
409
+ process.stdout.columns ?? 80,
410
+ defaultBranch.data ?? void 0
411
+ );
373
412
  const sizeParts = health.data ? sizePartsOf(health.data) : null;
413
+ const summaryCells = [
414
+ { key: "size", text: summary.size },
415
+ { key: "files", text: summary.files },
416
+ { key: "draft", text: summary.draft },
417
+ { key: "head", text: summary.head },
418
+ { key: "base", text: summary.base },
419
+ { key: "author", text: summary.author },
420
+ { key: "opened", text: summary.opened }
421
+ ].filter((c) => !!c.text);
422
+ const aiPeel = useRef(null);
423
+ const peel = () => {
424
+ if (menu.actions !== null) return menu.close(), true;
425
+ if (replying) return true;
426
+ if (log !== null) return setLog(null), true;
427
+ if (files) return setFiles(false), true;
428
+ if (ai) return aiPeel.current?.() ?? (setAi(false), true);
429
+ if (copy) return setCopy(false), true;
430
+ return false;
431
+ };
432
+ useEffect(() => {
433
+ registerPeel?.(peel);
434
+ return () => registerPeel?.(null);
435
+ });
436
+ useEffect(() => {
437
+ onTyping?.(replying);
438
+ }, [replying, onTyping]);
374
439
  const checkLabel = (c) => c.workflowName ? `${c.workflowName} / ${c.context ?? c.name}` : c.context ?? c.name ?? "";
375
440
  const onOpenCheck = (c) => {
376
441
  const url = c.detailsUrl ?? c.targetUrl ?? "";
@@ -383,7 +448,6 @@ var PrView = ({
383
448
  useInput(
384
449
  (input, key) => {
385
450
  if (menu.handleKey(key)) return;
386
- if (key.escape || input === "q") return onBack();
387
451
  if (input === "M") {
388
452
  menu.open([
389
453
  ...buildActions(
@@ -454,7 +518,8 @@ var PrView = ({
454
518
  item,
455
519
  login,
456
520
  prompt: seedPromptFor(item),
457
- onBack: () => setAi(false)
521
+ onBack: () => setAi(false),
522
+ peelRef: aiPeel
458
523
  }
459
524
  );
460
525
  if (copy)
@@ -488,7 +553,7 @@ var PrView = ({
488
553
  ["M", "actions"],
489
554
  ["\u2190\u2192", "tab"],
490
555
  ["o", "open PR"],
491
- ["q", "back"]
556
+ ["esc", "back"]
492
557
  ] : [
493
558
  ["\u2191\u2193", "thread"],
494
559
  ["x", "resolve"],
@@ -498,7 +563,7 @@ var PrView = ({
498
563
  ["a", "AI"],
499
564
  ["y", "copy prompt"],
500
565
  ["\u2190\u2192", "tab"],
501
- ["q", "back"]
566
+ ["esc", "back"]
502
567
  ];
503
568
  return /* @__PURE__ */ jsxs(
504
569
  DrillView,
@@ -507,17 +572,17 @@ var PrView = ({
507
572
  subtitle: item.title,
508
573
  hints,
509
574
  children: [
510
- summary.size || summary.files || summary.rest ? /* @__PURE__ */ jsxs(Box, { children: [
511
- sizeParts ? /* @__PURE__ */ jsxs(Text, { bold: true, children: [
575
+ summaryCells.length ? /* @__PURE__ */ jsx(Box, { children: summaryCells.map(({ key, text }, i) => /* @__PURE__ */ jsxs(React3.Fragment, { children: [
576
+ i > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: separatorBefore(
577
+ key,
578
+ summaryCells[i - 1].key
579
+ ) }) : null,
580
+ key === "size" && sizeParts ? /* @__PURE__ */ jsxs(Text, { bold: true, children: [
512
581
  /* @__PURE__ */ jsx(Text, { color: colors.success, children: sizeParts.added }),
513
582
  " ",
514
583
  /* @__PURE__ */ jsx(Text, { color: colors.error, children: sizeParts.removed })
515
- ] }) : null,
516
- summary.size && (summary.files || summary.rest) ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }) : null,
517
- summary.files ? /* @__PURE__ */ jsx(Text, { children: summary.files }) : null,
518
- summary.files && summary.rest ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }) : null,
519
- summary.rest ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: summary.rest }) : null
520
- ] }) : null,
584
+ ] }) : key === "files" || key === "base" ? /* @__PURE__ */ jsx(Text, { children: text }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: text })
585
+ ] }, key)) }) : null,
521
586
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, marginTop: 1, children: /* @__PURE__ */ jsx(Tabs, { active: tab, items: tabItems }) }),
522
587
  menu.actions ? /* @__PURE__ */ jsx(ActionMenu, { item, actions: menu.actions, cursor: menu.cursor }) : tab === "health" ? /* @__PURE__ */ jsx(
523
588
  HealthPanel,
@@ -549,7 +614,9 @@ var PrView = ({
549
614
  var IssueView = ({
550
615
  item,
551
616
  login,
552
- onBack
617
+ onBack,
618
+ registerPeel,
619
+ onTyping
553
620
  }) => {
554
621
  const [replying, setReplying] = useState(false);
555
622
  const [ai, setAi] = useState(false);
@@ -561,9 +628,22 @@ var IssueView = ({
561
628
  const tabItems = [
562
629
  { value: "conversation", label: "Conversation" }
563
630
  ];
631
+ const aiPeel = useRef(null);
632
+ const peel = () => {
633
+ if (replying) return true;
634
+ if (ai) return aiPeel.current?.() ?? (setAi(false), true);
635
+ if (copy) return setCopy(false), true;
636
+ return false;
637
+ };
638
+ useEffect(() => {
639
+ registerPeel?.(peel);
640
+ return () => registerPeel?.(null);
641
+ });
642
+ useEffect(() => {
643
+ onTyping?.(replying);
644
+ }, [replying, onTyping]);
564
645
  useInput(
565
646
  (input, key) => {
566
- if (key.escape || input === "q") return onBack();
567
647
  if (input === "o") $`open ${item.url}`.catch(() => {
568
648
  });
569
649
  if (input === "a") setAi(true);
@@ -578,7 +658,8 @@ var IssueView = ({
578
658
  item,
579
659
  login,
580
660
  prompt: seedPromptFor({ ...item, kind: "issue" }),
581
- onBack: () => setAi(false)
661
+ onBack: () => setAi(false),
662
+ peelRef: aiPeel
582
663
  }
583
664
  );
584
665
  if (copy)
@@ -599,7 +680,7 @@ var IssueView = ({
599
680
  ["a", "AI"],
600
681
  ["y", "copy prompt"],
601
682
  ["o", "open in browser"],
602
- ["q/esc", "back"]
683
+ ["esc", "back"]
603
684
  ],
604
685
  children: [
605
686
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Tabs, { active: "conversation", items: tabItems }) }),
@@ -627,13 +708,24 @@ var detailFor = (ctx) => ctx.kind === "pr" ? /* @__PURE__ */ jsx(
627
708
  onBack: ctx.onBack,
628
709
  onRefresh: ctx.onRefresh,
629
710
  onRemove: ctx.onRemove,
630
- onMerged: ctx.onMerged
711
+ onMerged: ctx.onMerged,
712
+ registerPeel: ctx.registerPeel,
713
+ onTyping: ctx.onTyping
631
714
  }
632
715
  ) : (
633
716
  // No refresh/remove/merged: IssueView has no action menu to hang them off,
634
717
  // so passing them would type-check into a handler nothing ever calls. They
635
718
  // belong here the day it grows the `M` menu PrView has.
636
- /* @__PURE__ */ jsx(IssueView, { item: ctx.item, login: ctx.login, onBack: ctx.onBack })
719
+ /* @__PURE__ */ jsx(
720
+ IssueView,
721
+ {
722
+ item: ctx.item,
723
+ login: ctx.login,
724
+ onBack: ctx.onBack,
725
+ registerPeel: ctx.registerPeel,
726
+ onTyping: ctx.onTyping
727
+ }
728
+ )
637
729
  );
638
730
 
639
731
  export { IssueView, PrView, checkDrillFor, defineCockpit, detailFor, hasPatterns, parseArgs, registerCheckDrills };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/gh-cockpit",
3
- "version": "0.5.6",
3
+ "version": "0.6.0",
4
4
  "description": "A configurable GitHub cockpit for the terminal — your PRs, reviews and issues in one Ink TUI, grouped by whose move it is.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -53,11 +53,11 @@
53
53
  "react": ">=19"
54
54
  },
55
55
  "dependencies": {
56
- "@kud/gh": "0.16.0",
57
- "@kud/gh-ink": "0.56.0",
56
+ "@kud/gh": "0.17.0",
57
+ "@kud/gh-ink": "0.57.0",
58
58
  "@kud/ink-ui": "0.29.0",
59
59
  "zx": "8.8.5",
60
- "@kud/gh-workflow": "0.12.0"
60
+ "@kud/gh-workflow": "0.12.1"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/node": "22.20.1",