@oh-my-pi/pi-tui 18.3.2 → 18.3.3

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 (112) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/types/app-keybindings.d.ts +2 -17
  3. package/dist/types/autocomplete.d.ts +18 -3
  4. package/dist/types/chrome/keybinding-hints.d.ts +16 -10
  5. package/dist/types/components/editor.d.ts +11 -0
  6. package/dist/types/key-hint-format.d.ts +29 -0
  7. package/dist/types/keys.d.ts +2 -1
  8. package/dist/types/overlays/agent-hub.d.ts +1 -1
  9. package/dist/types/overlays/agent-transcript-viewer.d.ts +1 -1
  10. package/dist/types/overlays/model-picker.d.ts +1 -3
  11. package/dist/types/prompt/composer-hints.d.ts +42 -0
  12. package/dist/types/prompt/composer.d.ts +2 -1
  13. package/dist/types/prompt/custom-editor.d.ts +8 -2
  14. package/dist/types/prompt/macos-spelling.d.ts +7 -16
  15. package/dist/types/prompt/prompt-action-autocomplete.d.ts +4 -1
  16. package/dist/types/prompt/prose-gate.d.ts +24 -0
  17. package/dist/types/prompt/word-completion.d.ts +46 -0
  18. package/dist/types/render/render-utils.d.ts +1 -1
  19. package/dist/types/theme/active-symbols.d.ts +12 -0
  20. package/dist/types/theme/symbols.d.ts +1 -1
  21. package/dist/types/theme/theme-class.d.ts +0 -1
  22. package/dist/types/tools/eval.d.ts +1 -1
  23. package/dist/types/tools/mcp.d.ts +3 -1
  24. package/dist/types/tools/task-yield-assembly.d.ts +10 -2
  25. package/dist/types/tools/task.d.ts +4 -0
  26. package/package.json +11 -10
  27. package/src/app-keybindings.ts +11 -104
  28. package/src/apps/autoresearch-dashboard.ts +10 -3
  29. package/src/apps/debug/log-viewer.ts +3 -2
  30. package/src/apps/debug/raw-sse.ts +2 -1
  31. package/src/apps/git/git-tui.ts +9 -5
  32. package/src/apps/live-visualizer.ts +2 -1
  33. package/src/apps/ps-top.ts +13 -4
  34. package/src/autocomplete.ts +42 -7
  35. package/src/chat/compaction-summary-message.ts +2 -1
  36. package/src/chat/execution-shared.ts +4 -2
  37. package/src/chat/tool-execution.ts +3 -1
  38. package/src/chat/ttsr-notification.ts +4 -3
  39. package/src/chat/user-message.ts +2 -2
  40. package/src/chrome/keybinding-hints.ts +38 -13
  41. package/src/components/editor.ts +180 -58
  42. package/src/components/settings-list.ts +10 -3
  43. package/src/components/tab-bar.ts +2 -1
  44. package/src/debug-server.ts +4 -1
  45. package/src/hotkeys-markdown.ts +14 -17
  46. package/src/key-hint-format.ts +122 -0
  47. package/src/keys.ts +2 -1
  48. package/src/overlays/advisor-config.ts +28 -6
  49. package/src/overlays/agent-hub.ts +8 -5
  50. package/src/overlays/agent-transcript-viewer.ts +6 -4
  51. package/src/overlays/agents-hub.ts +22 -8
  52. package/src/overlays/annotation-overlay.ts +31 -17
  53. package/src/overlays/ask-dialog.ts +14 -19
  54. package/src/overlays/bordered-loader.ts +2 -1
  55. package/src/overlays/btw-history-panel.ts +15 -12
  56. package/src/overlays/btw-panel.ts +12 -7
  57. package/src/overlays/cleanse-panel.ts +9 -6
  58. package/src/overlays/codex-reset-fireworks.ts +5 -3
  59. package/src/overlays/copy-selector.ts +13 -5
  60. package/src/overlays/extensions/extension-dashboard.ts +6 -1
  61. package/src/overlays/history-search.ts +3 -2
  62. package/src/overlays/hook-editor.ts +7 -2
  63. package/src/overlays/hook-input.ts +2 -1
  64. package/src/overlays/hook-selector.ts +5 -1
  65. package/src/overlays/login-dialog.ts +17 -4
  66. package/src/overlays/logout-account-selector.ts +10 -1
  67. package/src/overlays/mcp-add-wizard.ts +32 -20
  68. package/src/overlays/model-hub.ts +55 -28
  69. package/src/overlays/model-picker.ts +27 -12
  70. package/src/overlays/move-overlay.ts +5 -1
  71. package/src/overlays/omfg-panel.ts +8 -5
  72. package/src/overlays/pause-screen.ts +7 -3
  73. package/src/overlays/plan-review-overlay.ts +40 -14
  74. package/src/overlays/plan-save-overlay.ts +3 -1
  75. package/src/overlays/plugin-settings.ts +15 -8
  76. package/src/overlays/reset-usage-selector.ts +10 -3
  77. package/src/overlays/rewind-selector.ts +11 -5
  78. package/src/overlays/session-info-overlay.ts +4 -3
  79. package/src/overlays/session-selector.ts +12 -2
  80. package/src/overlays/settings-selector.ts +18 -11
  81. package/src/overlays/tree-selector.ts +27 -4
  82. package/src/overlays/usage-dashboard.ts +8 -2
  83. package/src/prompt/composer-cache.ts +2 -1
  84. package/src/prompt/composer-hints.ts +75 -0
  85. package/src/prompt/composer.ts +3 -2
  86. package/src/prompt/custom-editor.ts +22 -4
  87. package/src/prompt/macos-spelling.ts +17 -158
  88. package/src/prompt/prompt-action-autocomplete.ts +9 -2
  89. package/src/prompt/prose-gate.ts +87 -0
  90. package/src/prompt/tips.txt +6 -6
  91. package/src/prompt/welcome.ts +35 -2
  92. package/src/prompt/word-completion.ts +205 -0
  93. package/src/render/render-utils.ts +4 -4
  94. package/src/setup/scenes/composer.ts +5 -1
  95. package/src/setup/scenes/model.ts +3 -1
  96. package/src/setup/scenes/providers.ts +4 -1
  97. package/src/setup/scenes/sign-in.ts +4 -2
  98. package/src/setup/scenes/splash.ts +9 -4
  99. package/src/setup/scenes/theme.ts +18 -5
  100. package/src/setup/wizard-overlay.ts +5 -4
  101. package/src/theme/active-symbols.ts +20 -0
  102. package/src/theme/glyph-bundle.json +28 -1
  103. package/src/theme/symbols.ts +88 -6
  104. package/src/theme/theme-class.ts +0 -1
  105. package/src/theme/theme.ts +2 -0
  106. package/src/tools/bash-interactive.ts +2 -1
  107. package/src/tools/eval.ts +3 -3
  108. package/src/tools/mcp.ts +3 -1
  109. package/src/tools/task-yield-assembly.ts +34 -8
  110. package/src/tools/task.ts +14 -5
  111. package/dist/types/overlays/tiny-title-download-progress.d.ts +0 -21
  112. package/src/overlays/tiny-title-download-progress.ts +0 -102
@@ -5,6 +5,7 @@ import { replaceTabs } from "../render/render-utils";
5
5
  import { theme } from "../theme/theme";
6
6
  import { OverlayPanel } from "../chrome/overlay-box";
7
7
  import { StreamingPanelContent, type StreamingPanelPresentation } from "../chrome/streaming-panel";
8
+ import { interruptKey } from "../chrome/keybinding-hints";
8
9
  import type {
9
10
  CleanseBoardModel,
10
11
  CleanseCheckerDescriptor,
@@ -149,19 +150,21 @@ export class CleansePanelComponent extends OverlayPanel {
149
150
  }
150
151
 
151
152
  #footerLine(): string {
153
+ // The main editor routes `app.interrupt` (Escape by default) to the panel.
154
+ const esc = interruptKey();
152
155
  switch (this.#outcome) {
153
156
  case undefined:
154
- return theme.fg("muted", "Esc cancel /cleanse");
157
+ return theme.fg("muted", `${esc} cancel /cleanse`);
155
158
  case "clean":
156
- return theme.fg("success", `${theme.status.success} Clean · Esc dismiss`);
159
+ return theme.fg("success", `${theme.status.success} Clean · ${esc} dismiss`);
157
160
  case "unresolved":
158
- return theme.fg("warning", `${theme.status.warning} Diagnostics remain · Esc dismiss`);
161
+ return theme.fg("warning", `${theme.status.warning} Diagnostics remain · ${esc} dismiss`);
159
162
  case "unsupported":
160
- return theme.fg("warning", `${theme.status.warning} No runnable checker · Esc dismiss`);
163
+ return theme.fg("warning", `${theme.status.warning} No runnable checker · ${esc} dismiss`);
161
164
  case "cancelled":
162
- return theme.fg("warning", `${theme.status.warning} Cancelled · Esc dismiss`);
165
+ return theme.fg("warning", `${theme.status.warning} Cancelled · ${esc} dismiss`);
163
166
  case "error":
164
- return theme.fg("error", `${theme.status.error} Error · Esc dismiss`);
167
+ return theme.fg("error", `${theme.status.error} Error · ${esc} dismiss`);
165
168
  }
166
169
  }
167
170
  }
@@ -8,6 +8,7 @@ import {
8
8
  visibleWidth,
9
9
  } from "../index";
10
10
  import { type ThemeColor, theme } from "../theme/theme";
11
+ import { formatKeyHint } from "../app-keybindings";
11
12
 
12
13
  const FRAME_INTERVAL_MS = 85;
13
14
  const FRAME_COUNT = 34;
@@ -176,12 +177,13 @@ function drawBanner(
176
177
  const innerWidth = panelWidth - 2;
177
178
  const titleText =
178
179
  event.kind === "unscheduled-weekly-reset" ? " O P E N A I R E S E T " : " S A V E D R E S E T ";
180
+ const esc = formatKeyHint("escape");
179
181
  const subtitleText =
180
182
  event.kind === "unscheduled-weekly-reset"
181
- ? "Weekly usage cleared early · ESC to return"
183
+ ? `Weekly usage cleared early · ${esc} to return`
182
184
  : event.added === 1
183
- ? `New reset banked · ${event.available} available · ESC to return`
184
- : `${event.added} resets banked · ${event.available} available · ESC to return`;
185
+ ? `New reset banked · ${event.available} available · ${esc} to return`
186
+ : `${event.added} resets banked · ${event.available} available · ${esc} to return`;
185
187
  const title = truncateToWidth(titleText, innerWidth, "");
186
188
  const subtitle = truncateToWidth(subtitleText, innerWidth, "");
187
189
  const titleOffset = Math.floor((innerWidth - visibleWidth(title)) / 2);
@@ -24,7 +24,9 @@ import {
24
24
  userTurnDraft,
25
25
  } from "../chat/transcript-entry";
26
26
  import type { SessionMessageEntryLike as SessionMessageEntry } from "../chat/transcript-entry";
27
- import { replaceTabs } from "../render/render-utils";
27
+ import { expandKeyHint, replaceTabs } from "../render/render-utils";
28
+ import { formatKeyHint } from "../app-keybindings";
29
+ import { editorKey, editorKeys } from "../chrome/keybinding-hints";
28
30
  import { highlightCode, type ThemeColor, theme } from "../theme/theme";
29
31
  import { commandFromToolCall, extractBlocks, extractLinks } from "./copy-targets";
30
32
  import { matchesAppToolsExpand, matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../keybinding-matchers";
@@ -374,17 +376,23 @@ export class CopySelectorComponent implements Component {
374
376
  prepared,
375
377
  style: {
376
378
  color: OUTLINE_COLOR,
377
- caption: blocks.length > 0 ? `${blocks.length} block${blocks.length === 1 ? "" : "s"} →` : undefined,
379
+ caption:
380
+ blocks.length > 0
381
+ ? `${blocks.length} block${blocks.length === 1 ? "" : "s"} ${formatKeyHint("right")}`
382
+ : undefined,
378
383
  },
379
384
  }).column;
380
385
  }
381
386
 
382
387
  const selectedBlock = this.#blocks?.[this.#blockSelected];
383
- const openHint = selectedBlock?.href && this.deps.onOpen ? " o open" : "";
388
+ const openHint = selectedBlock?.href && this.deps.onOpen ? ` ${formatKeyHint("o")} open` : "";
384
389
  const action = this.deps.actionLabel ?? "copy";
390
+ const upDown = editorKeys("tui.select.up", "tui.select.down");
391
+ const enter = formatKeyHint("enter");
392
+ const cancel = editorKey("tui.select.cancel");
385
393
  const hint = this.#blocks
386
- ? `${this.#blockSelected + 1}/${this.#blocks.length} ↑/↓ block ←/esc back enter ${action}${openHint} click ${theme.cmd.copy}/${theme.cmd.share}`
387
- : `${this.#targets.length > 0 ? `${this.#selected + 1}/${this.#targets.length} ` : ""}↑/↓ step ${blocks.length > 0 ? "→ blocks " : ""}enter ${action} ${this.#truncated ? "a earlier turns " : ""}ctrl+o expand esc close`;
394
+ ? `${this.#blockSelected + 1}/${this.#blocks.length} ${upDown} block ${formatKeyHint("left")}/${cancel} back ${enter} ${action}${openHint} click ${theme.cmd.copy}/${theme.cmd.share}`
395
+ : `${this.#targets.length > 0 ? `${this.#selected + 1}/${this.#targets.length} ` : ""}${upDown} step ${blocks.length > 0 ? `${formatKeyHint("right")} blocks ` : ""}${enter} ${action} ${this.#truncated ? `${formatKeyHint("a")} earlier turns ` : ""}${expandKeyHint()} expand ${cancel} close`;
388
396
  const anchorId = target
389
397
  ? this.#blocks
390
398
  ? `copy:${target.turnId}:block:${this.#blockSelected}`
@@ -30,6 +30,8 @@ import {
30
30
  matchesSelectPageUp,
31
31
  } from "../../keybinding-matchers";
32
32
  import { expandKeyHint } from "../../render/render-utils";
33
+ import { formatKeyHint, formatKeyHints } from "../../app-keybindings";
34
+ import { editorKeys, interruptKey } from "../../chrome/keybinding-hints";
33
35
  import { bottomBorder, divider, PanelRows, row, topBorder } from "../../chrome/overlay-box";
34
36
  import { ExtensionList } from "./extension-list";
35
37
  import { InspectorPanel, type ToolRuntimeSource } from "./inspector-panel";
@@ -73,7 +75,10 @@ export interface ExtensionDashboardOptions {
73
75
  }
74
76
 
75
77
  function extFooter(): string {
76
- return ` ↑/↓: navigate · Space: toggle · ←/→: provider · PgUp/PgDn: inspector · ${expandKeyHint()}: expand · Esc: close`;
78
+ const upDown = editorKeys("tui.select.up", "tui.select.down");
79
+ const pages = editorKeys("tui.select.pageUp", "tui.select.pageDown");
80
+ const close = interruptKey();
81
+ return ` ${upDown}: navigate · ${formatKeyHint("space")}: toggle · ${formatKeyHints(["left", "right"])}: provider · ${pages}: inspector · ${expandKeyHint()}: expand · ${close}: close`;
77
82
  }
78
83
 
79
84
  /**
@@ -28,7 +28,7 @@ export interface HistorySource {
28
28
  search(query: string, limit: number): HistorySearchEntry[];
29
29
  getRecent(limit: number): HistorySearchEntry[];
30
30
  }
31
- import { rawKeyHint } from "../chrome/keybinding-hints";
31
+ import { editorKeys, keyHint, rawKeyHint } from "../chrome/keybinding-hints";
32
32
  import { OverlayPanel } from "../chrome/overlay-box";
33
33
  import { contentRowWidth, renderScrollableList } from "../chrome/selector-helpers";
34
34
  import { MenuSelection } from "../components/menu-selection";
@@ -195,7 +195,8 @@ export class HistorySearchComponent extends OverlayPanel {
195
195
  this.#resultsList = new HistoryResultsList(this.#menu);
196
196
 
197
197
  const dot = theme.fg("dim", theme.sep.dot);
198
- const hint = [rawKeyHint("↑↓", "navigate"), rawKeyHint("enter", "select"), rawKeyHint("esc", "cancel")].join(dot);
198
+ const navigate = theme.fg("dim", editorKeys("tui.select.up", "tui.select.down")) + theme.fg("muted", " navigate");
199
+ const hint = [navigate, rawKeyHint("enter", "select"), keyHint("tui.select.cancel", "cancel")].join(dot);
199
200
 
200
201
  this.addChild(new Spacer(1));
201
202
  this.addChild(this.#searchInput);
@@ -14,6 +14,8 @@ import { matchesAppExternalEditor, matchesAppFollowUp, matchesAppInterrupt } fro
14
14
  import { formTheme } from "../chrome/form-theme";
15
15
  import { OverlayPanel } from "../chrome/overlay-box";
16
16
  import { FormField } from "../components/form";
17
+ import { formatKeyHint, formatKeyHints } from "../app-keybindings";
18
+ import { boundKeys, editorKey, interruptKey } from "../chrome/keybinding-hints";
17
19
 
18
20
  export interface HookEditorOptions {
19
21
  /** Edit text with the host's configured external editor. */
@@ -81,9 +83,12 @@ export class HookEditorComponent extends OverlayPanel implements Focusable {
81
83
  this.#editor.setText(prefill);
82
84
  }
83
85
  // Hint
86
+ const followUpKeys = boundKeys("app.message.followUp", ["ctrl+q", "ctrl+enter"]);
87
+ const [primaryFollowUpKey = "ctrl+q"] = followUpKeys;
88
+ const externalEditorKey = editorKey("app.editor.external") || formatKeyHint("ctrl+g");
84
89
  const hint = this.#promptStyle
85
- ? "enter or ctrl+q submit esc cancel ctrl+g external editor"
86
- : "ctrl+q/ctrl+enter submit esc cancel ctrl+g external editor";
90
+ ? `${formatKeyHint("enter")} or ${formatKeyHint(primaryFollowUpKey)} submit ${formatKeyHint("escape")} cancel ${externalEditorKey} external editor`
91
+ : `${formatKeyHints(followUpKeys)} submit ${interruptKey()} cancel ${externalEditorKey} external editor`;
87
92
  this.#field = new FormField(this.#editor, {
88
93
  theme: formTheme,
89
94
  details:
@@ -7,6 +7,7 @@ import { CountdownTimer } from "../chrome/countdown-timer";
7
7
  import { formTheme } from "../chrome/form-theme";
8
8
  import { OverlayPanel } from "../chrome/overlay-box";
9
9
  import { Form, TextFormField } from "../components/form";
10
+ import { editorKey, interruptKey } from "../chrome/keybinding-hints";
10
11
 
11
12
  export interface HookInputOptions {
12
13
  tui?: TUI;
@@ -49,7 +50,7 @@ export class HookInputComponent extends OverlayPanel {
49
50
 
50
51
  this.#field = new TextFormField({
51
52
  theme: formTheme,
52
- hint: "enter submit esc cancel",
53
+ hint: `${editorKey("tui.input.submit")} submit ${interruptKey()} cancel`,
53
54
  empty: "submit",
54
55
  onSubmit: value => this.#onSubmitCallback(value),
55
56
  onCancel: () => this.#onCancelCallback(),
@@ -26,6 +26,8 @@ import {
26
26
  matchesSelectUp,
27
27
  } from "../keybinding-matchers";
28
28
  import { CountdownTimer } from "../chrome/countdown-timer";
29
+ import { editorKey, editorKeys } from "../chrome/keybinding-hints";
30
+ import { formatKeyHint } from "../app-keybindings";
29
31
  import { OverlayPanel } from "../chrome/overlay-box";
30
32
  import { renderSegmentTrack } from "../chrome/segment-track";
31
33
  import { MenuSelection, getMenuWindow } from "../components/menu-selection";
@@ -264,7 +266,9 @@ export class HookSelectorComponent extends OverlayPanel {
264
266
  this.addChild(this.#listContainer);
265
267
  }
266
268
  this.addChild(new Spacer(1));
267
- const controlsHint = opts?.helpText ?? "up/down navigate enter select esc cancel";
269
+ const controlsHint =
270
+ opts?.helpText ??
271
+ `${editorKeys("tui.select.up", "tui.select.down")} navigate ${formatKeyHint("enter")} select ${editorKey("tui.select.cancel")} cancel`;
268
272
  this.addChild(new Text(theme.fg("dim", controlsHint), 0, 0));
269
273
  this.addChild(new Spacer(1));
270
274
 
@@ -6,6 +6,8 @@ import { urlHyperlinkAlways, WidthAwareText } from "../render/index";
6
6
  import { formTheme } from "../chrome/form-theme";
7
7
  import { OverlayPanel } from "../chrome/overlay-box";
8
8
  import { TextFormField } from "../components/form";
9
+ import { formatKeyHint, keyHintPlatform } from "../app-keybindings";
10
+ import { editorKey } from "../chrome/keybinding-hints";
9
11
 
10
12
  /**
11
13
  * Login dialog component - replaces editor during OAuth login flow
@@ -98,7 +100,7 @@ export class LoginDialogComponent extends OverlayPanel {
98
100
  ),
99
101
  );
100
102
 
101
- const clickHint = process.platform === "darwin" ? "Cmd+click to open" : "Ctrl+click to open";
103
+ const clickHint = `${formatKeyHint(keyHintPlatform() === "darwin" ? "super" : "ctrl")}+click to open`;
102
104
  const hyperlink = `\x1b]8;;${url}\x07${clickHint}\x1b]8;;\x07`;
103
105
  this.#contentContainer.addChild(new Text(theme.fg("dim", hyperlink), 0, 0));
104
106
 
@@ -132,7 +134,9 @@ export class LoginDialogComponent extends OverlayPanel {
132
134
  this.#contentContainer.addChild(new Spacer(1));
133
135
  this.#contentContainer.addChild(new Text(theme.fg("dim", prompt), 0, 0));
134
136
  this.#contentContainer.addChild(this.#input);
135
- this.#contentContainer.addChild(new Text(theme.fg("dim", "(Escape to cancel)"), 0, 0));
137
+ this.#contentContainer.addChild(
138
+ new Text(theme.fg("dim", `(${editorKey("tui.select.cancel")} to cancel)`), 0, 0),
139
+ );
136
140
  }
137
141
  this.#tui.requestRender();
138
142
 
@@ -175,7 +179,16 @@ export class LoginDialogComponent extends OverlayPanel {
175
179
  this.#contentContainer.addChild(new Text(theme.fg("dim", `e.g., ${prompt.placeholder}`), 0, 0));
176
180
  }
177
181
  this.#contentContainer.addChild(this.#input);
178
- this.#contentContainer.addChild(new Text(theme.fg("dim", "(Escape to cancel, Enter to submit)"), 0, 0));
182
+ this.#contentContainer.addChild(
183
+ new Text(
184
+ theme.fg(
185
+ "dim",
186
+ `(${editorKey("tui.select.cancel")} to cancel, ${editorKey("tui.input.submit")} to submit)`,
187
+ ),
188
+ 0,
189
+ 0,
190
+ ),
191
+ );
179
192
 
180
193
  this.#tui.requestRender();
181
194
 
@@ -200,7 +213,7 @@ export class LoginDialogComponent extends OverlayPanel {
200
213
  showWaiting(message: string): void {
201
214
  this.#contentContainer.addChild(new Spacer(1));
202
215
  this.#contentContainer.addChild(new Text(theme.fg("dim", message), 0, 0));
203
- this.#contentContainer.addChild(new Text(theme.fg("dim", "(Escape to cancel)"), 0, 0));
216
+ this.#contentContainer.addChild(new Text(theme.fg("dim", `(${editorKey("tui.select.cancel")} to cancel)`), 0, 0));
204
217
  this.#tui.requestRender();
205
218
  }
206
219
 
@@ -4,6 +4,8 @@ import { matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../keyb
4
4
  import { OverlayPanel } from "../chrome/overlay-box";
5
5
  import { MenuSelection } from "../components/menu-selection";
6
6
  import { centeredViewportRange } from "../components/scroll-viewport";
7
+ import { formatKeyHint } from "../app-keybindings";
8
+ import { editorKey, editorKeys } from "../chrome/keybinding-hints";
7
9
 
8
10
  const LOGOUT_SELECTOR_MAX_VISIBLE = 10;
9
11
 
@@ -84,7 +86,14 @@ export class LogoutAccountSelectorComponent extends OverlayPanel {
84
86
  }
85
87
 
86
88
  this.#listContainer.addChild(
87
- new TruncatedText(theme.fg("muted", "↑/↓ select · ↵ log out account · Esc cancel"), 0, 0),
89
+ new TruncatedText(
90
+ theme.fg(
91
+ "muted",
92
+ `${editorKeys("tui.select.up", "tui.select.down")} select · ${formatKeyHint("enter")} log out account · ${editorKey("tui.select.cancel")} cancel`,
93
+ ),
94
+ 0,
95
+ 0,
96
+ ),
88
97
  );
89
98
  }
90
99
 
@@ -9,6 +9,8 @@ import { getMCPConfigPath, getProjectDir } from "@oh-my-pi/pi-utils";
9
9
  import { shortenPath } from "../render/render-utils";
10
10
  import { getSelectListTheme, theme } from "../theme/theme";
11
11
  import { matchesAppInterrupt, matchesSelectDown, matchesSelectUp } from "../keybinding-matchers";
12
+ import { formatKeyHint } from "../app-keybindings";
13
+ import { editorKey, editorKeys, interruptKey } from "../chrome/keybinding-hints";
12
14
  import { OverlayPanel } from "../chrome/overlay-box";
13
15
  import { TextFormField, type FormFieldTheme } from "../components/form";
14
16
  import { SelectList } from "../components/select-list";
@@ -163,6 +165,16 @@ function sanitize(text: string): string {
163
165
  return truncateToWidth(replaceTabs(text), MAX_DISPLAY_WIDTH);
164
166
  }
165
167
 
168
+ /** Footer for text steps; the field submits on `tui.input.submit` and cancels on `tui.select.cancel`. */
169
+ function inputHint(escapeAction: string): string {
170
+ return `[${editorKey("tui.input.submit")} to continue, ${editorKey("tui.select.cancel")} to ${escapeAction}]`;
171
+ }
172
+
173
+ /** Footer for choice steps; the wizard routes navigation, Enter, and Escape itself. */
174
+ function choiceHint(escapeAction: string): string {
175
+ return `[${editorKeys("tui.select.up", "tui.select.down")} to navigate, ${formatKeyHint("enter")} to select, ${interruptKey()} to ${escapeAction}]`;
176
+ }
177
+
166
178
  const mcpFormTheme: FormFieldTheme = {
167
179
  label: text => text,
168
180
  description: text => theme.fg("muted", text),
@@ -451,7 +463,7 @@ export class MCPAddWizard extends OverlayPanel {
451
463
  heading: "Step 1: Server Name",
452
464
  prompt: "Enter a unique name for this server:",
453
465
  initial: this.#state.name,
454
- hint: "[Only letters, numbers, dash, underscore, dot, colon]\n[Enter to continue, Esc to cancel]",
466
+ hint: `[Only letters, numbers, dash, underscore, dot, colon]\n${inputHint("cancel")}`,
455
467
  optional: false,
456
468
  error: this.#validationError,
457
469
  });
@@ -466,7 +478,7 @@ export class MCPAddWizard extends OverlayPanel {
466
478
  { label: "http (HTTP server)" },
467
479
  { label: "sse (Server-Sent Events)" },
468
480
  ],
469
- hint: "[↑↓ to navigate, Enter to select, Esc to cancel]",
481
+ hint: choiceHint("cancel"),
470
482
  });
471
483
  }
472
484
 
@@ -475,7 +487,7 @@ export class MCPAddWizard extends OverlayPanel {
475
487
  heading: "Step 3: Command",
476
488
  prompt: "Enter the command to run:",
477
489
  initial: this.#state.command,
478
- hint: "[Enter to continue, Esc to go back]",
490
+ hint: inputHint("go back"),
479
491
  optional: false,
480
492
  });
481
493
  }
@@ -485,7 +497,7 @@ export class MCPAddWizard extends OverlayPanel {
485
497
  heading: "Step 4: Arguments (Optional)",
486
498
  prompt: "Enter command arguments (space-separated):",
487
499
  initial: this.#state.args,
488
- hint: "[Press Enter to skip or continue]",
500
+ hint: `[Press ${editorKey("tui.input.submit")} to skip or continue]`,
489
501
  optional: true,
490
502
  });
491
503
  }
@@ -495,7 +507,7 @@ export class MCPAddWizard extends OverlayPanel {
495
507
  heading: "Step 3: Server URL",
496
508
  prompt: "Enter the server URL:",
497
509
  initial: this.#state.url,
498
- hint: "[Must start with http:// or https://]\n[Enter to continue, Esc to go back]",
510
+ hint: `[Must start with http:// or https://]\n${inputHint("go back")}`,
499
511
  optional: false,
500
512
  error: this.#validationError,
501
513
  });
@@ -505,7 +517,7 @@ export class MCPAddWizard extends OverlayPanel {
505
517
  this.#choiceStep({
506
518
  heading: "Step: How to provide the key?",
507
519
  choices: [{ label: "Environment variable" }, { label: "HTTP header" }],
508
- hint: "[↑↓ to navigate, Enter to select, Esc to go back]",
520
+ hint: choiceHint("go back"),
509
521
  });
510
522
  }
511
523
 
@@ -514,7 +526,7 @@ export class MCPAddWizard extends OverlayPanel {
514
526
  heading: "Step: Environment Variable Name",
515
527
  prompt: "Enter the environment variable name:",
516
528
  initial: this.#state.envVarName,
517
- hint: "[Enter to continue, Esc to go back]",
529
+ hint: inputHint("go back"),
518
530
  optional: false,
519
531
  });
520
532
  }
@@ -524,7 +536,7 @@ export class MCPAddWizard extends OverlayPanel {
524
536
  heading: "Step: HTTP Header Name",
525
537
  prompt: "Enter the HTTP header name:",
526
538
  initial: this.#state.headerName,
527
- hint: "[Enter to continue, Esc to go back]",
539
+ hint: inputHint("go back"),
528
540
  optional: false,
529
541
  });
530
542
  }
@@ -536,7 +548,7 @@ export class MCPAddWizard extends OverlayPanel {
536
548
  this.#choiceStep({
537
549
  heading: "Step: Configuration Scope",
538
550
  choices: [{ label: `User level (${userPathLabel})` }, { label: `Project level (${projectPathLabel})` }],
539
- hint: "[↑↓ to navigate, Enter to select, Esc to go back]",
551
+ hint: choiceHint("go back"),
540
552
  });
541
553
  }
542
554
 
@@ -576,7 +588,7 @@ export class MCPAddWizard extends OverlayPanel {
576
588
  heading: "Review Configuration",
577
589
  intro: summary,
578
590
  choices: [{ label: "Yes" }, { label: "No" }],
579
- hint: "[↑↓ to navigate, Enter to select, Esc to go back]",
591
+ hint: choiceHint("go back"),
580
592
  kind: "confirm",
581
593
  });
582
594
  }
@@ -931,7 +943,7 @@ export class MCPAddWizard extends OverlayPanel {
931
943
  { label: "OAuth flow (web-based)", description: "(opens browser)" },
932
944
  { label: "Manual API key/token", description: "(paste or use shell command)" },
933
945
  ],
934
- hint: "[↑↓ to navigate, Enter to select, Esc to go back]",
946
+ hint: choiceHint("go back"),
935
947
  });
936
948
  }
937
949
 
@@ -941,7 +953,7 @@ export class MCPAddWizard extends OverlayPanel {
941
953
  prompt: "Enter the OAuth authorization endpoint:",
942
954
  initial: this.#state.oauthAuthUrl,
943
955
  details: ["e.g., https://auth.example.com/oauth/authorize"],
944
- hint: "[Enter to continue, Esc to go back]",
956
+ hint: inputHint("go back"),
945
957
  optional: false,
946
958
  });
947
959
  }
@@ -952,7 +964,7 @@ export class MCPAddWizard extends OverlayPanel {
952
964
  prompt: "Enter the OAuth token endpoint:",
953
965
  initial: this.#state.oauthTokenUrl,
954
966
  details: ["e.g., https://auth.example.com/oauth/token"],
955
- hint: "[Enter to continue, Esc to go back]",
967
+ hint: inputHint("go back"),
956
968
  optional: false,
957
969
  });
958
970
  }
@@ -962,7 +974,7 @@ export class MCPAddWizard extends OverlayPanel {
962
974
  heading: "OAuth: Client ID",
963
975
  prompt: "Enter your OAuth client ID:",
964
976
  initial: this.#state.oauthClientId,
965
- hint: "[Enter to continue, Esc to go back]",
977
+ hint: inputHint("go back"),
966
978
  optional: false,
967
979
  });
968
980
  }
@@ -973,7 +985,7 @@ export class MCPAddWizard extends OverlayPanel {
973
985
  prompt: "Enter your OAuth client secret:",
974
986
  initial: this.#state.oauthClientSecret,
975
987
  details: ["(Leave empty for PKCE-only flows)"],
976
- hint: "[Enter to continue, Esc to go back]",
988
+ hint: inputHint("go back"),
977
989
  optional: true,
978
990
  });
979
991
  }
@@ -984,7 +996,7 @@ export class MCPAddWizard extends OverlayPanel {
984
996
  prompt: "Enter OAuth scopes (space-separated):",
985
997
  initial: this.#state.oauthScopes,
986
998
  details: ["e.g., read write"],
987
- hint: "[Enter to continue, Esc to go back]",
999
+ hint: inputHint("go back"),
988
1000
  optional: true,
989
1001
  });
990
1002
  }
@@ -1002,7 +1014,7 @@ export class MCPAddWizard extends OverlayPanel {
1002
1014
  headingTone: this.#oauthErrorHeading?.tone ?? "error",
1003
1015
  intro,
1004
1016
  choices: [{ label: "Retry OAuth authentication" }, { label: "Edit OAuth settings" }],
1005
- hint: "[↑↓ to navigate, Enter to select, Esc to go back]",
1017
+ hint: choiceHint("go back"),
1006
1018
  });
1007
1019
  }
1008
1020
 
@@ -1012,7 +1024,7 @@ export class MCPAddWizard extends OverlayPanel {
1012
1024
  prompt: "Enter your API key or token:",
1013
1025
  initial: this.#state.apiKey,
1014
1026
  details: ["(Supports !command for password manager)"],
1015
- hint: "[Enter to continue, Esc to go back]",
1027
+ hint: inputHint("go back"),
1016
1028
  optional: false,
1017
1029
  });
1018
1030
  }
@@ -1210,7 +1222,7 @@ export class MCPAddWizard extends OverlayPanel {
1210
1222
  "OAuth configuration incomplete",
1211
1223
  "error",
1212
1224
  incompleteBody,
1213
- new Text(theme.fg("muted", "[Press Esc to go back]"), 0, 0),
1225
+ new Text(theme.fg("muted", `[Press ${interruptKey()} to go back]`), 0, 0),
1214
1226
  );
1215
1227
  this.#requestRender();
1216
1228
  return;
@@ -1226,7 +1238,7 @@ export class MCPAddWizard extends OverlayPanel {
1226
1238
  "OAuth Authentication",
1227
1239
  "accent",
1228
1240
  authBody,
1229
- new Text(theme.fg("muted", "(Press Esc to cancel)"), 0, 0),
1241
+ new Text(theme.fg("muted", `(Press ${interruptKey()} to cancel)`), 0, 0),
1230
1242
  );
1231
1243
  this.#requestRender();
1232
1244