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. π