@tyvm/knowhow 0.0.54 → 0.0.56

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 (98) hide show
  1. package/docs/input-queue-manager.md +142 -0
  2. package/docs/multi-worker-management.md +142 -0
  3. package/package.json +1 -1
  4. package/scripts/README.md +119 -0
  5. package/scripts/restore_keys.sh +59 -0
  6. package/scripts/unset_keys.sh +60 -0
  7. package/src/agents/base/base.ts +2 -2
  8. package/src/agents/tools/askHuman.ts +2 -0
  9. package/src/agents/tools/startAgentTask.ts +2 -2
  10. package/src/ai.ts +3 -1
  11. package/src/chat/CliChatService.ts +2 -2
  12. package/src/chat/modules/AgentModule.ts +25 -2
  13. package/src/chat-old.ts +2 -2
  14. package/src/cli.ts +56 -3
  15. package/src/clients/anthropic.ts +7 -5
  16. package/src/clients/knowhow.ts +2 -2
  17. package/src/clients/openai.ts +5 -0
  18. package/src/index.ts +6 -6
  19. package/src/microphone.ts +12 -4
  20. package/src/services/DockerService.ts +473 -0
  21. package/src/services/KnowhowClient.ts +4 -1
  22. package/src/services/index.ts +5 -1
  23. package/src/types.ts +7 -0
  24. package/src/utils/InputQueueManager.ts +324 -0
  25. package/src/utils/index.ts +5 -152
  26. package/src/worker.ts +158 -9
  27. package/src/workerRegistry.ts +152 -0
  28. package/tests/clients/AIClient.test.ts +177 -92
  29. package/tests/manual/test-concurrent-ask.ts +43 -0
  30. package/tests/services/DockerService.test.ts +24 -0
  31. package/tests/unit/input-queue.test.ts +80 -0
  32. package/ts_build/package.json +1 -1
  33. package/ts_build/src/agents/base/base.js +2 -2
  34. package/ts_build/src/agents/tools/askHuman.d.ts +1 -1
  35. package/ts_build/src/agents/tools/askHuman.js.map +1 -1
  36. package/ts_build/src/agents/tools/startAgentTask.js +2 -1
  37. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  38. package/ts_build/src/ai.js +3 -1
  39. package/ts_build/src/ai.js.map +1 -1
  40. package/ts_build/src/chat/CliChatService.js +1 -1
  41. package/ts_build/src/chat/CliChatService.js.map +1 -1
  42. package/ts_build/src/chat/modules/AgentModule.js +11 -1
  43. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  44. package/ts_build/src/chat-old.js +1 -1
  45. package/ts_build/src/chat-old.js.map +1 -1
  46. package/ts_build/src/cli.js +46 -3
  47. package/ts_build/src/cli.js.map +1 -1
  48. package/ts_build/src/clients/anthropic.js +7 -5
  49. package/ts_build/src/clients/anthropic.js.map +1 -1
  50. package/ts_build/src/clients/knowhow.js +1 -1
  51. package/ts_build/src/clients/knowhow.js.map +1 -1
  52. package/ts_build/src/clients/openai.js +5 -0
  53. package/ts_build/src/clients/openai.js.map +1 -1
  54. package/ts_build/src/dockerWorker.d.ts +22 -0
  55. package/ts_build/src/dockerWorker.js +210 -0
  56. package/ts_build/src/dockerWorker.js.map +1 -0
  57. package/ts_build/src/index.js +4 -4
  58. package/ts_build/src/index.js.map +1 -1
  59. package/ts_build/src/microphone.js +8 -3
  60. package/ts_build/src/microphone.js.map +1 -1
  61. package/ts_build/src/services/DockerService.d.ts +26 -0
  62. package/ts_build/src/services/DockerService.js +363 -0
  63. package/ts_build/src/services/DockerService.js.map +1 -0
  64. package/ts_build/src/services/KnowhowClient.d.ts +1 -1
  65. package/ts_build/src/services/KnowhowClient.js +1 -1
  66. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  67. package/ts_build/src/services/index.d.ts +3 -0
  68. package/ts_build/src/services/index.js +4 -1
  69. package/ts_build/src/services/index.js.map +1 -1
  70. package/ts_build/src/types.d.ts +5 -0
  71. package/ts_build/src/types.js +4 -0
  72. package/ts_build/src/types.js.map +1 -1
  73. package/ts_build/src/utils/InputQueueManager.d.ts +19 -0
  74. package/ts_build/src/utils/InputQueueManager.js +234 -0
  75. package/ts_build/src/utils/InputQueueManager.js.map +1 -0
  76. package/ts_build/src/utils/index.d.ts +1 -3
  77. package/ts_build/src/utils/index.js +4 -114
  78. package/ts_build/src/utils/index.js.map +1 -1
  79. package/ts_build/src/worker-entrypoint.d.ts +2 -0
  80. package/ts_build/src/worker-entrypoint.js +39 -0
  81. package/ts_build/src/worker-entrypoint.js.map +1 -0
  82. package/ts_build/src/worker.d.ts +7 -1
  83. package/ts_build/src/worker.js +117 -9
  84. package/ts_build/src/worker.js.map +1 -1
  85. package/ts_build/src/workerRegistry.d.ts +11 -0
  86. package/ts_build/src/workerRegistry.js +143 -0
  87. package/ts_build/src/workerRegistry.js.map +1 -0
  88. package/ts_build/tests/clients/AIClient.test.js +88 -42
  89. package/ts_build/tests/clients/AIClient.test.js.map +1 -1
  90. package/ts_build/tests/manual/test-concurrent-ask.d.ts +1 -0
  91. package/ts_build/tests/manual/test-concurrent-ask.js +22 -0
  92. package/ts_build/tests/manual/test-concurrent-ask.js.map +1 -0
  93. package/ts_build/tests/services/DockerService.test.d.ts +1 -0
  94. package/ts_build/tests/services/DockerService.test.js +22 -0
  95. package/ts_build/tests/services/DockerService.test.js.map +1 -0
  96. package/ts_build/tests/unit/input-queue.test.d.ts +1 -0
  97. package/ts_build/tests/unit/input-queue.test.js +32 -0
  98. package/ts_build/tests/unit/input-queue.test.js.map +1 -0
@@ -9,6 +9,7 @@ import { McpService } from "./Mcp";
9
9
  import { S3Service } from "./S3";
10
10
  import { ToolsService } from "./Tools";
11
11
  import { PluginService } from "../plugins/plugins";
12
+ import { DockerService } from "./DockerService";
12
13
 
13
14
  export * from "./AgentService";
14
15
  export * from "./EventService";
@@ -18,6 +19,7 @@ export * from "./S3";
18
19
  export * from "./Tools";
19
20
  export * as MCP from "./Mcp";
20
21
  export * from "./EmbeddingService";
22
+ export * from "./DockerService";
21
23
  export { Clients } from "../clients";
22
24
 
23
25
  let Singletons = {} as {
@@ -28,6 +30,7 @@ let Singletons = {} as {
28
30
  GitHub: GitHubService;
29
31
  Mcp: McpService;
30
32
  AwsS3: S3Service;
33
+ Docker: DockerService;
31
34
  knowhowApiClient: KnowhowSimpleClient;
32
35
  Plugins: PluginService;
33
36
  Clients: AIClient;
@@ -51,6 +54,7 @@ export const services = (): typeof Singletons => {
51
54
  Agents,
52
55
  AwsS3: new S3Service(),
53
56
  Clients,
57
+ Docker: new DockerService(),
54
58
  Downloader,
55
59
  Events,
56
60
  Flags: new FlagsService(),
@@ -58,7 +62,7 @@ export const services = (): typeof Singletons => {
58
62
  Mcp: new McpService(),
59
63
  Plugins,
60
64
  Tools,
61
- knowhowApiClient: new KnowhowSimpleClient(process.env.KNOWHOW_API_URL),
65
+ knowhowApiClient: new KnowhowSimpleClient(),
62
66
  };
63
67
 
64
68
  Singletons.Tools.setContext({
package/src/types.ts CHANGED
@@ -63,6 +63,9 @@ export type Config = {
63
63
 
64
64
  worker?: {
65
65
  allowedTools?: string[];
66
+ sandbox?: boolean;
67
+ volumes?: string[];
68
+ envFile?: string;
66
69
  };
67
70
  };
68
71
 
@@ -138,6 +141,7 @@ export type ChatInteraction = {
138
141
 
139
142
  export const Models = {
140
143
  anthropic: {
144
+ Opus4_5: "claude-opus-4-5-20251101",
141
145
  Opus4: "claude-opus-4-20250514",
142
146
  Opus4_1: "claude-opus-4-1-20250805",
143
147
  Sonnet4_5: "claude-sonnet-4-5-20250929",
@@ -162,6 +166,7 @@ export const Models = {
162
166
  Grok2Vision1212: "grok-2-vision-1212",
163
167
  },
164
168
  openai: {
169
+ GPT_5_2: "gpt-5.2",
165
170
  GPT_5_1: "gpt-5.1",
166
171
  GPT_5: "gpt-5",
167
172
  GPT_5_Mini: "gpt-5-mini",
@@ -231,6 +236,8 @@ export const OpenAiReasoningModels = [
231
236
  Models.openai.GPT_5,
232
237
  Models.openai.GPT_5_Mini,
233
238
  Models.openai.GPT_5_Nano,
239
+ Models.openai.GPT_5_1,
240
+ Models.openai.GPT_5_2,
234
241
  ];
235
242
 
236
243
  export const OpenAiEmbeddingModels = [
@@ -0,0 +1,324 @@
1
+ import readline from "node:readline";
2
+
3
+ export const askHistory: string[] = [];
4
+
5
+ type AskOptions = {
6
+ question: string;
7
+ options?: string[];
8
+ history?: string[];
9
+ resolve: (value: string) => void;
10
+ };
11
+
12
+ export class InputQueueManager {
13
+ private stack: AskOptions[] = [];
14
+ private rl: readline.Interface | null = null;
15
+
16
+ // We keep one “live” buffer shared across stacked questions
17
+ // (so typing is preserved when questions change)
18
+ private currentLine = "";
19
+
20
+ // History navigation state (custom: global askHistory + per-question history)
21
+ private historyIndex = -1;
22
+ private savedLineBeforeHistory = "";
23
+
24
+ private ensureRl(): readline.Interface {
25
+ if (this.rl) return this.rl;
26
+
27
+ this.rl = readline.createInterface({
28
+ input: process.stdin,
29
+ output: process.stdout,
30
+ terminal: true,
31
+ historySize: 500,
32
+
33
+ /**
34
+ * Use readline's built-in completion system so Tab does NOT insert a literal tab.
35
+ */
36
+ completer: (line: string) => {
37
+ const current = this.peek();
38
+ const opts = current?.options ?? [];
39
+ if (opts.length === 0) return [[], line];
40
+
41
+ // Identify the "word" at the end of the line that we want to complete
42
+ // (default readline behavior is word-based completion)
43
+ const lastSpace = Math.max(
44
+ line.lastIndexOf(" "),
45
+ line.lastIndexOf("\t")
46
+ );
47
+ const word = line.slice(lastSpace + 1); // the token to complete
48
+
49
+ const hits = opts.filter((c) => c.startsWith(word));
50
+
51
+ // Return [matches, wordToReplace]
52
+ // Readline will replace `word` with the selected match (or extend if unique)
53
+ return [hits, word];
54
+ },
55
+ });
56
+
57
+ // When user presses Enter, resolve ONLY the top question
58
+ this.rl.on("line", (line) => {
59
+ const current = this.peek();
60
+ if (!current) return;
61
+
62
+ // IMPORTANT: do not allow embedded newlines in history / answers
63
+ const answer = this.sanitizeHistoryEntry(line);
64
+
65
+ // Pop & resolve current question
66
+ const resolved = this.stack.pop();
67
+ resolved?.resolve(answer);
68
+
69
+ // Add to global history
70
+ if (answer && !askHistory.includes(answer)) {
71
+ askHistory.push(answer);
72
+ }
73
+
74
+ // Reset preserved buffer + history nav state for the next question
75
+ this.currentLine = "";
76
+ this.historyIndex = -1;
77
+ this.savedLineBeforeHistory = "";
78
+
79
+ // Update prompt for next stacked question (if any)
80
+ this.renderTopOrClose();
81
+ });
82
+
83
+ // Handle Ctrl+C (readline SIGINT)
84
+ this.rl.on("SIGINT", () => {
85
+ // If there’s an active question, cancel it (like Esc)
86
+ if (this.stack.length > 0) {
87
+ const cancelled = this.stack.pop();
88
+ cancelled?.resolve("");
89
+ this.currentLine = "";
90
+ this.historyIndex = -1;
91
+ this.savedLineBeforeHistory = "";
92
+ this.renderTopOrClose();
93
+ return;
94
+ }
95
+ // Otherwise exit
96
+ this.close();
97
+ process.exit(0);
98
+ });
99
+
100
+ // Capture keypresses for ESC + history nav while still using readline.
101
+ // Tab is handled by rl completer.
102
+ readline.emitKeypressEvents(process.stdin);
103
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
104
+
105
+ process.stdin.on("keypress", (_str, key) => {
106
+ // Handle Ctrl+C in raw mode (some terminals deliver this here instead of SIGINT)
107
+ if (key?.ctrl && key?.name === "c") {
108
+ if (this.stack.length > 0) {
109
+ const cancelled = this.stack.pop();
110
+ cancelled?.resolve("");
111
+ this.currentLine = "";
112
+ }
113
+ this.close();
114
+ process.exit(0);
115
+ }
116
+
117
+ // If RL is closed or nothing to ask, ignore
118
+ if (!this.rl || this.stack.length === 0) return;
119
+
120
+ // Keep our buffer in sync with readline’s live line
121
+ this.syncFromReadline();
122
+
123
+ // Any "real typing" should exit history mode
124
+ // (we'll treat left/right as not exiting; you can tweak)
125
+ const exitsHistoryMode =
126
+ key &&
127
+ (key.name === "return" ||
128
+ key.name === "enter" ||
129
+ key.name === "backspace" ||
130
+ (key.sequence &&
131
+ key.sequence.length === 1 &&
132
+ !key.ctrl &&
133
+ !key.meta));
134
+ if (exitsHistoryMode && this.historyIndex !== -1) {
135
+ this.historyIndex = -1;
136
+ this.savedLineBeforeHistory = "";
137
+ }
138
+
139
+ if (key?.name === "escape") {
140
+ // Cancel only the current (top) question
141
+ const cancelled = this.stack.pop();
142
+ cancelled?.resolve("");
143
+
144
+ this.currentLine = "";
145
+ this.historyIndex = -1;
146
+ this.savedLineBeforeHistory = "";
147
+
148
+ // clear the current input in readline and redraw
149
+ this.replaceLine("");
150
+ this.renderTopOrClose();
151
+ return;
152
+ }
153
+
154
+ // Custom Up/Down history: global askHistory + per-question history
155
+ if (key?.name === "up") {
156
+ const fullHistory = this.getFullHistory();
157
+ if (fullHistory.length === 0) return;
158
+
159
+ if (this.historyIndex === -1) {
160
+ // entering history mode: remember current typed text
161
+ this.savedLineBeforeHistory = this.currentLine;
162
+ }
163
+
164
+ if (this.historyIndex < fullHistory.length - 1) {
165
+ this.historyIndex++;
166
+ const next =
167
+ fullHistory[fullHistory.length - 1 - this.historyIndex] ?? "";
168
+ this.replaceLine(next);
169
+ this.currentLine = next;
170
+ }
171
+ return;
172
+ }
173
+
174
+ if (key?.name === "down") {
175
+ const fullHistory = this.getFullHistory();
176
+ if (fullHistory.length === 0) return;
177
+
178
+ if (this.historyIndex > 0) {
179
+ this.historyIndex--;
180
+ const next =
181
+ fullHistory[fullHistory.length - 1 - this.historyIndex] ?? "";
182
+ this.replaceLine(next);
183
+ this.currentLine = next;
184
+ return;
185
+ }
186
+
187
+ if (this.historyIndex === 0) {
188
+ // leave history mode, restore what user was typing
189
+ this.historyIndex = -1;
190
+ const restore = this.savedLineBeforeHistory ?? "";
191
+ this.savedLineBeforeHistory = "";
192
+ this.replaceLine(restore);
193
+ this.currentLine = restore;
194
+ return;
195
+ }
196
+
197
+ return;
198
+ }
199
+ });
200
+
201
+ return this.rl;
202
+ }
203
+
204
+ async ask(question: string, options: string[] = [], history: string[] = []) {
205
+ return new Promise<string>((resolve) => {
206
+ this.stack.push({ question, options, history, resolve });
207
+
208
+ const rl = this.ensureRl();
209
+
210
+ // IMPORTANT: snapshot readline's current buffer before we redraw/switch prompts.
211
+ // This prevents us from clobbering tab-completed text with a stale currentLine.
212
+ this.syncFromReadline();
213
+
214
+ // Update prompt to top-of-stack
215
+ this.render();
216
+
217
+ // Preserve what user typed so far
218
+ this.replaceLine(this.currentLine);
219
+
220
+ rl.prompt(true);
221
+ });
222
+ }
223
+
224
+ private peek(): AskOptions | undefined {
225
+ return this.stack[this.stack.length - 1];
226
+ }
227
+
228
+ private syncFromReadline(): void {
229
+ if (!this.rl) return;
230
+ this.currentLine = (this.rl as any).line ?? "";
231
+ }
232
+
233
+ private sanitizeHistoryEntry(value: string): string {
234
+ // Prevent embedded newlines from triggering readline's "line" event
235
+ return value.replace(/[\r\n]+/g, " ").trim();
236
+ }
237
+
238
+ private getFullHistory(): string[] {
239
+ const current = this.peek();
240
+ const local = current?.history ?? [];
241
+
242
+ // De-dup while preserving order preference (older -> newer)
243
+ const merged = [...askHistory, ...local];
244
+ const seen = new Set<string>();
245
+ const out: string[] = [];
246
+
247
+ for (const item of merged) {
248
+ const clean = this.sanitizeHistoryEntry(item);
249
+ if (!clean) continue;
250
+ if (seen.has(clean)) continue;
251
+ seen.add(clean);
252
+ out.push(clean);
253
+ }
254
+
255
+ return out;
256
+ }
257
+
258
+ private render(): void {
259
+ if (!this.rl) return;
260
+ const current = this.peek();
261
+ if (!current) return;
262
+
263
+ // Make prompt be the question (readline manages wrapping/cursor)
264
+ this.rl.setPrompt(current.question);
265
+ this.rl.prompt(true);
266
+ }
267
+
268
+ private renderTopOrClose(): void {
269
+ if (this.stack.length === 0) {
270
+ this.close();
271
+ return;
272
+ }
273
+
274
+ // IMPORTANT: snapshot readline's current buffer before we redraw/switch prompts.
275
+ // This prevents us from clobbering tab-completed text with a stale currentLine.
276
+ this.syncFromReadline();
277
+
278
+ this.render();
279
+ this.replaceLine(this.currentLine);
280
+ this.rl?.prompt(true);
281
+ }
282
+
283
+ private replaceLine(next: string): void {
284
+ if (!this.rl) return;
285
+
286
+ const safe = this.sanitizeHistoryEntry(next);
287
+
288
+ // Clear current line and write next input without affecting terminal scrollback
289
+ this.rl.write(null, { ctrl: true, name: "u" }); // Ctrl+U clears the line
290
+ if (safe) this.rl.write(safe);
291
+ }
292
+
293
+ /**
294
+ * Returns the longest common prefix of all strings in the array.
295
+ */
296
+ private longestCommonPrefix(items: string[]): string {
297
+ if (items.length === 0) return "";
298
+ let prefix = items[0];
299
+
300
+ for (let i = 1; i < items.length; i++) {
301
+ const s = items[i];
302
+ let j = 0;
303
+ while (j < prefix.length && j < s.length && prefix[j] === s[j]) j++;
304
+ prefix = prefix.slice(0, j);
305
+ if (!prefix) break;
306
+ }
307
+
308
+ return prefix;
309
+ }
310
+
311
+ private close(): void {
312
+ if (!this.rl) return;
313
+ this.rl.close();
314
+ this.rl = null;
315
+
316
+ if (process.stdin.isTTY) {
317
+ try {
318
+ process.stdin.setRawMode(false);
319
+ } catch {
320
+ // ignore
321
+ }
322
+ }
323
+ }
324
+ }
@@ -5,6 +5,7 @@ import { exec } from "child_process";
5
5
  import * as fs from "fs";
6
6
  import { marked } from "marked";
7
7
  import { markedTerminal } from "marked-terminal";
8
+ import { InputQueueManager } from "./InputQueueManager";
8
9
 
9
10
  marked.use(markedTerminal());
10
11
 
@@ -16,164 +17,16 @@ export const execAsync = util.promisify(exec);
16
17
  export const fileStat = promisify(fs.stat);
17
18
  export const wait = promisify(setTimeout);
18
19
 
19
- export const askHistory = [];
20
+
21
+ // Create singleton instance
22
+ const inputQueue = new InputQueueManager();
20
23
 
21
24
  export const ask = async (
22
25
  question: string,
23
26
  options: string[] = [],
24
27
  history = []
25
- ) => {
26
- const fullHistory = [...askHistory, ...history];
27
- const readline = require("readline").createInterface({
28
- input: process.stdin,
29
- output: process.stdout,
30
- history: fullHistory,
31
- completer: (line) => {
32
- const hits = options.filter((c) => c?.startsWith(line));
33
- return [hits.length ? hits : options, line];
34
- },
35
- terminal: true,
36
- });
37
-
38
- const _ask = util.promisify(readline.question).bind(readline);
39
- const answer = await _ask(question);
40
- readline.close();
41
-
42
- return answer;
43
- };
44
-
45
- /**
46
- * Enhanced ask function that handles paste operations with newlines
47
- * and provides a better multi-line input experience
48
- */
49
- export const askWithPaste = async (
50
- question: string,
51
- options: string[] = [],
52
- history: string[] = [],
53
- submitKeys: string[] = ["ctrl+d", "ctrl+enter"]
54
28
  ): Promise<string> => {
55
- const readline = require("readline");
56
- const fullHistory = [...askHistory, ...history];
57
-
58
- return new Promise((resolve) => {
59
- const rl = readline.createInterface({
60
- input: process.stdin,
61
- output: process.stdout,
62
- history: fullHistory,
63
- completer: (line: string) => {
64
- const hits = options.filter((c) => c?.startsWith(line));
65
- return [hits.length ? hits : options, line];
66
- },
67
- terminal: true,
68
- });
69
-
70
- let buffer = "";
71
- let currentLine = "";
72
- let cursorPos = 0;
73
- const historyIndex = -1;
74
-
75
- // Display the question
76
- process.stdout.write(question);
77
-
78
- // Handle raw input to detect paste operations and special keys
79
- process.stdin.setRawMode(true);
80
- process.stdin.resume();
81
-
82
- const cleanup = () => {
83
- process.stdin.setRawMode(false);
84
- process.stdin.pause();
85
- rl.close();
86
- };
87
-
88
- process.stdin.on("data", (data) => {
89
- const input = data.toString();
90
-
91
- // Handle Ctrl+C
92
- if (input === "\u0003") {
93
- cleanup();
94
- process.exit(0);
95
- return;
96
- }
97
-
98
- // Handle Ctrl+D (submit)
99
- if (input === "\u0004") {
100
- process.stdout.write("\n");
101
- cleanup();
102
- resolve(buffer.trim());
103
- return;
104
- }
105
-
106
- // Handle Ctrl+Enter (submit)
107
- if (input === "\r\n" || input === "\n\r") {
108
- process.stdout.write("\n");
109
- cleanup();
110
- resolve(buffer.trim());
111
- return;
112
- }
113
-
114
- // Handle regular Enter (add newline to buffer)
115
- if (input === "\r" || input === "\n") {
116
- buffer += currentLine + "\n";
117
- currentLine = "";
118
- cursorPos = 0;
119
- process.stdout.write("\n");
120
- return;
121
- }
122
-
123
- // Handle Backspace
124
- if (input === "\u007f" || input === "\b") {
125
- if (cursorPos > 0) {
126
- currentLine =
127
- currentLine.slice(0, cursorPos - 1) + currentLine.slice(cursorPos);
128
- cursorPos--;
129
- // Redraw current line
130
- process.stdout.write(
131
- "\r" +
132
- " ".repeat(question.length + currentLine.length + 1) +
133
- "\r" +
134
- question +
135
- currentLine
136
- );
137
- process.stdout.write("\u001b[" + (question.length + cursorPos) + "G");
138
- }
139
- return;
140
- }
141
-
142
- // Handle Tab (autocomplete)
143
- if (input === "\t" && options.length > 0) {
144
- const hits = options.filter((c) => c?.startsWith(currentLine));
145
- if (hits.length === 1) {
146
- currentLine = hits[0];
147
- cursorPos = currentLine.length;
148
- process.stdout.write("\r" + question + currentLine);
149
- }
150
- return;
151
- }
152
-
153
- // Handle regular characters (including pasted content)
154
- if (input.length > 1) {
155
- // This is likely a paste operation
156
- currentLine =
157
- currentLine.slice(0, cursorPos) +
158
- input +
159
- currentLine.slice(cursorPos);
160
- cursorPos += input.length;
161
- } else if (input >= " ") {
162
- // Single printable character
163
- currentLine =
164
- currentLine.slice(0, cursorPos) +
165
- input +
166
- currentLine.slice(cursorPos);
167
- cursorPos++;
168
- }
169
-
170
- // Redraw current line
171
- process.stdout.write("\r" + question + currentLine);
172
- if (cursorPos < currentLine.length) {
173
- process.stdout.write("\u001b[" + (question.length + cursorPos) + "G");
174
- }
175
- });
176
- });
29
+ return inputQueue.ask(question, options, history);
177
30
  };
178
31
 
179
32
  export const Marked = marked;