diff --git a/src/resources/extensions/bg-shell/index.ts b/src/resources/extensions/bg-shell/index.ts index 490ee5000..ee4662b4b 100644 --- a/src/resources/extensions/bg-shell/index.ts +++ b/src/resources/extensions/bg-shell/index.ts @@ -48,6 +48,7 @@ import { createConnection } from "node:net"; import { randomUUID } from "node:crypto"; import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs"; import { join } from "node:path"; +import { shortcutDesc } from "../shared/terminal.js"; import { createRequire } from "node:module"; // ── Windows VT Input Restoration ──────────────────────────────────────────── @@ -2356,7 +2357,7 @@ export default function (pi: ExtensionAPI) { // ── Ctrl+Alt+B shortcut ────────────────────────────────────────────── pi.registerShortcut(Key.ctrlAlt("b"), { - description: "Open background process manager", + description: shortcutDesc("Open background process manager", "/bg"), handler: async (ctx) => { latestCtx = ctx; await ctx.ui.custom( diff --git a/src/resources/extensions/gsd/index.ts b/src/resources/extensions/gsd/index.ts index 573baac72..b353b2917 100644 --- a/src/resources/extensions/gsd/index.ts +++ b/src/resources/extensions/gsd/index.ts @@ -47,6 +47,7 @@ import { import { Key } from "@mariozechner/pi-tui"; import { join } from "node:path"; import { existsSync } from "node:fs"; +import { shortcutDesc } from "../shared/terminal.js"; import { Text } from "@mariozechner/pi-tui"; // ── ASCII logo ──────────────────────────────────────────────────────────── @@ -184,10 +185,8 @@ export default function (pi: ExtensionAPI) { }); // ── Ctrl+Alt+G shortcut — GSD dashboard overlay ──────────────────────── - // Requires Kitty keyboard protocol or modifyOtherKeys support. - // Terminals without support (macOS Terminal.app, JetBrains): use /gsd status instead. pi.registerShortcut(Key.ctrlAlt("g"), { - description: "Open GSD dashboard (or use /gsd status)", + description: shortcutDesc("Open GSD dashboard", "/gsd status"), handler: async (ctx) => { // Only show if .gsd/ exists if (!existsSync(join(process.cwd(), ".gsd"))) { diff --git a/src/resources/extensions/shared/terminal.ts b/src/resources/extensions/shared/terminal.ts new file mode 100644 index 000000000..f87719995 --- /dev/null +++ b/src/resources/extensions/shared/terminal.ts @@ -0,0 +1,23 @@ +/** + * Terminal capability detection for keyboard shortcut support. + * + * Ctrl+Alt shortcuts require the Kitty keyboard protocol or modifyOtherKeys. + * Terminals that lack this support silently swallow the key combos. + */ + +const UNSUPPORTED_TERMS = ["apple_terminal"]; + +export function supportsCtrlAltShortcuts(): boolean { + const term = (process.env.TERM_PROGRAM || "").toLowerCase(); + const jetbrains = (process.env.TERMINAL_EMULATOR || "").toLowerCase().includes("jetbrains"); + return !UNSUPPORTED_TERMS.some((t) => term.includes(t)) && !jetbrains; +} + +/** + * Returns a shortcut description that includes a slash-command fallback hint + * when the current terminal likely can't fire Ctrl+Alt combos. + */ +export function shortcutDesc(base: string, fallbackCmd: string): string { + if (supportsCtrlAltShortcuts()) return base; + return `${base} — shortcut may not work in this terminal, use ${fallbackCmd}`; +} diff --git a/src/resources/extensions/voice/index.ts b/src/resources/extensions/voice/index.ts index 39433dbf4..3184efb7c 100644 --- a/src/resources/extensions/voice/index.ts +++ b/src/resources/extensions/voice/index.ts @@ -1,4 +1,5 @@ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; +import { shortcutDesc } from "../shared/terminal.js"; import type { AssistantMessage } from "@mariozechner/pi-ai"; import { isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; import { spawn, execSync, type ChildProcess } from "node:child_process"; @@ -131,7 +132,7 @@ export default function (pi: ExtensionAPI) { }); pi.registerShortcut("ctrl+alt+v", { - description: "Toggle voice mode", + description: shortcutDesc("Toggle voice mode", "/voice"), handler: async (ctx) => toggleVoice(ctx), });