auq-mcp-server 2.0.1 → 2.1.1

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/README.md CHANGED
@@ -62,6 +62,8 @@ First, install the **AUQ CLI**:
62
62
  bun add -g auq-mcp-server
63
63
  ```
64
64
 
65
+ This serves as the 'answering interface' for you, the human.
66
+
65
67
  _**Note:** Other package managers (npm, pnpm..) also work but not recommended._
66
68
 
67
69
  <details><summary>Local (Project-specific) Installation</summary>
@@ -210,6 +212,17 @@ Whenever you need clarification on what you are working on, never guess, and cal
210
212
 
211
213
  When the AI asks questions, you'll see them appear in the AUQ TUI. Answer them **at your convenience**.
212
214
 
215
+ ### Markdown rendering in question prompts
216
+
217
+ Question prompts now support **Markdown formatting** in the `prompt` text.
218
+
219
+ - Supported: **bold**, _italic_, ~~strikethrough~~, `inline code`, links, and fenced code blocks (with syntax highlighting)
220
+ - Links render as `text (url)` for broad terminal compatibility
221
+ - Code blocks use theme-aware colors (background/text/border)
222
+ - Always enabled (no configuration needed)
223
+ - Plain text prompts pass through unchanged
224
+ - Graceful fallback: if Markdown parsing fails, the raw text is shown
225
+
213
226
  > _Note: AUQ is an unopinionated tool and doesn't include prompts on **HOW** AI should leverage it. It is expected that you do your own prompt engineering to make the most out of it in your own workflows._
214
227
  > _I personally enjoy prompting it to ask at least 30 questions repeatedly before action!_
215
228
 
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auq-mcp-server",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "auq": "dist/bin/auq.js"
@@ -53,11 +53,14 @@
53
53
  "@modelcontextprotocol/sdk": "1.17.2",
54
54
  "@types/uuid": "^10.0.0",
55
55
  "chalk": "^5.6.2",
56
+ "cli-highlight": "^2.1.11",
56
57
  "fastmcp": "^3.23.0",
57
58
  "gradient-string": "^3.0.0",
58
59
  "ink": "^6.4.0",
59
60
  "ink-gradient": "^3.0.0",
61
+ "ink-markdown-es": "^1.1.0",
60
62
  "ink-text-input": "^6.0.0",
63
+ "marked": "^17.0.3",
61
64
  "node-notifier": "^10.0.1",
62
65
  "react": "^19.2.0",
63
66
  "string-width": "^8.1.1",
@@ -190,9 +193,7 @@
190
193
  "package.json",
191
194
  "CHANGELOG.md"
192
195
  ],
193
- "message": "chore(release): ${nextRelease.version} [skip ci]",
194
- "authorName": "Paul Park",
195
- "authorEmail": "2000mageia@gmail.com"
196
+ "message": "chore(release): ${nextRelease.version} [skip ci]"
196
197
  }
197
198
  ]
198
199
  ]
@@ -208,6 +209,7 @@
208
209
  "conventional-changelog-conventionalcommits": "^9.1.0",
209
210
  "eslint-config-prettier": "^10.1.3",
210
211
  "eslint-plugin-perfectionist": "^4.12.3",
212
+ "ink-testing-library": "^4.0.0",
211
213
  "prettier": "^3.5.3",
212
214
  "semantic-release": "^24.2.3",
213
215
  "typescript": "^5.8.3",
@@ -0,0 +1,59 @@
1
+ import React from "react";
2
+ import { Text } from "ink";
3
+ import Markdown from "ink-markdown-es";
4
+ import { lexer } from "marked";
5
+ import { useTheme } from "../ThemeContext.js";
6
+ /**
7
+ * MarkdownPrompt renders markdown text with basic formatting support.
8
+ * Falls back to plain text if markdown parsing fails.
9
+ *
10
+ * Supports:
11
+ * - **bold** text
12
+ * - *italic* text
13
+ * - `inline code`
14
+ * - ~~strikethrough~~
15
+ * - [links](url) rendered as "text (url)"
16
+ * - Fenced code blocks with syntax highlighting
17
+ */
18
+ export const MarkdownPrompt = ({ text }) => {
19
+ const { theme } = useTheme();
20
+ // Fallback to plain text if text is empty
21
+ if (!text || text.trim().length === 0) {
22
+ return React.createElement(Text, null, text);
23
+ }
24
+ try {
25
+ const tokens = lexer(text);
26
+ const hasBlockElements = tokens.some((token) => ["code", "list", "blockquote", "heading", "hr", "table"].includes(token.type));
27
+ const styles = {
28
+ code: {
29
+ backgroundColor: theme.components.markdown.codeBlockBg,
30
+ color: theme.components.markdown.codeBlockText,
31
+ borderColor: theme.components.markdown.codeBlockBorder,
32
+ borderStyle: "round",
33
+ paddingX: 1,
34
+ },
35
+ codespan: {
36
+ backgroundColor: theme.components.markdown.codeBlockBg,
37
+ color: theme.components.markdown.codeBlockText,
38
+ },
39
+ };
40
+ const baseRenderers = {
41
+ link: (linkText, href) => (React.createElement(Text, null,
42
+ linkText,
43
+ " (",
44
+ href,
45
+ ")")),
46
+ };
47
+ if (!hasBlockElements) {
48
+ return (React.createElement(Markdown, { styles: styles, renderers: {
49
+ ...baseRenderers,
50
+ paragraph: (content) => React.createElement(Text, null, content),
51
+ }, highlight: true }, text));
52
+ }
53
+ return (React.createElement(Markdown, { styles: styles, renderers: baseRenderers, highlight: true }, text));
54
+ }
55
+ catch {
56
+ // Silently fall back to plain text on any error
57
+ return React.createElement(Text, null, text);
58
+ }
59
+ };
@@ -4,7 +4,7 @@ import { t } from "../../i18n/index.js";
4
4
  import { useConfig } from "../ConfigContext.js";
5
5
  import { useTheme } from "../ThemeContext.js";
6
6
  import { isRecommendedOption } from "../utils/recommended.js";
7
- import { fitToVisualWidth } from "../utils/visualWidth.js";
7
+ import { fitToVisualWidth, getVisualWidth, padToVisualWidth, } from "../utils/visualWidth.js";
8
8
  import { MultiLineTextInput } from "./MultiLineTextInput.js";
9
9
  // isRecommendedOption is imported from ../utils/recommended.js
10
10
  /**
@@ -23,6 +23,35 @@ export const OptionsList = ({ isFocused, onSelect, options, selectedOption, show
23
23
  const fitRow = (text) => {
24
24
  return fitToVisualWidth(text, rowWidth);
25
25
  };
26
+ // Wrap text to multiple lines respecting visual width and explicit newlines
27
+ const wrapText = (text, width) => {
28
+ // First split on explicit newlines, then wrap each segment by visual width
29
+ const segments = text.split("\n");
30
+ const lines = [];
31
+ for (const segment of segments) {
32
+ if (getVisualWidth(segment) <= width) {
33
+ lines.push(segment);
34
+ continue;
35
+ }
36
+ let currentLine = "";
37
+ let currentWidth = 0;
38
+ for (const char of segment) {
39
+ const charWidth = getVisualWidth(char);
40
+ if (currentWidth + charWidth > width && currentLine.length > 0) {
41
+ lines.push(currentLine);
42
+ currentLine = char;
43
+ currentWidth = charWidth;
44
+ }
45
+ else {
46
+ currentLine += char;
47
+ currentWidth += charWidth;
48
+ }
49
+ }
50
+ if (currentLine)
51
+ lines.push(currentLine);
52
+ }
53
+ return lines;
54
+ };
26
55
  // Calculate max index: include custom input and elaborate options if enabled
27
56
  // Options: [0..n-1] = regular options, [n] = custom input, [n+1] = elaborate
28
57
  const customInputIndex = options.length;
@@ -141,11 +170,41 @@ export const OptionsList = ({ isFocused, onSelect, options, selectedOption, show
141
170
  : `${isFocusedOption ? ">" : " "} ${option.label}${isSelected ? " ✓" : ""}${starSuffix}`;
142
171
  return (React.createElement(Box, { key: index, flexDirection: "column" },
143
172
  React.createElement(Text, { backgroundColor: rowBg, bold: isFocusedOption || isSelected, color: rowColor }, fitRow(mainLine)),
144
- option.description && (React.createElement(Text, { backgroundColor: isFocusedOption
145
- ? theme.components.options.focusedBg
146
- : isSelected
147
- ? theme.components.options.selectedBg
148
- : undefined, color: theme.components.options.description, dimColor: !isFocusedOption && !isSelected }, fitRow(` ${option.description}`)))));
173
+ option.description &&
174
+ (() => {
175
+ const descText = ` ${option.description}`;
176
+ const descBg = isFocusedOption
177
+ ? theme.components.options.focusedBg
178
+ : isSelected
179
+ ? theme.components.options.selectedBg
180
+ : undefined;
181
+ const wouldWrap = descText.includes("\n") ||
182
+ getVisualWidth(descText) > rowWidth;
183
+ if (isFocusedOption && wouldWrap) {
184
+ // Focused + multi-line: show full description wrapped across lines
185
+ const wrappedLines = wrapText(descText, rowWidth);
186
+ return (React.createElement(Box, { flexDirection: "column" }, wrappedLines.map((line, lineIdx) => (React.createElement(Text, { key: lineIdx, backgroundColor: descBg, color: theme.components.options.description, dimColor: false }, padToVisualWidth(line, rowWidth))))));
187
+ }
188
+ else if (!isFocusedOption && wouldWrap) {
189
+ // Not focused + would wrap: truncate to 1 line with "..."
190
+ const maxWidth = rowWidth - 3; // Reserve 3 chars for "..."
191
+ let result = "";
192
+ let width = 0;
193
+ for (const char of descText) {
194
+ const charWidth = getVisualWidth(char);
195
+ if (width + charWidth > maxWidth)
196
+ break;
197
+ result += char;
198
+ width += charWidth;
199
+ }
200
+ const finalText = `${result}...`;
201
+ return (React.createElement(Text, { backgroundColor: descBg, color: theme.components.options.description, dimColor: true }, padToVisualWidth(finalText, rowWidth)));
202
+ }
203
+ else {
204
+ // Fits in 1 line (focused or not): show normally with padding
205
+ return (React.createElement(Text, { backgroundColor: descBg, color: theme.components.options.description, dimColor: !isFocusedOption && !isSelected }, fitRow(descText)));
206
+ }
207
+ })()));
149
208
  }),
150
209
  showCustomInput && (React.createElement(Box, { marginTop: 0 },
151
210
  React.createElement(Box, { flexDirection: "column" },
@@ -3,6 +3,7 @@ import React, { useState } from "react";
3
3
  import { t } from "../../i18n/index.js";
4
4
  import { useTheme } from "../ThemeContext.js";
5
5
  import { Footer } from "./Footer.js";
6
+ import { MarkdownPrompt } from "./MarkdownPrompt.js";
6
7
  import { OptionsList } from "./OptionsList.js";
7
8
  import { TabBar } from "./TabBar.js";
8
9
  /**
@@ -35,19 +36,17 @@ export const QuestionDisplay = ({ currentQuestion, currentQuestionIndex, customA
35
36
  React.createElement(Text, { color: theme.components.directory.label, dimColor: true }, "\uD83D\uDCC1"),
36
37
  React.createElement(Text, { color: theme.components.directory.path }, ` ${workingDirectory}`))),
37
38
  React.createElement(TabBar, { currentIndex: currentQuestionIndex, questions: questions, answers: answers, elaborateMarks: elaborateMarks }),
38
- React.createElement(Box, null,
39
- React.createElement(Text, null,
40
- React.createElement(Text, { bold: true },
41
- " ",
42
- currentQuestion.prompt,
43
- " "),
39
+ React.createElement(Box, { flexDirection: "column" },
40
+ React.createElement(Box, null,
41
+ React.createElement(MarkdownPrompt, { text: currentQuestion.prompt }),
44
42
  React.createElement(Text, { color: theme.components.questionDisplay.typeIndicator },
43
+ " ",
45
44
  "[",
46
45
  multiSelect
47
46
  ? t("question.multipleChoice")
48
47
  : t("question.singleChoice"),
49
- "]"),
50
- React.createElement(Text, null, " "),
48
+ "]")),
49
+ React.createElement(Box, null,
51
50
  React.createElement(Text, { color: theme.components.questionDisplay.elapsed, dimColor: true }, elapsedLabel))),
52
51
  React.createElement(OptionsList, { customValue: customAnswer, isFocused: true, onAdvance: onAdvanceToNext, onCustomChange: handleCustomAnswerChange, onSelect: handleSelectOption, options: currentQuestion.options, selectedOption: selectedOption, showCustomInput: true, onToggle: onToggleOption, multiSelect: multiSelect, selectedOptions: answers.get(currentQuestionIndex)?.selectedOptions, onFocusContextChange: handleFocusContextChange, onRecommendedDetected: onRecommendedDetected, questionKey: currentQuestionIndex, isElaborateMarked: elaborateMarks?.has(currentQuestionIndex), onElaborateSelect: onElaborateSelect, elaborateText: elaborateText, onElaborateTextChange: onElaborateTextChange }),
53
52
  React.createElement(Footer, { focusContext: focusContext, multiSelect: multiSelect ?? false, customInputValue: customAnswer, hasRecommendedOptions: hasRecommendedOptions, hasAnyRecommendedInSession: hasAnyRecommendedInSession })));
@@ -3,6 +3,7 @@ import React from "react";
3
3
  import { t } from "../../i18n/index.js";
4
4
  import { useTheme } from "../ThemeContext.js";
5
5
  import { Footer } from "./Footer.js";
6
+ import { MarkdownPrompt } from "./MarkdownPrompt.js";
6
7
  /**
7
8
  * ReviewScreen displays a summary of all answers for confirmation
8
9
  * User can press Enter to confirm and submit, or 'n' to go back and edit
@@ -45,16 +46,18 @@ export const ReviewScreen = ({ answers, elapsedLabel, onConfirm, onGoBack, quest
45
46
  const questionId = `Q${index}`;
46
47
  const questionTitle = question.title || questionId;
47
48
  return (React.createElement(Box, { flexDirection: "column", key: index, marginBottom: 1 },
48
- React.createElement(Box, null,
49
- React.createElement(Text, { color: theme.components.review.questionId },
50
- "[",
51
- questionId,
52
- "]",
53
- " "),
54
- React.createElement(Text, { bold: true },
55
- questionTitle,
56
- ". ",
57
- question.prompt)),
49
+ React.createElement(Box, { flexDirection: "column" },
50
+ React.createElement(Box, null,
51
+ React.createElement(Text, { color: theme.components.review.questionId },
52
+ "[",
53
+ questionId,
54
+ "]",
55
+ " "),
56
+ React.createElement(Text, { bold: true },
57
+ questionTitle,
58
+ ".",
59
+ " ")),
60
+ React.createElement(MarkdownPrompt, { text: question.prompt })),
58
61
  React.createElement(Box, { flexDirection: "column", marginLeft: 2, marginTop: 0.5 },
59
62
  answer?.selectedOptions &&
60
63
  answer.selectedOptions.length > 0 && (React.createElement(React.Fragment, null, answer.selectedOptions.map((option, idx) => (React.createElement(Text, { key: idx, color: theme.components.review.selectedOption },
@@ -1,4 +1,4 @@
1
- import { Box, useApp, useInput } from "ink";
1
+ import { Box, useApp, useInput, useStdout } from "ink";
2
2
  import React, { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { t } from "../../i18n/index.js";
4
4
  import { ResponseFormatter } from "../../session/ResponseFormatter.js";
@@ -44,6 +44,10 @@ export const StepperView = ({ onComplete, onProgress, sessionId, sessionRequest,
44
44
  .map((value) => value.toString().padStart(2, "0"))
45
45
  .join(":");
46
46
  }, [elapsedSeconds]);
47
+ // Detect if content overflows terminal height to pause periodic re-renders
48
+ const { stdout } = useStdout();
49
+ const terminalRows = stdout?.rows ?? 24;
50
+ const [isOverflowing, setIsOverflowing] = useState(false);
47
51
  // Report progress when question index changes
48
52
  useEffect(() => {
49
53
  if (onProgress) {
@@ -151,13 +155,26 @@ export const StepperView = ({ onComplete, onProgress, sessionId, sessionRequest,
151
155
  setHasAnyRecommendedInSession(anyHasRecommended);
152
156
  }, [sessionId, sessionRequest.questions]);
153
157
  // Update elapsed time since session creation
158
+ // IMPORTANT: Pause when content overflows terminal to prevent scroll-snapping
154
159
  useEffect(() => {
160
+ if (isOverflowing)
161
+ return;
155
162
  const timer = setInterval(() => {
156
163
  const elapsed = Math.floor((Date.now() - sessionCreatedAt) / 1000);
157
164
  setElapsedSeconds(elapsed >= 0 ? elapsed : 0);
158
165
  }, 1000);
159
166
  return () => clearInterval(timer);
160
- }, [sessionCreatedAt]);
167
+ }, [sessionCreatedAt, isOverflowing]);
168
+ // Detect overflow: estimate content height vs terminal rows
169
+ useEffect(() => {
170
+ const currentQ = sessionRequest.questions[safeIndex];
171
+ const optionCount = currentQ?.options?.length ?? 0;
172
+ // Conservative estimate: header(2) + tabbar(3) + prompt(3) + options(2 each)
173
+ // + footer(2) + custom/elaborate(6) + padding(2)
174
+ const estimatedContentHeight = 2 + 3 + 3 + optionCount * 2 + 2 + 6 + 2;
175
+ const nextOverflow = estimatedContentHeight > terminalRows;
176
+ setIsOverflowing((prev) => (prev === nextOverflow ? prev : nextOverflow));
177
+ }, [safeIndex, sessionRequest.questions, terminalRows]);
161
178
  // Handle answer confirmation
162
179
  const handleConfirm = async (userAnswers) => {
163
180
  setSubmitting(true);
@@ -0,0 +1,96 @@
1
+ import React from "react";
2
+ import { cleanup, render } from "ink-testing-library";
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+ import { ThemeContext } from "../../ThemeContext.js";
5
+ import { darkTheme } from "../../themes/dark.js";
6
+ import { MarkdownPrompt } from "../MarkdownPrompt.js";
7
+ const mockThemeValue = {
8
+ theme: darkTheme,
9
+ themeName: "AUQ dark",
10
+ cycleTheme: () => { },
11
+ };
12
+ function renderWithTheme(ui) {
13
+ return render(React.createElement(ThemeContext.Provider, { value: mockThemeValue }, ui));
14
+ }
15
+ function getOutput(frame) {
16
+ return (frame ?? "").replace(/\x1b\[[0-9;]*m/g, "").replace(/\r/g, "");
17
+ }
18
+ function compact(text) {
19
+ return text.replace(/\s+/g, " ").trim();
20
+ }
21
+ afterEach(() => {
22
+ cleanup();
23
+ vi.restoreAllMocks();
24
+ });
25
+ describe("MarkdownPrompt", () => {
26
+ it("renders plain text unchanged", () => {
27
+ const text = "Choose one option";
28
+ const instance = renderWithTheme(React.createElement(MarkdownPrompt, { text: text }));
29
+ expect(compact(getOutput(instance.lastFrame()))).toBe(text);
30
+ });
31
+ it("renders core inline markdown without raw markers", () => {
32
+ const text = "**bold** *italic* `code` ~~strike~~";
33
+ const instance = renderWithTheme(React.createElement(MarkdownPrompt, { text: text }));
34
+ const output = getOutput(instance.lastFrame());
35
+ expect(output).toContain("bold");
36
+ expect(output).toContain("italic");
37
+ expect(output).toContain("code");
38
+ expect(output).toContain("strike");
39
+ expect(output).not.toContain("**");
40
+ expect(output).not.toContain("~~");
41
+ expect(output).not.toContain("`code`");
42
+ });
43
+ it("keeps inline-only markdown in inline flow without extra block spacing", () => {
44
+ const text = "Hello **there** with `inline` markdown";
45
+ const instance = renderWithTheme(React.createElement(MarkdownPrompt, { text: text }));
46
+ const output = getOutput(instance.lastFrame());
47
+ expect(output).not.toContain("\n\n");
48
+ expect(compact(output)).toContain("Hello there with inline markdown");
49
+ });
50
+ it("renders links as text followed by URL in parentheses", () => {
51
+ const text = "Read [docs](https://example.com/docs) now";
52
+ const instance = renderWithTheme(React.createElement(MarkdownPrompt, { text: text }));
53
+ expect(compact(getOutput(instance.lastFrame()))).toContain("Read docs (https://example.com/docs) now");
54
+ });
55
+ it("renders fenced code blocks as distinct multi-line block content", () => {
56
+ const text = "Before\n\n```ts\nconst x = 1;\n```\n\nAfter";
57
+ const instance = renderWithTheme(React.createElement(MarkdownPrompt, { text: text }));
58
+ const output = getOutput(instance.lastFrame());
59
+ expect(output).toContain("Before");
60
+ expect(output).toContain("const x = 1;");
61
+ expect(output).toContain("After");
62
+ expect(output.split("\n").length).toBeGreaterThan(2);
63
+ });
64
+ it("handles empty and whitespace-only inputs without crashing", () => {
65
+ const empty = renderWithTheme(React.createElement(MarkdownPrompt, { text: "" }));
66
+ const spaces = renderWithTheme(React.createElement(MarkdownPrompt, { text: " " }));
67
+ expect(getOutput(empty.lastFrame())).toBe("");
68
+ expect(getOutput(spaces.lastFrame())).toBe("");
69
+ });
70
+ it("normalizes CRLF content during rendering", () => {
71
+ const text = "Line1\r\nLine2\r\n**Bold**";
72
+ const instance = renderWithTheme(React.createElement(MarkdownPrompt, { text: text }));
73
+ const output = getOutput(instance.lastFrame());
74
+ expect(output).not.toContain("\r");
75
+ expect(output).toContain("Line1");
76
+ expect(output).toContain("Line2");
77
+ expect(output).toContain("Bold");
78
+ });
79
+ it("renders nested inline markdown content", () => {
80
+ const text = "**bold and *italic* with `code`**";
81
+ const instance = renderWithTheme(React.createElement(MarkdownPrompt, { text: text }));
82
+ const output = getOutput(instance.lastFrame());
83
+ expect(output).toContain("bold and");
84
+ expect(output).toContain("italic");
85
+ expect(output).toContain("code");
86
+ expect(output).not.toContain("**");
87
+ expect(output).not.toContain("`code`");
88
+ });
89
+ it("does not crash on malformed markdown", () => {
90
+ const malformed = "Broken [link](https://example.com and **mixed";
91
+ expect(() => renderWithTheme(React.createElement(MarkdownPrompt, { text: malformed }))).not.toThrow();
92
+ const instance = renderWithTheme(React.createElement(MarkdownPrompt, { text: malformed }));
93
+ const output = getOutput(instance.lastFrame());
94
+ expect(output.length).toBeGreaterThan(0);
95
+ });
96
+ });
@@ -93,5 +93,10 @@ export const catppuccinLatteTheme = {
93
93
  info: "#1e66f5",
94
94
  border: "#ccd0da",
95
95
  },
96
+ markdown: {
97
+ codeBlockBg: "#eff1f5",
98
+ codeBlockText: "#4c4f69",
99
+ codeBlockBorder: "#ccd0da",
100
+ },
96
101
  },
97
102
  };
@@ -93,5 +93,10 @@ export const catppuccinMochaTheme = {
93
93
  info: "#89b4fa",
94
94
  border: "#313244",
95
95
  },
96
+ markdown: {
97
+ codeBlockBg: "#1e1e2e",
98
+ codeBlockText: "#cdd6f4",
99
+ codeBlockBorder: "#313244",
100
+ },
96
101
  },
97
102
  };
@@ -94,5 +94,10 @@ export const darkTheme = {
94
94
  info: "#46D9FF",
95
95
  border: "#2A3238",
96
96
  },
97
+ markdown: {
98
+ codeBlockBg: "#0F1419",
99
+ codeBlockText: "#E7EEF5",
100
+ codeBlockBorder: "#2A3238",
101
+ },
97
102
  },
98
103
  };
@@ -93,5 +93,10 @@ export const draculaTheme = {
93
93
  info: "#bd93f9",
94
94
  border: "#44475a",
95
95
  },
96
+ markdown: {
97
+ codeBlockBg: "#282a36",
98
+ codeBlockText: "#f8f8f2",
99
+ codeBlockBorder: "#44475a",
100
+ },
96
101
  },
97
102
  };
@@ -92,5 +92,10 @@ export const githubDarkTheme = {
92
92
  info: "#58a6ff",
93
93
  border: "#30363d",
94
94
  },
95
+ markdown: {
96
+ codeBlockBg: "#0d1117",
97
+ codeBlockText: "#c9d1d9",
98
+ codeBlockBorder: "#30363d",
99
+ },
95
100
  },
96
101
  };
@@ -92,5 +92,10 @@ export const githubLightTheme = {
92
92
  info: "#0969DA",
93
93
  border: "#D0D7DE",
94
94
  },
95
+ markdown: {
96
+ codeBlockBg: "#f6f8fa",
97
+ codeBlockText: "#24292F",
98
+ codeBlockBorder: "#D0D7DE",
99
+ },
95
100
  },
96
101
  };
@@ -93,5 +93,10 @@ export const gruvboxDarkTheme = {
93
93
  info: "#458588",
94
94
  border: "#3c3836",
95
95
  },
96
+ markdown: {
97
+ codeBlockBg: "#282828",
98
+ codeBlockText: "#ebdbb2",
99
+ codeBlockBorder: "#3c3836",
100
+ },
96
101
  },
97
102
  };
@@ -93,5 +93,10 @@ export const gruvboxLightTheme = {
93
93
  info: "#076678",
94
94
  border: "#d5c4a1",
95
95
  },
96
+ markdown: {
97
+ codeBlockBg: "#f9f5d9",
98
+ codeBlockText: "#3c3836",
99
+ codeBlockBorder: "#d5c4a1",
100
+ },
96
101
  },
97
102
  };
@@ -93,5 +93,10 @@ export const lightTheme = {
93
93
  info: "#007EA7",
94
94
  border: "#D0D7DE",
95
95
  },
96
+ markdown: {
97
+ codeBlockBg: "#F6F8FA",
98
+ codeBlockText: "#24292F",
99
+ codeBlockBorder: "#D0D7DE",
100
+ },
96
101
  },
97
102
  };
@@ -94,5 +94,10 @@ export const monokaiTheme = {
94
94
  info: "#66D9EF",
95
95
  border: "#49483E",
96
96
  },
97
+ markdown: {
98
+ codeBlockBg: "#272822",
99
+ codeBlockText: "#F8F8F2",
100
+ codeBlockBorder: "#49483E",
101
+ },
97
102
  },
98
103
  };
@@ -93,5 +93,10 @@ export const nordTheme = {
93
93
  info: "#88c0d0", // nord8
94
94
  border: "#3b4252", // nord1
95
95
  },
96
+ markdown: {
97
+ codeBlockBg: "#2e3440",
98
+ codeBlockText: "#eceff4",
99
+ codeBlockBorder: "#3b4252",
100
+ },
96
101
  },
97
102
  };
@@ -93,5 +93,10 @@ export const oneDarkTheme = {
93
93
  info: "#61afef",
94
94
  border: "#3e4451",
95
95
  },
96
+ markdown: {
97
+ codeBlockBg: "#21252b",
98
+ codeBlockText: "#abb2bf",
99
+ codeBlockBorder: "#3e4451",
100
+ },
96
101
  },
97
102
  };
@@ -94,5 +94,10 @@ export const rosePineTheme = {
94
94
  info: "#9ccfd8",
95
95
  border: "#26233a",
96
96
  },
97
+ markdown: {
98
+ codeBlockBg: "#191724",
99
+ codeBlockText: "#e0def4",
100
+ codeBlockBorder: "#26233a",
101
+ },
97
102
  },
98
103
  };
@@ -93,5 +93,10 @@ export const solarizedDarkTheme = {
93
93
  info: "#268bd2",
94
94
  border: "#073642",
95
95
  },
96
+ markdown: {
97
+ codeBlockBg: "#002b36",
98
+ codeBlockText: "#839496",
99
+ codeBlockBorder: "#073642",
100
+ },
96
101
  },
97
102
  };
@@ -93,5 +93,10 @@ export const solarizedLightTheme = {
93
93
  info: "#268bd2",
94
94
  border: "#eee8d5",
95
95
  },
96
+ markdown: {
97
+ codeBlockBg: "#fdf6e3",
98
+ codeBlockText: "#657b83",
99
+ codeBlockBorder: "#eee8d5",
100
+ },
96
101
  },
97
102
  };
@@ -93,5 +93,10 @@ export const tokyoNightTheme = {
93
93
  info: "#7aa2f7",
94
94
  border: "#24283b",
95
95
  },
96
+ markdown: {
97
+ codeBlockBg: "#1a1b26",
98
+ codeBlockText: "#c0caf5",
99
+ codeBlockBorder: "#24283b",
100
+ },
96
101
  },
97
102
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auq-mcp-server",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "auq": "dist/bin/auq.js"
@@ -53,11 +53,14 @@
53
53
  "@modelcontextprotocol/sdk": "1.17.2",
54
54
  "@types/uuid": "^10.0.0",
55
55
  "chalk": "^5.6.2",
56
+ "cli-highlight": "^2.1.11",
56
57
  "fastmcp": "^3.23.0",
57
58
  "gradient-string": "^3.0.0",
58
59
  "ink": "^6.4.0",
59
60
  "ink-gradient": "^3.0.0",
61
+ "ink-markdown-es": "^1.1.0",
60
62
  "ink-text-input": "^6.0.0",
63
+ "marked": "^17.0.3",
61
64
  "node-notifier": "^10.0.1",
62
65
  "react": "^19.2.0",
63
66
  "string-width": "^8.1.1",
@@ -190,9 +193,7 @@
190
193
  "package.json",
191
194
  "CHANGELOG.md"
192
195
  ],
193
- "message": "chore(release): ${nextRelease.version} [skip ci]",
194
- "authorName": "Paul Park",
195
- "authorEmail": "2000mageia@gmail.com"
196
+ "message": "chore(release): ${nextRelease.version} [skip ci]"
196
197
  }
197
198
  ]
198
199
  ]
@@ -208,6 +209,7 @@
208
209
  "conventional-changelog-conventionalcommits": "^9.1.0",
209
210
  "eslint-config-prettier": "^10.1.3",
210
211
  "eslint-plugin-perfectionist": "^4.12.3",
212
+ "ink-testing-library": "^4.0.0",
211
213
  "prettier": "^3.5.3",
212
214
  "semantic-release": "^24.2.3",
213
215
  "typescript": "^5.8.3",