auq-mcp-server 0.1.24 → 1.2.5

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/dist/bin/auq.js CHANGED
@@ -321,12 +321,23 @@ const App = () => {
321
321
  // Clear terminal before showing app
322
322
  console.clear();
323
323
  const { waitUntilExit } = render(React.createElement(App, null));
324
+ // Periodically clear performance measures to prevent memory leak warnings
325
+ // React development mode creates performance marks that can accumulate over time
326
+ const clearPerformanceBuffer = () => {
327
+ if (typeof performance !== 'undefined' && performance.clearMeasures) {
328
+ performance.clearMeasures();
329
+ }
330
+ };
331
+ // Clear performance buffer every 5 minutes to prevent accumulation
332
+ const performanceCleanupInterval = setInterval(clearPerformanceBuffer, 5 * 60 * 1000);
324
333
  // Handle Ctrl+C gracefully
325
334
  process.on("SIGINT", () => {
335
+ clearInterval(performanceCleanupInterval);
326
336
  process.exit(0);
327
337
  });
328
338
  // Show goodbye after Ink unmounts
329
339
  waitUntilExit().then(() => {
340
+ clearInterval(performanceCleanupInterval);
330
341
  process.stdout.write("\n");
331
342
  console.log("👋 Goodbye! See you next time.");
332
343
  });
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auq-mcp-server",
3
- "version": "0.1.24",
3
+ "version": "1.2.5",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "auq": "dist/bin/auq.js"
@@ -15,7 +15,7 @@
15
15
  "packages/opencode-plugin"
16
16
  ],
17
17
  "scripts": {
18
- "build": "tsc",
18
+ "build": "npm run sync-schemas && tsc",
19
19
  "prepare": "npm run build",
20
20
  "postinstall": "node scripts/postinstall.cjs",
21
21
  "deploy": "node scripts/deploy.mjs",
@@ -23,6 +23,7 @@
23
23
  "start": "tsx src/server.ts",
24
24
  "dev": "fastmcp dev src/server.ts",
25
25
  "lint": "prettier --check . && eslint . && tsc --noEmit",
26
+ "sync-schemas": "node scripts/sync-schemas.mjs",
26
27
  "test": "vitest run",
27
28
  "format": "prettier --write . && eslint --fix ."
28
29
  },
@@ -83,7 +83,7 @@ describe("Schema Validation - Edge Cases", () => {
83
83
  const parsed = QuestionSchema.parse(validQuestion);
84
84
  expect(parsed.title).toBe("Language");
85
85
  expect(parsed.prompt).toBe("What is your choice?");
86
- expect(parsed.options).toHaveLength(1);
86
+ expect(parsed.options).toHaveLength(2);
87
87
  });
88
88
  it("should accept valid question with all fields", () => {
89
89
  const validQuestion = {
@@ -104,7 +104,7 @@ describe("Schema Validation - Edge Cases", () => {
104
104
  expect(() => QuestionSchema.parse(validQuestion)).not.toThrow();
105
105
  const parsed = QuestionSchema.parse(validQuestion);
106
106
  expect(parsed.prompt).toBe("What is your choice?");
107
- expect(parsed.options).toHaveLength(1);
107
+ expect(parsed.options).toHaveLength(2);
108
108
  });
109
109
  it("should accept valid question with description omitted", () => {
110
110
  const validQuestion = {
@@ -1,47 +1,8 @@
1
- import { z } from "zod";
2
1
  import { SessionManager } from "../session/index.js";
3
2
  import { getSessionDirectory } from "../session/utils.js";
4
- export const OptionSchema = z.object({
5
- label: z
6
- .string()
7
- .describe("The display text for this option that the user will see and select. " +
8
- "Should be concise (1-5 words) and clearly describe the choice."),
9
- description: z
10
- .string()
11
- .optional()
12
- .describe("Explanation of what this option means or what will happen if chosen. " +
13
- "Useful for providing context about trade-offs or implications."),
14
- });
15
- export const QuestionSchema = z.object({
16
- prompt: z
17
- .string()
18
- .describe("The complete question to ask the user. Should be clear, specific, and end with a question mark. " +
19
- "Example: 'Which programming language do you want to use?' " +
20
- "If multiSelect is true, phrase it accordingly, e.g. 'Which features do you want to enable?'"),
21
- title: z
22
- .string()
23
- .min(1, "Question title is required. Provide a short summary like 'Language' or 'Framework'.")
24
- .describe("Very short label displayed as a chip/tag (max 12 chars). " +
25
- "Examples: 'Auth method', 'Library', 'Approach'. " +
26
- "This title appears in the interface to help users quickly identify questions."),
27
- options: z
28
- .array(OptionSchema)
29
- .min(2)
30
- .max(4)
31
- .describe("The available choices for this question. Must have 2-4 options. " +
32
- "Each option should be a distinct, mutually exclusive choice (unless multiSelect is enabled). " +
33
- "There should be no 'Other' option, that will be provided automatically."),
34
- multiSelect: z
35
- .boolean()
36
- .describe("Set to true to allow the user to select multiple options instead of just one. " +
37
- "Use when choices are not mutually exclusive. Default: false (single-select)"),
38
- });
39
- export const QuestionsSchema = z.array(QuestionSchema).min(1).max(4);
40
- export const AskUserQuestionsParametersSchema = z.object({
41
- questions: QuestionsSchema.describe("Questions to ask the user (1-4 questions). " +
42
- "Each question must include: prompt (full question text), title (short label, max 12 chars), " +
43
- "options (2-4 choices with labels and descriptions), and multiSelect (boolean)."),
44
- });
3
+ import { AskUserQuestionsParametersSchema, QuestionSchema, QuestionsSchema, } from "../shared/schemas.js";
4
+ // Re-export schemas for backward compatibility
5
+ export { QuestionSchema, QuestionsSchema, AskUserQuestionsParametersSchema };
45
6
  export const createAskUserQuestionsCore = (options = {}) => {
46
7
  const baseDir = options.baseDir ?? getSessionDirectory();
47
8
  const sessionManager = options.sessionManager ?? new SessionManager({ baseDir });
@@ -0,0 +1,64 @@
1
+ import { z } from "zod";
2
+ export const OptionSchema = z.object({
3
+ label: z
4
+ .string()
5
+ .describe("The display text for this option that the user will see and select. " +
6
+ "Should be concise (1-5 words) and clearly describe the choice."),
7
+ description: z
8
+ .string()
9
+ .optional()
10
+ .describe("Explanation of what this option means or what will happen if chosen. " +
11
+ "Useful for providing context about trade-offs or implications."),
12
+ });
13
+ export const QuestionSchema = z.object({
14
+ prompt: z
15
+ .string()
16
+ .describe("The complete question to ask the user. Should be clear, specific, and end with a question mark. " +
17
+ "Example: 'Which programming language do you want to use?' " +
18
+ "If multiSelect is true, phrase it accordingly, e.g. 'Which features do you want to enable?'"),
19
+ title: z
20
+ .string()
21
+ .min(1, "Question title is required. Provide a short summary like 'Language' or 'Framework'.")
22
+ .describe("Very short label displayed as a chip/tag (max 12 chars). " +
23
+ "Examples: 'Auth method', 'Library', 'Approach'. " +
24
+ "This title appears in the interface to help users quickly identify questions."),
25
+ options: z
26
+ .array(OptionSchema)
27
+ .min(2)
28
+ .max(4)
29
+ .describe("The available choices for this question. Must have 2-4 options. " +
30
+ "Each option should be a distinct, mutually exclusive choice (unless multiSelect is enabled). " +
31
+ "There should be no 'Other' option, that will be provided automatically."),
32
+ multiSelect: z
33
+ .boolean()
34
+ .describe("Set to true to allow the user to select multiple options instead of just one. " +
35
+ "Use when choices are not mutually exclusive. Default: false (single-select)"),
36
+ });
37
+ export const QuestionsSchema = z.array(QuestionSchema).min(1).max(4);
38
+ export const AskUserQuestionsParametersSchema = z.object({
39
+ questions: QuestionsSchema.describe("Questions to ask the user (1-4 questions). " +
40
+ "Each question must include: prompt (full question text), title (short label, max 12 chars), " +
41
+ "options (2-4 choices with labels and descriptions), and multiSelect (boolean). " +
42
+ "Mark one choice as recommended if possible."),
43
+ });
44
+ export const TOOL_DESCRIPTION = "Use this tool when you need to ask the user questions during execution. " +
45
+ "This allows you to:\n" +
46
+ "1. Gather user preferences or requirements\n" +
47
+ "2. Clarify ambiguous instructions\n" +
48
+ "3. Get decisions on implementation choices as you work\n" +
49
+ "4. Offer choices to the user about what direction to take\n\n" +
50
+ "FEATURES:\n" +
51
+ "- Ask 1-4 structured questions via an interactive terminal interface\n" +
52
+ "- Each question includes 2-4 multiple-choice options with explanatory descriptions\n" +
53
+ "- Users can always provide custom free-text input as an alternative to predefined options\n" +
54
+ "- Single-select mode (default): User picks ONE option or provides custom text\n" +
55
+ "- Multi-select mode (multiSelect: true): User can select MULTIPLE options\n\n" +
56
+ "USAGE NOTES:\n" +
57
+ "- Always provide a descriptive 'title' field (max 12 chars) for each question\n" +
58
+ "- Use multiSelect: true when choices are not mutually exclusive\n" +
59
+ "- Option labels should be concise (1-5 words)\n" +
60
+ "- Questions should end with a question mark\n" +
61
+ "- Don't include an 'Other' option - it's provided automatically\n" +
62
+ "- Mark one choice as recommended if possible.\n\n" +
63
+ "- If user seems not aware how to answer the question, inform them that they need to install the 'auq' cli tool.\n\n" +
64
+ "Returns a formatted summary of all questions and answers.";
@@ -1,12 +1,19 @@
1
1
  import { Box, Text, useInput } from "ink";
2
- import React from "react";
2
+ import React, { useState } from "react";
3
3
  import { theme } from "../theme.js";
4
4
  /**
5
- * Multi-line text input component for Ink
6
- * Append-only mode: Shift+Enter for newlines, Enter to submit
7
- * Note: No cursor positioning - text is append-only for simplicity
5
+ * Multi-line text input component for Ink with cursor positioning
6
+ * Supports left/right arrow keys for cursor movement
7
+ * Shift+Enter for newlines, Enter to submit
8
8
  */
9
9
  export const MultiLineTextInput = ({ isFocused = true, onChange, onSubmit, placeholder = "Type your answer...", value, }) => {
10
+ const [cursorPosition, setCursorPosition] = useState(value.length);
11
+ // Update cursor position when value changes externally
12
+ React.useEffect(() => {
13
+ if (cursorPosition > value.length) {
14
+ setCursorPosition(value.length);
15
+ }
16
+ }, [value.length, cursorPosition]);
10
17
  useInput((input, key) => {
11
18
  if (!isFocused)
12
19
  return;
@@ -14,7 +21,9 @@ export const MultiLineTextInput = ({ isFocused = true, onChange, onSubmit, place
14
21
  // Prevent accidental carriage return insertion which causes line overwrite in terminals.
15
22
  if (input === "\r" || input === "\n") {
16
23
  if (key.shift) {
17
- onChange(value + "\n");
24
+ const newValue = value.slice(0, cursorPosition) + "\n" + value.slice(cursorPosition);
25
+ onChange(newValue);
26
+ setCursorPosition(cursorPosition + 1);
18
27
  }
19
28
  else if (onSubmit) {
20
29
  onSubmit();
@@ -23,7 +32,9 @@ export const MultiLineTextInput = ({ isFocused = true, onChange, onSubmit, place
23
32
  }
24
33
  // Shift+Enter: Add newline
25
34
  if (key.return && key.shift) {
26
- onChange(value + "\n");
35
+ const newValue = value.slice(0, cursorPosition) + "\n" + value.slice(cursorPosition);
36
+ onChange(newValue);
37
+ setCursorPosition(cursorPosition + 1);
27
38
  return;
28
39
  }
29
40
  // Enter: Submit (empty input allowed)
@@ -33,33 +44,61 @@ export const MultiLineTextInput = ({ isFocused = true, onChange, onSubmit, place
33
44
  }
34
45
  return;
35
46
  }
36
- // Backspace: Remove last character
47
+ // Left arrow: Move cursor left
48
+ if (key.leftArrow) {
49
+ setCursorPosition(Math.max(0, cursorPosition - 1));
50
+ return;
51
+ }
52
+ // Right arrow: Move cursor right
53
+ if (key.rightArrow) {
54
+ setCursorPosition(Math.min(value.length, cursorPosition + 1));
55
+ return;
56
+ }
57
+ // Backspace: Remove character before cursor
37
58
  if (key.backspace || key.delete) {
38
- onChange(value.slice(0, -1));
59
+ if (cursorPosition > 0) {
60
+ const newValue = value.slice(0, cursorPosition - 1) + value.slice(cursorPosition);
61
+ onChange(newValue);
62
+ setCursorPosition(cursorPosition - 1);
63
+ }
39
64
  return;
40
65
  }
41
- // Regular character input (append)
66
+ // Regular character input (insert at cursor)
42
67
  if (input &&
43
68
  !key.ctrl &&
44
69
  !key.meta &&
45
70
  !key.escape &&
46
71
  input !== "\r" &&
47
72
  input !== "\n") {
48
- onChange(value + input);
73
+ const newValue = value.slice(0, cursorPosition) + input + value.slice(cursorPosition);
74
+ onChange(newValue);
75
+ setCursorPosition(cursorPosition + 1);
49
76
  }
50
77
  }, { isActive: isFocused });
51
78
  // Normalize any carriage returns that might already be present in value
52
79
  const normalizedValue = value.replace(/\r\n?/g, "\n");
53
80
  const hasContent = normalizedValue.length > 0;
54
81
  const lines = hasContent ? normalizedValue.split("\n") : [placeholder];
82
+ // Calculate which line and position the cursor is on
83
+ const cursorLineIndex = normalizedValue.slice(0, cursorPosition).split("\n").length - 1;
84
+ const cursorLineStart = normalizedValue.split("\n").slice(0, cursorLineIndex).join("\n").length + (cursorLineIndex > 0 ? cursorLineIndex : 0);
85
+ const cursorPositionInLine = cursorPosition - cursorLineStart;
55
86
  return (React.createElement(Box, { flexDirection: "column" }, lines.map((line, index) => {
56
- const isLastLine = index === lines.length - 1;
57
- const showCursor = isFocused && isLastLine;
87
+ const isCursorLine = isFocused && index === cursorLineIndex && hasContent;
58
88
  const isPlaceholder = !hasContent;
59
89
  const lineHasContent = line.length > 0;
60
- const displayText = lineHasContent ? line : showCursor ? "" : " ";
90
+ const displayText = lineHasContent ? line : isCursorLine ? "" : " ";
91
+ if (isCursorLine) {
92
+ // Split the line at cursor position
93
+ const beforeCursor = line.slice(0, cursorPositionInLine);
94
+ const afterCursor = line.slice(cursorPositionInLine);
95
+ return (React.createElement(Text, { key: index },
96
+ beforeCursor,
97
+ React.createElement(Text, { color: theme.colors.focused, dimColor: true }, "\u258C"),
98
+ afterCursor));
99
+ }
61
100
  return (React.createElement(Text, { key: index, dimColor: isPlaceholder },
62
101
  displayText,
63
- showCursor && (React.createElement(Text, { color: theme.colors.focused, dimColor: true }, "\u258C"))));
102
+ isFocused && index === lines.length - 1 && !hasContent && (React.createElement(Text, { color: theme.colors.focused, dimColor: true }, "\u258C"))));
64
103
  })));
65
104
  };
@@ -22,6 +22,14 @@ export const OptionsList = ({ isFocused, onSelect, options, selectedOption, show
22
22
  useInput((input, key) => {
23
23
  if (!isFocused)
24
24
  return;
25
+ // When custom input is focused, only handle escape to exit, let MultiLineTextInput handle other keys
26
+ if (isCustomInputFocused) {
27
+ if (key.escape) {
28
+ // Escape: Exit custom input mode and go back to option navigation
29
+ setFocusedIndex(options.length - 1); // Focus on last option
30
+ }
31
+ return;
32
+ }
25
33
  if (key.upArrow) {
26
34
  setFocusedIndex((prev) => Math.max(0, prev - 1));
27
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auq-mcp-server",
3
- "version": "0.1.24",
3
+ "version": "1.2.5",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "auq": "dist/bin/auq.js"
@@ -15,7 +15,7 @@
15
15
  "packages/opencode-plugin"
16
16
  ],
17
17
  "scripts": {
18
- "build": "tsc",
18
+ "build": "npm run sync-schemas && tsc",
19
19
  "prepare": "npm run build",
20
20
  "postinstall": "node scripts/postinstall.cjs",
21
21
  "deploy": "node scripts/deploy.mjs",
@@ -23,6 +23,7 @@
23
23
  "start": "tsx src/server.ts",
24
24
  "dev": "fastmcp dev src/server.ts",
25
25
  "lint": "prettier --check . && eslint . && tsc --noEmit",
26
+ "sync-schemas": "node scripts/sync-schemas.mjs",
26
27
  "test": "vitest run",
27
28
  "format": "prettier --write . && eslint --fix ."
28
29
  },