auq-mcp-server 1.2.8 β 1.3.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 +44 -21
- package/dist/package.json +2 -1
- package/dist/src/__tests__/schema-validation.test.js +1 -1
- package/dist/src/tui/components/ConfirmationDialog.js +3 -3
- package/dist/src/tui/components/Footer.js +7 -9
- package/dist/src/tui/components/MultiLineTextInput.js +11 -24
- package/dist/src/tui/components/OptionsList.js +13 -9
- package/dist/src/tui/components/QuestionDisplay.js +6 -3
- package/dist/src/tui/components/SingleLineTextInput.js +59 -0
- package/dist/src/tui/components/StepperView.js +17 -4
- package/dist/src/tui/components/WaitingScreen.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
-
# AUQ -
|
|
3
|
+
# AUQ - Ask User Questions
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/auq-mcp-server)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
[](https://cursor.com/en-US/install-mcp?name=ask-user-questions&config=eyJlbnYiOnt9LCJjb21tYW5kIjoibnB4IC15IGF1cS1tY3Atc2VydmVyIHNlcnZlciJ9)
|
|
8
8
|
|
|
9
|
-
**A lightweight
|
|
9
|
+
**A lightweight CLI tool that allows your LLMs to ask questions to you in a separate space with clean terminal UX. Supports both MCP server and official OpenCode plugin integration. Made for multi-agent parallel coding workflows.**
|
|
10
|
+
|
|
11
|
+
π€ [Why do I need it when I already have question tool in CC/OC?](#-why-auq-vs-built-in-ask-tools)
|
|
12
|
+
|
|
13
|
+
[Setup](#setup-instructions) β’ [Features](#-features)
|
|
10
14
|
|
|
11
15
|
---
|
|
12
16
|
|
|
13
17
|
## What does it do?
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
AUQ lets your AI assistants generate clarifying questions consisting of multiple-choice/single-choice questions (with an "Other" option for custom input) while coding or working, and wait for your answers through a separate CLI tool without messing up your workflow.
|
|
16
20
|
|
|
17
21
|
You can keep the CLI running in advance, or start it when questions are pending. With simple arrow key navigation, you can select answers and send them back to the AIβall within a clean terminal interface.
|
|
18
22
|
|
|
@@ -22,7 +26,7 @@ In AI-assisted coding, guiding LLMs to ask **clarifying questions** have been wi
|
|
|
22
26
|
|
|
23
27
|
On October 18th, Claude Code 2.0.21 introduced an internal `ask-user-question` tool. Inspired by it, I decided to build a similar tool that is:
|
|
24
28
|
|
|
25
|
-
- **
|
|
29
|
+
- **Integration-flexible** - Works with MCP clients (Claude Desktop, Cursor, etc.) and has official OpenCode plugin support
|
|
26
30
|
- **Non-invasive** - Doesn't heavily integrate with your coding CLI workflow or occupy UI space
|
|
27
31
|
- **Multi-agent friendly** - Supports receiving questions from multiple agents simultaneously in parallel workflows
|
|
28
32
|
|
|
@@ -56,9 +60,35 @@ Recent AI workflows often use parallel sub-agents for concurrent coding. AUQ han
|
|
|
56
60
|
|
|
57
61
|
---
|
|
58
62
|
|
|
63
|
+
## π€ Why AUQ vs. Built-in Ask Tools?
|
|
64
|
+
|
|
65
|
+
**Why should I use AUQ instead of the built-in "Question" tools in OpenCode, Claude Code, or other coding agents?**
|
|
66
|
+
|
|
67
|
+
AUQ is designed for the era of parallel multi-agent workflows, with several key advantages:
|
|
68
|
+
|
|
69
|
+
### π Non-Blocking Parallel Operation
|
|
70
|
+
|
|
71
|
+
Unlike built-in ask tools that halt the entire AI workflow until you respond, AUQ **doesn't block the AI from continuing work**. Questions are queued asynchronously, allowing your AI assistants to keep coding while you review and answer questions at your own pace.
|
|
72
|
+
|
|
73
|
+
### π― Multi-Agent Question Set Support
|
|
74
|
+
|
|
75
|
+
AUQ can handle question sets from **multiple agents simultaneously**. In modern AI coding workflows, you often have several sub-agents working in parallelβeach might need clarification on different aspects of your codebase. With AUQ:
|
|
76
|
+
|
|
77
|
+
- **No more screen switching** between different agent conversations
|
|
78
|
+
- **Unified queue** for all agent questions, regardless of which AI tool they're coming from
|
|
79
|
+
- **Sequential processing** of questions from multiple sources in one interface
|
|
80
|
+
|
|
81
|
+
### π Question Set Rejection Support
|
|
82
|
+
|
|
83
|
+
**Skip irrelevant question sets entirely** - reject whole question batches that don't apply to your current context, saving time and maintaining focus on relevant AI-agent questions.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
59
87
|
# Setup Instructions
|
|
60
88
|
|
|
61
|
-
## π
|
|
89
|
+
## π Install CLI Tool
|
|
90
|
+
|
|
91
|
+
First, install the AUQ CLI tool:
|
|
62
92
|
|
|
63
93
|
### Global Installation (Recommended)
|
|
64
94
|
|
|
@@ -66,8 +96,6 @@ Recent AI workflows often use parallel sub-agents for concurrent coding. AUQ han
|
|
|
66
96
|
# Install globally
|
|
67
97
|
npm install -g auq-mcp-server
|
|
68
98
|
|
|
69
|
-
# Start the TUI
|
|
70
|
-
auq
|
|
71
99
|
```
|
|
72
100
|
|
|
73
101
|
### Local Installation (Project-specific)
|
|
@@ -76,8 +104,6 @@ auq
|
|
|
76
104
|
# Install in your project
|
|
77
105
|
npm install auq-mcp-server
|
|
78
106
|
|
|
79
|
-
# Start the TUI from project directory
|
|
80
|
-
npx auq
|
|
81
107
|
```
|
|
82
108
|
|
|
83
109
|
**Session Storage:**
|
|
@@ -87,7 +113,11 @@ npx auq
|
|
|
87
113
|
|
|
88
114
|
---
|
|
89
115
|
|
|
90
|
-
## π
|
|
116
|
+
## π Choose Your Integration Method
|
|
117
|
+
|
|
118
|
+
AUQ supports multiple AI coding environments. Choose the one that fits your workflow:
|
|
119
|
+
|
|
120
|
+
### Option A: MCP Server
|
|
91
121
|
|
|
92
122
|
### Cursor
|
|
93
123
|
|
|
@@ -168,29 +198,22 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
|
168
198
|
|
|
169
199
|
**Restart Claude Desktop** after saving.
|
|
170
200
|
|
|
171
|
-
|
|
201
|
+
### Option B: Official OpenCode Plugin
|
|
172
202
|
|
|
173
|
-
|
|
203
|
+
We now have **official OpenCode plugin support**! We support OpenCode because OpenCode's MCP server seems to have some limitations (timeout), so we created an OpenCode-specific plugin that works perfectly together with OpenCode.
|
|
174
204
|
|
|
175
|
-
|
|
176
|
-
the plugin package and add it to your OpenCode config.
|
|
205
|
+
The OpenCode plugin allows OpenCode to call `auq ask` directly (without MCP), providing seamless integration with OpenCode's workflow.
|
|
177
206
|
|
|
178
|
-
|
|
179
|
-
npm install -g auq-mcp-server
|
|
180
|
-
npm install -g @paulp-o/opencode-auq
|
|
181
|
-
```
|
|
207
|
+
#### Configuration
|
|
182
208
|
|
|
183
209
|
Add to `opencode.json`:
|
|
184
210
|
|
|
185
211
|
```json
|
|
186
212
|
{
|
|
187
|
-
"$schema": "https://opencode.ai/config.json",
|
|
188
213
|
"plugin": ["@paulp-o/opencode-auq"]
|
|
189
214
|
}
|
|
190
215
|
```
|
|
191
216
|
|
|
192
|
-
The plugin assumes `auq` is available on `PATH` (global install or equivalent).
|
|
193
|
-
|
|
194
217
|
---
|
|
195
218
|
|
|
196
219
|
## π» Usage
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auq-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"auq": "dist/bin/auq.js"
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"prepare": "npm run build",
|
|
20
20
|
"postinstall": "node scripts/postinstall.cjs",
|
|
21
21
|
"deploy": "node scripts/deploy.mjs",
|
|
22
|
+
"semantic-release": "semantic-release",
|
|
22
23
|
"server": "node dist/src/server.js",
|
|
23
24
|
"start": "tsx src/server.ts",
|
|
24
25
|
"dev": "fastmcp dev src/server.ts",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Tests the most common edge cases to catch obvious bugs
|
|
4
4
|
*/
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
6
|
-
import { QuestionSchema, QuestionsSchema
|
|
6
|
+
import { QuestionSchema, QuestionsSchema } from "../core/ask-user-questions.js";
|
|
7
7
|
describe("Schema Validation - Edge Cases", () => {
|
|
8
8
|
describe("Invalid Input (should reject)", () => {
|
|
9
9
|
it("should reject missing title field", () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Box, Text, useInput } from "ink";
|
|
2
2
|
import React, { useState } from "react";
|
|
3
3
|
import { theme } from "../theme.js";
|
|
4
|
-
import {
|
|
4
|
+
import { SingleLineTextInput } from "./SingleLineTextInput.js";
|
|
5
5
|
/**
|
|
6
6
|
* ConfirmationDialog shows a 3-option prompt for session rejection
|
|
7
7
|
* Options: Reject & inform AI, Cancel, or Quit CLI
|
|
@@ -64,9 +64,9 @@ export const ConfirmationDialog = ({ message, onReject, onCancel, onQuit, }) =>
|
|
|
64
64
|
React.createElement(Box, { marginBottom: 1 },
|
|
65
65
|
React.createElement(Text, { dimColor: true }, "(Optional - helps the AI improve)")),
|
|
66
66
|
React.createElement(Box, { marginBottom: 1 },
|
|
67
|
-
React.createElement(
|
|
67
|
+
React.createElement(SingleLineTextInput, { isFocused: true, onChange: setRejectionReason, onSubmit: handleReasonSubmit, placeholder: "Type your reason here...", value: rejectionReason })),
|
|
68
68
|
React.createElement(Box, { marginTop: 1 },
|
|
69
|
-
React.createElement(Text, { dimColor: true }, "Enter Submit |
|
|
69
|
+
React.createElement(Text, { dimColor: true }, "Enter Submit | Esc Skip"))));
|
|
70
70
|
}
|
|
71
71
|
// Step 1: Confirmation options
|
|
72
72
|
return (React.createElement(Box, { borderColor: theme.borders.warning, borderStyle: "single", flexDirection: "column", padding: 1 },
|
|
@@ -16,25 +16,23 @@ export const Footer = ({ focusContext, multiSelect, isReviewScreen = false, cust
|
|
|
16
16
|
}
|
|
17
17
|
// Custom input focused
|
|
18
18
|
if (focusContext === "custom-input") {
|
|
19
|
-
|
|
20
|
-
const bindings = [
|
|
19
|
+
return [
|
|
21
20
|
{ key: "ββ", action: "Options" },
|
|
22
|
-
{ key: "
|
|
21
|
+
{ key: "ββ", action: "Cursor" },
|
|
22
|
+
{ key: "Tab/S+Tab", action: "Questions" },
|
|
23
|
+
{ key: "Enter", action: "Newline" },
|
|
24
|
+
{ key: "Esc", action: "Reject" },
|
|
23
25
|
];
|
|
24
|
-
if (hasContent) {
|
|
25
|
-
bindings.push({ key: "Enter", action: "Submit" });
|
|
26
|
-
}
|
|
27
|
-
bindings.push({ key: "Shift+Enter", action: "Newline" }, { key: "Esc", action: "Reject" });
|
|
28
|
-
return bindings;
|
|
29
26
|
}
|
|
30
27
|
// Option focused
|
|
31
28
|
if (focusContext === "option") {
|
|
32
29
|
const bindings = [
|
|
33
30
|
{ key: "ββ", action: "Options" },
|
|
34
31
|
{ key: "ββ", action: "Questions" },
|
|
32
|
+
{ key: "Tab/S+Tab", action: "Questions" },
|
|
35
33
|
];
|
|
36
34
|
if (multiSelect) {
|
|
37
|
-
bindings.push({ key: "Space", action: "Toggle" }
|
|
35
|
+
bindings.push({ key: "Space", action: "Toggle" });
|
|
38
36
|
}
|
|
39
37
|
else {
|
|
40
38
|
bindings.push({ key: "Enter", action: "Select" });
|
|
@@ -4,7 +4,7 @@ import { theme } from "../theme.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* Multi-line text input component for Ink with cursor positioning
|
|
6
6
|
* Supports left/right arrow keys for cursor movement
|
|
7
|
-
*
|
|
7
|
+
* Enter for newlines, Tab to submit (portable across terminals)
|
|
8
8
|
*/
|
|
9
9
|
export const MultiLineTextInput = ({ isFocused = true, onChange, onSubmit, placeholder = "Type your answer...", value, }) => {
|
|
10
10
|
const [cursorPosition, setCursorPosition] = useState(value.length);
|
|
@@ -17,33 +17,22 @@ export const MultiLineTextInput = ({ isFocused = true, onChange, onSubmit, place
|
|
|
17
17
|
useInput((input, key) => {
|
|
18
18
|
if (!isFocused)
|
|
19
19
|
return;
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
else if (onSubmit) {
|
|
29
|
-
onSubmit();
|
|
30
|
-
}
|
|
20
|
+
// Tab: Submit (also triggers question navigation via parent)
|
|
21
|
+
if (key.tab && !key.shift) {
|
|
22
|
+
onSubmit?.();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Shift+Tab: Let parent handle for previous question navigation
|
|
26
|
+
if (key.tab && key.shift) {
|
|
31
27
|
return;
|
|
32
28
|
}
|
|
33
|
-
//
|
|
34
|
-
if (
|
|
29
|
+
// Enter: Always add newline (portable behavior)
|
|
30
|
+
if (input === "\r" || input === "\n" || key.return) {
|
|
35
31
|
const newValue = value.slice(0, cursorPosition) + "\n" + value.slice(cursorPosition);
|
|
36
32
|
onChange(newValue);
|
|
37
33
|
setCursorPosition(cursorPosition + 1);
|
|
38
34
|
return;
|
|
39
35
|
}
|
|
40
|
-
// Enter: Submit (empty input allowed)
|
|
41
|
-
if (key.return) {
|
|
42
|
-
if (onSubmit) {
|
|
43
|
-
onSubmit();
|
|
44
|
-
}
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
36
|
// Left arrow: Move cursor left
|
|
48
37
|
if (key.leftArrow) {
|
|
49
38
|
setCursorPosition(Math.max(0, cursorPosition - 1));
|
|
@@ -75,13 +64,11 @@ export const MultiLineTextInput = ({ isFocused = true, onChange, onSubmit, place
|
|
|
75
64
|
setCursorPosition(cursorPosition + 1);
|
|
76
65
|
}
|
|
77
66
|
}, { isActive: isFocused });
|
|
78
|
-
// Normalize any carriage returns that might already be present in value
|
|
79
67
|
const normalizedValue = value.replace(/\r\n?/g, "\n");
|
|
80
68
|
const hasContent = normalizedValue.length > 0;
|
|
81
69
|
const lines = hasContent ? normalizedValue.split("\n") : [placeholder];
|
|
82
|
-
// Calculate which line and position the cursor is on
|
|
83
70
|
const cursorLineIndex = normalizedValue.slice(0, cursorPosition).split("\n").length - 1;
|
|
84
|
-
const cursorLineStart = normalizedValue.
|
|
71
|
+
const cursorLineStart = normalizedValue.lastIndexOf("\n", cursorPosition - 1) + 1;
|
|
85
72
|
const cursorPositionInLine = cursorPosition - cursorLineStart;
|
|
86
73
|
return (React.createElement(Box, { flexDirection: "column" }, lines.map((line, index) => {
|
|
87
74
|
const isCursorLine = isFocused && index === cursorLineIndex && hasContent;
|
|
@@ -22,6 +22,15 @@ export const OptionsList = ({ isFocused, onSelect, options, selectedOption, show
|
|
|
22
22
|
useInput((input, key) => {
|
|
23
23
|
if (!isFocused)
|
|
24
24
|
return;
|
|
25
|
+
// Handle up/down navigation even when custom input is focused
|
|
26
|
+
if (key.upArrow) {
|
|
27
|
+
setFocusedIndex((prev) => Math.max(0, prev - 1));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (key.downArrow) {
|
|
31
|
+
setFocusedIndex((prev) => Math.min(maxIndex, prev + 1));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
25
34
|
// When custom input is focused, only handle escape to exit, let MultiLineTextInput handle other keys
|
|
26
35
|
if (isCustomInputFocused) {
|
|
27
36
|
if (key.escape) {
|
|
@@ -30,12 +39,6 @@ export const OptionsList = ({ isFocused, onSelect, options, selectedOption, show
|
|
|
30
39
|
}
|
|
31
40
|
return;
|
|
32
41
|
}
|
|
33
|
-
if (key.upArrow) {
|
|
34
|
-
setFocusedIndex((prev) => Math.max(0, prev - 1));
|
|
35
|
-
}
|
|
36
|
-
if (key.downArrow) {
|
|
37
|
-
setFocusedIndex((prev) => Math.min(maxIndex, prev + 1));
|
|
38
|
-
}
|
|
39
42
|
if (multiSelect) {
|
|
40
43
|
// Multi-select mode
|
|
41
44
|
if (input === " ") {
|
|
@@ -44,8 +47,9 @@ export const OptionsList = ({ isFocused, onSelect, options, selectedOption, show
|
|
|
44
47
|
onToggle?.(options[focusedIndex].label);
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
|
-
if (key.return
|
|
48
|
-
// Enter
|
|
50
|
+
if (key.return) {
|
|
51
|
+
// Enter: Advance to next question (don't toggle)
|
|
52
|
+
// Note: Tab is handled globally in StepperView for question navigation
|
|
49
53
|
if (!isCustomInputFocused && onAdvance) {
|
|
50
54
|
onAdvance();
|
|
51
55
|
}
|
|
@@ -114,7 +118,7 @@ export const OptionsList = ({ isFocused, onSelect, options, selectedOption, show
|
|
|
114
118
|
customValue ? "β" : "β",
|
|
115
119
|
" Other (custom answer)"),
|
|
116
120
|
isCustomInputFocused && onCustomChange && (React.createElement(Box, { marginLeft: 2, marginTop: 0.5 },
|
|
117
|
-
React.createElement(MultiLineTextInput, { isFocused: true, onChange: onCustomChange, onSubmit: onAdvance, placeholder: "Type your answer... (
|
|
121
|
+
React.createElement(MultiLineTextInput, { isFocused: true, onChange: onCustomChange, onSubmit: onAdvance, placeholder: "Type your answer... (Enter for newline, Tab to submit)", value: customValue }))),
|
|
118
122
|
!isCustomInputFocused && customValue && (React.createElement(Box, { marginLeft: 2, marginTop: 0.5 },
|
|
119
123
|
React.createElement(Text, { dimColor: true }, customLines.map((line, idx) => (React.createElement(React.Fragment, { key: idx },
|
|
120
124
|
idx === 0 ? "β― " : " ",
|
|
@@ -7,9 +7,12 @@ import { TabBar } from "./TabBar.js";
|
|
|
7
7
|
* QuestionDisplay shows a single question with its options
|
|
8
8
|
* Includes TabBar, question prompt, options list, and footer
|
|
9
9
|
*/
|
|
10
|
-
export const QuestionDisplay = ({ currentQuestion, currentQuestionIndex, customAnswer = "", elapsedLabel, onChangeCustomAnswer, onSelectOption, questions, selectedOption, onAdvanceToNext, answers, onToggleOption, multiSelect, }) => {
|
|
11
|
-
// Track focus context for Footer component
|
|
10
|
+
export const QuestionDisplay = ({ currentQuestion, currentQuestionIndex, customAnswer = "", elapsedLabel, onChangeCustomAnswer, onSelectOption, questions, selectedOption, onAdvanceToNext, answers, onToggleOption, multiSelect, onFocusContextChange, }) => {
|
|
12
11
|
const [focusContext, setFocusContext] = useState("option");
|
|
12
|
+
const handleFocusContextChange = (context) => {
|
|
13
|
+
setFocusContext(context);
|
|
14
|
+
onFocusContextChange?.(context);
|
|
15
|
+
};
|
|
13
16
|
// Handle option selection - clears custom answer only in single-select mode
|
|
14
17
|
const handleSelectOption = (label) => {
|
|
15
18
|
onSelectOption(label);
|
|
@@ -35,6 +38,6 @@ export const QuestionDisplay = ({ currentQuestion, currentQuestionIndex, customA
|
|
|
35
38
|
React.createElement(Text, { dimColor: true },
|
|
36
39
|
"Elapsed ",
|
|
37
40
|
elapsedLabel)),
|
|
38
|
-
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:
|
|
41
|
+
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 }),
|
|
39
42
|
React.createElement(Footer, { focusContext: focusContext, multiSelect: multiSelect ?? false, customInputValue: customAnswer })));
|
|
40
43
|
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Text, useInput } from "ink";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { theme } from "../theme.js";
|
|
4
|
+
export const SingleLineTextInput = ({ isFocused = true, onChange, onSubmit, placeholder = "Type here...", value, }) => {
|
|
5
|
+
const [cursorPosition, setCursorPosition] = useState(value.length);
|
|
6
|
+
React.useEffect(() => {
|
|
7
|
+
if (cursorPosition > value.length) {
|
|
8
|
+
setCursorPosition(value.length);
|
|
9
|
+
}
|
|
10
|
+
}, [value.length, cursorPosition]);
|
|
11
|
+
useInput((input, key) => {
|
|
12
|
+
if (!isFocused)
|
|
13
|
+
return;
|
|
14
|
+
if (key.return) {
|
|
15
|
+
onSubmit?.();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (key.leftArrow) {
|
|
19
|
+
setCursorPosition(Math.max(0, cursorPosition - 1));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (key.rightArrow) {
|
|
23
|
+
setCursorPosition(Math.min(value.length, cursorPosition + 1));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (key.backspace || key.delete) {
|
|
27
|
+
if (cursorPosition > 0) {
|
|
28
|
+
const newValue = value.slice(0, cursorPosition - 1) + value.slice(cursorPosition);
|
|
29
|
+
onChange(newValue);
|
|
30
|
+
setCursorPosition(cursorPosition - 1);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (input &&
|
|
35
|
+
!key.ctrl &&
|
|
36
|
+
!key.meta &&
|
|
37
|
+
!key.escape &&
|
|
38
|
+
!key.tab &&
|
|
39
|
+
input !== "\r" &&
|
|
40
|
+
input !== "\n") {
|
|
41
|
+
const newValue = value.slice(0, cursorPosition) + input + value.slice(cursorPosition);
|
|
42
|
+
onChange(newValue);
|
|
43
|
+
setCursorPosition(cursorPosition + 1);
|
|
44
|
+
}
|
|
45
|
+
}, { isActive: isFocused });
|
|
46
|
+
const hasContent = value.length > 0;
|
|
47
|
+
const displayText = hasContent ? value : placeholder;
|
|
48
|
+
if (hasContent && isFocused) {
|
|
49
|
+
const beforeCursor = value.slice(0, cursorPosition);
|
|
50
|
+
const afterCursor = value.slice(cursorPosition);
|
|
51
|
+
return (React.createElement(Text, null,
|
|
52
|
+
beforeCursor,
|
|
53
|
+
React.createElement(Text, { color: theme.colors.focused, dimColor: true }, "\u258C"),
|
|
54
|
+
afterCursor));
|
|
55
|
+
}
|
|
56
|
+
return (React.createElement(Text, { dimColor: !hasContent },
|
|
57
|
+
displayText,
|
|
58
|
+
isFocused && !hasContent && (React.createElement(Text, { color: theme.colors.focused, dimColor: true }, "\u258C"))));
|
|
59
|
+
};
|
|
@@ -18,6 +18,7 @@ export const StepperView = ({ onComplete, sessionId, sessionRequest, }) => {
|
|
|
18
18
|
const [submitting, setSubmitting] = useState(false);
|
|
19
19
|
const [showRejectionConfirm, setShowRejectionConfirm] = useState(false);
|
|
20
20
|
const [elapsedSeconds, setElapsedSeconds] = useState(0);
|
|
21
|
+
const [focusContext, setFocusContext] = useState("option");
|
|
21
22
|
const currentQuestion = sessionRequest.questions[currentQuestionIndex];
|
|
22
23
|
const sessionCreatedAt = useMemo(() => {
|
|
23
24
|
const parsed = Date.parse(sessionRequest.timestamp);
|
|
@@ -163,11 +164,23 @@ export const StepperView = ({ onComplete, sessionId, sessionRequest, }) => {
|
|
|
163
164
|
setShowRejectionConfirm(true);
|
|
164
165
|
return;
|
|
165
166
|
}
|
|
166
|
-
//
|
|
167
|
-
if (key.
|
|
167
|
+
// Tab/Shift+Tab: Global question navigation (works in all contexts)
|
|
168
|
+
if (key.tab && key.shift && currentQuestionIndex > 0) {
|
|
169
|
+
setCurrentQuestionIndex((prev) => prev - 1);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (key.tab &&
|
|
173
|
+
!key.shift &&
|
|
174
|
+
currentQuestionIndex < sessionRequest.questions.length - 1) {
|
|
175
|
+
setCurrentQuestionIndex((prev) => prev + 1);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const shouldNavigate = focusContext !== "custom-input";
|
|
179
|
+
if (shouldNavigate && key.leftArrow && currentQuestionIndex > 0) {
|
|
168
180
|
setCurrentQuestionIndex((prev) => prev - 1);
|
|
169
181
|
}
|
|
170
|
-
if (
|
|
182
|
+
if (shouldNavigate &&
|
|
183
|
+
key.rightArrow &&
|
|
171
184
|
currentQuestionIndex < sessionRequest.questions.length - 1) {
|
|
172
185
|
setCurrentQuestionIndex((prev) => prev + 1);
|
|
173
186
|
}
|
|
@@ -189,5 +202,5 @@ export const StepperView = ({ onComplete, sessionId, sessionRequest, }) => {
|
|
|
189
202
|
return (React.createElement(ReviewScreen, { answers: answers, elapsedLabel: elapsedLabel, onConfirm: handleConfirm, onGoBack: handleGoBack, questions: sessionRequest.questions, sessionId: sessionId }));
|
|
190
203
|
}
|
|
191
204
|
// Show question display (default)
|
|
192
|
-
return (React.createElement(QuestionDisplay, { currentQuestion: currentQuestion, currentQuestionIndex: currentQuestionIndex, customAnswer: currentAnswer?.customText, elapsedLabel: elapsedLabel, onAdvanceToNext: handleAdvanceToNext, onChangeCustomAnswer: handleChangeCustomAnswer, onSelectOption: handleSelectOption, onToggleOption: handleToggleOption, multiSelect: currentQuestion.multiSelect, questions: sessionRequest.questions, selectedOption: currentAnswer?.selectedOption, answers: answers }));
|
|
205
|
+
return (React.createElement(QuestionDisplay, { currentQuestion: currentQuestion, currentQuestionIndex: currentQuestionIndex, customAnswer: currentAnswer?.customText, elapsedLabel: elapsedLabel, onAdvanceToNext: handleAdvanceToNext, onChangeCustomAnswer: handleChangeCustomAnswer, onSelectOption: handleSelectOption, onToggleOption: handleToggleOption, multiSelect: currentQuestion.multiSelect, questions: sessionRequest.questions, selectedOption: currentAnswer?.selectedOption, answers: answers, onFocusContextChange: setFocusContext }));
|
|
193
206
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auq-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"auq": "dist/bin/auq.js"
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"prepare": "npm run build",
|
|
20
20
|
"postinstall": "node scripts/postinstall.cjs",
|
|
21
21
|
"deploy": "node scripts/deploy.mjs",
|
|
22
|
+
"semantic-release": "semantic-release",
|
|
22
23
|
"server": "node dist/src/server.js",
|
|
23
24
|
"start": "tsx src/server.ts",
|
|
24
25
|
"dev": "fastmcp dev src/server.ts",
|