auq-mcp-server 2.7.1 → 2.7.2

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.
Files changed (33) hide show
  1. package/dist/package.json +3 -3
  2. package/dist/src/i18n/types.js +0 -1
  3. package/dist/src/tui/shared/session-events.js +0 -1
  4. package/dist/src/tui/shared/themes/types.js +0 -1
  5. package/dist/src/tui/shared/types.js +0 -1
  6. package/dist/src/tui-opentui/ConfigContext.js +10 -0
  7. package/dist/src/tui-opentui/ThemeProvider.js +73 -0
  8. package/dist/src/tui-opentui/app.js +536 -0
  9. package/dist/src/tui-opentui/components/AnimatedGradient.js +56 -0
  10. package/dist/src/tui-opentui/components/ConfirmationDialog.js +89 -0
  11. package/dist/src/tui-opentui/components/CustomInput.js +25 -0
  12. package/dist/src/tui-opentui/components/ErrorBoundary.js +26 -0
  13. package/dist/src/tui-opentui/components/Footer.js +92 -0
  14. package/dist/src/tui-opentui/components/Header.js +46 -0
  15. package/dist/src/tui-opentui/components/MarkdownPrompt.js +13 -0
  16. package/dist/src/tui-opentui/components/OptionsList.js +258 -0
  17. package/dist/src/tui-opentui/components/QuestionDisplay.js +23 -0
  18. package/dist/src/tui-opentui/components/ReviewScreen.js +81 -0
  19. package/dist/src/tui-opentui/components/SessionDots.js +86 -0
  20. package/dist/src/tui-opentui/components/SessionPicker.js +162 -0
  21. package/dist/src/tui-opentui/components/SingleLineTextInput.js +9 -0
  22. package/dist/src/tui-opentui/components/StepperView.js +493 -0
  23. package/dist/src/tui-opentui/components/TabBar.js +79 -0
  24. package/dist/src/tui-opentui/components/ThemeIndicator.js +35 -0
  25. package/dist/src/tui-opentui/components/Toast.js +44 -0
  26. package/dist/src/tui-opentui/components/UpdateBadge.js +24 -0
  27. package/dist/src/tui-opentui/components/UpdateOverlay.js +162 -0
  28. package/dist/src/tui-opentui/components/WaitingScreen.js +44 -0
  29. package/dist/src/tui-opentui/hooks/useSessionWatcher.js +69 -0
  30. package/dist/src/tui-opentui/hooks/useTerminalDimensions.js +8 -0
  31. package/dist/src/tui-opentui/utils/syntaxStyle.js +64 -0
  32. package/dist/src/update/types.js +0 -1
  33. package/package.json +3 -3
@@ -0,0 +1,81 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { useKeyboard } from "@opentui/react";
3
+ import { t } from "../../i18n/index.js";
4
+ import { useTheme } from "../ThemeProvider.js";
5
+ import { Footer } from "./Footer.js";
6
+ import { MarkdownPrompt } from "./MarkdownPrompt.js";
7
+ import { KEYS } from "../../tui/constants/keybindings.js";
8
+ /**
9
+ * ReviewScreen displays a summary of all answers for confirmation.
10
+ * User can press Enter to confirm and submit, or 'n' to go back and edit.
11
+ */
12
+ export const ReviewScreen = ({ answers, elapsedLabel, onConfirm, onGoBack, questions, elaborateMarks, isSubmitting = false, }) => {
13
+ const { theme } = useTheme();
14
+ useKeyboard((key) => {
15
+ // Disable input while submitting
16
+ if (isSubmitting)
17
+ return;
18
+ if (key.name === "return") {
19
+ // Convert answers to UserAnswer format
20
+ const userAnswers = [];
21
+ answers.forEach((answer, questionIndex) => {
22
+ if (answer.selectedOption ||
23
+ answer.selectedOptions ||
24
+ answer.customText) {
25
+ userAnswers.push({
26
+ customText: answer.customText,
27
+ questionIndex,
28
+ selectedOption: answer.selectedOption,
29
+ selectedOptions: answer.selectedOptions,
30
+ timestamp: new Date().toISOString(),
31
+ });
32
+ }
33
+ });
34
+ onConfirm(userAnswers);
35
+ }
36
+ if (key.name && KEYS.GO_BACK.test(key.name)) {
37
+ onGoBack();
38
+ }
39
+ });
40
+ return (_jsxs("box", { style: { flexDirection: "column" }, children: [_jsx("box", { style: {
41
+ flexDirection: "column",
42
+ borderColor: theme.components.review.confirmBorder,
43
+ borderStyle: "rounded",
44
+ marginBottom: 1,
45
+ paddingX: 1,
46
+ }, children: _jsxs("box", { style: { flexDirection: "row", justifyContent: "space-between", width: "100%" }, children: [_jsx("text", { style: { bold: true }, children: t("review.title") }), _jsx("text", { fg: theme.colors.textDim, children: elapsedLabel })] }) }), _jsx("box", { style: { flexDirection: "column", marginBottom: 1 }, children: questions.map((question, index) => {
47
+ const answer = answers.get(index);
48
+ const questionId = `Q${index}`;
49
+ const questionTitle = question.title || questionId;
50
+ return (_jsxs("box", { style: { flexDirection: "column", marginBottom: 1 }, children: [_jsxs("box", { style: { flexDirection: "column" }, children: [_jsxs("box", { children: [_jsx("text", { fg: theme.components.review.questionId, children: `[${questionId}] ` }), _jsx("text", { style: { bold: true }, children: `${questionTitle}. ` })] }), _jsx(MarkdownPrompt, { text: question.prompt })] }), _jsxs("box", { style: { flexDirection: "column", marginLeft: 2, marginTop: 1 }, children: [answer?.selectedOptions &&
51
+ answer.selectedOptions.length > 0 &&
52
+ answer.selectedOptions.map((option, idx) => (_jsx("text", { style: { fg: theme.components.review.selectedOption }, children: `> ${option}` }, idx))), answer?.selectedOption && (_jsx("text", { style: { fg: theme.components.review.selectedOption }, children: `> ${answer.selectedOption}` })), answer?.customText && (_jsx("box", { style: { flexDirection: "column" }, children: answer.customText
53
+ .replace(/\r\n?/g, "\n")
54
+ .split("\n")
55
+ .map((line, lineIndex, lines) => {
56
+ const isFirstLine = lineIndex === 0;
57
+ const isLastLine = lineIndex === lines.length - 1;
58
+ return (_jsx("text", { style: { fg: theme.components.review.customAnswer }, children: `${isFirstLine ? ` ${t("review.customAnswer")}: "` : " "}${line}${isLastLine ? '"' : ""}` }, lineIndex));
59
+ }) })), !answer?.selectedOption &&
60
+ !answer?.selectedOptions &&
61
+ !answer?.customText && (_jsxs("box", { style: { flexDirection: "row" }, children: [_jsx("text", { style: { fg: theme.colors.unansweredHighlight }, children: ` ${t("review.unanswered")}` }), elaborateMarks?.has(index) && (() => {
62
+ const elaborateText = elaborateMarks.get(index);
63
+ const elaborationStr = elaborateText
64
+ ? `, ${t("review.markedForElaboration")}: "${elaborateText.length > 50 ? elaborateText.slice(0, 50) + "..." : elaborateText}"`
65
+ : `, ${t("review.markedForElaboration")}`;
66
+ return (_jsx("text", { style: { fg: theme.colors.warning }, children: elaborationStr }));
67
+ })()] })), (answer?.selectedOption ||
68
+ answer?.selectedOptions ||
69
+ answer?.customText) &&
70
+ elaborateMarks?.has(index) && (_jsx("box", { style: { marginTop: 1 }, children: _jsx("text", { style: { fg: theme.colors.warning }, children: (() => {
71
+ const elaborateText = elaborateMarks.get(index);
72
+ if (elaborateText) {
73
+ const displayText = elaborateText.length > 50
74
+ ? elaborateText.slice(0, 50) + "..."
75
+ : elaborateText;
76
+ return `${t("review.markedForElaboration")}: "${displayText}"`;
77
+ }
78
+ return t("review.markedForElaboration");
79
+ })() }) }))] })] }, index));
80
+ }) }), _jsx(Footer, { focusContext: "option", multiSelect: false, isReviewScreen: true, isSubmitting: isSubmitting })] }));
81
+ };
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { useTheme } from "../ThemeProvider.js";
3
+ /* ------------------------------------------------------------------ */
4
+ /* Helpers */
5
+ /* ------------------------------------------------------------------ */
6
+ /** Check whether at least one answer in the map has meaningful content. */
7
+ function hasAnswers(answers) {
8
+ if (!answers || answers.size === 0)
9
+ return false;
10
+ for (const ans of answers.values()) {
11
+ if (ans.selectedOption || ans.customText)
12
+ return true;
13
+ if (ans.selectedOptions && ans.selectedOptions.length > 0)
14
+ return true;
15
+ }
16
+ return false;
17
+ }
18
+ /* ------------------------------------------------------------------ */
19
+ /* Component */
20
+ /* ------------------------------------------------------------------ */
21
+ /**
22
+ * SessionDots — a compact row of numbered dots rendered below the footer.
23
+ *
24
+ * Visual language:
25
+ * ● 1 ○ 2 ✕ 3 ○ 4
26
+ *
27
+ * • Active session: filled ● + bold number in theme primary
28
+ * • Abandoned: red ✕ + \"(AI disconnected)\" when active
29
+ * • Stale: yellow ○ + \"(stale)\" when active
30
+ * • Has answers: green (theme.success)
31
+ * • Touched/no answers: yellow (theme.warning)
32
+ * • Untouched: dim (theme.textDim)
33
+ */
34
+ export const SessionDots = ({ sessions, activeIndex, sessionUIStates, onSelectIndex, }) => {
35
+ const { theme } = useTheme();
36
+ // Don't render when fewer than 2 sessions
37
+ if (sessions.length < 2)
38
+ return null;
39
+ return (_jsx("box", { style: { flexDirection: "row", justifyContent: "center", paddingLeft: 2, paddingRight: 2 }, children: sessions.map((session, idx) => {
40
+ const isActive = idx === activeIndex;
41
+ const uiState = sessionUIStates[session.sessionId];
42
+ const isStale = session.isStale ?? false;
43
+ const isAbandoned = session.isAbandoned ?? false;
44
+ // Determine the progress color for this session's dot
45
+ // Abandoned/stale take priority over normal state colors
46
+ let dotColor;
47
+ if (isAbandoned) {
48
+ dotColor =
49
+ theme.components.sessionDots
50
+ .abandoned ?? theme.colors.error;
51
+ }
52
+ else if (isStale) {
53
+ dotColor =
54
+ theme.components.sessionDots
55
+ .stale ?? theme.colors.warning;
56
+ }
57
+ else if (isActive) {
58
+ dotColor = theme.components.sessionDots.active;
59
+ }
60
+ else if (uiState && hasAnswers(uiState.answers)) {
61
+ dotColor = theme.components.sessionDots.answered;
62
+ }
63
+ else if (uiState) {
64
+ dotColor = theme.components.sessionDots.inProgress;
65
+ }
66
+ else {
67
+ dotColor = theme.components.sessionDots.untouched;
68
+ }
69
+ // Abandoned inactive sessions use ✕ to signal a problem
70
+ const dot = isAbandoned && !isActive
71
+ ? "✕"
72
+ : isActive
73
+ ? "●"
74
+ : "○";
75
+ const numberColor = isActive
76
+ ? theme.components.sessionDots.activeNumber
77
+ : theme.components.sessionDots.number;
78
+ // Status label shown next to active abandoned/stale sessions
79
+ const statusLabel = isActive && isAbandoned
80
+ ? "(AI disconnected)"
81
+ : isActive && isStale
82
+ ? "(stale)"
83
+ : null;
84
+ return (_jsxs("box", { style: { flexDirection: "row", paddingRight: idx < sessions.length - 1 ? 1 : 0 }, onMouseDown: () => onSelectIndex?.(idx), children: [_jsx("text", { style: { fg: dotColor, bold: isActive }, children: dot }), _jsx("text", { style: { fg: numberColor, bold: isActive }, children: ` ${idx + 1}` }), statusLabel ? (_jsx("text", { style: { fg: dotColor, dim: true }, children: ` ${statusLabel}` })) : null] }, session.sessionId));
85
+ }) }));
86
+ };
@@ -0,0 +1,162 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
+ import { TextAttributes } from "@opentui/core";
3
+ import { useEffect, useState } from "react";
4
+ import { useKeyboard } from "@opentui/react";
5
+ import { useTheme } from "../ThemeProvider.js";
6
+ import { formatRelativeTime } from "../../tui/shared/utils/relativeTime.js";
7
+ import { KEYS } from "../../tui/constants/keybindings.js";
8
+ import { useTerminalDimensions } from "../hooks/useTerminalDimensions.js";
9
+ /* ------------------------------------------------------------------ */
10
+ /* Helpers */
11
+ /* ------------------------------------------------------------------ */
12
+ function countAnswered(answers) {
13
+ if (!answers)
14
+ return 0;
15
+ let count = 0;
16
+ for (const ans of answers.values()) {
17
+ if (ans.selectedOption || ans.customText) {
18
+ count++;
19
+ continue;
20
+ }
21
+ if (ans.selectedOptions && ans.selectedOptions.length > 0)
22
+ count++;
23
+ }
24
+ return count;
25
+ }
26
+ function truncate(text, max) {
27
+ if (text.length <= max)
28
+ return text;
29
+ return text.slice(0, max - 1) + "\u2026";
30
+ }
31
+ /* ------------------------------------------------------------------ */
32
+ /* Component */
33
+ /* ------------------------------------------------------------------ */
34
+ /**
35
+ * SessionPicker \u2014 a modal overlay listing all queued sessions.
36
+ *
37
+ * Opened via Ctrl+S. Each row shows:
38
+ * {index}. {title} \u2014 {workDir} [{answered}/{total}] {age}
39
+ *
40
+ * Navigation:
41
+ * \u2191 / \u2193 : move highlight
42
+ * Enter : select highlighted session
43
+ * Esc : close without switching
44
+ */
45
+ export const SessionPicker = ({ isOpen, sessions, activeIndex, sessionUIStates, onSelectIndex, onClose, }) => {
46
+ const { theme } = useTheme();
47
+ const { width: termWidth, height: termHeight } = useTerminalDimensions();
48
+ // \u2500\u2500 Highlight state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
49
+ const [highlightIndex, setHighlightIndex] = useState(activeIndex);
50
+ // Reset highlight to active index each time the picker opens
51
+ useEffect(() => {
52
+ if (isOpen) {
53
+ setHighlightIndex(activeIndex);
54
+ }
55
+ }, [isOpen, activeIndex]);
56
+ // \u2500\u2500 Keyboard handling (only active when overlay is open) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
57
+ useKeyboard((key) => {
58
+ if (!isOpen)
59
+ return;
60
+ if (key.name === "up") {
61
+ setHighlightIndex((prev) => Math.max(0, prev - 1));
62
+ }
63
+ else if (key.name === "down") {
64
+ setHighlightIndex((prev) => Math.min(sessions.length - 1, prev + 1));
65
+ }
66
+ else if (key.name === "return") {
67
+ onSelectIndex(highlightIndex);
68
+ onClose();
69
+ }
70
+ else if (key.name === "escape") {
71
+ onClose();
72
+ }
73
+ else {
74
+ // Direct number jump (1-9)
75
+ const num = parseInt(key.sequence || key.name, 10);
76
+ if (num >= KEYS.SESSION_JUMP_MIN &&
77
+ num <= Math.min(KEYS.SESSION_JUMP_MAX, sessions.length)) {
78
+ onSelectIndex(num - 1);
79
+ onClose();
80
+ }
81
+ }
82
+ });
83
+ // \u2500\u2500 Render nothing when closed \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
84
+ if (!isOpen)
85
+ return null;
86
+ // \u2500\u2500 Scrolling logic \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
87
+ const chromeLines = 7;
88
+ const maxVisibleRows = Math.max(1, termHeight - chromeLines);
89
+ const needsScroll = sessions.length > maxVisibleRows;
90
+ let scrollOffset = 0;
91
+ if (needsScroll) {
92
+ scrollOffset = Math.max(0, Math.min(highlightIndex - Math.floor(maxVisibleRows / 2), sessions.length - maxVisibleRows));
93
+ }
94
+ const visibleSessions = sessions.slice(scrollOffset, scrollOffset + maxVisibleRows);
95
+ // \u2500\u2500 Derive max label widths from terminal size \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
96
+ const innerWidth = Math.max(30, termWidth - 6);
97
+ const fixedOverhead = 18;
98
+ const dynamicWidth = Math.max(10, innerWidth - fixedOverhead);
99
+ const titleMax = Math.max(6, Math.floor(dynamicWidth * 0.55));
100
+ const dirMax = Math.max(4, dynamicWidth - titleMax);
101
+ return (_jsx("box", { style: {
102
+ flexDirection: "column",
103
+ alignItems: "center",
104
+ justifyContent: "center",
105
+ flexGrow: 1,
106
+ }, children: _jsxs("box", { style: {
107
+ flexDirection: "column",
108
+ border: true,
109
+ borderStyle: "rounded",
110
+ borderColor: theme.colors.surface,
111
+ paddingX: 2,
112
+ paddingY: 1,
113
+ width: Math.min(innerWidth + 6, termWidth),
114
+ }, children: [_jsxs("box", { style: { flexDirection: "row", justifyContent: "center", marginBottom: 1 }, children: [_jsx("text", { style: { attributes: TextAttributes.BOLD, fg: theme.components.sessionPicker.title }, children: "Switch Session" }), _jsx("text", { style: { fg: theme.components.sessionPicker.rowDim }, children: ` (${sessions.length} queued)` })] }), needsScroll && scrollOffset > 0 && (_jsx("box", { style: { justifyContent: "center" }, children: _jsx("text", { style: { fg: theme.components.sessionPicker.rowDim }, children: "\\u25b2 more" }) })), _jsx("box", { onMouseScroll: (event) => {
115
+ if (event.scroll?.direction === "down") {
116
+ setHighlightIndex((prev) => Math.min(sessions.length - 1, prev + 1));
117
+ }
118
+ else if (event.scroll?.direction === "up") {
119
+ setHighlightIndex((prev) => Math.max(0, prev - 1));
120
+ }
121
+ }, children: visibleSessions.map((session, visibleIdx) => {
122
+ const realIdx = scrollOffset + visibleIdx;
123
+ const isHighlighted = realIdx === highlightIndex;
124
+ const isActive = realIdx === activeIndex;
125
+ const uiState = sessionUIStates[session.sessionId];
126
+ const questions = session.sessionRequest.questions;
127
+ const title = truncate(questions[0]?.title || "Untitled", titleMax);
128
+ const dir = truncate(session.sessionRequest.workingDirectory || "unknown", dirMax);
129
+ const total = questions.length;
130
+ const answered = countAnswered(uiState?.answers);
131
+ const age = formatRelativeTime(session.timestamp);
132
+ // Row colors
133
+ const rowBg = isHighlighted
134
+ ? theme.components.sessionPicker.highlightBg
135
+ : undefined;
136
+ const textColor = isHighlighted
137
+ ? theme.components.sessionPicker.highlightFg
138
+ : isActive
139
+ ? theme.components.sessionPicker.activeMark
140
+ : theme.components.sessionPicker.rowText;
141
+ // Progress color
142
+ const progressColor = answered === total && total > 0
143
+ ? theme.components.sessionPicker.progress
144
+ : answered > 0
145
+ ? theme.components.sessionPicker.progress
146
+ : theme.components.sessionPicker.rowDim;
147
+ const isStaleOrAbandoned = session.isStale || session.isAbandoned;
148
+ return (_jsxs("box", { style: { flexDirection: "column", backgroundColor: isHighlighted ? theme.colors.surfaceAlt : undefined }, onMouseDown: () => { onSelectIndex(realIdx); onClose(); }, children: [_jsxs("box", { style: { flexDirection: "row" }, children: [isStaleOrAbandoned && (_jsx("text", { style: {
149
+ fg: theme.components.sessionPicker.staleIcon,
150
+ }, children: "⚠ " })), _jsx("text", { style: {
151
+ attributes: isHighlighted ? TextAttributes.BOLD : TextAttributes.NONE,
152
+ fg: isStaleOrAbandoned
153
+ ? theme.components.sessionPicker.staleText
154
+ : textColor,
155
+ }, children: `${isActive ? "►" : " "} ${realIdx + 1}. ${title}` }), _jsx("text", { style: { fg: theme.components.sessionPicker.rowDim }, children: ` — ${dir}` }), _jsx("text", { style: { fg: progressColor }, children: ` [${answered}/${total}]` }), _jsx("text", { style: {
156
+ fg: isStaleOrAbandoned
157
+ ? theme.components.sessionPicker.staleAge
158
+ : theme.components.sessionPicker.rowDim,
159
+ attributes: !isStaleOrAbandoned ? TextAttributes.DIM : TextAttributes.NONE,
160
+ }, children: age })] }), session.isStale && !session.isAbandoned && (_jsx("box", { style: { marginLeft: 4 }, children: _jsx("text", { style: { fg: theme.components.sessionPicker.staleSubtitle, attributes: TextAttributes.DIM }, children: "may be orphaned" }) })), session.isAbandoned && (_jsx("box", { style: { marginLeft: 4 }, children: _jsx("text", { style: { fg: theme.components.sessionPicker.staleSubtitle, attributes: TextAttributes.BOLD }, children: "session abandoned" }) }))] }, session.sessionId));
161
+ }) }), needsScroll && scrollOffset + maxVisibleRows < sessions.length && (_jsx("box", { style: { justifyContent: "center" }, children: _jsx("text", { style: { fg: theme.components.sessionPicker.rowDim }, children: "\\u25bc more" }) })), _jsx("box", { style: { justifyContent: "center", marginTop: 1 }, children: _jsx("text", { style: { fg: theme.components.sessionPicker.rowDim, attributes: TextAttributes.DIM }, children: "\u2191\u2193 navigate \u00B7 Enter select \u00B7 Esc close \u00B7 1-9 jump \u00B7 scroll" }) })] }) }));
162
+ };
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "@opentui/react/jsx-runtime";
2
+ import { t } from "../../i18n/index.js";
3
+ /**
4
+ * SingleLineTextInput wraps OpenTUI's native <input> for single-line text entry.
5
+ * Used for rejection reasons and other short text fields.
6
+ */
7
+ export const SingleLineTextInput = ({ isFocused = true, onChange, onSubmit, placeholder = t("input.singleLinePlaceholder"), value, }) => {
8
+ return (_jsx("input", { placeholder: placeholder, value: value, focused: isFocused, onInput: (val) => onChange(val), onSubmit: () => onSubmit?.() }));
9
+ };