@kud/gh-cockpit 0.9.1 → 0.10.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/{chunk-PP3BVSMA.js → chunk-B3FS2IVR.js} +1 -1
- package/dist/extensions/index.js +1 -1
- package/dist/index.d.ts +28 -4
- package/dist/index.js +248 -18
- package/package.json +4 -4
|
@@ -2,7 +2,7 @@ import { useWindowSize, Box, Text, useInput } from 'ink';
|
|
|
2
2
|
import { Panel, colors, FooterHints, useListCursor } from '@kud/ink-ui';
|
|
3
3
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
4
|
import { inboxConfig, clipboard, buildCheckoutCmd, runHere, openInTab, runInPane, runInPaneHorizontal } from '@kud/gh-ink';
|
|
5
|
-
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, MIN_SIDEBAR_COLS, PIN_MARK, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, bandOf, budgetNotice, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, counts, drillCmd, explainGhAction, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, impliedLabels, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, labelPriority, layoutGHItems, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, profileOf, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, sortByRecency, sortItems, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, workCount, writeCache } from '@kud/gh-ink';
|
|
5
|
+
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, MIN_SIDEBAR_COLS, NEEDS_YOU_SECTION, PIN_MARK, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, bandOf, budgetNotice, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, counts, drillCmd, explainGhAction, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, impliedLabels, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, labelPriority, layoutGHItems, matchesFilter, maxViewStart, moveCursor, numberColumnsOf, openInTab, parsePatterns, profileOf, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, sortByRecency, sortItems, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, workCount, writeCache } from '@kud/gh-ink';
|
|
6
6
|
export { toGHItem } from '@kud/gh-workflow';
|
|
7
7
|
import { mkdirSync } from 'fs';
|
|
8
8
|
import { homedir } from 'os';
|
package/dist/extensions/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
-
import { RepoFilter, InboxExtension,
|
|
2
|
+
import { RepoFilter, InboxExtension, GHItem, NeedsYou, DetailContext } from '@kud/gh-ink';
|
|
3
3
|
export * from '@kud/gh-ink';
|
|
4
|
-
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, MIN_SIDEBAR_COLS, PIN_MARK, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, bandOf, budgetNotice, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, counts, drillCmd, explainGhAction, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, impliedLabels, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, labelPriority, layoutGHItems, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, profileOf, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, sortByRecency, sortItems, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, workCount, writeCache } from '@kud/gh-ink';
|
|
4
|
+
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, MIN_SIDEBAR_COLS, NEEDS_YOU_SECTION, PIN_MARK, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, bandOf, budgetNotice, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, counts, drillCmd, explainGhAction, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, impliedLabels, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, labelPriority, layoutGHItems, matchesFilter, maxViewStart, moveCursor, numberColumnsOf, openInTab, parsePatterns, profileOf, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, sortByRecency, sortItems, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, workCount, writeCache } from '@kud/gh-ink';
|
|
5
5
|
import { Health } from '@kud/gh';
|
|
6
6
|
export { InboxSource, buildInboxQueries, buildInboxQuery, mergeInboxData } from '@kud/gh';
|
|
7
7
|
export { CockpitItem, toGHItem } from '@kud/gh-workflow';
|
|
@@ -137,6 +137,27 @@ type PromptForms = {
|
|
|
137
137
|
/** Supply the host's prompt forms. Call once, before rendering. */
|
|
138
138
|
declare const registerPrompts: (forms: PromptForms) => void;
|
|
139
139
|
|
|
140
|
+
/**
|
|
141
|
+
* What the hook is told: the item's identity and labels, which is what a label
|
|
142
|
+
* is cleared by. Not the full row — the issue drill holds only a subset of one,
|
|
143
|
+
* and a hook that needs more is better off asking GitHub than trusting a row
|
|
144
|
+
* cached before the answer was posted.
|
|
145
|
+
*/
|
|
146
|
+
type DecisionTarget = Pick<GHItem, "repo" | "number" | "url" | "labels"> & {
|
|
147
|
+
kind: "pr" | "issue";
|
|
148
|
+
};
|
|
149
|
+
type DecisionHooks = {
|
|
150
|
+
/** Called after an answer posted from a drill view. */
|
|
151
|
+
answered?: (target: DecisionTarget) => void | Promise<void>;
|
|
152
|
+
};
|
|
153
|
+
/** Supply the host's decision follow-through. Call once, before rendering. */
|
|
154
|
+
declare const registerDecisionHooks: (hooks: DecisionHooks) => void;
|
|
155
|
+
declare const decisionHooks: () => DecisionHooks;
|
|
156
|
+
|
|
157
|
+
declare const DecisionBlock: ({ needsYou }: {
|
|
158
|
+
needsYou: NeedsYou;
|
|
159
|
+
}) => React.JSX.Element;
|
|
160
|
+
|
|
140
161
|
declare const detailFor: (ctx: DetailContext) => React.JSX.Element;
|
|
141
162
|
|
|
142
163
|
declare const DrillView: ({ title, subtitle, hints, children, }: {
|
|
@@ -159,18 +180,21 @@ declare const PrView: ({ item, login, onBack, defaultTab, onRefresh, onRemove, o
|
|
|
159
180
|
onTyping?: (typing: boolean) => void;
|
|
160
181
|
}) => React.JSX.Element;
|
|
161
182
|
|
|
162
|
-
declare const IssueView: ({ item, login, onBack, registerPeel, onTyping, }: {
|
|
183
|
+
declare const IssueView: ({ item, login, onBack, onRefresh, onRemove, registerPeel, onTyping, }: {
|
|
163
184
|
item: {
|
|
164
185
|
number: number;
|
|
165
186
|
repo: string;
|
|
166
187
|
url: string;
|
|
167
188
|
title?: string;
|
|
168
189
|
labels?: readonly string[];
|
|
190
|
+
needsYou?: NeedsYou;
|
|
169
191
|
};
|
|
170
192
|
login: string;
|
|
171
193
|
onBack: () => void;
|
|
194
|
+
onRefresh?: () => void;
|
|
195
|
+
onRemove?: () => void;
|
|
172
196
|
registerPeel?: (peel: (() => boolean) | null) => void;
|
|
173
197
|
onTyping?: (typing: boolean) => void;
|
|
174
198
|
}) => React.JSX.Element;
|
|
175
199
|
|
|
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 };
|
|
200
|
+
export { type CheckDrill, type CheckDrillContext, type CockpitArgs, type CockpitConfig, DecisionBlock, type DecisionHooks, type DecisionTarget, DrillView, IssueView, PrView, type PromptContext, type PromptForms, type TabSearch, type TabSpec, checkDrillFor, computeHealth, decisionHooks, defineCockpit, detailFor, hasPatterns, parseArgs, registerCheckDrills, registerDecisionHooks, registerPrompts, signalPath, withRetry };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { useActionMenu, buildActions, AiLauncher, seedPromptFor, CopyPromptNotice, DrillView, ActionMenu, resolveRepoPath, openInTab } from './chunk-
|
|
2
|
-
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, DrillView, HealthPanel, MIN_SIDEBAR_COLS, PIN_MARK, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, 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, railHeights, railRowsOf, railWidth, railsOf, readCache, registerPrompts, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signalPath, signatureOf, sortByRecency, sortItems, stripGlyph, stripLayout, stripSignature, toCiStatusState, toGHItem, trailingColumnsOf, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withRetry, withoutItem, workCount, writeCache } from './chunk-
|
|
1
|
+
import { useActionMenu, buildActions, AiLauncher, seedPromptFor, CopyPromptNotice, DrillView, ActionMenu, resolveRepoPath, openInTab } from './chunk-B3FS2IVR.js';
|
|
2
|
+
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, DrillView, HealthPanel, MIN_SIDEBAR_COLS, NEEDS_YOU_SECTION, PIN_MARK, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, 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, numberColumnsOf, openInTab, parsePatterns, profileOf, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, registerPrompts, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signalPath, signatureOf, sortByRecency, sortItems, stripGlyph, stripLayout, stripSignature, toCiStatusState, toGHItem, trailingColumnsOf, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withRetry, withoutItem, workCount, writeCache } from './chunk-B3FS2IVR.js';
|
|
3
3
|
import { parsePatterns, HealthPanel, CommentsPanel, inboxConfig } from '@kud/gh-ink';
|
|
4
|
+
import { Box, Text, useStdin, useInput } from 'ink';
|
|
5
|
+
import { colors, useTabs, Tabs, TextInput, useListCursor, ScrollView } from '@kud/ink-ui';
|
|
6
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
7
|
import { $ } from 'zx';
|
|
8
|
+
import { spawnSync } from 'child_process';
|
|
5
9
|
import React3, { useState, useRef, useEffect } from 'react';
|
|
6
|
-
import {
|
|
7
|
-
import { useTabs, colors, Tabs, useListCursor, ScrollView } from '@kud/ink-ui';
|
|
8
|
-
import { fetchHealth, fetchPrComments, fetchDefaultBranch } from '@kud/gh';
|
|
10
|
+
import { mergePr, fetchHealth, fetchPrComments, fetchDefaultBranch } from '@kud/gh';
|
|
9
11
|
import { sizePartsOf, sizeOf, filesOf } from '@kud/gh-workflow';
|
|
10
|
-
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
11
12
|
import { readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
12
13
|
import { homedir } from 'os';
|
|
13
14
|
import { join } from 'path';
|
|
@@ -53,6 +54,17 @@ var registerCheckDrills = (drills) => {
|
|
|
53
54
|
registered = drills;
|
|
54
55
|
};
|
|
55
56
|
var checkDrillFor = (url) => registered.find((d) => d.match(url)) ?? null;
|
|
57
|
+
|
|
58
|
+
// src/decision.ts
|
|
59
|
+
var registered2 = {};
|
|
60
|
+
var registerDecisionHooks = (hooks) => {
|
|
61
|
+
registered2 = hooks;
|
|
62
|
+
};
|
|
63
|
+
var decisionHooks = () => registered2;
|
|
64
|
+
var DecisionBlock = ({ needsYou }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
65
|
+
/* @__PURE__ */ jsx(Text, { color: colors.accent, bold: true, children: "Decision" }),
|
|
66
|
+
needsYou.decision ? /* @__PURE__ */ jsx(Text, { children: needsYou.decision }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Nothing to decide: ready to merge." })
|
|
67
|
+
] });
|
|
56
68
|
var fetchComments = async (repo, number, kind = "pr") => {
|
|
57
69
|
if (kind === "issue") return fetchIssueConversation(repo, number);
|
|
58
70
|
const [owner, name] = repo.split("/");
|
|
@@ -375,6 +387,11 @@ var PrView = ({
|
|
|
375
387
|
const [files, setFiles] = useState(false);
|
|
376
388
|
const [copy, setCopy] = useState(false);
|
|
377
389
|
const [replying, setReplying] = useState(false);
|
|
390
|
+
const [answering, setAnswering] = useState(false);
|
|
391
|
+
const [confirm, setConfirm] = useState(null);
|
|
392
|
+
const [note, setNote] = useState(null);
|
|
393
|
+
const [paging, setPaging] = useState(false);
|
|
394
|
+
const { setRawMode } = useStdin();
|
|
378
395
|
const menu = useActionMenu();
|
|
379
396
|
const comments = useCachedResource(
|
|
380
397
|
`pr-comments-${item.repo}-${item.number}`,
|
|
@@ -388,7 +405,7 @@ var PrView = ({
|
|
|
388
405
|
{ value: "health", label: "Health" },
|
|
389
406
|
{ value: "conversation", label: conversationLabel }
|
|
390
407
|
];
|
|
391
|
-
const inputActive = log === null && !ai && !files && !replying && !copy;
|
|
408
|
+
const inputActive = log === null && !ai && !files && !replying && !copy && !answering;
|
|
392
409
|
const menuOpen = menu.actions !== null;
|
|
393
410
|
const { active, setActive } = useTabs(tabItems, {
|
|
394
411
|
initial: defaultTab ?? "health",
|
|
@@ -421,6 +438,8 @@ var PrView = ({
|
|
|
421
438
|
].filter((c) => !!c.text);
|
|
422
439
|
const aiPeel = useRef(null);
|
|
423
440
|
const peel = () => {
|
|
441
|
+
if (answering) return setAnswering(false), true;
|
|
442
|
+
if (confirm !== null) return setConfirm(null), true;
|
|
424
443
|
if (menu.actions !== null) return menu.close(), true;
|
|
425
444
|
if (replying) return true;
|
|
426
445
|
if (log !== null) return setLog(null), true;
|
|
@@ -434,8 +453,8 @@ var PrView = ({
|
|
|
434
453
|
return () => registerPeel?.(null);
|
|
435
454
|
});
|
|
436
455
|
useEffect(() => {
|
|
437
|
-
onTyping?.(replying);
|
|
438
|
-
}, [replying, onTyping]);
|
|
456
|
+
onTyping?.(replying || answering);
|
|
457
|
+
}, [replying, answering, onTyping]);
|
|
439
458
|
const checkLabel = (c) => c.workflowName ? `${c.workflowName} / ${c.context ?? c.name}` : c.context ?? c.name ?? "";
|
|
440
459
|
const onOpenCheck = (c) => {
|
|
441
460
|
const url = c.detailsUrl ?? c.targetUrl ?? "";
|
|
@@ -445,9 +464,118 @@ var PrView = ({
|
|
|
445
464
|
else if (url) $`open ${url}`.catch(() => {
|
|
446
465
|
});
|
|
447
466
|
};
|
|
467
|
+
const doMerge = async () => {
|
|
468
|
+
setNote("\u22EF merging\u2026");
|
|
469
|
+
try {
|
|
470
|
+
await mergePr(item.repo, item.number);
|
|
471
|
+
setNote(null);
|
|
472
|
+
if (onMerged) onMerged(item);
|
|
473
|
+
else onRefresh?.();
|
|
474
|
+
} catch (e) {
|
|
475
|
+
setNote(`\u2717 merge failed: ${e.message}`);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
const doClose = async () => {
|
|
479
|
+
setNote("\u22EF closing\u2026");
|
|
480
|
+
try {
|
|
481
|
+
await $`gh pr close ${item.number} --repo ${item.repo}`.quiet();
|
|
482
|
+
setNote(null);
|
|
483
|
+
if (onRemove) onRemove(item);
|
|
484
|
+
else onBack();
|
|
485
|
+
} catch (e) {
|
|
486
|
+
setNote(`\u2717 close failed: ${e.message}`);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
const markReady = async () => {
|
|
490
|
+
if (item.health !== "draft") {
|
|
491
|
+
setNote("Not a draft \u2014 nothing to mark ready");
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
setNote("\u22EF marking ready\u2026");
|
|
495
|
+
try {
|
|
496
|
+
await $`gh pr ready ${item.number} --repo ${item.repo}`.quiet();
|
|
497
|
+
setNote(null);
|
|
498
|
+
onRefresh?.();
|
|
499
|
+
} catch (e) {
|
|
500
|
+
setNote(`\u2717 ready failed: ${e.message}`);
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
const submitAnswer = async (body) => {
|
|
504
|
+
const text = body.trim();
|
|
505
|
+
setAnswering(false);
|
|
506
|
+
if (!text) return;
|
|
507
|
+
setNote("Posting\u2026");
|
|
508
|
+
try {
|
|
509
|
+
await $`gh pr comment ${item.number} --repo ${item.repo} --body ${text}`.quiet();
|
|
510
|
+
setNote("\u2713 Answered");
|
|
511
|
+
await decisionHooks().answered?.({
|
|
512
|
+
kind: "pr",
|
|
513
|
+
repo: item.repo,
|
|
514
|
+
number: item.number,
|
|
515
|
+
url: item.url,
|
|
516
|
+
labels: item.labels
|
|
517
|
+
});
|
|
518
|
+
} catch (e) {
|
|
519
|
+
setNote(`\u2717 ${e.message}`);
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
useEffect(() => {
|
|
523
|
+
if (!paging) return;
|
|
524
|
+
const timer = setTimeout(() => {
|
|
525
|
+
setRawMode(false);
|
|
526
|
+
spawnSync(
|
|
527
|
+
"sh",
|
|
528
|
+
[
|
|
529
|
+
"-c",
|
|
530
|
+
'gh pr diff "$0" --repo "$1" --color=always | ${PAGER:-less -R}',
|
|
531
|
+
String(item.number),
|
|
532
|
+
item.repo
|
|
533
|
+
],
|
|
534
|
+
{ stdio: "inherit" }
|
|
535
|
+
);
|
|
536
|
+
process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H");
|
|
537
|
+
setRawMode(true);
|
|
538
|
+
setNote(null);
|
|
539
|
+
setPaging(false);
|
|
540
|
+
}, 50);
|
|
541
|
+
return () => clearTimeout(timer);
|
|
542
|
+
}, [paging]);
|
|
448
543
|
useInput(
|
|
449
544
|
(input, key) => {
|
|
450
545
|
if (menu.handleKey(key)) return;
|
|
546
|
+
if (item.needsYou) {
|
|
547
|
+
if (confirm === "merge") {
|
|
548
|
+
if (input === "y" || input === "Y") void doMerge();
|
|
549
|
+
setConfirm(null);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (confirm === "close") {
|
|
553
|
+
if (input === "y" || input === "Y") void doClose();
|
|
554
|
+
setConfirm(null);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (input === "c") {
|
|
558
|
+
setAnswering(true);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (input === "d") {
|
|
562
|
+
setNote("\u22EF opening the diff in your pager\u2026");
|
|
563
|
+
setPaging(true);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (input === "X") {
|
|
567
|
+
setConfirm("close");
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
if (input === "m" && tab === "conversation") {
|
|
571
|
+
setConfirm("merge");
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (input === "R" && tab === "health") {
|
|
575
|
+
void markReady();
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
451
579
|
if (input === "M") {
|
|
452
580
|
menu.open([
|
|
453
581
|
...buildActions(
|
|
@@ -542,11 +670,23 @@ var PrView = ({
|
|
|
542
670
|
}
|
|
543
671
|
);
|
|
544
672
|
}
|
|
673
|
+
const decisionHints = !item.needsYou ? [] : tab === "health" ? [
|
|
674
|
+
["c", "answer"],
|
|
675
|
+
["d", "diff"],
|
|
676
|
+
["X", "close"],
|
|
677
|
+
["R", "ready"]
|
|
678
|
+
] : [
|
|
679
|
+
["c", "answer"],
|
|
680
|
+
["d", "diff"],
|
|
681
|
+
["m", "merge"],
|
|
682
|
+
["X", "close"]
|
|
683
|
+
];
|
|
545
684
|
const hints = tab === "health" ? [
|
|
546
685
|
["\u2191\u2193", "nav"],
|
|
547
686
|
["\u21B5/l", "log"],
|
|
548
687
|
["r", "retrigger"],
|
|
549
688
|
["m", "merge"],
|
|
689
|
+
...decisionHints,
|
|
550
690
|
["a", "AI"],
|
|
551
691
|
["y", "copy prompt"],
|
|
552
692
|
["e", "files"],
|
|
@@ -559,6 +699,7 @@ var PrView = ({
|
|
|
559
699
|
["x", "resolve"],
|
|
560
700
|
["r", "reply"],
|
|
561
701
|
["R", "show resolved"],
|
|
702
|
+
...decisionHints,
|
|
562
703
|
["M", "actions"],
|
|
563
704
|
["a", "AI"],
|
|
564
705
|
["y", "copy prompt"],
|
|
@@ -583,8 +724,25 @@ var PrView = ({
|
|
|
583
724
|
/* @__PURE__ */ jsx(Text, { color: colors.error, children: sizeParts.removed })
|
|
584
725
|
] }) : key === "files" || key === "base" ? /* @__PURE__ */ jsx(Text, { children: text }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: text })
|
|
585
726
|
] }, key)) }) : null,
|
|
727
|
+
item.needsYou ? /* @__PURE__ */ jsx(DecisionBlock, { needsYou: item.needsYou }) : null,
|
|
586
728
|
/* @__PURE__ */ jsx(Box, { marginBottom: 1, marginTop: 1, children: /* @__PURE__ */ jsx(Tabs, { active: tab, items: tabItems }) }),
|
|
587
|
-
|
|
729
|
+
confirm === "merge" ? /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { color: colors.warning, children: [
|
|
730
|
+
` Merge #${item.number}? `,
|
|
731
|
+
/* @__PURE__ */ jsx(Text, { color: colors.success, children: "y" }),
|
|
732
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " confirm \xB7 any other key cancels" })
|
|
733
|
+
] }) }) : confirm === "close" ? /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { color: colors.warning, children: [
|
|
734
|
+
` Close #${item.number}? `,
|
|
735
|
+
/* @__PURE__ */ jsx(Text, { color: colors.success, children: "y" }),
|
|
736
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " confirm \xB7 any other key cancels" })
|
|
737
|
+
] }) }) : note ? /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: colors.warning, bold: true, children: " " + note }) }) : null,
|
|
738
|
+
answering ? /* @__PURE__ */ jsx(
|
|
739
|
+
TextInput,
|
|
740
|
+
{
|
|
741
|
+
placeholder: "Answer\u2026 (\u21B5 send \xB7 esc cancel)",
|
|
742
|
+
onSubmit: (value) => void submitAnswer(value),
|
|
743
|
+
onCancel: () => setAnswering(false)
|
|
744
|
+
}
|
|
745
|
+
) : menu.actions ? /* @__PURE__ */ jsx(ActionMenu, { item, actions: menu.actions, cursor: menu.cursor }) : tab === "health" ? /* @__PURE__ */ jsx(
|
|
588
746
|
HealthPanel,
|
|
589
747
|
{
|
|
590
748
|
repo: item.repo,
|
|
@@ -615,12 +773,17 @@ var IssueView = ({
|
|
|
615
773
|
item,
|
|
616
774
|
login,
|
|
617
775
|
onBack,
|
|
776
|
+
onRefresh,
|
|
777
|
+
onRemove,
|
|
618
778
|
registerPeel,
|
|
619
779
|
onTyping
|
|
620
780
|
}) => {
|
|
621
781
|
const [replying, setReplying] = useState(false);
|
|
622
782
|
const [ai, setAi] = useState(false);
|
|
623
783
|
const [copy, setCopy] = useState(false);
|
|
784
|
+
const [answering, setAnswering] = useState(false);
|
|
785
|
+
const [confirmClose, setConfirmClose] = useState(false);
|
|
786
|
+
const [note, setNote] = useState(null);
|
|
624
787
|
const comments = useCachedResource(
|
|
625
788
|
`issue-comments-${item.repo}-${item.number}`,
|
|
626
789
|
() => fetchComments(item.repo, item.number, "issue")
|
|
@@ -630,6 +793,8 @@ var IssueView = ({
|
|
|
630
793
|
];
|
|
631
794
|
const aiPeel = useRef(null);
|
|
632
795
|
const peel = () => {
|
|
796
|
+
if (answering) return setAnswering(false), true;
|
|
797
|
+
if (confirmClose) return setConfirmClose(false), true;
|
|
633
798
|
if (replying) return true;
|
|
634
799
|
if (ai) return aiPeel.current?.() ?? (setAi(false), true);
|
|
635
800
|
if (copy) return setCopy(false), true;
|
|
@@ -640,16 +805,61 @@ var IssueView = ({
|
|
|
640
805
|
return () => registerPeel?.(null);
|
|
641
806
|
});
|
|
642
807
|
useEffect(() => {
|
|
643
|
-
onTyping?.(replying);
|
|
644
|
-
}, [replying, onTyping]);
|
|
808
|
+
onTyping?.(replying || answering);
|
|
809
|
+
}, [replying, answering, onTyping]);
|
|
810
|
+
const doClose = async () => {
|
|
811
|
+
setNote("\u22EF closing\u2026");
|
|
812
|
+
try {
|
|
813
|
+
await $`gh issue close ${item.number} --repo ${item.repo}`.quiet();
|
|
814
|
+
setNote(null);
|
|
815
|
+
if (onRemove) onRemove();
|
|
816
|
+
else onBack();
|
|
817
|
+
} catch (e) {
|
|
818
|
+
setNote(`\u2717 close failed: ${e.message}`);
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
const submitAnswer = async (body) => {
|
|
822
|
+
const text = body.trim();
|
|
823
|
+
setAnswering(false);
|
|
824
|
+
if (!text) return;
|
|
825
|
+
setNote("Posting\u2026");
|
|
826
|
+
try {
|
|
827
|
+
await $`gh issue comment ${item.number} --repo ${item.repo} --body ${text}`.quiet();
|
|
828
|
+
setNote("\u2713 Answered");
|
|
829
|
+
await decisionHooks().answered?.({
|
|
830
|
+
kind: "issue",
|
|
831
|
+
repo: item.repo,
|
|
832
|
+
number: item.number,
|
|
833
|
+
url: item.url,
|
|
834
|
+
labels: item.labels
|
|
835
|
+
});
|
|
836
|
+
} catch (e) {
|
|
837
|
+
setNote(`\u2717 ${e.message}`);
|
|
838
|
+
}
|
|
839
|
+
};
|
|
645
840
|
useInput(
|
|
646
841
|
(input, key) => {
|
|
842
|
+
if (item.needsYou) {
|
|
843
|
+
if (confirmClose) {
|
|
844
|
+
if (input === "y" || input === "Y") void doClose();
|
|
845
|
+
setConfirmClose(false);
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
if (input === "c") {
|
|
849
|
+
setAnswering(true);
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
if (input === "X") {
|
|
853
|
+
setConfirmClose(true);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
647
857
|
if (input === "o") $`open ${item.url}`.catch(() => {
|
|
648
858
|
});
|
|
649
859
|
if (input === "a") setAi(true);
|
|
650
860
|
if (input === "y") setCopy(true);
|
|
651
861
|
},
|
|
652
|
-
{ isActive: !replying && !ai && !copy }
|
|
862
|
+
{ isActive: !replying && !ai && !copy && !answering }
|
|
653
863
|
);
|
|
654
864
|
if (ai)
|
|
655
865
|
return /* @__PURE__ */ jsx(
|
|
@@ -677,14 +887,31 @@ var IssueView = ({
|
|
|
677
887
|
subtitle: item.title,
|
|
678
888
|
hints: [
|
|
679
889
|
["\u2191\u2193", "scroll"],
|
|
890
|
+
...item.needsYou ? [
|
|
891
|
+
["c", "answer"],
|
|
892
|
+
["X", "close"]
|
|
893
|
+
] : [],
|
|
680
894
|
["a", "AI"],
|
|
681
895
|
["y", "copy prompt"],
|
|
682
896
|
["o", "open in browser"],
|
|
683
897
|
["esc", "back"]
|
|
684
898
|
],
|
|
685
899
|
children: [
|
|
900
|
+
item.needsYou ? /* @__PURE__ */ jsx(DecisionBlock, { needsYou: item.needsYou }) : null,
|
|
901
|
+
confirmClose ? /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { color: colors.warning, children: [
|
|
902
|
+
` Close #${item.number}? `,
|
|
903
|
+
/* @__PURE__ */ jsx(Text, { color: colors.success, children: "y" }),
|
|
904
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " confirm \xB7 any other key cancels" })
|
|
905
|
+
] }) }) : note ? /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: colors.warning, bold: true, children: " " + note }) }) : null,
|
|
686
906
|
/* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Tabs, { active: "conversation", items: tabItems }) }),
|
|
687
|
-
/* @__PURE__ */ jsx(
|
|
907
|
+
answering ? /* @__PURE__ */ jsx(
|
|
908
|
+
TextInput,
|
|
909
|
+
{
|
|
910
|
+
placeholder: "Answer\u2026 (\u21B5 send \xB7 esc cancel)",
|
|
911
|
+
onSubmit: (value) => void submitAnswer(value),
|
|
912
|
+
onCancel: () => setAnswering(false)
|
|
913
|
+
}
|
|
914
|
+
) : /* @__PURE__ */ jsx(
|
|
688
915
|
CommentsPanel,
|
|
689
916
|
{
|
|
690
917
|
repo: item.repo,
|
|
@@ -713,19 +940,22 @@ var detailFor = (ctx) => ctx.kind === "pr" ? /* @__PURE__ */ jsx(
|
|
|
713
940
|
onTyping: ctx.onTyping
|
|
714
941
|
}
|
|
715
942
|
) : (
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
//
|
|
943
|
+
// Refresh/remove ride along for the close key (`X`): `onRemove` drops the
|
|
944
|
+
// row the way PrView's does, closing over the full item here because
|
|
945
|
+
// IssueView only holds its subset and reports removal without handing a
|
|
946
|
+
// row back. `onMerged` stays absent — an issue never merges.
|
|
719
947
|
/* @__PURE__ */ jsx(
|
|
720
948
|
IssueView,
|
|
721
949
|
{
|
|
722
950
|
item: ctx.item,
|
|
723
951
|
login: ctx.login,
|
|
724
952
|
onBack: ctx.onBack,
|
|
953
|
+
onRefresh: ctx.onRefresh,
|
|
954
|
+
onRemove: () => ctx.onRemove(ctx.item),
|
|
725
955
|
registerPeel: ctx.registerPeel,
|
|
726
956
|
onTyping: ctx.onTyping
|
|
727
957
|
}
|
|
728
958
|
)
|
|
729
959
|
);
|
|
730
960
|
|
|
731
|
-
export { IssueView, PrView, checkDrillFor, defineCockpit, detailFor, hasPatterns, parseArgs, registerCheckDrills };
|
|
961
|
+
export { DecisionBlock, IssueView, PrView, checkDrillFor, decisionHooks, defineCockpit, detailFor, hasPatterns, parseArgs, registerCheckDrills, registerDecisionHooks };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/gh-cockpit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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.
|
|
57
|
-
"@kud/gh-ink": "0.
|
|
56
|
+
"@kud/gh": "0.19.0",
|
|
57
|
+
"@kud/gh-ink": "0.64.0",
|
|
58
58
|
"@kud/ink-ui": "0.31.0",
|
|
59
59
|
"zx": "8.8.5",
|
|
60
|
-
"@kud/gh-workflow": "0.
|
|
60
|
+
"@kud/gh-workflow": "0.14.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/node": "22.20.1",
|