@dungle-scrubs/tallow 0.8.26 → 0.8.28

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 (114) hide show
  1. package/README.md +42 -1
  2. package/dist/cli.js +7 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config.d.ts +1 -1
  5. package/dist/config.js +1 -1
  6. package/dist/interactive-mode-patch.d.ts +1 -0
  7. package/dist/interactive-mode-patch.d.ts.map +1 -1
  8. package/dist/interactive-mode-patch.js +40 -1
  9. package/dist/interactive-mode-patch.js.map +1 -1
  10. package/dist/model-metadata-overrides.d.ts +2 -5
  11. package/dist/model-metadata-overrides.d.ts.map +1 -1
  12. package/dist/model-metadata-overrides.js +23 -12
  13. package/dist/model-metadata-overrides.js.map +1 -1
  14. package/dist/pid-manager.d.ts +2 -9
  15. package/dist/pid-manager.d.ts.map +1 -1
  16. package/dist/pid-manager.js +1 -58
  17. package/dist/pid-manager.js.map +1 -1
  18. package/dist/pid-schema.d.ts +51 -0
  19. package/dist/pid-schema.d.ts.map +1 -0
  20. package/dist/pid-schema.js +70 -0
  21. package/dist/pid-schema.js.map +1 -0
  22. package/dist/sdk.d.ts.map +1 -1
  23. package/dist/sdk.js +24 -17
  24. package/dist/sdk.js.map +1 -1
  25. package/dist/workspace-transition-interactive.d.ts.map +1 -1
  26. package/dist/workspace-transition-interactive.js +53 -3
  27. package/dist/workspace-transition-interactive.js.map +1 -1
  28. package/dist/workspace-transition.d.ts +2 -1
  29. package/dist/workspace-transition.d.ts.map +1 -1
  30. package/dist/workspace-transition.js +16 -4
  31. package/dist/workspace-transition.js.map +1 -1
  32. package/extensions/__integration__/audit-findings.test.ts +309 -0
  33. package/extensions/__integration__/cd-tool-guidelines.test.ts +46 -0
  34. package/extensions/__integration__/tasks-runtime.test.ts +63 -12
  35. package/extensions/__integration__/welcome-screen.test.ts +240 -0
  36. package/extensions/_shared/lazy-init.ts +88 -3
  37. package/extensions/_shared/pid-registry.ts +8 -82
  38. package/extensions/background-task-tool/index.ts +1 -1
  39. package/extensions/cd-tool/index.ts +4 -1
  40. package/extensions/cheatsheet/__tests__/cheatsheet.test.ts +47 -0
  41. package/extensions/clear/__tests__/clear.test.ts +38 -0
  42. package/extensions/edit-tool-enhanced/index.ts +3 -1
  43. package/extensions/git-status/__tests__/git-status.test.ts +32 -0
  44. package/extensions/health/__tests__/diagnostics.test.ts +25 -0
  45. package/extensions/health/index.ts +61 -0
  46. package/extensions/loop/__tests__/loop.test.ts +365 -1
  47. package/extensions/loop/index.ts +213 -3
  48. package/extensions/mcp-adapter-tool/index.ts +1 -1
  49. package/extensions/minimal-skill-display/__tests__/minimal-skill-display.test.ts +20 -0
  50. package/extensions/permissions/__tests__/permissions.test.ts +213 -0
  51. package/extensions/progress-indicator/__tests__/progress-indicator.test.ts +104 -0
  52. package/extensions/prompt-suggestions/__tests__/autocomplete.test.ts +111 -3
  53. package/extensions/prompt-suggestions/autocomplete.ts +23 -5
  54. package/extensions/prompt-suggestions/index.ts +62 -3
  55. package/extensions/random-spinner/__tests__/random-spinner.test.ts +35 -0
  56. package/extensions/read-tool-enhanced/index.ts +5 -1
  57. package/extensions/session-memory/index.ts +1 -1
  58. package/extensions/session-namer/index.ts +1 -1
  59. package/extensions/show-system-prompt/__tests__/show-system-prompt.test.ts +51 -0
  60. package/extensions/subagent-tool/__tests__/presentation-rendering.test.ts +9 -8
  61. package/extensions/subagent-tool/__tests__/process-liveness.test.ts +51 -0
  62. package/extensions/subagent-tool/__tests__/subprocess-args.test.ts +120 -0
  63. package/extensions/subagent-tool/formatting.ts +2 -0
  64. package/extensions/subagent-tool/index.ts +160 -97
  65. package/extensions/subagent-tool/process.ts +152 -40
  66. package/extensions/tasks/commands/register-tasks-extension.ts +64 -20
  67. package/extensions/tasks/extension.json +1 -0
  68. package/extensions/tasks/index.ts +2 -12
  69. package/extensions/tasks/state/index.ts +26 -0
  70. package/extensions/teams-tool/dashboard.ts +13 -1
  71. package/extensions/teams-tool/sessions/spawn.ts +2 -2
  72. package/extensions/teams-tool/tools/register-extension.ts +10 -2
  73. package/extensions/upstream-check/__tests__/upstream-check.test.ts +49 -0
  74. package/extensions/welcome-screen/__tests__/welcome-screen.test.ts +35 -0
  75. package/extensions/welcome-screen/extension.json +20 -0
  76. package/extensions/welcome-screen/index.ts +189 -0
  77. package/extensions/wezterm-notify/__tests__/index.test.ts +49 -11
  78. package/extensions/wezterm-notify/index.ts +5 -3
  79. package/extensions/write-tool-enhanced/__tests__/write-tool-enhanced.test.ts +296 -0
  80. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +2 -2
  81. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
  82. package/node_modules/@mariozechner/pi-tui/dist/index.js +2 -2
  83. package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
  84. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +309 -25
  85. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
  86. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +392 -72
  87. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
  88. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +30 -0
  89. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
  90. package/node_modules/@mariozechner/pi-tui/dist/keys.js +50 -6
  91. package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
  92. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +27 -0
  93. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
  94. package/node_modules/@mariozechner/pi-tui/dist/terminal.js +59 -4
  95. package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
  96. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +9 -0
  97. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
  98. package/node_modules/@mariozechner/pi-tui/dist/tui.js +50 -1
  99. package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
  100. package/node_modules/@mariozechner/pi-tui/package.json +1 -1
  101. package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +134 -0
  102. package/node_modules/@mariozechner/pi-tui/src/__tests__/tmux-compat.test.ts +204 -0
  103. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +49 -0
  104. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +2 -0
  105. package/node_modules/@mariozechner/pi-tui/src/index.ts +11 -0
  106. package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +478 -140
  107. package/node_modules/@mariozechner/pi-tui/src/keys.ts +84 -6
  108. package/node_modules/@mariozechner/pi-tui/src/terminal.ts +69 -4
  109. package/node_modules/@mariozechner/pi-tui/src/tui.ts +64 -1
  110. package/package.json +11 -10
  111. package/runtime/config.ts +7 -0
  112. package/runtime/model-metadata-overrides.ts +7 -0
  113. package/runtime/pid-schema.ts +13 -0
  114. package/skills/tallow-expert/SKILL.md +7 -5
@@ -1,183 +1,521 @@
1
1
  import { type KeyId, matchesKey } from "./keys.js";
2
2
 
3
+ /** Modern TUI keybinding identifiers. */
4
+ export interface Keybindings {
5
+ "tui.editor.cursorDown": true;
6
+ "tui.editor.cursorLeft": true;
7
+ "tui.editor.cursorLineEnd": true;
8
+ "tui.editor.cursorLineStart": true;
9
+ "tui.editor.cursorRight": true;
10
+ "tui.editor.cursorUp": true;
11
+ "tui.editor.cursorWordLeft": true;
12
+ "tui.editor.cursorWordRight": true;
13
+ "tui.editor.deleteCharBackward": true;
14
+ "tui.editor.deleteCharForward": true;
15
+ "tui.editor.deleteToLineEnd": true;
16
+ "tui.editor.deleteToLineStart": true;
17
+ "tui.editor.deleteWordBackward": true;
18
+ "tui.editor.deleteWordForward": true;
19
+ "tui.editor.jumpBackward": true;
20
+ "tui.editor.jumpForward": true;
21
+ "tui.editor.pageDown": true;
22
+ "tui.editor.pageUp": true;
23
+ "tui.editor.undo": true;
24
+ "tui.editor.yank": true;
25
+ "tui.editor.yankPop": true;
26
+ "tui.input.copy": true;
27
+ "tui.input.newLine": true;
28
+ "tui.input.submit": true;
29
+ "tui.input.tab": true;
30
+ "tui.select.cancel": true;
31
+ "tui.select.confirm": true;
32
+ "tui.select.down": true;
33
+ "tui.select.pageDown": true;
34
+ "tui.select.pageUp": true;
35
+ "tui.select.up": true;
36
+ }
37
+
38
+ const LEGACY_TO_MODERN_KEYBINDINGS = {
39
+ copy: "tui.input.copy",
40
+ cursorDown: "tui.editor.cursorDown",
41
+ cursorLeft: "tui.editor.cursorLeft",
42
+ cursorLineEnd: "tui.editor.cursorLineEnd",
43
+ cursorLineStart: "tui.editor.cursorLineStart",
44
+ cursorRight: "tui.editor.cursorRight",
45
+ cursorUp: "tui.editor.cursorUp",
46
+ cursorWordLeft: "tui.editor.cursorWordLeft",
47
+ cursorWordRight: "tui.editor.cursorWordRight",
48
+ deleteCharBackward: "tui.editor.deleteCharBackward",
49
+ deleteCharForward: "tui.editor.deleteCharForward",
50
+ deleteSession: "app.session.delete",
51
+ deleteSessionNoninvasive: "app.session.deleteNoninvasive",
52
+ deleteToLineEnd: "tui.editor.deleteToLineEnd",
53
+ deleteToLineStart: "tui.editor.deleteToLineStart",
54
+ deleteWordBackward: "tui.editor.deleteWordBackward",
55
+ deleteWordForward: "tui.editor.deleteWordForward",
56
+ expandTools: "app.tools.expand",
57
+ jumpBackward: "tui.editor.jumpBackward",
58
+ jumpForward: "tui.editor.jumpForward",
59
+ newLine: "tui.input.newLine",
60
+ pageDown: "tui.editor.pageDown",
61
+ pageUp: "tui.editor.pageUp",
62
+ renameSession: "app.session.rename",
63
+ selectCancel: "tui.select.cancel",
64
+ selectConfirm: "tui.select.confirm",
65
+ selectDown: "tui.select.down",
66
+ selectPageDown: "tui.select.pageDown",
67
+ selectPageUp: "tui.select.pageUp",
68
+ selectUp: "tui.select.up",
69
+ submit: "tui.input.submit",
70
+ tab: "tui.input.tab",
71
+ toggleSessionPath: "app.session.togglePath",
72
+ toggleSessionSort: "app.session.toggleSort",
73
+ undo: "tui.editor.undo",
74
+ yank: "tui.editor.yank",
75
+ yankPop: "tui.editor.yankPop",
76
+ } as const;
77
+
78
+ /** Backward-compatible legacy editor actions. */
79
+ export type EditorAction = keyof typeof LEGACY_TO_MODERN_KEYBINDINGS;
80
+
81
+ /** Modern keybinding identifier. */
82
+ export type Keybinding = keyof Keybindings | EditorAction;
83
+
84
+ /** Single keybinding definition with defaults and UI help text. */
85
+ export interface KeybindingDefinition {
86
+ readonly defaultKeys: KeyId | readonly KeyId[];
87
+ readonly description: string;
88
+ }
89
+
90
+ /** User-provided keybinding overrides. */
91
+ export type KeybindingsConfig = Partial<Record<Keybinding, KeyId | readonly KeyId[]>>;
92
+
93
+ /** Backward-compatible legacy editor config. */
94
+ export type EditorKeybindingsConfig = Partial<Record<EditorAction, KeyId | readonly KeyId[]>>;
95
+
96
+ /** Default legacy editor keybindings preserved for compatibility. */
97
+ export const DEFAULT_EDITOR_KEYBINDINGS: Required<Record<EditorAction, KeyId | readonly KeyId[]>> =
98
+ {
99
+ copy: "ctrl+shift+c",
100
+ cursorDown: "down",
101
+ cursorLeft: ["left", "ctrl+b"],
102
+ cursorLineEnd: ["end", "ctrl+e"],
103
+ cursorLineStart: ["home", "ctrl+a"],
104
+ cursorRight: ["right", "ctrl+f"],
105
+ cursorUp: "up",
106
+ cursorWordLeft: ["alt+left", "ctrl+left", "alt+b"],
107
+ cursorWordRight: ["alt+right", "ctrl+right", "alt+f"],
108
+ deleteCharBackward: "backspace",
109
+ deleteCharForward: ["delete", "ctrl+d"],
110
+ deleteSession: "ctrl+d",
111
+ deleteSessionNoninvasive: "ctrl+backspace",
112
+ deleteToLineEnd: "ctrl+k",
113
+ deleteToLineStart: "ctrl+u",
114
+ deleteWordBackward: ["ctrl+w", "alt+backspace"],
115
+ deleteWordForward: ["alt+d", "alt+delete"],
116
+ expandTools: "ctrl+o",
117
+ jumpBackward: "ctrl+alt+]",
118
+ jumpForward: "ctrl+]",
119
+ newLine: "shift+enter",
120
+ pageDown: "pageDown",
121
+ pageUp: "pageUp",
122
+ renameSession: "ctrl+r",
123
+ selectCancel: ["escape", "ctrl+c"],
124
+ selectConfirm: "enter",
125
+ selectDown: "down",
126
+ selectPageDown: "pageDown",
127
+ selectPageUp: "pageUp",
128
+ selectUp: "up",
129
+ submit: "enter",
130
+ tab: "tab",
131
+ toggleSessionPath: "ctrl+p",
132
+ toggleSessionSort: "ctrl+s",
133
+ undo: "ctrl+-",
134
+ yank: "ctrl+y",
135
+ yankPop: "alt+y",
136
+ };
137
+
138
+ /** Modern TUI keybinding definitions consumed by pi-coding-agent 0.61+. */
139
+ export const TUI_KEYBINDINGS = {
140
+ "tui.editor.cursorDown": {
141
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.cursorDown,
142
+ description: "Move cursor down",
143
+ },
144
+ "tui.editor.cursorLeft": {
145
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.cursorLeft,
146
+ description: "Move cursor left",
147
+ },
148
+ "tui.editor.cursorLineEnd": {
149
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.cursorLineEnd,
150
+ description: "Move to line end",
151
+ },
152
+ "tui.editor.cursorLineStart": {
153
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.cursorLineStart,
154
+ description: "Move to line start",
155
+ },
156
+ "tui.editor.cursorRight": {
157
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.cursorRight,
158
+ description: "Move cursor right",
159
+ },
160
+ "tui.editor.cursorUp": {
161
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.cursorUp,
162
+ description: "Move cursor up",
163
+ },
164
+ "tui.editor.cursorWordLeft": {
165
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.cursorWordLeft,
166
+ description: "Move cursor word left",
167
+ },
168
+ "tui.editor.cursorWordRight": {
169
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.cursorWordRight,
170
+ description: "Move cursor word right",
171
+ },
172
+ "tui.editor.deleteCharBackward": {
173
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.deleteCharBackward,
174
+ description: "Delete character backward",
175
+ },
176
+ "tui.editor.deleteCharForward": {
177
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.deleteCharForward,
178
+ description: "Delete character forward",
179
+ },
180
+ "tui.editor.deleteToLineEnd": {
181
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.deleteToLineEnd,
182
+ description: "Delete to line end",
183
+ },
184
+ "tui.editor.deleteToLineStart": {
185
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.deleteToLineStart,
186
+ description: "Delete to line start",
187
+ },
188
+ "tui.editor.deleteWordBackward": {
189
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.deleteWordBackward,
190
+ description: "Delete word backward",
191
+ },
192
+ "tui.editor.deleteWordForward": {
193
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.deleteWordForward,
194
+ description: "Delete word forward",
195
+ },
196
+ "tui.editor.jumpBackward": {
197
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.jumpBackward,
198
+ description: "Jump backward to character",
199
+ },
200
+ "tui.editor.jumpForward": {
201
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.jumpForward,
202
+ description: "Jump forward to character",
203
+ },
204
+ "tui.editor.pageDown": {
205
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.pageDown,
206
+ description: "Page down",
207
+ },
208
+ "tui.editor.pageUp": {
209
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.pageUp,
210
+ description: "Page up",
211
+ },
212
+ "tui.editor.undo": {
213
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.undo,
214
+ description: "Undo",
215
+ },
216
+ "tui.editor.yank": {
217
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.yank,
218
+ description: "Yank",
219
+ },
220
+ "tui.editor.yankPop": {
221
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.yankPop,
222
+ description: "Yank pop",
223
+ },
224
+ "tui.input.copy": {
225
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.copy,
226
+ description: "Copy selection",
227
+ },
228
+ "tui.input.newLine": {
229
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.newLine,
230
+ description: "Insert newline",
231
+ },
232
+ "tui.input.submit": {
233
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.submit,
234
+ description: "Submit input",
235
+ },
236
+ "tui.input.tab": {
237
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.tab,
238
+ description: "Tab / autocomplete",
239
+ },
240
+ "tui.select.cancel": {
241
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.selectCancel,
242
+ description: "Cancel selection",
243
+ },
244
+ "tui.select.confirm": {
245
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.selectConfirm,
246
+ description: "Confirm selection",
247
+ },
248
+ "tui.select.down": {
249
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.selectDown,
250
+ description: "Move selection down",
251
+ },
252
+ "tui.select.pageDown": {
253
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.selectPageDown,
254
+ description: "Selection page down",
255
+ },
256
+ "tui.select.pageUp": {
257
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.selectPageUp,
258
+ description: "Selection page up",
259
+ },
260
+ "tui.select.up": {
261
+ defaultKeys: DEFAULT_EDITOR_KEYBINDINGS.selectUp,
262
+ description: "Move selection up",
263
+ },
264
+ } as const satisfies Record<keyof Keybindings, KeybindingDefinition>;
265
+
266
+ type ModernKeybinding = keyof Keybindings;
267
+
268
+ type NormalizedKeybindingsConfig = Partial<Record<ModernKeybinding, readonly KeyId[]>>;
269
+
3
270
  /**
4
- * Editor actions that can be bound to keys.
271
+ * Check whether a keybinding name is a supported modern identifier.
272
+ *
273
+ * @param {string} keybinding - Candidate keybinding name.
274
+ * @returns {keybinding is ModernKeybinding} True when the keybinding exists.
5
275
  */
6
- export type EditorAction =
7
- // Cursor movement
8
- | "cursorUp"
9
- | "cursorDown"
10
- | "cursorLeft"
11
- | "cursorRight"
12
- | "cursorWordLeft"
13
- | "cursorWordRight"
14
- | "cursorLineStart"
15
- | "cursorLineEnd"
16
- | "jumpForward"
17
- | "jumpBackward"
18
- | "pageUp"
19
- | "pageDown"
20
- // Deletion
21
- | "deleteCharBackward"
22
- | "deleteCharForward"
23
- | "deleteWordBackward"
24
- | "deleteWordForward"
25
- | "deleteToLineStart"
26
- | "deleteToLineEnd"
27
- // Text input
28
- | "newLine"
29
- | "submit"
30
- | "tab"
31
- // Selection/autocomplete
32
- | "selectUp"
33
- | "selectDown"
34
- | "selectPageUp"
35
- | "selectPageDown"
36
- | "selectConfirm"
37
- | "selectCancel"
38
- // Clipboard
39
- | "copy"
40
- // Kill ring
41
- | "yank"
42
- | "yankPop"
43
- // Undo
44
- | "undo"
45
- // Tool output
46
- | "expandTools"
47
- // Session
48
- | "toggleSessionPath"
49
- | "toggleSessionSort"
50
- | "renameSession"
51
- | "deleteSession"
52
- | "deleteSessionNoninvasive";
53
-
54
- // Re-export KeyId from keys.ts
55
- export type { KeyId };
276
+ function isModernKeybinding(keybinding: string): keybinding is ModernKeybinding {
277
+ return keybinding in TUI_KEYBINDINGS;
278
+ }
279
+
280
+ /**
281
+ * Normalize a legacy or modern keybinding identifier to the modern name.
282
+ * Only checks TUI_KEYBINDINGS and legacy mappings. For consumer-defined
283
+ * keybindings (e.g. app.*), use {@link KeybindingsManager.resolveKeybinding}.
284
+ *
285
+ * @param {Keybinding} keybinding - Keybinding identifier to normalize.
286
+ * @returns {ModernKeybinding | null} Modern keybinding or null when unsupported.
287
+ */
288
+ function normalizeKeybinding(keybinding: Keybinding): ModernKeybinding | null {
289
+ if (isModernKeybinding(keybinding)) {
290
+ return keybinding;
291
+ }
292
+
293
+ const normalized = LEGACY_TO_MODERN_KEYBINDINGS[keybinding as EditorAction];
294
+ return normalized && isModernKeybinding(normalized) ? normalized : null;
295
+ }
56
296
 
57
297
  /**
58
- * Editor keybindings configuration.
298
+ * Convert a raw keybinding value into an array form.
299
+ *
300
+ * @param {KeyId | readonly KeyId[]} value - Raw keybinding value.
301
+ * @returns {readonly KeyId[]} Normalized key array.
59
302
  */
60
- export type EditorKeybindingsConfig = {
61
- [K in EditorAction]?: KeyId | KeyId[];
62
- };
303
+ function normalizeKeyArray(value: KeyId | readonly KeyId[]): readonly KeyId[] {
304
+ return Array.isArray(value) ? [...value] : [value as KeyId];
305
+ }
63
306
 
64
307
  /**
65
- * Default editor keybindings.
308
+ * Normalize user bindings to modern identifiers and array values.
309
+ *
310
+ * @param {KeybindingsConfig} userBindings - Raw user bindings.
311
+ * @returns {NormalizedKeybindingsConfig} Normalized modern binding map.
66
312
  */
67
- export const DEFAULT_EDITOR_KEYBINDINGS: Required<EditorKeybindingsConfig> = {
68
- // Cursor movement
69
- cursorUp: "up",
70
- cursorDown: "down",
71
- cursorLeft: ["left", "ctrl+b"],
72
- cursorRight: ["right", "ctrl+f"],
73
- cursorWordLeft: ["alt+left", "ctrl+left", "alt+b"],
74
- cursorWordRight: ["alt+right", "ctrl+right", "alt+f"],
75
- cursorLineStart: ["home", "ctrl+a"],
76
- cursorLineEnd: ["end", "ctrl+e"],
77
- jumpForward: "ctrl+]",
78
- jumpBackward: "ctrl+alt+]",
79
- pageUp: "pageUp",
80
- pageDown: "pageDown",
81
- // Deletion
82
- deleteCharBackward: "backspace",
83
- deleteCharForward: ["delete", "ctrl+d"],
84
- deleteWordBackward: ["ctrl+w", "alt+backspace"],
85
- deleteWordForward: ["alt+d", "alt+delete"],
86
- deleteToLineStart: "ctrl+u",
87
- deleteToLineEnd: "ctrl+k",
88
- // Text input
89
- newLine: "shift+enter",
90
- submit: "enter",
91
- tab: "tab",
92
- // Selection/autocomplete
93
- selectUp: "up",
94
- selectDown: "down",
95
- selectPageUp: "pageUp",
96
- selectPageDown: "pageDown",
97
- selectConfirm: "enter",
98
- selectCancel: ["escape", "ctrl+c"],
99
- // Clipboard (ctrl+c in editor is handled as selectCancel/interrupt, not copy)
100
- copy: "ctrl+shift+c",
101
- // Kill ring
102
- yank: "ctrl+y",
103
- yankPop: "alt+y",
104
- // Undo
105
- undo: "ctrl+-",
106
- // Tool output
107
- expandTools: "ctrl+o",
108
- // Session
109
- toggleSessionPath: "ctrl+p",
110
- toggleSessionSort: "ctrl+s",
111
- renameSession: "ctrl+r",
112
- deleteSession: "ctrl+d",
113
- deleteSessionNoninvasive: "ctrl+backspace",
114
- };
313
+ function normalizeUserBindings(userBindings: KeybindingsConfig): NormalizedKeybindingsConfig {
314
+ const normalized: NormalizedKeybindingsConfig = {};
315
+
316
+ for (const [rawKey, rawValue] of Object.entries(userBindings)) {
317
+ if (rawValue === undefined) continue;
318
+
319
+ const key = rawKey as Keybinding;
320
+ const modernKey = normalizeKeybinding(key);
321
+ if (!modernKey) continue;
322
+ if (key !== modernKey && userBindings[modernKey] !== undefined) continue;
323
+
324
+ normalized[modernKey] = [...normalizeKeyArray(rawValue)];
325
+ }
326
+
327
+ return normalized;
328
+ }
115
329
 
116
330
  /**
117
- * Manages keybindings for the editor.
331
+ * Keybinding manager compatible with pi-tui 0.61+ while preserving legacy names.
118
332
  */
119
- export class EditorKeybindingsManager {
120
- private actionToKeys: Map<EditorAction, KeyId[]>;
333
+ export class KeybindingsManager {
334
+ private readonly definitions: Readonly<Record<ModernKeybinding, KeybindingDefinition>>;
335
+ private readonly resolvedKeys = new Map<ModernKeybinding, KeyId[]>();
336
+ private userBindings: NormalizedKeybindingsConfig;
121
337
 
122
- constructor(config: EditorKeybindingsConfig = {}) {
123
- this.actionToKeys = new Map();
124
- this.buildMaps(config);
338
+ /**
339
+ * Create a keybindings manager.
340
+ *
341
+ * @param {Readonly<Record<ModernKeybinding, KeybindingDefinition>>} definitions - Keybinding definitions.
342
+ * @param {KeybindingsConfig} userBindings - Optional user overrides.
343
+ */
344
+ constructor(
345
+ definitions: Readonly<Record<ModernKeybinding, KeybindingDefinition>> = TUI_KEYBINDINGS,
346
+ userBindings: KeybindingsConfig = {}
347
+ ) {
348
+ this.definitions = definitions;
349
+ this.userBindings = this.normalizeUserBindingsWithDefinitions(userBindings);
350
+ this.rebuild();
125
351
  }
126
352
 
127
- private buildMaps(config: EditorKeybindingsConfig): void {
128
- this.actionToKeys.clear();
353
+ /**
354
+ * Rebuild resolved bindings from defaults plus user overrides.
355
+ *
356
+ * @returns {void} Nothing.
357
+ */
358
+ private rebuild(): void {
359
+ this.resolvedKeys.clear();
129
360
 
130
- // Start with defaults
131
- for (const [action, keys] of Object.entries(DEFAULT_EDITOR_KEYBINDINGS)) {
132
- const keyArray = Array.isArray(keys) ? keys : [keys];
133
- this.actionToKeys.set(action as EditorAction, [...keyArray]);
361
+ for (const [keybinding, definition] of Object.entries(this.definitions)) {
362
+ const modernKey = keybinding as ModernKeybinding;
363
+ const override = this.userBindings[modernKey];
364
+ const keys = override ?? normalizeKeyArray(definition.defaultKeys);
365
+ this.resolvedKeys.set(modernKey, [...keys]);
134
366
  }
367
+ }
135
368
 
136
- // Override with user config
137
- for (const [action, keys] of Object.entries(config)) {
138
- if (keys === undefined) continue;
139
- const keyArray = Array.isArray(keys) ? keys : [keys];
140
- this.actionToKeys.set(action as EditorAction, keyArray);
141
- }
369
+ /**
370
+ * Resolve a keybinding name to its lookup key in resolvedKeys.
371
+ * Checks the TUI namespace and legacy names first, then falls back to
372
+ * a direct lookup in definitions/resolvedKeys for consumer-defined
373
+ * keybindings (e.g. app.* keybindings registered by pi-coding-agent).
374
+ *
375
+ * @param {string} keybinding - Keybinding identifier.
376
+ * @returns {ModernKeybinding | null} Resolved key or null when unknown.
377
+ */
378
+ private resolveKeybinding(keybinding: string): ModernKeybinding | null {
379
+ const normalized = normalizeKeybinding(keybinding as Keybinding);
380
+ if (normalized) return normalized;
381
+
382
+ // Fall back to direct lookup for keybindings registered via definitions
383
+ // but outside the tui.* namespace (e.g. app.interrupt, app.clear).
384
+ // Check definitions first (available at construction) then resolvedKeys.
385
+ const asKey = keybinding as ModernKeybinding;
386
+ return asKey in this.definitions || this.resolvedKeys.has(asKey) ? asKey : null;
142
387
  }
143
388
 
144
389
  /**
145
- * Check if input matches a specific action.
390
+ * Check whether input matches a keybinding.
391
+ *
392
+ * @param {string} data - Raw terminal input.
393
+ * @param {Keybinding | string} keybinding - Keybinding identifier.
394
+ * @returns {boolean} True when the input matches.
146
395
  */
147
- matches(data: string, action: EditorAction): boolean {
148
- const keys = this.actionToKeys.get(action);
149
- if (!keys) return false;
150
- for (const key of keys) {
396
+ matches(data: string, keybinding: Keybinding | string): boolean {
397
+ const resolved = this.resolveKeybinding(keybinding as string);
398
+ if (!resolved) return false;
399
+
400
+ for (const key of this.resolvedKeys.get(resolved) ?? []) {
151
401
  if (matchesKey(data, key)) return true;
152
402
  }
153
403
  return false;
154
404
  }
155
405
 
156
406
  /**
157
- * Get keys bound to an action.
407
+ * Get the keys currently bound to a keybinding.
408
+ *
409
+ * @param {Keybinding | string} keybinding - Keybinding identifier.
410
+ * @returns {KeyId[]} Resolved key list.
411
+ */
412
+ getKeys(keybinding: Keybinding | string): KeyId[] {
413
+ const resolved = this.resolveKeybinding(keybinding as string);
414
+ return resolved ? [...(this.resolvedKeys.get(resolved) ?? [])] : [];
415
+ }
416
+
417
+ /**
418
+ * Replace user bindings and rebuild the resolved map.
419
+ *
420
+ * @param {KeybindingsConfig} userBindings - New user bindings.
421
+ * @returns {void} Nothing.
422
+ */
423
+ setUserBindings(userBindings: KeybindingsConfig): void {
424
+ this.userBindings = this.normalizeUserBindingsWithDefinitions(userBindings);
425
+ this.rebuild();
426
+ }
427
+
428
+ /**
429
+ * Normalize user bindings using both TUI namespace and consumer definitions.
430
+ * This extends the module-level normalizeUserBindings to also accept
431
+ * keybinding names that exist in this.definitions (e.g. app.*).
432
+ *
433
+ * @param {KeybindingsConfig} userBindings - Raw user bindings.
434
+ * @returns {NormalizedKeybindingsConfig} Normalized modern binding map.
435
+ */
436
+ private normalizeUserBindingsWithDefinitions(
437
+ userBindings: KeybindingsConfig
438
+ ): NormalizedKeybindingsConfig {
439
+ const normalized: NormalizedKeybindingsConfig = {};
440
+
441
+ for (const [rawKey, rawValue] of Object.entries(userBindings)) {
442
+ if (rawValue === undefined) continue;
443
+
444
+ const resolved = this.resolveKeybinding(rawKey);
445
+ if (!resolved) continue;
446
+ if (rawKey !== resolved && userBindings[resolved] !== undefined) continue;
447
+
448
+ normalized[resolved] = [...normalizeKeyArray(rawValue)];
449
+ }
450
+
451
+ return normalized;
452
+ }
453
+
454
+ /**
455
+ * Get the fully resolved modern keybinding config.
456
+ *
457
+ * @returns {NormalizedKeybindingsConfig} Resolved keybinding config.
158
458
  */
159
- getKeys(action: EditorAction): KeyId[] {
160
- return this.actionToKeys.get(action) ?? [];
459
+ getResolvedBindings(): NormalizedKeybindingsConfig {
460
+ const resolved: NormalizedKeybindingsConfig = {};
461
+ for (const [keybinding, keys] of this.resolvedKeys.entries()) {
462
+ resolved[keybinding] = [...keys];
463
+ }
464
+ return resolved;
161
465
  }
466
+ }
162
467
 
468
+ /** Backward-compatible alias for legacy callers. */
469
+ export class EditorKeybindingsManager extends KeybindingsManager {
163
470
  /**
164
- * Update configuration.
471
+ * Create a legacy editor keybindings manager.
472
+ *
473
+ * @param {EditorKeybindingsConfig} config - Legacy editor keybinding overrides.
165
474
  */
166
- setConfig(config: EditorKeybindingsConfig): void {
167
- this.buildMaps(config);
475
+ constructor(config: EditorKeybindingsConfig = {}) {
476
+ super(TUI_KEYBINDINGS, config);
168
477
  }
169
478
  }
170
479
 
171
- // Global instance
172
- let globalEditorKeybindings: EditorKeybindingsManager | null = null;
480
+ let globalKeybindings: KeybindingsManager | null = null;
173
481
 
174
- export function getEditorKeybindings(): EditorKeybindingsManager {
175
- if (!globalEditorKeybindings) {
176
- globalEditorKeybindings = new EditorKeybindingsManager();
482
+ /**
483
+ * Get the shared keybindings manager.
484
+ *
485
+ * @returns {KeybindingsManager} Shared keybindings manager.
486
+ */
487
+ export function getKeybindings(): KeybindingsManager {
488
+ if (!globalKeybindings) {
489
+ globalKeybindings = new EditorKeybindingsManager();
177
490
  }
178
- return globalEditorKeybindings;
491
+ return globalKeybindings;
492
+ }
493
+
494
+ /**
495
+ * Replace the shared keybindings manager.
496
+ *
497
+ * @param {KeybindingsManager} manager - Keybindings manager to install.
498
+ * @returns {void} Nothing.
499
+ */
500
+ export function setKeybindings(manager: KeybindingsManager): void {
501
+ globalKeybindings = manager;
179
502
  }
180
503
 
504
+ /**
505
+ * Get the shared legacy editor keybindings manager.
506
+ *
507
+ * @returns {EditorKeybindingsManager} Shared legacy-compatible manager.
508
+ */
509
+ export function getEditorKeybindings(): EditorKeybindingsManager {
510
+ return getKeybindings() as EditorKeybindingsManager;
511
+ }
512
+
513
+ /**
514
+ * Replace the shared legacy editor keybindings manager.
515
+ *
516
+ * @param {EditorKeybindingsManager} manager - Legacy keybindings manager.
517
+ * @returns {void} Nothing.
518
+ */
181
519
  export function setEditorKeybindings(manager: EditorKeybindingsManager): void {
182
- globalEditorKeybindings = manager;
520
+ setKeybindings(manager);
183
521
  }