auq-mcp-server 0.1.9 → 0.1.24

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 (39) hide show
  1. package/README.md +24 -0
  2. package/dist/bin/auq.js +127 -7
  3. package/dist/package.json +15 -5
  4. package/dist/src/__tests__/schema-validation.test.js +32 -24
  5. package/dist/src/core/ask-user-questions.js +74 -0
  6. package/dist/src/server.js +11 -74
  7. package/dist/src/tui/components/Header.js +9 -1
  8. package/dist/src/tui/components/QuestionDisplay.js +10 -6
  9. package/dist/src/tui/components/ReviewScreen.js +6 -2
  10. package/dist/src/tui/components/StepperView.js +25 -3
  11. package/dist/src/tui/components/WaitingScreen.js +31 -4
  12. package/package.json +7 -1
  13. package/dist/__tests__/schema-validation.test.js +0 -137
  14. package/dist/__tests__/server.integration.test.js +0 -263
  15. package/dist/add.js +0 -1
  16. package/dist/add.test.js +0 -5
  17. package/dist/bin/test-session-menu.js +0 -28
  18. package/dist/bin/test-tabbar.js +0 -42
  19. package/dist/file-utils.js +0 -59
  20. package/dist/format/ResponseFormatter.js +0 -206
  21. package/dist/format/__tests__/ResponseFormatter.test.js +0 -380
  22. package/dist/server.js +0 -107
  23. package/dist/session/ResponseFormatter.js +0 -130
  24. package/dist/session/SessionManager.js +0 -474
  25. package/dist/session/__tests__/ResponseFormatter.test.js +0 -417
  26. package/dist/session/__tests__/SessionManager.test.js +0 -553
  27. package/dist/session/__tests__/atomic-operations.test.js +0 -345
  28. package/dist/session/__tests__/file-watcher.test.js +0 -311
  29. package/dist/session/__tests__/workflow.integration.test.js +0 -334
  30. package/dist/session/atomic-operations.js +0 -307
  31. package/dist/session/file-watcher.js +0 -218
  32. package/dist/session/index.js +0 -7
  33. package/dist/session/types.js +0 -20
  34. package/dist/session/utils.js +0 -125
  35. package/dist/session-manager.js +0 -171
  36. package/dist/session-watcher.js +0 -110
  37. package/dist/src/tui/components/SessionSelectionMenu.js +0 -151
  38. package/dist/tui/__tests__/session-watcher.test.js +0 -368
  39. package/dist/tui/session-watcher.js +0 -183
package/dist/server.js DELETED
@@ -1,107 +0,0 @@
1
- import { FastMCP } from "fastmcp";
2
- import { z } from "zod";
3
- import { SessionManager } from "./session/index.js";
4
- // import { resolveSessionDirectory } from "./session/utils.js";
5
- // Initialize session manager
6
- const sessionManager = new SessionManager();
7
- const server = new FastMCP({
8
- instructions: "This MCP server provides a tool to ask users structured questions via the terminal. " +
9
- "The ask_user_questions tool allows AI models to pause and gather direct user input through " +
10
- "an interactive TUI, returning formatted responses for continued reasoning.",
11
- name: "AskUserQuery",
12
- version: "0.1.0",
13
- });
14
- // Define the question and option schemas
15
- const OptionSchema = z.object({
16
- description: z
17
- .string()
18
- .optional()
19
- .describe("Optional explanatory note for this option"),
20
- label: z.string().describe("The visible text of the choice"),
21
- });
22
- const QuestionSchema = z.object({
23
- title: z.string().describe("Short 1-2 word summary for UI display"),
24
- options: z
25
- .array(OptionSchema)
26
- .min(1)
27
- .describe("Non-empty list of predefined answer choices"),
28
- prompt: z.string().describe("The full question text"),
29
- });
30
- // Add the ask_user_questions tool
31
- server.addTool({
32
- annotations: {
33
- openWorldHint: true, // This tool interacts with the user's terminal
34
- readOnlyHint: false, // This tool waits for user input
35
- title: "Ask User Questions",
36
- },
37
- description: "Ask the user one or more structured questions via an interactive terminal interface. " +
38
- "Each question includes multiple-choice options and allows custom free-text responses. " +
39
- "Returns a formatted summary of all questions and answers.",
40
- execute: async (args, { log }) => {
41
- try {
42
- // Initialize session manager if not already done
43
- await sessionManager.initialize();
44
- // Clean up old sessions on startup (non-blocking)
45
- sessionManager
46
- .cleanupExpiredSessions()
47
- .then((count) => {
48
- if (count > 0) {
49
- log.info(`Cleaned up ${count} expired session(s)`);
50
- }
51
- })
52
- .catch((error) => {
53
- log.warn("Cleanup failed:", { error: String(error) });
54
- });
55
- // Validate questions (using existing Zod schema validation)
56
- if (!args.questions || args.questions.length === 0) {
57
- throw new Error("At least one question is required");
58
- }
59
- // Convert Zod-validated questions to our internal Question type
60
- const questions = args.questions.map((q) => ({
61
- title: q.title,
62
- options: q.options.map((opt) => ({
63
- description: opt.description,
64
- label: opt.label,
65
- })),
66
- prompt: q.prompt,
67
- }));
68
- log.info("Starting session and waiting for user answers...", {
69
- questionCount: questions.length,
70
- });
71
- // Start complete session lifecycle - this will wait for user answers
72
- const { formattedResponse, sessionId } = await sessionManager.startSession(questions);
73
- log.info("Session completed successfully", { sessionId });
74
- // Return formatted response to AI model
75
- return {
76
- content: [
77
- {
78
- text: formattedResponse,
79
- type: "text",
80
- },
81
- ],
82
- };
83
- }
84
- catch (error) {
85
- log.error("Session failed", { error: String(error) });
86
- return {
87
- content: [
88
- {
89
- text: `Error in session: ${error}`,
90
- type: "text",
91
- },
92
- ],
93
- };
94
- }
95
- },
96
- name: "ask_user_questions",
97
- parameters: z.object({
98
- questions: z
99
- .array(QuestionSchema)
100
- .min(1)
101
- .describe("Array of questions to ask the user"),
102
- }),
103
- });
104
- // Start the server with stdio transport
105
- server.start({
106
- transportType: "stdio",
107
- });
@@ -1,130 +0,0 @@
1
- /**
2
- * Response Formatter
3
- *
4
- * Formats user answers according to PRD specification for returning to AI models
5
- */
6
- /**
7
- * ResponseFormatter - Formats session answers into human-readable text
8
- * according to the PRD specification
9
- */
10
- export class ResponseFormatter {
11
- /**
12
- * Format user answers into PRD-compliant text response
13
- *
14
- * Format specification:
15
- * - Header: "Here are the user's answers:"
16
- * - Numbered questions: "1. {prompt}"
17
- * - Arrow symbol for answers: "→ {label} — {description}"
18
- * - Custom text: "→ Other: '{customText}'"
19
- * - Double newline separation between questions
20
- *
21
- * @param answers - Session answer data containing user responses
22
- * @param questions - Original questions asked to the user
23
- * @returns Formatted text response ready to send to AI model
24
- */
25
- static formatUserResponse(answers, questions) {
26
- // Validate that we have matching questions and answers
27
- if (answers.answers.length === 0) {
28
- throw new Error("No answers provided in session");
29
- }
30
- if (questions.length === 0) {
31
- throw new Error("No questions provided");
32
- }
33
- // Start with header
34
- const lines = ["Here are the user's answers:", ""];
35
- // Format each question and its answer
36
- const formattedQuestions = [];
37
- for (let i = 0; i < questions.length; i++) {
38
- const question = questions[i];
39
- const answer = answers.answers.find((a) => a.questionIndex === i);
40
- if (!answer) {
41
- // If no answer found for this question, skip it
42
- // (This shouldn't happen in normal operation, but handle gracefully)
43
- continue;
44
- }
45
- // Format the question and answer
46
- const formattedQA = this.formatQuestion(question, answer, i + 1);
47
- formattedQuestions.push(formattedQA);
48
- }
49
- // Join formatted questions with blank lines between them
50
- lines.push(formattedQuestions.join("\n\n"));
51
- return lines.join("\n");
52
- }
53
- /**
54
- * Validate that answers match the questions
55
- *
56
- * @param answers - Session answer data
57
- * @param questions - Original questions
58
- * @throws Error if validation fails
59
- */
60
- static validateAnswers(answers, questions) {
61
- // Check that we have answers
62
- if (!answers || !answers.answers || answers.answers.length === 0) {
63
- throw new Error("No answers provided");
64
- }
65
- // Check that we have questions
66
- if (!questions || questions.length === 0) {
67
- throw new Error("No questions provided");
68
- }
69
- // Check each answer references a valid question
70
- for (const answer of answers.answers) {
71
- if (answer.questionIndex < 0 ||
72
- answer.questionIndex >= questions.length) {
73
- throw new Error(`Answer references invalid question index: ${answer.questionIndex}`);
74
- }
75
- // Check that answer has either selectedOption or customText
76
- if (!answer.selectedOption && !answer.customText) {
77
- throw new Error(`Answer for question ${answer.questionIndex} has neither selectedOption nor customText`);
78
- }
79
- // If selectedOption is provided, verify it exists in the question's options
80
- if (answer.selectedOption) {
81
- const question = questions[answer.questionIndex];
82
- const optionExists = question.options.some((opt) => opt.label === answer.selectedOption);
83
- if (!optionExists) {
84
- throw new Error(`Answer for question ${answer.questionIndex} references non-existent option: ${answer.selectedOption}`);
85
- }
86
- }
87
- }
88
- }
89
- /**
90
- * Format a single question and its answer
91
- *
92
- * @param question - The question data
93
- * @param answer - The user's answer
94
- * @param index - Question number (1-indexed for display)
95
- * @returns Formatted string for this question/answer pair
96
- */
97
- static formatQuestion(question, answer, index) {
98
- const lines = [];
99
- // Add question with number
100
- lines.push(`${index}. ${question.prompt}`);
101
- // Format the answer
102
- if (answer.customText) {
103
- // Custom text answer - escape single quotes for display
104
- const escapedText = answer.customText.replace(/'/g, "\\'");
105
- lines.push(`→ Other: '${escapedText}'`);
106
- }
107
- else if (answer.selectedOption) {
108
- // Selected option - find the option details
109
- const option = question.options.find((opt) => opt.label === answer.selectedOption);
110
- if (option) {
111
- // Format with description if available
112
- if (option.description) {
113
- lines.push(`→ ${option.label} — ${option.description}`);
114
- }
115
- else {
116
- lines.push(`→ ${option.label}`);
117
- }
118
- }
119
- else {
120
- // Option not found - shouldn't happen, but handle gracefully
121
- lines.push(`→ ${answer.selectedOption}`);
122
- }
123
- }
124
- else {
125
- // No answer provided - this shouldn't happen
126
- lines.push("→ No answer provided");
127
- }
128
- return lines.join("\n");
129
- }
130
- }