Giving Agentic Tools a Voice: Introducing GetUserInput
There’s a quiet shift happening in how we work with AI assistants.
They’re no longer just responders. Increasingly, they’re acting as agents — performing multi-step workflows, using our tools, running commands, inspecting output, and deciding what to do next.
And that raises a new problem:
Sometimes an AI shouldn’t continue — it should ask.
Recently while working inside JetBrains IDE with Junie (the JetBrains AI Agent), I noticed moments where Junie really needed clarification. Continuing would have risked the wrong change. But stopping entirely also broke the workflow.
So I built a tiny tool to bridge that gap.
The Idea
Junie already has terminal access inside the IDE.
So instead of forcing Junie to cancel, restart, or guess, I gave it a new ability:
Junie can now pause, ask me a question, hear my response, and continue.
The tool is called GetUserInput.
- Junie runs it from the terminal.
- A GTK window pops up with a prompt.
- I type an answer.
- The program prints my response to stdout and exits with code
0. - If I cancel, it exits with
1.
That means Junie can use it mid-round without aborting the task.
All I needed to teach Junie was:
“Junie, you have a custom terminal command named GetUserInput. You can run it anytime you need clarification.”
And suddenly the workflow feels more like a conversation.
How You Can Use This Pattern
If your agent can:
✔ run a terminal
✔ read command output
✔ branch logic based on exit codes
…then you can give it a voice — a way to ask for direction instead of guessing.
Here’s the simple convention I use:
exit 0→ continue with the user’s textexit 1→ stop or fall back
That’s it. No magic — just clean IO semantics used well.
GetUserInput — The Source
This version uses GtkSharp on .NET 8 so it works great on Linux (and anywhere GTK runs).
GetUserInput.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GtkSharp" Version="3.24.24.95" />
</ItemGroup>
</Project>
Program.cs
using System;
using Gtk;
namespace GetUserInput
{
internal static class Program
{
public static int Main(string[] args)
{
if (args.Length == 0 ||
(args.Length == 1 && (args[0] == "-help" || args[0] == "--help" || args[0] == "-?")))
{
Console.WriteLine("Usage: GetUserInput [prompt]");
Console.WriteLine();
Console.WriteLine("Purpose: Displays a graphical window with a prompt and a text area.");
Console.WriteLine(" The user's input is printed to standard output upon clicking 'OK'.");
Console.WriteLine(" Returns exit code 0 on success, and 1 if cancelled or closed.");
return 0;
}
string prompt = string.Join(" ", args);
Application.Init();
var win = new InputWindow(prompt);
win.ShowAll();
Application.Run();
return 0;
}
}
public class InputWindow : Window
{
private readonly TextView _textView;
private readonly Button _okButton;
private readonly Button _cancelButton;
public InputWindow(string prompt)
: base("Junie – User Input")
{
DefaultWidth = 480;
DefaultHeight = 260;
WindowPosition = WindowPosition.Center;
BorderWidth = 10;
DeleteEvent += (_, __) => Environment.Exit(1);
var vbox = new VBox(false, 8);
var label = new Label { Xalign = 0f, LineWrap = true, WrapMode = Pango.WrapMode.WordChar };
label.Text = prompt;
vbox.PackStart(label, false, false, 0);
var scrolled = new ScrolledWindow();
_textView = new TextView { WrapMode = WrapMode.WordChar };
scrolled.Add(_textView);
vbox.PackStart(scrolled, true, true, 0);
var buttonBox = new HButtonBox { Layout = ButtonBoxStyle.End };
_okButton = new Button("OK");
_cancelButton = new Button("Cancel");
_okButton.Clicked += (_, __) =>
{
Console.WriteLine(_textView.Buffer.Text ?? "");
Console.Out.Flush();
Environment.Exit(0);
};
_cancelButton.Clicked += (_, __) => Environment.Exit(1);
buttonBox.Add(_cancelButton);
buttonBox.Add(_okButton);
vbox.PackStart(buttonBox, false, false, 0);
Add(vbox);
ShowAll();
}
}
}
Build Instructions
dotnet restore
dotnet build -c Release
Optional: Publish as a single-file binary
dotnet publish -c Release -r linux-x64 \
-p:PublishSingleFile=true \
-p:IncludeAllContentForSelfExtract=true
You’ll find the binary under:
bin/Release/net8.0/linux-x64/publish/
Add that directory to your PATH — or drop the binary somewhere global.
Teaching Your Agent to Use It
I simply told Junie:
“You have a terminal command named GetUserInput. Run it whenever you need clarification, and use my answer to continue the workflow.”
And optionally:
“If the command exits with code 1, stop or fall back.”
That’s it.
Why This Matters
When an AI tool pauses and asks instead of guessing…
…the workflow becomes collaborative instead of brittle.
And that’s a direction I’m very excited about.
We’re not trying to replace agency — we’re trying to share it.
If you think of a better delivery prompt — or extend the idea — I’d love to hear about it in the comments. And if this inspires your own tools and agents, even better. That’s how living systems evolve. 😊