@paulp-o/opencode-auq 1.3.2 → 2.0.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 CHANGED
@@ -5,8 +5,8 @@ OpenCode plugin that forwards `ask_user_questions` to the `auq ask` CLI.
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install -g auq-mcp-server
9
- npm install -g @paulp-o/opencode-auq
8
+ bun add -g auq-mcp-server
9
+ bun add -g @paulp-o/opencode-auq
10
10
  ```
11
11
 
12
12
  ## Configure OpenCode
@@ -0,0 +1,16 @@
1
+ /**
2
+ * AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
3
+ *
4
+ * This file is generated from src/shared/schemas.ts by scripts/sync-plugin-schemas.mjs
5
+ * Run "npm run sync-plugin-schemas" to regenerate.
6
+ *
7
+ * Generated at: 2026-02-06T03:21:22.056Z
8
+ */
9
+ /**
10
+ * Comprehensive tool description - single source of truth.
11
+ * Used by MCP server and should be synced to opencode-plugin.
12
+ */
13
+ declare const TOOL_DESCRIPTION: string;
14
+ export declare const AskUserQuestionsArgs: any;
15
+ export { TOOL_DESCRIPTION };
16
+ //# sourceMappingURL=generated-schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated-schemas.d.ts","sourceRoot":"","sources":["../src/generated-schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA4IH;;;GAGG;AACH,QAAA,MAAM,gBAAgB,QAWoD,CAAC;AAI3E,eAAO,MAAM,oBAAoB,EAAE,GAA4C,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
3
+ *
4
+ * This file is generated from src/shared/schemas.ts by scripts/sync-plugin-schemas.mjs
5
+ * Run "npm run sync-plugin-schemas" to regenerate.
6
+ *
7
+ * Generated at: 2026-02-06T03:21:22.056Z
8
+ */
9
+ import { tool } from "@opencode-ai/plugin/tool";
10
+ const z = tool.schema;
11
+ /**
12
+ * Default limits - can be overridden by config
13
+ * These are the hard maximums enforced by the schema (config can only reduce, not increase)
14
+ */
15
+ const SCHEMA_LIMITS = {
16
+ MAX_OPTIONS: 10,
17
+ MAX_QUESTIONS: 10,
18
+ MIN_OPTIONS: 2,
19
+ MIN_QUESTIONS: 1,
20
+ };
21
+ /**
22
+ * Default recommended limits (used when no config is provided)
23
+ */
24
+ const DEFAULT_LIMITS = {
25
+ maxOptions: 5,
26
+ maxQuestions: 5,
27
+ recommendedOptions: 4,
28
+ recommendedQuestions: 4,
29
+ };
30
+ const OptionSchema = z.object({
31
+ label: z
32
+ .string()
33
+ .describe("The display text for this option. Should be concise (1-5 words). " +
34
+ "To mark as recommended, append '(recommended)' to the label text."),
35
+ description: z
36
+ .string()
37
+ .optional()
38
+ .describe("Explanation of what this option means or what will happen if chosen. " +
39
+ "Useful for providing context about trade-offs or implications."),
40
+ });
41
+ /**
42
+ * Create a QuestionSchema with configurable option limits
43
+ * @param maxOptions - Maximum number of options allowed (default: 4, max: 10)
44
+ */
45
+ function createQuestionSchema(maxOptions = DEFAULT_LIMITS.maxOptions) {
46
+ // Clamp to valid range
47
+ const effectiveMax = Math.min(Math.max(maxOptions, SCHEMA_LIMITS.MIN_OPTIONS), SCHEMA_LIMITS.MAX_OPTIONS);
48
+ return z.object({
49
+ prompt: z
50
+ .string()
51
+ .describe("The complete question to ask the user. Should be clear, specific, and end with a question mark. " +
52
+ "Example: 'Which programming language do you want to use?' " +
53
+ "If multiSelect is true, phrase it accordingly, e.g. 'Which features do you want to enable?'"),
54
+ title: z
55
+ .string()
56
+ .min(1, "Question title is required. Provide a short summary like 'Language' or 'Framework'.")
57
+ .describe("Very short label displayed as a chip/tag (max 12 chars). " +
58
+ "Examples: 'Auth method', 'Library', 'Approach'. " +
59
+ "This title appears in the interface to help users quickly identify questions."),
60
+ options: z
61
+ .array(OptionSchema)
62
+ .min(SCHEMA_LIMITS.MIN_OPTIONS)
63
+ .max(effectiveMax)
64
+ .describe(`The available choices for this question. Must have ${SCHEMA_LIMITS.MIN_OPTIONS}-${effectiveMax} options. ` +
65
+ "Each option should be a distinct, mutually exclusive choice (unless multiSelect is enabled). " +
66
+ "There should be no 'Other' option, that will be provided automatically."),
67
+ multiSelect: z
68
+ .boolean()
69
+ .describe("Set to true to allow the user to select multiple options instead of just one. " +
70
+ "Use when choices are not mutually exclusive. Default: false (single-select)"),
71
+ });
72
+ }
73
+ /**
74
+ * Create a QuestionsSchema with configurable limits
75
+ * @param maxQuestions - Maximum number of questions allowed (default: 4, max: 10)
76
+ * @param maxOptions - Maximum number of options per question (default: 4, max: 10)
77
+ */
78
+ function createQuestionsSchema(maxQuestions = DEFAULT_LIMITS.maxQuestions, maxOptions = DEFAULT_LIMITS.maxOptions) {
79
+ const effectiveMaxQuestions = Math.min(Math.max(maxQuestions, SCHEMA_LIMITS.MIN_QUESTIONS), SCHEMA_LIMITS.MAX_QUESTIONS);
80
+ const questionSchema = createQuestionSchema(maxOptions);
81
+ return z
82
+ .array(questionSchema)
83
+ .min(SCHEMA_LIMITS.MIN_QUESTIONS)
84
+ .max(effectiveMaxQuestions);
85
+ }
86
+ /**
87
+ * Create the full parameters schema with configurable limits
88
+ * @param maxQuestions - Maximum number of questions (default: 4, max: 10)
89
+ * @param maxOptions - Maximum number of options per question (default: 4, max: 10)
90
+ */
91
+ function createAskUserQuestionsParametersSchema(maxQuestions = DEFAULT_LIMITS.maxQuestions, maxOptions = DEFAULT_LIMITS.maxOptions) {
92
+ const questionsSchema = createQuestionsSchema(maxQuestions, maxOptions);
93
+ return z.object({
94
+ questions: questionsSchema.describe(`Questions to ask the user (1-${maxQuestions} questions). ` +
95
+ `Each question must include: prompt (full question text), title (short label, max 12 chars), ` +
96
+ `options (2-${maxOptions} choices with labels and descriptions), and multiSelect (boolean).`),
97
+ });
98
+ }
99
+ // Default schemas for backward compatibility (using DEFAULT_LIMITS)
100
+ const QuestionSchema = createQuestionSchema();
101
+ const QuestionsSchema = createQuestionsSchema();
102
+ const AskUserQuestionsParametersSchema = createAskUserQuestionsParametersSchema();
103
+ /**
104
+ * Comprehensive tool description - single source of truth.
105
+ * Used by MCP server and should be synced to opencode-plugin.
106
+ */
107
+ const TOOL_DESCRIPTION = "Use this tool when you need to ask the user questions during execution. This allows you to:\n\n" +
108
+ "Gather user preferences or requirements\n" +
109
+ "Clarify ambiguous instructions\n" +
110
+ "Get decisions on implementation choices as you work\n" +
111
+ "Offer choices to the user about what direction to take.\n" +
112
+ "Usage notes:\n\n" +
113
+ 'Users will always be able to select "Other" to provide custom text input\n' +
114
+ "Use multiSelect: true to allow multiple answers to be selected for a question\n" +
115
+ 'Recommend an option unless absolutely necessary, make it the first option in the list and add "(Recommended)" at the end of the label\n' +
116
+ 'For multiSelect questions, you MAY mark multiple options as "(Recommended)" if several choices are advisable\n' +
117
+ 'Do NOT use this tool to ask "Is my plan ready?" or "Should I proceed?"';
118
+ // Only export what the plugin needs
119
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
+ export const AskUserQuestionsArgs = AskUserQuestionsParametersSchema.shape;
121
+ export { TOOL_DESCRIPTION };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAyHlD,eAAO,MAAM,sBAAsB,EAAE,MAcnC,CAAC;AAEH,eAAe,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAuFlD,eAAO,MAAM,sBAAsB,EAAE,MAgDnC,CAAC;AAEH,eAAe,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -1,55 +1,35 @@
1
1
  import { spawn } from "child_process";
2
2
  import { tool } from "@opencode-ai/plugin/tool";
3
- const z = tool.schema;
4
- 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
- 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
- const QuestionsSchema = z.array(QuestionSchema).min(1).max(4);
40
- 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
- "Mark one choice as recommended if possible."),
45
- });
46
- const TOOL_DESCRIPTION = "Ask users structured questions during execution to gather preferences, clarify requirements, or make implementation decisions.\n\n" +
47
- "FEATURES:\n" +
48
- "- Non-blocking: doesn't halt AI workflow\n" +
49
- "- 1-4 questions with 2-4 options each\n" +
50
- "- Single/multi-select modes\n" +
51
- "- Custom text input always available\n\n" +
52
- "Returns formatted responses for continued reasoning.";
3
+ // Schemas are auto-generated from src/shared/schemas.ts
4
+ // Run "npm run sync-plugin-schemas" to regenerate
5
+ import { AskUserQuestionsArgs, TOOL_DESCRIPTION } from "./generated-schemas.js";
6
+ // Map tool callID -> derived title (used by tool.execute.after)
7
+ const titleByCallId = new Map();
8
+ /**
9
+ * Generate a summary title for the question set
10
+ * Format: "Questions: [Q0] Language, [Q1] Framework"
11
+ */
12
+ function generateQuestionSummary(questions) {
13
+ const questionLabels = questions
14
+ .map((q, i) => `[Q${i}] ${q.title}`)
15
+ .join(", ");
16
+ return `Questions: ${questionLabels}`;
17
+ }
18
+ function safeSummaryFromArgs(args) {
19
+ if (!args || typeof args !== "object")
20
+ return null;
21
+ const maybeQuestions = args.questions;
22
+ if (!Array.isArray(maybeQuestions))
23
+ return null;
24
+ const questions = maybeQuestions;
25
+ const labels = questions
26
+ .map((q, i) => {
27
+ const title = (q.title || q.prompt || "").toString().trim();
28
+ return `[Q${i}] ${title || "(untitled)"}`;
29
+ })
30
+ .join(", ");
31
+ return `Questions: ${labels}`;
32
+ }
53
33
  const runAuqAsk = async (payload) => new Promise((resolve, reject) => {
54
34
  const child = spawn("auq", ["ask"], {
55
35
  stdio: ["pipe", "pipe", "pipe"],
@@ -87,10 +67,26 @@ export const AskUserQuestionsPlugin = async () => ({
87
67
  tool: {
88
68
  ask_user_questions: tool({
89
69
  description: TOOL_DESCRIPTION,
90
- args: AskUserQuestionsParametersSchema.shape,
91
- async execute(args) {
70
+ args: AskUserQuestionsArgs,
71
+ async execute(args, context) {
72
+ // Best-effort: set call metadata (some OpenCode UIs won't render this title).
73
+ const questions = args.questions;
74
+ const summary = generateQuestionSummary(questions);
75
+ context.metadata({
76
+ title: summary,
77
+ metadata: { auqQuestionSummary: summary },
78
+ });
92
79
  try {
93
- return await runAuqAsk({ questions: args.questions });
80
+ // Get working directory from OpenCode context
81
+ const workingDirectory = context.directory || context.worktree;
82
+ // Build payload explicitly
83
+ const payload = {
84
+ questions,
85
+ };
86
+ if (workingDirectory) {
87
+ payload.workingDirectory = workingDirectory;
88
+ }
89
+ return await runAuqAsk(payload);
94
90
  }
95
91
  catch (error) {
96
92
  return `Error in session: ${String(error)}`;
@@ -98,5 +94,22 @@ export const AskUserQuestionsPlugin = async () => ({
98
94
  },
99
95
  }),
100
96
  },
97
+ // Force the tool call title in OpenCode UI.
98
+ "tool.execute.before": async (input, output) => {
99
+ if (input.tool !== "ask_user_questions")
100
+ return;
101
+ const summary = safeSummaryFromArgs(output.args);
102
+ if (summary)
103
+ titleByCallId.set(input.callID, summary);
104
+ },
105
+ "tool.execute.after": async (input, output) => {
106
+ if (input.tool !== "ask_user_questions")
107
+ return;
108
+ const summary = titleByCallId.get(input.callID);
109
+ if (summary) {
110
+ output.title = summary;
111
+ }
112
+ titleByCallId.delete(input.callID);
113
+ },
101
114
  });
102
115
  export default AskUserQuestionsPlugin;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@paulp-o/opencode-auq",
4
- "version": "1.3.2",
4
+ "version": "2.0.0",
5
5
  "description": "OpenCode plugin that forwards ask_user_questions to the auq CLI",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -13,7 +13,7 @@
13
13
  ],
14
14
  "scripts": {
15
15
  "build": "tsc -p tsconfig.json",
16
- "prepublishOnly": "npm run build",
16
+ "prepublishOnly": "bun run build",
17
17
  "typecheck": "tsc -p tsconfig.json --noEmit"
18
18
  },
19
19
  "keywords": [
@@ -35,7 +35,7 @@
35
35
  "@opencode-ai/plugin": ">=1.0.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@opencode-ai/plugin": "^1.1.3",
38
+ "@opencode-ai/plugin": "^1.1.25",
39
39
  "@tsconfig/node22": "^22.0.1",
40
40
  "@types/node": "^22.13.0",
41
41
  "typescript": "^5.8.3"
package/dist/schemas.d.ts DELETED
@@ -1,37 +0,0 @@
1
- import { z } from "zod";
2
- export declare const OptionSchema: z.ZodObject<{
3
- label: z.ZodString;
4
- description: z.ZodOptional<z.ZodString>;
5
- }, z.core.$strip>;
6
- export declare const QuestionSchema: z.ZodObject<{
7
- prompt: z.ZodString;
8
- title: z.ZodString;
9
- options: z.ZodArray<z.ZodObject<{
10
- label: z.ZodString;
11
- description: z.ZodOptional<z.ZodString>;
12
- }, z.core.$strip>>;
13
- multiSelect: z.ZodBoolean;
14
- }, z.core.$strip>;
15
- export declare const QuestionsSchema: z.ZodArray<z.ZodObject<{
16
- prompt: z.ZodString;
17
- title: z.ZodString;
18
- options: z.ZodArray<z.ZodObject<{
19
- label: z.ZodString;
20
- description: z.ZodOptional<z.ZodString>;
21
- }, z.core.$strip>>;
22
- multiSelect: z.ZodBoolean;
23
- }, z.core.$strip>>;
24
- export declare const AskUserQuestionsParametersSchema: z.ZodObject<{
25
- questions: z.ZodArray<z.ZodObject<{
26
- prompt: z.ZodString;
27
- title: z.ZodString;
28
- options: z.ZodArray<z.ZodObject<{
29
- label: z.ZodString;
30
- description: z.ZodOptional<z.ZodString>;
31
- }, z.core.$strip>>;
32
- multiSelect: z.ZodBoolean;
33
- }, z.core.$strip>>;
34
- }, z.core.$strip>;
35
- export declare const TOOL_DESCRIPTION: string;
36
- export type QuestionInput = z.infer<typeof QuestionSchema>;
37
- //# sourceMappingURL=schemas.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,YAAY;;;iBAcvB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;iBAkCzB,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;kBAAwC,CAAC;AAErE,eAAO,MAAM,gCAAgC;;;;;;;;;;iBAO3C,CAAC;AAEH,eAAO,MAAM,gBAAgB,QAqBgC,CAAC;AAE9D,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
package/dist/schemas.js DELETED
@@ -1,64 +0,0 @@
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.";