auq-mcp-server 0.1.6 → 0.1.8

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
@@ -3,9 +3,8 @@
3
3
  import { readFileSync } from "fs";
4
4
  import { dirname, join } from "path";
5
5
  import { fileURLToPath } from "url";
6
- import { Box, render, Text, useApp, useInput } from "ink";
6
+ import { Box, render, Text } from "ink";
7
7
  import React, { useEffect, useState } from "react";
8
- import { SessionManager } from "../src/session/SessionManager.js";
9
8
  import { ensureDirectoryExists, getSessionDirectory, } from "../src/session/utils.js";
10
9
  import { Header } from "../src/tui/components/Header.js";
11
10
  import { StepperView } from "../src/tui/components/StepperView.js";
@@ -62,7 +61,6 @@ if (command === "server") {
62
61
  await new Promise(() => { });
63
62
  }
64
63
  const App = () => {
65
- const { exit } = useApp();
66
64
  const [state, setState] = useState({ mode: "WAITING" });
67
65
  const [sessionQueue, setSessionQueue] = useState([]);
68
66
  const [isInitialized, setIsInitialized] = useState(false);
@@ -147,38 +145,20 @@ const App = () => {
147
145
  setState({ mode: "PROCESSING", session: nextSession });
148
146
  }
149
147
  }, [state, sessionQueue, isInitialized]);
150
- // Global 'q' to quit anytime
151
- useInput((input) => {
152
- if (input === "q") {
153
- // If processing a session, reject it before exiting
154
- if (state.mode === "PROCESSING") {
155
- const sessionManager = new SessionManager();
156
- sessionManager
157
- .rejectSession(state.session.sessionId)
158
- .catch((error) => {
159
- console.error("Failed to reject session on quit:", error);
160
- })
161
- .finally(() => {
162
- exit();
163
- });
164
- }
165
- else {
166
- exit();
167
- }
168
- }
169
- });
170
148
  // Show toast notification
171
- const showToast = (message, type = "success") => {
172
- setToast({ message, type });
149
+ const showToast = (message, type = "success", title) => {
150
+ setToast({ message, type, title });
173
151
  };
174
152
  // Handle session completion
175
153
  const handleSessionComplete = (wasRejected = false, rejectionReason) => {
176
154
  // Show appropriate toast
177
155
  if (wasRejected) {
178
- const message = rejectionReason
179
- ? `**Question set rejected**\nRejection reason: ${rejectionReason}`
180
- : "**Question set rejected**";
181
- showToast(message, "info");
156
+ if (rejectionReason) {
157
+ showToast(`Rejection reason: ${rejectionReason}`, "info", "Question set rejected");
158
+ }
159
+ else {
160
+ showToast("", "info", "Question set rejected");
161
+ }
182
162
  }
183
163
  else {
184
164
  showToast("✓ Answers submitted successfully!", "success");
@@ -211,7 +191,7 @@ const App = () => {
211
191
  return (React.createElement(Box, { flexDirection: "column", paddingX: 1 },
212
192
  React.createElement(Header, { pendingCount: sessionQueue.length }),
213
193
  toast && (React.createElement(Box, { marginBottom: 1, marginTop: 1 },
214
- React.createElement(Toast, { message: toast.message, onDismiss: () => setToast(null), type: toast.type }))),
194
+ React.createElement(Toast, { message: toast.message, onDismiss: () => setToast(null), type: toast.type, title: toast.title }))),
215
195
  mainContent,
216
196
  showSessionLog && (React.createElement(Box, { marginTop: 1 },
217
197
  React.createElement(Text, { dimColor: true },
@@ -21,7 +21,7 @@ const server = new FastMCP({
21
21
  "returning formatted responses for continued reasoning. " +
22
22
  "Each question supports 2-4 multiple-choice options with descriptions, and users can always provide custom text input. " +
23
23
  "Both single-select and multi-select modes are supported.",
24
- version: "0.1.5",
24
+ version: "0.1.8",
25
25
  });
26
26
  // Define the question and option schemas
27
27
  const OptionSchema = z.object({
@@ -73,7 +73,9 @@ export class ResponseFormatter {
73
73
  throw new Error(`Answer references invalid question index: ${answer.questionIndex}`);
74
74
  }
75
75
  // Check that answer has either selectedOption, selectedOptions, or customText
76
- if (!answer.selectedOption && !answer.customText && !answer.selectedOptions) {
76
+ if (!answer.selectedOption &&
77
+ !answer.customText &&
78
+ !answer.selectedOptions) {
77
79
  throw new Error(`Answer for question ${answer.questionIndex} has neither selectedOption, selectedOptions, nor customText`);
78
80
  }
79
81
  // If selectedOption is provided, verify it exists in the question's options
@@ -125,7 +127,9 @@ export class ResponseFormatter {
125
127
  }
126
128
  }
127
129
  }
128
- else if (answer.selectedOptions && answer.selectedOptions.length === 0 && !answer.customText) {
130
+ else if (answer.selectedOptions &&
131
+ answer.selectedOptions.length === 0 &&
132
+ !answer.customText) {
129
133
  // Multi-select with no selections and no custom text
130
134
  hasAnswer = true;
131
135
  lines.push("→ (No selection)");
@@ -485,10 +485,7 @@ describe("ResponseFormatter", () => {
485
485
  it("should validate empty selectedOptions array (no selections)", () => {
486
486
  const questions = [
487
487
  {
488
- options: [
489
- { label: "Feature A" },
490
- { label: "Feature B" },
491
- ],
488
+ options: [{ label: "Feature A" }, { label: "Feature B" }],
492
489
  prompt: "Which features do you want?",
493
490
  title: "Features",
494
491
  multiSelect: true,
@@ -551,11 +548,7 @@ describe("ResponseFormatter", () => {
551
548
  it("should format multi-select question with multiple selections (without descriptions)", () => {
552
549
  const questions = [
553
550
  {
554
- options: [
555
- { label: "Red" },
556
- { label: "Green" },
557
- { label: "Blue" },
558
- ],
551
+ options: [{ label: "Red" }, { label: "Green" }, { label: "Blue" }],
559
552
  prompt: "Select your favorite colors",
560
553
  title: "Colors",
561
554
  multiSelect: true,
@@ -581,10 +574,7 @@ describe("ResponseFormatter", () => {
581
574
  it("should format multi-select question with empty selections", () => {
582
575
  const questions = [
583
576
  {
584
- options: [
585
- { label: "Feature A" },
586
- { label: "Feature B" },
587
- ],
577
+ options: [{ label: "Feature A" }, { label: "Feature B" }],
588
578
  prompt: "Which optional features do you want?",
589
579
  title: "Optional Features",
590
580
  multiSelect: true,
@@ -54,8 +54,11 @@ export class PromiseFileWatcher extends EventEmitter {
54
54
  */
55
55
  async waitForFile(watchPath, fileName) {
56
56
  const fullPath = join(watchPath, fileName);
57
+ // Set isWatching early so active() returns true immediately
58
+ this.isWatching = true;
57
59
  // Fast-path: if file already exists, resolve immediately
58
60
  if (await fileExists(fullPath)) {
61
+ this.isWatching = false; // Not actually watching since file exists
59
62
  return fullPath;
60
63
  }
61
64
  return new Promise((resolve, reject) => {
@@ -96,11 +99,11 @@ export class PromiseFileWatcher extends EventEmitter {
96
99
  this.cleanup();
97
100
  reject(new Error(`File watcher error: ${error.message}`));
98
101
  });
99
- this.isWatching = true;
100
102
  }
101
103
  catch (error) {
102
104
  if (timeoutId)
103
105
  clearTimeout(timeoutId);
106
+ this.isWatching = false;
104
107
  reject(new Error(`File watcher setup error: ${error}`));
105
108
  }
106
109
  });
@@ -18,9 +18,12 @@ export const ConfirmationDialog = ({ message, onReject, onCancel, onQuit, }) =>
18
18
  onReject(null);
19
19
  };
20
20
  const options = [
21
- { key: "y", label: "Yes, inform the AI that I rejected this question set", action: () => setShowReasonInput(true) },
21
+ {
22
+ key: "y",
23
+ label: "Yes, inform the AI that I rejected this question set",
24
+ action: () => setShowReasonInput(true),
25
+ },
22
26
  { key: "n", label: "No, go back to answering questions", action: onCancel },
23
- { key: "q", label: "I'm just trying to quit the CLI, I'll answer later", action: onQuit },
24
27
  ];
25
28
  useInput((input, key) => {
26
29
  // If in reason input mode, handle Esc to skip
@@ -48,9 +51,6 @@ export const ConfirmationDialog = ({ message, onReject, onCancel, onQuit, }) =>
48
51
  if (input === "n" || input === "N") {
49
52
  onCancel();
50
53
  }
51
- if (input === "q" || input === "Q") {
52
- onQuit();
53
- }
54
54
  // Esc key - same as quit
55
55
  if (key.escape) {
56
56
  onQuit();
@@ -85,5 +85,5 @@ export const ConfirmationDialog = ({ message, onReject, onCancel, onQuit, }) =>
85
85
  ")")));
86
86
  }),
87
87
  React.createElement(Box, { marginTop: 1 },
88
- React.createElement(Text, { dimColor: true }, "\u2191\u2193 Navigate | Enter Select | y/n/q Shortcuts"))));
88
+ React.createElement(Text, { dimColor: true }, "\u2191\u2193 Navigate | Enter Select | y/n Shortcuts | Esc Quit"))));
89
89
  };
@@ -39,7 +39,7 @@ export const Footer = ({ focusContext, multiSelect, isReviewScreen = false, cust
39
39
  else {
40
40
  bindings.push({ key: "Enter", action: "Select" });
41
41
  }
42
- bindings.push({ key: "Esc", action: "Reject" }, { key: "q", action: "Quit" });
42
+ bindings.push({ key: "Esc", action: "Reject" });
43
43
  return bindings;
44
44
  }
45
45
  return [];
@@ -12,7 +12,9 @@ export const ReviewScreen = ({ answers, onConfirm, onGoBack, questions, }) => {
12
12
  // Convert answers to UserAnswer format
13
13
  const userAnswers = [];
14
14
  answers.forEach((answer, questionIndex) => {
15
- if (answer.selectedOption || answer.selectedOptions || answer.customText) {
15
+ if (answer.selectedOption ||
16
+ answer.selectedOptions ||
17
+ answer.customText) {
16
18
  userAnswers.push({
17
19
  customText: answer.customText,
18
20
  questionIndex,
@@ -40,21 +42,26 @@ export const ReviewScreen = ({ answers, onConfirm, onGoBack, questions, }) => {
40
42
  ". ",
41
43
  question.prompt),
42
44
  React.createElement(Box, { flexDirection: "column", marginLeft: 2, marginTop: 0.5 },
43
- answer?.selectedOptions && answer.selectedOptions.length > 0 && (React.createElement(React.Fragment, null, answer.selectedOptions.map((option, idx) => (React.createElement(Text, { key: idx, color: theme.components.review.selectedOption },
45
+ answer?.selectedOptions &&
46
+ answer.selectedOptions.length > 0 && (React.createElement(React.Fragment, null, answer.selectedOptions.map((option, idx) => (React.createElement(Text, { key: idx, color: theme.components.review.selectedOption },
44
47
  "\u2192 ",
45
48
  option))))),
46
49
  answer?.selectedOption && (React.createElement(Text, { color: theme.components.review.selectedOption },
47
50
  "\u2192 ",
48
51
  answer.selectedOption)),
49
- answer?.customText && (React.createElement(React.Fragment, null, answer.customText.split("\n").map((line, lineIndex, lines) => {
52
+ answer?.customText && (React.createElement(React.Fragment, null, answer.customText
53
+ .split("\n")
54
+ .map((line, lineIndex, lines) => {
50
55
  const isFirstLine = lineIndex === 0;
51
56
  const isLastLine = lineIndex === lines.length - 1;
52
57
  return (React.createElement(Text, { key: lineIndex, color: theme.components.review.customAnswer },
53
- isFirstLine ? "→ Custom: \"" : " ",
58
+ isFirstLine ? '→ Custom: "' : " ",
54
59
  line,
55
- isLastLine ? "\"" : ""));
60
+ isLastLine ? '"' : ""));
56
61
  }))),
57
- !answer?.selectedOption && !answer?.selectedOptions && !answer?.customText && (React.createElement(Text, { dimColor: true }, "\u2192 (No answer provided)")))));
62
+ !answer?.selectedOption &&
63
+ !answer?.selectedOptions &&
64
+ !answer?.customText && (React.createElement(Text, { dimColor: true }, "\u2192 (No answer provided)")))));
58
65
  })),
59
66
  React.createElement(Footer, { focusContext: "option", multiSelect: false, isReviewScreen: true })));
60
67
  };
@@ -78,7 +78,9 @@ export const StepperView = ({ onComplete, sessionId, sessionRequest, }) => {
78
78
  const handleConfirm = async (userAnswers) => {
79
79
  setSubmitting(true);
80
80
  try {
81
- const sessionManager = new SessionManager({ baseDir: getSessionDirectory() });
81
+ const sessionManager = new SessionManager({
82
+ baseDir: getSessionDirectory(),
83
+ });
82
84
  await sessionManager.saveSessionAnswers(sessionId, {
83
85
  answers: userAnswers,
84
86
  sessionId,
@@ -115,7 +117,9 @@ export const StepperView = ({ onComplete, sessionId, sessionRequest, }) => {
115
117
  // Handle session rejection
116
118
  const handleRejectSession = async (reason) => {
117
119
  try {
118
- const sessionManager = new SessionManager({ baseDir: getSessionDirectory() });
120
+ const sessionManager = new SessionManager({
121
+ baseDir: getSessionDirectory(),
122
+ });
119
123
  await sessionManager.rejectSession(sessionId, reason);
120
124
  // Call onComplete with rejection flag and reason
121
125
  if (onComplete) {
@@ -4,7 +4,7 @@ import React, { useEffect } from "react";
4
4
  * Toast component for brief non-blocking notifications
5
5
  * Auto-dismisses after specified duration (default 2000ms)
6
6
  */
7
- export const Toast = ({ message, type = "success", onDismiss, duration = 2000, }) => {
7
+ export const Toast = ({ message, type = "success", onDismiss, duration = 2000, title, }) => {
8
8
  // Auto-dismiss after duration
9
9
  useEffect(() => {
10
10
  const timer = setTimeout(() => {
@@ -14,6 +14,7 @@ export const Toast = ({ message, type = "success", onDismiss, duration = 2000, }
14
14
  }, [duration, onDismiss]);
15
15
  // Color based on type
16
16
  const color = type === "success" ? "green" : type === "error" ? "red" : "cyan";
17
- return (React.createElement(Box, { borderColor: color, borderStyle: "round", paddingX: 2, paddingY: 0 },
18
- React.createElement(Text, { bold: true, color: color }, message)));
17
+ return (React.createElement(Box, { borderColor: color, borderStyle: "round", paddingX: 2, paddingY: 0, flexDirection: "column" },
18
+ title && (React.createElement(Text, { bold: true, color: color }, title)),
19
+ React.createElement(Text, { color: color }, message)));
19
20
  };
@@ -55,7 +55,8 @@ export class EnhancedTUISessionWatcher extends TUISessionWatcher {
55
55
  const status = JSON.parse(statusContent);
56
56
  // Only include sessions that are actually pending or in-progress
57
57
  // Exclude: rejected, completed, timed_out, abandoned
58
- if (status.status === "pending" || status.status === "in-progress") {
58
+ if (status.status === "pending" ||
59
+ status.status === "in-progress") {
59
60
  pendingSessions.push(entry.name);
60
61
  }
61
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auq-mcp-server",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "auq": "dist/bin/auq.js"
@@ -5,47 +5,49 @@
5
5
  * Provides instructions for setting up shell aliases
6
6
  */
7
7
 
8
- const os = require('os');
9
- const path = require('path');
8
+ const os = require("os");
9
+ const path = require("path");
10
10
 
11
11
  // Check if this is a global installation
12
- const isGlobal = process.env.npm_config_global === 'true';
12
+ const isGlobal = process.env.npm_config_global === "true";
13
13
 
14
14
  if (!isGlobal) {
15
15
  // Local install - no setup needed
16
16
  process.exit(0);
17
17
  }
18
18
 
19
- console.log('\n✅ AUQ MCP Server installed successfully!\n');
20
- console.log('📝 Optional: Set up shell aliases for convenience\n');
19
+ console.log("\n✅ AUQ MCP Server installed successfully!\n");
20
+ console.log("📝 Optional: Set up shell aliases for convenience\n");
21
21
 
22
22
  const homeDir = os.homedir();
23
- const shell = process.env.SHELL || '';
23
+ const shell = process.env.SHELL || "";
24
24
 
25
25
  // Determine shell config file
26
- let configFile = '';
27
- let aliasCommand = '';
26
+ let configFile = "";
27
+ let aliasCommand = "";
28
28
 
29
- if (shell.includes('zsh')) {
30
- configFile = path.join(homeDir, '.zshrc');
29
+ if (shell.includes("zsh")) {
30
+ configFile = path.join(homeDir, ".zshrc");
31
31
  aliasCommand = 'alias auq="npx auq-mcp-server"';
32
- } else if (shell.includes('bash')) {
33
- configFile = path.join(homeDir, '.bashrc');
32
+ } else if (shell.includes("bash")) {
33
+ configFile = path.join(homeDir, ".bashrc");
34
34
  aliasCommand = 'alias auq="npx auq-mcp-server"';
35
- } else if (shell.includes('fish')) {
36
- configFile = path.join(homeDir, '.config/fish/config.fish');
35
+ } else if (shell.includes("fish")) {
36
+ configFile = path.join(homeDir, ".config/fish/config.fish");
37
37
  aliasCommand = 'alias auq "npx auq-mcp-server"';
38
38
  }
39
39
 
40
40
  if (configFile) {
41
- console.log(`To set up a shell alias, add this to your ${path.basename(configFile)}:\n`);
41
+ console.log(
42
+ `To set up a shell alias, add this to your ${path.basename(configFile)}:\n`,
43
+ );
42
44
  console.log(` ${aliasCommand}\n`);
43
45
  console.log(`Then restart your terminal or run: source ${configFile}\n`);
44
46
  } else {
45
- console.log('To set up a shell alias, add this to your shell config:\n');
47
+ console.log("To set up a shell alias, add this to your shell config:\n");
46
48
  console.log(' alias auq="npx auq-mcp-server"\n');
47
- console.log('Then restart your terminal.\n');
49
+ console.log("Then restart your terminal.\n");
48
50
  }
49
51
 
50
- console.log('For MCP server setup with Claude Desktop or Cursor, see:');
51
- console.log(' https://github.com/paulp-o/ask-user-questions-mcp#setup\n');
52
+ console.log("For MCP server setup with Claude Desktop or Cursor, see:");
53
+ console.log(" https://github.com/paulp-o/ask-user-questions-mcp#setup\n");