Bridges between IntelliJ IDEA's integrated terminal and its notification system. Terminal processes can send notifications via a local HTTP endpoint, and clicking one focuses the tab that sent it.
- Exposes a local HTTP endpoint that lets terminal processes trigger notifications
- Focuses the terminal tab on notification click
- Automatically suppresses notifications when the tab is already active or visible
- Provides a keyboard shortcut to pick a notification and jump to its terminal tab
- IntelliJ IDEA 2025.3.1 or later (or compatible IDE)
- Download ZIP from Releases
- Settings → Plugins → ⚙️ → Install Plugin from Disk...
When you open a terminal tab in IntelliJ, the plugin sets:
| Variable | Description |
|---|---|
TERMBACK_ENDPOINT |
Notification endpoint URL |
TERMBACK_SESSION_ID |
UUID identifying the tab |
Send a notification:
curl -fsS --json "$(jq -n --arg msg "Done" '{sessionId: env.TERMBACK_SESSION_ID, message: $msg}')" "$TERMBACK_ENDPOINT"Press Alt+Shift+T (default) to pick a notification and jump to its terminal tab.
termback() {
test -z "$TERMBACK_ENDPOINT" && return 0
curl -fsS --json "$(jq -n --arg msg "$1" '{sessionId: env.TERMBACK_SESSION_ID, message: $msg}')" "$TERMBACK_ENDPOINT" > /dev/null 2>&1
}
# Example
./build.sh && termback "Build completed"Add to .claude/settings.json to receive IDE notifications when Claude Code is waiting for input:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "test -z \"$TERMBACK_ENDPOINT\" || { jq '{sessionId: env.TERMBACK_SESSION_ID, message: .message}' | curl -fsS --json @- \"$TERMBACK_ENDPOINT\" > /dev/null; }"
}
]
}
]
}
}Create .opencode/plugins/termback.js:
export const TermbackPlugin = async () => {
return {
event: async ({ event }) => {
if (!process.env.TERMBACK_ENDPOINT) return;
const termback = (message) =>
fetch(process.env.TERMBACK_ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
sessionId: process.env.TERMBACK_SESSION_ID,
message,
}),
});
switch (event.type) {
case "permission.asked":
await termback(`Need permission: ${event.properties?.permission || "unknown"}`);
break;
case "session.idle":
await termback("OpenCode is waiting for input");
break;
}
}
};
};| Setting | Description |
|---|---|
| Notification destination | IDE or System |
| Skip popup when single | Skip notification picker when only one exists |
POST /api/termback
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
sessionId |
string | ✓ | Tab session ID | |
message |
string | ✓ | Notification body | |
title |
string | null |
Notification title | |
suppress |
string | "whenActive" |
Suppress condition | |
onNext |
string | "expire" |
Behavior on next notification |
Controls notification suppression based on tab state. When the condition is met:
- At creation time: notification is skipped (not created)
- After creation: notification is expired (dismissed)
| Value | Behavior |
|---|---|
"none" |
Never suppress |
"whenActive" |
Suppress when tab is active (selected and tool window has focus) |
"whenVisible" |
Suppress when tab is visible (selected and tool window is open) |
Controls behavior when a new notification arrives for the same tab.
| Value | Behavior |
|---|---|
"keep" |
Keep notification when a new one arrives |
"expire" |
Expire notification when a new one arrives |
| Status | Description |
|---|---|
| 202 | Request accepted for processing |
| 400 | Invalid request |
| 404 | Session not found |
A 202 response indicates the request was accepted. The notification may be silently skipped if suppress conditions are met.
- JDK 21
./gradlew build # Build
./gradlew buildPlugin # Build plugin ZIP (build/distributions/)
./gradlew runIde # Run in sandbox IDE (logs: build/idea-sandbox/{IDE-version}/log/idea.log)
./gradlew test # Test