auq-mcp-server 2.0.0 → 2.1.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/README.md +13 -0
- package/dist/package.json +5 -1
- package/dist/src/tui/components/MarkdownPrompt.js +59 -0
- package/dist/src/tui/components/QuestionDisplay.js +7 -8
- package/dist/src/tui/components/ReviewScreen.js +13 -10
- package/dist/src/tui/components/__tests__/MarkdownPrompt.test.js +96 -0
- package/dist/src/tui/themes/catppuccin-latte.js +5 -0
- package/dist/src/tui/themes/catppuccin-mocha.js +5 -0
- package/dist/src/tui/themes/dark.js +5 -0
- package/dist/src/tui/themes/dracula.js +5 -0
- package/dist/src/tui/themes/github-dark.js +5 -0
- package/dist/src/tui/themes/github-light.js +5 -0
- package/dist/src/tui/themes/gruvbox-dark.js +5 -0
- package/dist/src/tui/themes/gruvbox-light.js +5 -0
- package/dist/src/tui/themes/light.js +5 -0
- package/dist/src/tui/themes/monokai.js +5 -0
- package/dist/src/tui/themes/nord.js +5 -0
- package/dist/src/tui/themes/one-dark.js +5 -0
- package/dist/src/tui/themes/rose-pine.js +5 -0
- package/dist/src/tui/themes/solarized-dark.js +5 -0
- package/dist/src/tui/themes/solarized-light.js +5 -0
- package/dist/src/tui/themes/tokyo-night.js +5 -0
- package/package.json +5 -1
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.
|
|
3
|
+
"version": "2.1.0",
|
|
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",
|
|
@@ -206,6 +209,7 @@
|
|
|
206
209
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
207
210
|
"eslint-config-prettier": "^10.1.3",
|
|
208
211
|
"eslint-plugin-perfectionist": "^4.12.3",
|
|
212
|
+
"ink-testing-library": "^4.0.0",
|
|
209
213
|
"prettier": "^3.5.3",
|
|
210
214
|
"semantic-release": "^24.2.3",
|
|
211
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
|
+
};
|
|
@@ -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,
|
|
39
|
-
React.createElement(
|
|
40
|
-
React.createElement(
|
|
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
|
-
|
|
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,
|
|
49
|
-
React.createElement(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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 },
|
|
@@ -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
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auq-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
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",
|
|
@@ -206,6 +209,7 @@
|
|
|
206
209
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
207
210
|
"eslint-config-prettier": "^10.1.3",
|
|
208
211
|
"eslint-plugin-perfectionist": "^4.12.3",
|
|
212
|
+
"ink-testing-library": "^4.0.0",
|
|
209
213
|
"prettier": "^3.5.3",
|
|
210
214
|
"semantic-release": "^24.2.3",
|
|
211
215
|
"typescript": "^5.8.3",
|