@imisbahk/hive 0.1.0 → 0.1.1

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 (69) hide show
  1. package/.github/workflows/publish.yml +31 -0
  2. package/.rocket/README.md +8 -8
  3. package/.rocket/SYMBOLS.md +260 -117
  4. package/CONTRIBUTING.md +2 -1
  5. package/FEATURES.md +55 -0
  6. package/README.md +13 -11
  7. package/dist/agent/agent.d.ts +10 -1
  8. package/dist/agent/agent.d.ts.map +1 -1
  9. package/dist/agent/agent.js +351 -1
  10. package/dist/agent/agent.js.map +1 -1
  11. package/dist/browser/browser.d.ts +9 -0
  12. package/dist/browser/browser.d.ts.map +1 -0
  13. package/dist/browser/browser.js +338 -0
  14. package/dist/browser/browser.js.map +1 -0
  15. package/dist/cli/commands/chat.d.ts +5 -1
  16. package/dist/cli/commands/chat.d.ts.map +1 -1
  17. package/dist/cli/commands/chat.js +560 -38
  18. package/dist/cli/commands/chat.js.map +1 -1
  19. package/dist/cli/commands/config.d.ts +11 -0
  20. package/dist/cli/commands/config.d.ts.map +1 -1
  21. package/dist/cli/commands/config.js +50 -15
  22. package/dist/cli/commands/config.js.map +1 -1
  23. package/dist/cli/commands/init.d.ts.map +1 -1
  24. package/dist/cli/commands/init.js +39 -14
  25. package/dist/cli/commands/init.js.map +1 -1
  26. package/dist/cli/commands/nuke.d.ts.map +1 -1
  27. package/dist/cli/commands/nuke.js +5 -4
  28. package/dist/cli/commands/nuke.js.map +1 -1
  29. package/dist/cli/commands/status.d.ts +5 -0
  30. package/dist/cli/commands/status.d.ts.map +1 -1
  31. package/dist/cli/commands/status.js +15 -5
  32. package/dist/cli/commands/status.js.map +1 -1
  33. package/dist/cli/index.js +34 -12
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/cli/ui.d.ts +7 -0
  36. package/dist/cli/ui.d.ts.map +1 -0
  37. package/dist/cli/ui.js +96 -0
  38. package/dist/cli/ui.js.map +1 -0
  39. package/dist/providers/base.d.ts +37 -1
  40. package/dist/providers/base.d.ts.map +1 -1
  41. package/dist/providers/base.js +104 -0
  42. package/dist/providers/base.js.map +1 -1
  43. package/dist/providers/openai-compatible.d.ts +2 -1
  44. package/dist/providers/openai-compatible.d.ts.map +1 -1
  45. package/dist/providers/openai-compatible.js +18 -1
  46. package/dist/providers/openai-compatible.js.map +1 -1
  47. package/package.json +9 -1
  48. package/prompts/Browser.md +13 -0
  49. package/prompts/Debugging.md +15 -0
  50. package/prompts/Execution.md +13 -0
  51. package/prompts/Planning.md +13 -0
  52. package/prompts/Product.md +14 -0
  53. package/prompts/Review.md +15 -0
  54. package/prompts/Safety.md +12 -0
  55. package/prompts/Search.md +14 -0
  56. package/prompts/Tools.md +14 -0
  57. package/prompts/Writing.md +13 -0
  58. package/releases/v1/v0.1/RELEASE-NOTES.md +46 -0
  59. package/src/agent/agent.ts +442 -2
  60. package/src/browser/browser.ts +410 -0
  61. package/src/cli/commands/chat.ts +705 -34
  62. package/src/cli/commands/config.ts +78 -15
  63. package/src/cli/commands/init.ts +60 -14
  64. package/src/cli/commands/nuke.ts +11 -7
  65. package/src/cli/commands/status.ts +28 -5
  66. package/src/cli/index.ts +37 -9
  67. package/src/cli/ui.ts +120 -0
  68. package/src/providers/base.ts +176 -1
  69. package/src/providers/openai-compatible.ts +24 -0
@@ -1,83 +1,245 @@
1
1
  import { stdin, stdout } from "node:process";
2
+ import * as readline from "node:readline";
2
3
  import { createInterface } from "node:readline/promises";
3
4
  import chalk from "chalk";
4
- import { HiveAgent } from "../../agent/agent.js";
5
+ import { buildBrowserAugmentedPrompt, HiveAgent } from "../../agent/agent.js";
5
6
  import { closeHiveDatabase, getPrimaryAgent, openHiveDatabase, } from "../../storage/db.js";
6
7
  import { createProvider } from "../../providers/index.js";
8
+ import { renderError, renderHiveHeader, renderInfo, renderSeparator, } from "../ui.js";
9
+ import { runConfigKeyCommandWithOptions, runConfigModelCommandWithOptions, runConfigProviderCommandWithOptions, runConfigShowCommandWithOptions, } from "./config.js";
10
+ import { runStatusCommandWithOptions } from "./status.js";
11
+ const USER_PROMPT = "you› ";
12
+ const HIVE_SHORTCUT_PREFIX = "/hive";
13
+ const MAX_COMMAND_SUGGESTIONS = 8;
14
+ const COMMAND_LABEL_WIDTH = 24;
15
+ const COMMAND_HELP_TEXT = [
16
+ "Commands:",
17
+ " /help show commands",
18
+ " /new start a new conversation",
19
+ " /browse <url> read a webpage",
20
+ " browse <url> same as /browse",
21
+ " /search <query> search the web",
22
+ " search <query> same as /search",
23
+ " /hive help show Hive command shortcuts",
24
+ " /hive status run `hive status`",
25
+ " /hive config show run `hive config show`",
26
+ " /hive config provider interactive provider setup",
27
+ " /hive config model interactive model setup",
28
+ " /hive config key interactive key setup",
29
+ " /exit quit",
30
+ ].join("\n");
31
+ const HIVE_SHORTCUT_HELP_TEXT = [
32
+ "Hive shortcuts:",
33
+ " /hive help list shortcuts",
34
+ " /hive status run hive status",
35
+ " /hive config show run hive config show",
36
+ "",
37
+ "Interactive config commands (in chat):",
38
+ " /hive config provider",
39
+ " /hive config model",
40
+ " /hive config key",
41
+ "",
42
+ "Safety commands still run from shell:",
43
+ " /hive init",
44
+ " /hive nuke",
45
+ ].join("\n");
46
+ const CHAT_HINT_TEXT = "? for help | /exit to quit";
47
+ const EXCHANGE_SEPARATOR = "────";
48
+ const PREVIEW_AGENT_NAME = "jarvis";
49
+ const PREVIEW_PROVIDER = "google";
50
+ const PREVIEW_MODEL = "gemini-2.0-flash";
51
+ const PREVIEW_NEW_MESSAGE = "Started a new preview conversation context.";
52
+ const COMMAND_SUGGESTIONS = [
53
+ {
54
+ label: "/help",
55
+ insertText: "/help",
56
+ description: "show chat commands",
57
+ },
58
+ {
59
+ label: "/new",
60
+ insertText: "/new",
61
+ description: "start a new conversation",
62
+ },
63
+ {
64
+ label: "/browse <url>",
65
+ insertText: "/browse ",
66
+ description: "read a webpage",
67
+ },
68
+ {
69
+ label: "/search <query>",
70
+ insertText: "/search ",
71
+ description: "search the web",
72
+ },
73
+ {
74
+ label: "/exit",
75
+ insertText: "/exit",
76
+ description: "quit chat",
77
+ },
78
+ {
79
+ label: "/hive help",
80
+ insertText: "/hive help",
81
+ description: "show Hive command shortcuts",
82
+ },
83
+ {
84
+ label: "/hive status",
85
+ insertText: "/hive status",
86
+ description: "run hive status",
87
+ },
88
+ {
89
+ label: "/hive config show",
90
+ insertText: "/hive config show",
91
+ description: "run hive config show",
92
+ },
93
+ {
94
+ label: "/hive init",
95
+ insertText: "/hive init",
96
+ description: "run hive init (outside chat)",
97
+ },
98
+ {
99
+ label: "/hive config provider",
100
+ insertText: "/hive config provider",
101
+ description: "interactive provider setup",
102
+ },
103
+ {
104
+ label: "/hive config model",
105
+ insertText: "/hive config model",
106
+ description: "interactive model setup",
107
+ },
108
+ {
109
+ label: "/hive config key",
110
+ insertText: "/hive config key",
111
+ description: "interactive key setup",
112
+ },
113
+ {
114
+ label: "/hive nuke",
115
+ insertText: "/hive nuke",
116
+ description: "run hive nuke (outside chat)",
117
+ },
118
+ ];
7
119
  export function registerChatCommand(program) {
8
120
  program
9
121
  .command("chat")
10
- .description("Talk to your Hive agent")
122
+ .description("(Deprecated) Talk to your Hive agent. Use `hive`.")
11
123
  .option("-m, --message <text>", "send a single message and exit")
12
124
  .option("-c, --conversation <id>", "continue an existing conversation")
13
125
  .option("--model <model>", "override model for this session")
14
126
  .option("--title <title>", "title for a newly created conversation")
15
127
  .option("-t, --temperature <value>", "sampling temperature")
128
+ .option("--preview", "run chat UI preview without Hive initialization")
16
129
  .action(async (options) => {
17
- await runChatCommand(options);
130
+ await runChatCommand(options, { entrypoint: "chat-command" });
18
131
  });
19
132
  }
20
- export async function runChatCommand(options) {
133
+ export async function runChatCommand(options, context = {}) {
134
+ console.clear();
135
+ renderHiveHeader("Chat");
136
+ const entrypoint = context.entrypoint ?? "chat-command";
137
+ if (entrypoint === "chat-command") {
138
+ renderInfo("`hive chat` is deprecated. Run `hive`.");
139
+ }
140
+ if (options.preview) {
141
+ await runPreviewSession(options);
142
+ return;
143
+ }
21
144
  const temperature = parseTemperature(options.temperature);
22
145
  const db = openHiveDatabase();
23
146
  try {
24
147
  const profile = getPrimaryAgent(db);
25
148
  if (!profile) {
26
- console.error(chalk.red("Hive is not initialized. Run `hive init` first."));
149
+ renderError("Hive is not initialized. Run `hive init` first.");
27
150
  return;
28
151
  }
29
- const provider = await createProvider(profile.provider);
30
- const agent = new HiveAgent(db, provider, profile);
152
+ let activeProfile = profile;
153
+ let provider = await createProvider(activeProfile.provider);
154
+ let agent = new HiveAgent(db, provider, activeProfile);
155
+ let agentName = resolveAgentName(activeProfile.agent_name);
156
+ const model = options.model ?? activeProfile.model;
31
157
  let conversationId = options.conversation;
32
158
  const runOptions = {
33
- model: options.model,
159
+ model,
34
160
  title: options.title,
35
161
  temperature,
36
162
  };
163
+ renderChatPreamble({
164
+ agentName,
165
+ provider: profile.provider,
166
+ model,
167
+ });
37
168
  if (options.message) {
38
- conversationId = await streamReply(agent, options.message, conversationId, runOptions);
39
- console.log(chalk.dim(`conversation: ${conversationId}`));
169
+ const augmentedMessage = await buildBrowserAugmentedPrompt(options.message, {
170
+ locationHint: profile.location ?? undefined,
171
+ });
172
+ conversationId = await streamReply(agent, augmentedMessage, conversationId, runOptions, agentName);
173
+ renderInfo(`conversation: ${conversationId}`);
40
174
  return;
41
175
  }
42
- const rl = createInterface({
43
- input: stdin,
44
- output: stdout,
45
- terminal: true,
46
- });
47
- console.log(chalk.dim("Type /exit to quit, /new to start a fresh conversation."));
48
- try {
49
- while (true) {
50
- const prompt = (await rl.question(chalk.cyan("you> "))).trim();
51
- if (prompt.length === 0) {
176
+ while (true) {
177
+ const prompt = await readPromptWithSuggestions();
178
+ if (prompt.length === 0) {
179
+ continue;
180
+ }
181
+ if (prompt === "/") {
182
+ printChatHelp();
183
+ continue;
184
+ }
185
+ if (prompt === "/help") {
186
+ printChatHelp();
187
+ continue;
188
+ }
189
+ if (prompt === "/exit" || prompt === "/quit") {
190
+ break;
191
+ }
192
+ if (prompt === "/new") {
193
+ conversationId = undefined;
194
+ renderInfo("Started a new conversation context.");
195
+ continue;
196
+ }
197
+ try {
198
+ const shortcutResult = await handleHiveShortcut(prompt, {
199
+ allowInteractiveConfig: true,
200
+ });
201
+ if (shortcutResult === "handled") {
52
202
  continue;
53
203
  }
54
- if (prompt === "/exit" || prompt === "/quit") {
55
- break;
56
- }
57
- if (prompt === "/new") {
204
+ if (shortcutResult === "config-updated") {
205
+ const latestProfile = getPrimaryAgent(db);
206
+ if (!latestProfile) {
207
+ renderError("Hive is not initialized. Run `hive init` first.");
208
+ continue;
209
+ }
210
+ activeProfile = latestProfile;
211
+ provider = await createProvider(activeProfile.provider);
212
+ agent = new HiveAgent(db, provider, activeProfile);
213
+ agentName = resolveAgentName(activeProfile.agent_name);
214
+ if (!options.model) {
215
+ runOptions.model = activeProfile.model;
216
+ }
58
217
  conversationId = undefined;
59
- console.log(chalk.dim("Started a new conversation context."));
218
+ renderInfo(`Switched to ${activeProfile.provider} · ${runOptions.model ?? activeProfile.model}.`);
219
+ renderInfo("Started a new conversation context.");
60
220
  continue;
61
221
  }
62
- try {
63
- conversationId = await streamReply(agent, prompt, conversationId, runOptions);
64
- }
65
- catch (error) {
66
- process.stdout.write("\n");
67
- console.error(formatError(error));
222
+ if (isUnknownSlashCommand(prompt)) {
223
+ renderError(`Unknown command: ${prompt}`);
224
+ renderInfo("Run `/help` to view supported commands.");
225
+ continue;
68
226
  }
227
+ const augmentedPrompt = await buildBrowserAugmentedPrompt(prompt, {
228
+ locationHint: profile.location ?? undefined,
229
+ });
230
+ conversationId = await streamReply(agent, augmentedPrompt, conversationId, runOptions, agentName);
231
+ }
232
+ catch (error) {
233
+ renderError(formatError(error));
69
234
  }
70
- }
71
- finally {
72
- rl.close();
73
235
  }
74
236
  }
75
237
  finally {
76
238
  closeHiveDatabase(db);
77
239
  }
78
240
  }
79
- async function streamReply(agent, prompt, conversationId, options) {
80
- process.stdout.write(chalk.green("hive> "));
241
+ async function streamReply(agent, prompt, conversationId, options, agentName) {
242
+ process.stdout.write(chalk.whiteBright(`${agentName}› `));
81
243
  let activeConversationId = conversationId;
82
244
  for await (const event of agent.chat(prompt, {
83
245
  conversationId: activeConversationId,
@@ -93,6 +255,7 @@ async function streamReply(agent, prompt, conversationId, options) {
93
255
  activeConversationId = event.conversationId;
94
256
  }
95
257
  process.stdout.write("\n");
258
+ renderSeparator(EXCHANGE_SEPARATOR);
96
259
  if (!activeConversationId) {
97
260
  throw new Error("Conversation state was not returned by the agent.");
98
261
  }
@@ -110,8 +273,367 @@ function parseTemperature(raw) {
110
273
  }
111
274
  function formatError(error) {
112
275
  if (error instanceof Error) {
113
- return chalk.red(error.message);
276
+ return error.message;
277
+ }
278
+ return String(error);
279
+ }
280
+ function resolveAgentName(agentName) {
281
+ const normalized = agentName?.trim();
282
+ if (normalized && normalized.length > 0) {
283
+ return normalized;
284
+ }
285
+ return "hive";
286
+ }
287
+ function renderChatPreamble(input) {
288
+ renderInfo(`${input.agentName} · ${input.provider} · ${input.model}`);
289
+ renderInfo(CHAT_HINT_TEXT);
290
+ }
291
+ function printChatHelp() {
292
+ renderInfo(COMMAND_HELP_TEXT);
293
+ }
294
+ async function runPreviewSession(options) {
295
+ const model = options.model ?? PREVIEW_MODEL;
296
+ const agentName = PREVIEW_AGENT_NAME;
297
+ renderChatPreamble({
298
+ agentName,
299
+ provider: PREVIEW_PROVIDER,
300
+ model,
301
+ });
302
+ if (options.message) {
303
+ await streamPreviewReply(options.message, agentName);
304
+ return;
305
+ }
306
+ while (true) {
307
+ const prompt = await readPromptWithSuggestions();
308
+ if (prompt.length === 0) {
309
+ continue;
310
+ }
311
+ if (prompt === "/") {
312
+ printChatHelp();
313
+ continue;
314
+ }
315
+ if (prompt === "/help") {
316
+ printChatHelp();
317
+ continue;
318
+ }
319
+ if (prompt === "/exit" || prompt === "/quit") {
320
+ break;
321
+ }
322
+ if (prompt === "/new") {
323
+ renderInfo(PREVIEW_NEW_MESSAGE);
324
+ continue;
325
+ }
326
+ if (isHiveShortcut(prompt)) {
327
+ renderInfo("Hive shortcuts are unavailable in preview mode.");
328
+ continue;
329
+ }
330
+ if (isUnknownSlashCommand(prompt)) {
331
+ renderError(`Unknown command: ${prompt}`);
332
+ renderInfo("Run `/help` to view supported commands.");
333
+ continue;
334
+ }
335
+ await streamPreviewReply(prompt, agentName);
336
+ }
337
+ }
338
+ async function streamPreviewReply(prompt, agentName) {
339
+ const response = `preview mode: received "${prompt}"`;
340
+ process.stdout.write(chalk.whiteBright(`${agentName}› `));
341
+ process.stdout.write(response);
342
+ process.stdout.write("\n");
343
+ renderSeparator(EXCHANGE_SEPARATOR);
344
+ }
345
+ function isHiveShortcut(prompt) {
346
+ const normalized = prompt.trim().toLowerCase();
347
+ return normalized === HIVE_SHORTCUT_PREFIX || normalized.startsWith(`${HIVE_SHORTCUT_PREFIX} `);
348
+ }
349
+ function isUnknownSlashCommand(prompt) {
350
+ const normalized = prompt.trim().toLowerCase();
351
+ if (!normalized.startsWith("/")) {
352
+ return false;
353
+ }
354
+ if (normalized === "/help" ||
355
+ normalized === "/new" ||
356
+ normalized === "/exit" ||
357
+ normalized === "/quit" ||
358
+ normalized === "/browse" ||
359
+ normalized.startsWith("/browse ") ||
360
+ normalized === "/search" ||
361
+ normalized.startsWith("/search ") ||
362
+ normalized === HIVE_SHORTCUT_PREFIX ||
363
+ normalized.startsWith(`${HIVE_SHORTCUT_PREFIX} `)) {
364
+ return false;
365
+ }
366
+ return true;
367
+ }
368
+ async function handleHiveShortcut(prompt, options = {}) {
369
+ const normalized = prompt.trim().replace(/\s+/g, " ");
370
+ const lower = normalized.toLowerCase();
371
+ if (lower === HIVE_SHORTCUT_PREFIX) {
372
+ renderInfo(HIVE_SHORTCUT_HELP_TEXT);
373
+ return "handled";
374
+ }
375
+ if (!lower.startsWith(`${HIVE_SHORTCUT_PREFIX} `)) {
376
+ return "not-handled";
377
+ }
378
+ const rawSubcommand = normalized.slice(HIVE_SHORTCUT_PREFIX.length).trim();
379
+ const subcommand = rawSubcommand.toLowerCase();
380
+ if (subcommand.length === 0 || subcommand === "help") {
381
+ renderInfo(HIVE_SHORTCUT_HELP_TEXT);
382
+ return "handled";
383
+ }
384
+ if (subcommand === "status") {
385
+ await runStatusCommandWithOptions({ showHeader: false });
386
+ restoreChatInputAfterInteractiveCommand();
387
+ return "handled";
388
+ }
389
+ if (subcommand === "config show") {
390
+ await runConfigShowCommandWithOptions({ showHeader: false });
391
+ restoreChatInputAfterInteractiveCommand();
392
+ return "handled";
393
+ }
394
+ if (subcommand === "config provider") {
395
+ if (!options.allowInteractiveConfig) {
396
+ renderInfo("Interactive config commands are unavailable here.");
397
+ return "handled";
398
+ }
399
+ await runConfigProviderCommandWithOptions({ showHeader: false });
400
+ restoreChatInputAfterInteractiveCommand();
401
+ return "config-updated";
402
+ }
403
+ if (subcommand === "config model") {
404
+ if (!options.allowInteractiveConfig) {
405
+ renderInfo("Interactive config commands are unavailable here.");
406
+ return "handled";
407
+ }
408
+ await runConfigModelCommandWithOptions({ showHeader: false });
409
+ restoreChatInputAfterInteractiveCommand();
410
+ return "config-updated";
411
+ }
412
+ if (subcommand === "config key") {
413
+ if (!options.allowInteractiveConfig) {
414
+ renderInfo("Interactive config commands are unavailable here.");
415
+ return "handled";
416
+ }
417
+ await runConfigKeyCommandWithOptions({ showHeader: false });
418
+ restoreChatInputAfterInteractiveCommand();
419
+ return "handled";
420
+ }
421
+ if (subcommand === "init" ||
422
+ subcommand === "nuke") {
423
+ renderInfo(`Run \`hive ${rawSubcommand}\` from your shell. This command is interactive.`);
424
+ return "handled";
425
+ }
426
+ renderError(`Unknown Hive shortcut: /hive ${rawSubcommand}`);
427
+ renderInfo("Use `/hive help` to list available shortcuts.");
428
+ return "handled";
429
+ }
430
+ function getCommandSuggestions(input) {
431
+ const normalized = input.trimStart().toLowerCase();
432
+ if (!normalized.startsWith("/")) {
433
+ return [];
434
+ }
435
+ const prefixMatches = COMMAND_SUGGESTIONS.filter((suggestion) => suggestion.insertText.toLowerCase().startsWith(normalized) ||
436
+ suggestion.label.toLowerCase().startsWith(normalized));
437
+ const fallbackMatches = COMMAND_SUGGESTIONS.filter((suggestion) => !prefixMatches.includes(suggestion) &&
438
+ suggestion.label.toLowerCase().includes(normalized.slice(1)));
439
+ return [...prefixMatches, ...fallbackMatches];
440
+ }
441
+ async function readPromptWithSuggestions() {
442
+ if (!stdin.isTTY || !stdout.isTTY) {
443
+ const rl = createInterface({
444
+ input: stdin,
445
+ output: stdout,
446
+ terminal: true,
447
+ });
448
+ try {
449
+ return (await rl.question(chalk.whiteBright(USER_PROMPT))).trim();
450
+ }
451
+ finally {
452
+ rl.close();
453
+ }
454
+ }
455
+ return new Promise((resolve) => {
456
+ stdin.resume();
457
+ readline.emitKeypressEvents(stdin);
458
+ const wasRaw = stdin.isRaw ?? false;
459
+ if (!wasRaw) {
460
+ stdin.setRawMode(true);
461
+ }
462
+ let buffer = "";
463
+ let selectedSuggestionIndex = 0;
464
+ let suggestionWindowStart = 0;
465
+ let renderedSuggestionRows = 0;
466
+ const cleanup = () => {
467
+ stdin.off("keypress", onKeypress);
468
+ if (!wasRaw) {
469
+ stdin.setRawMode(false);
470
+ }
471
+ };
472
+ const commit = () => {
473
+ const suggestions = getCommandSuggestions(buffer);
474
+ const selected = suggestions[selectedSuggestionIndex];
475
+ let value = buffer.trim();
476
+ if (selected &&
477
+ value.startsWith("/") &&
478
+ (value === "/" ||
479
+ selected.insertText.toLowerCase().startsWith(value.toLowerCase()) ||
480
+ selected.label.toLowerCase().startsWith(value.toLowerCase()))) {
481
+ value = selected.insertText.trimEnd();
482
+ }
483
+ if (value === "/") {
484
+ value = "/help";
485
+ }
486
+ readline.cursorTo(stdout, 0);
487
+ readline.clearLine(stdout, 0);
488
+ stdout.write(chalk.whiteBright(`${USER_PROMPT}${buffer}`));
489
+ for (let index = 0; index < renderedSuggestionRows; index += 1) {
490
+ readline.moveCursor(stdout, 0, 1);
491
+ readline.cursorTo(stdout, 0);
492
+ readline.clearLine(stdout, 0);
493
+ }
494
+ for (let index = 0; index < renderedSuggestionRows; index += 1) {
495
+ readline.moveCursor(stdout, 0, -1);
496
+ }
497
+ renderedSuggestionRows = 0;
498
+ readline.cursorTo(stdout, USER_PROMPT.length + buffer.length);
499
+ stdout.write("\n");
500
+ cleanup();
501
+ resolve(value);
502
+ };
503
+ const render = () => {
504
+ const suggestions = getCommandSuggestions(buffer);
505
+ if (selectedSuggestionIndex >= suggestions.length) {
506
+ selectedSuggestionIndex = Math.max(0, suggestions.length - 1);
507
+ }
508
+ if (suggestions.length === 0) {
509
+ suggestionWindowStart = 0;
510
+ }
511
+ const visibleSuggestionCount = Math.min(MAX_COMMAND_SUGGESTIONS, suggestions.length);
512
+ if (selectedSuggestionIndex < suggestionWindowStart) {
513
+ suggestionWindowStart = selectedSuggestionIndex;
514
+ }
515
+ if (visibleSuggestionCount > 0 &&
516
+ selectedSuggestionIndex >= suggestionWindowStart + visibleSuggestionCount) {
517
+ suggestionWindowStart = selectedSuggestionIndex - visibleSuggestionCount + 1;
518
+ }
519
+ const visibleSuggestions = suggestions.slice(suggestionWindowStart, suggestionWindowStart + visibleSuggestionCount);
520
+ readline.cursorTo(stdout, 0);
521
+ readline.clearLine(stdout, 0);
522
+ stdout.write(chalk.whiteBright(`${USER_PROMPT}${buffer}`));
523
+ const rowsToRender = Math.max(renderedSuggestionRows, visibleSuggestions.length);
524
+ for (let index = 0; index < rowsToRender; index += 1) {
525
+ readline.moveCursor(stdout, 0, 1);
526
+ readline.cursorTo(stdout, 0);
527
+ readline.clearLine(stdout, 0);
528
+ if (index >= visibleSuggestions.length) {
529
+ continue;
530
+ }
531
+ const suggestion = visibleSuggestions[index];
532
+ const absoluteIndex = suggestionWindowStart + index;
533
+ const marker = absoluteIndex === selectedSuggestionIndex ? ">" : " ";
534
+ const label = suggestion.label.padEnd(COMMAND_LABEL_WIDTH, " ");
535
+ const text = `${marker} ${label} ${suggestion.description}`;
536
+ if (absoluteIndex === selectedSuggestionIndex) {
537
+ stdout.write(chalk.whiteBright(text));
538
+ }
539
+ else {
540
+ stdout.write(chalk.dim(text));
541
+ }
542
+ }
543
+ for (let index = 0; index < rowsToRender; index += 1) {
544
+ readline.moveCursor(stdout, 0, -1);
545
+ }
546
+ readline.cursorTo(stdout, USER_PROMPT.length + buffer.length);
547
+ renderedSuggestionRows = visibleSuggestions.length;
548
+ };
549
+ const onKeypress = (str, key) => {
550
+ if ((key.ctrl && key.name === "c") || (key.ctrl && key.name === "d")) {
551
+ buffer = "/exit";
552
+ commit();
553
+ return;
554
+ }
555
+ if (key.name === "return" || key.name === "enter") {
556
+ const suggestions = getCommandSuggestions(buffer);
557
+ const selected = suggestions[selectedSuggestionIndex];
558
+ const trimmed = buffer.trim();
559
+ if (selected &&
560
+ trimmed.startsWith("/") &&
561
+ (trimmed === "/" ||
562
+ selected.insertText.toLowerCase().startsWith(trimmed.toLowerCase()) ||
563
+ selected.label.toLowerCase().startsWith(trimmed.toLowerCase()))) {
564
+ buffer = selected.insertText;
565
+ selectedSuggestionIndex = 0;
566
+ suggestionWindowStart = 0;
567
+ // Commands that expect extra input should stay in edit mode.
568
+ if (buffer.endsWith(" ")) {
569
+ render();
570
+ return;
571
+ }
572
+ }
573
+ commit();
574
+ return;
575
+ }
576
+ if (key.name === "backspace") {
577
+ const chars = Array.from(buffer);
578
+ chars.pop();
579
+ buffer = chars.join("");
580
+ selectedSuggestionIndex = 0;
581
+ suggestionWindowStart = 0;
582
+ render();
583
+ return;
584
+ }
585
+ if (key.name === "up" || key.name === "down") {
586
+ const suggestions = getCommandSuggestions(buffer);
587
+ if (suggestions.length === 0) {
588
+ return;
589
+ }
590
+ if (key.name === "up") {
591
+ selectedSuggestionIndex =
592
+ selectedSuggestionIndex > 0
593
+ ? selectedSuggestionIndex - 1
594
+ : suggestions.length - 1;
595
+ }
596
+ else {
597
+ selectedSuggestionIndex =
598
+ selectedSuggestionIndex < suggestions.length - 1
599
+ ? selectedSuggestionIndex + 1
600
+ : 0;
601
+ }
602
+ render();
603
+ return;
604
+ }
605
+ if (key.name === "tab") {
606
+ const suggestions = getCommandSuggestions(buffer);
607
+ if (suggestions.length === 0) {
608
+ return;
609
+ }
610
+ buffer = suggestions[selectedSuggestionIndex]?.insertText ?? buffer;
611
+ selectedSuggestionIndex = 0;
612
+ suggestionWindowStart = 0;
613
+ render();
614
+ return;
615
+ }
616
+ if (typeof str === "string" && str.length > 0 && !key.ctrl && !key.meta) {
617
+ buffer += str;
618
+ selectedSuggestionIndex = 0;
619
+ suggestionWindowStart = 0;
620
+ render();
621
+ }
622
+ };
623
+ stdin.on("keypress", onKeypress);
624
+ render();
625
+ });
626
+ }
627
+ function restoreChatInputAfterInteractiveCommand() {
628
+ if (!stdin.isTTY) {
629
+ return;
630
+ }
631
+ try {
632
+ stdin.setRawMode(false);
633
+ }
634
+ catch {
635
+ // Ignore terminal mode recovery errors.
114
636
  }
115
- return chalk.red(String(error));
637
+ stdin.resume();
116
638
  }
117
639
  //# sourceMappingURL=chat.js.map