@crouton-kit/crouter 0.3.70 → 0.3.78

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 (147) hide show
  1. package/README.md +1 -1
  2. package/dist/builtin-memory/00-runtime-base.md +3 -10
  3. package/dist/builtin-memory/04-base-worker.md +18 -0
  4. package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
  5. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
  6. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +622 -724
  7. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +231 -68
  8. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
  9. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +361 -90
  10. package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
  11. package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
  12. package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
  13. package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
  14. package/dist/builtin-views/canvas/tui.mjs +8 -9
  15. package/dist/builtin-views/chat/tui.mjs +12 -12
  16. package/dist/builtin-views/git-pr/tui.mjs +7 -8
  17. package/dist/builtin-views/inbox/tui.mjs +27 -41
  18. package/dist/builtin-views/linkedin/tui.mjs +23 -37
  19. package/dist/builtin-views/prompt-review/tui.mjs +11 -11
  20. package/dist/builtin-views/settings/tui.mjs +11 -11
  21. package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
  22. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +3 -3
  23. package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
  24. package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
  25. package/dist/clients/attach/__tests__/mermaid-render.test.js +20 -0
  26. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
  27. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
  28. package/dist/clients/attach/attach-cmd.d.ts +1 -1
  29. package/dist/clients/attach/attach-cmd.js +738 -735
  30. package/dist/clients/attach/canvas-panels.js +2 -3
  31. package/dist/clients/attach/chat-view.d.ts +7 -8
  32. package/dist/clients/attach/chat-view.js +119 -79
  33. package/dist/clients/attach/edit-diff-render.d.ts +6 -0
  34. package/dist/clients/attach/edit-diff-render.js +263 -0
  35. package/dist/clients/attach/input-controller.d.ts +28 -2
  36. package/dist/clients/attach/input-controller.js +38 -5
  37. package/dist/clients/attach/mermaid-render.js +5 -2
  38. package/dist/clients/attach/pickers.d.ts +8 -7
  39. package/dist/clients/attach/pickers.js +11 -17
  40. package/dist/clients/attach/slash-commands.d.ts +9 -0
  41. package/dist/clients/attach/slash-commands.js +127 -7
  42. package/dist/clients/attach/titled-editor.d.ts +12 -1
  43. package/dist/clients/attach/titled-editor.js +103 -8
  44. package/dist/commands/canvas-browse.js +2 -2
  45. package/dist/commands/memory/lint.js +39 -5
  46. package/dist/commands/memory/write.js +1 -0
  47. package/dist/commands/node.js +9 -2
  48. package/dist/commands/surface-tmux-spread.js +1 -3
  49. package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
  50. package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
  51. package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
  52. package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
  53. package/dist/commands/sys/config.js +18 -21
  54. package/dist/commands/sys/setup-core.d.ts +12 -1
  55. package/dist/commands/sys/setup-core.js +23 -3
  56. package/dist/commands/sys/setup.js +42 -12
  57. package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
  58. package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
  59. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
  60. package/dist/core/__tests__/fault-classifier.test.js +15 -0
  61. package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
  62. package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
  63. package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
  64. package/dist/core/__tests__/stream-watchdog.test.js +70 -0
  65. package/dist/core/__tests__/tmux-surface.test.js +72 -0
  66. package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
  67. package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
  68. package/dist/core/canvas/browse/app.js +164 -5
  69. package/dist/core/canvas/browse/model.d.ts +10 -6
  70. package/dist/core/canvas/browse/model.js +20 -12
  71. package/dist/core/canvas/browse/pins.d.ts +4 -0
  72. package/dist/core/canvas/browse/pins.js +29 -0
  73. package/dist/core/canvas/browse/render.d.ts +13 -1
  74. package/dist/core/canvas/browse/render.js +72 -15
  75. package/dist/core/canvas/nav-model.js +5 -11
  76. package/dist/core/canvas/paths.d.ts +5 -0
  77. package/dist/core/canvas/paths.js +7 -0
  78. package/dist/core/config.d.ts +53 -2
  79. package/dist/core/config.js +228 -110
  80. package/dist/core/fault-classifier.js +1 -1
  81. package/dist/core/fs-utils.d.ts +1 -0
  82. package/dist/core/fs-utils.js +15 -1
  83. package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
  84. package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
  85. package/dist/core/keybindings/catalog.d.ts +14 -0
  86. package/dist/core/keybindings/catalog.js +256 -0
  87. package/dist/core/keybindings/index.d.ts +5 -0
  88. package/dist/core/keybindings/index.js +4 -0
  89. package/dist/core/keybindings/match.d.ts +29 -0
  90. package/dist/core/keybindings/match.js +99 -0
  91. package/dist/core/keybindings/persistence.d.ts +14 -0
  92. package/dist/core/keybindings/persistence.js +31 -0
  93. package/dist/core/keybindings/resolve.d.ts +14 -0
  94. package/dist/core/keybindings/resolve.js +305 -0
  95. package/dist/core/keybindings/types.d.ts +37 -0
  96. package/dist/core/keybindings/types.js +1 -0
  97. package/dist/core/memory-resolver.d.ts +1 -1
  98. package/dist/core/memory-resolver.js +10 -3
  99. package/dist/core/predicate.d.ts +5 -3
  100. package/dist/core/predicate.js +5 -3
  101. package/dist/core/runtime/broker-protocol.d.ts +33 -5
  102. package/dist/core/runtime/broker.js +163 -14
  103. package/dist/core/runtime/launch.d.ts +39 -6
  104. package/dist/core/runtime/launch.js +78 -20
  105. package/dist/core/runtime/naming.js +3 -3
  106. package/dist/core/runtime/pi-cli.d.ts +6 -0
  107. package/dist/core/runtime/pi-cli.js +16 -2
  108. package/dist/core/runtime/placement.d.ts +2 -2
  109. package/dist/core/runtime/placement.js +4 -1
  110. package/dist/core/runtime/promote.js +4 -0
  111. package/dist/core/runtime/recap.d.ts +3 -3
  112. package/dist/core/runtime/recap.js +47 -44
  113. package/dist/core/runtime/recycle.js +6 -1
  114. package/dist/core/runtime/reset.js +5 -0
  115. package/dist/core/runtime/session-list-cache.d.ts +23 -0
  116. package/dist/core/runtime/session-list-cache.js +270 -0
  117. package/dist/core/runtime/spawn.js +51 -32
  118. package/dist/core/runtime/stream-watchdog.d.ts +26 -0
  119. package/dist/core/runtime/stream-watchdog.js +75 -0
  120. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  121. package/dist/core/runtime/tmux-chrome.js +2 -2
  122. package/dist/core/runtime/tmux.d.ts +18 -15
  123. package/dist/core/runtime/tmux.js +216 -110
  124. package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
  125. package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
  126. package/dist/core/tui/host.d.ts +30 -1
  127. package/dist/core/tui/host.js +148 -34
  128. package/dist/core/view/contract.d.ts +17 -3
  129. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
  130. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
  131. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
  132. package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
  133. package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
  134. package/dist/pi-extensions/canvas-recap.d.ts +4 -0
  135. package/dist/pi-extensions/canvas-recap.js +38 -37
  136. package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
  137. package/dist/pi-extensions/canvas-stophook.js +33 -4
  138. package/dist/types.d.ts +3 -13
  139. package/dist/types.js +2 -26
  140. package/dist/web-client/assets/{index-DiFuLcp6.js → index--md2ylfi.js} +1 -1
  141. package/dist/web-client/index.html +1 -1
  142. package/dist/web-client/sw.js +1 -1
  143. package/package.json +3 -3
  144. package/dist/pi-extensions/canvas-nav.d.ts +0 -43
  145. package/dist/pi-extensions/canvas-nav.js +0 -640
  146. package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
  147. package/dist/pi-extensions/widget-order-bus.js +0 -34
@@ -0,0 +1,224 @@
1
+ import { after, before, beforeEach, test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { BINDING_CATALOG, KeybindingConcurrentEditError, KeybindingValidationError, SAFETY_REQUIREMENTS, canonicalTerminalStroke, formatStroke, matchesTerminalInput, normalizeGesture, normalizeSparseOverrides, persistUserKeybindings, resolveKeybindings, resolveUserKeybindings, } from '../index.js';
7
+ import { readConfig } from '../../config.js';
8
+ const typeCheckedBindingId = 'crtr.graph.down';
9
+ // @ts-expect-error The catalog type rejects IDs that are not built in.
10
+ const rejectedBindingId = 'crtr.graph.not-real';
11
+ void typeCheckedBindingId;
12
+ void rejectedBindingId;
13
+ function ids(prefix, actions) {
14
+ return actions.map((action) => `${prefix}.${action}`);
15
+ }
16
+ const REQUIRED_IDS = [
17
+ ...ids('crtr.tmux.menu', ['open', 'promote', 'resume', 'demote', 'detach', 'close-subtree', 'context', 'graph', 'focus-manager', 'issues']),
18
+ ...ids('crtr.tmux.menu.issue', ['idea', 'bug']),
19
+ ...ids('crtr.tmux.node', ['next', 'previous']),
20
+ ...ids('crtr.tmux.view', ['next', 'previous']),
21
+ ...ids('crtr.attach', ['detach', 'clear-or-detach', 'graph.toggle', 'model-ladder.next']),
22
+ 'crtr.mode.cycle',
23
+ ...ids('crtr.graph', ['down', 'up', 'first', 'last', 'collapse-or-manager', 'expand-or-child', 'focus', 'focus-manager', 'close-subtree', 'dismiss']),
24
+ ...ids('crtr.graph.confirm', ['accept', 'cancel']),
25
+ ...ids('crtr.view.host', ['force-quit', 'editor.backspace']),
26
+ ...ids('crtr.view.canvas', ['down', 'up', 'refresh', 'quit']),
27
+ ...ids('crtr.view.chat', ['submit', 'contextual-cancel', 'reconnect', 'request-control', 'release-control', 'quit']),
28
+ ...ids('crtr.view.git-pr', ['down', 'up', 'refresh', 'quit']),
29
+ ...ids('crtr.view.inbox', ['down', 'up', 'open', 'reply', 'react', 'filter', 'refresh', 'connect-linkedin', 'connect-gmail', 'quit', 'submit-reply', 'cancel', 'reaction-previous', 'reaction-next', 'submit-reaction']),
30
+ ...ids('crtr.view.linkedin', ['down', 'up', 'open', 'reply', 'react', 'refresh', 'quit', 'submit-reply', 'cancel', 'reaction-previous', 'reaction-next', 'submit-reaction']),
31
+ ...ids('crtr.view.prompt-review', ['down', 'up', 'compare-previous', 'compare-next', 'toggle-mode', 'expand', 'export', 'refresh', 'quit']),
32
+ ...ids('crtr.view.settings', ['down', 'up', 'cell-previous', 'cell-next', 'refresh', 'quit', 'edit-or-submit', 'cancel-edit']),
33
+ ...ids('crtr.view.workspace-sidebar', ['down', 'up', 'open', 'refresh', 'quit']),
34
+ ...ids('crtr.view-picker', ['down', 'up', 'run', 'quit']),
35
+ ...ids('crtr.browse', ['quit', 'down', 'up', 'first', 'last', 'expand-or-child', 'collapse-or-parent', 'toggle-fold', 'tab-next', 'tab-previous', 'tab.1', 'tab.2', 'tab.3', 'tab.4', 'close-subtree', 'confirm-close', 'sort', 'scope', 'residents', 'preview', 'search', 'revive-now', 'focus']),
36
+ ...ids('crtr.browse.search', ['cancel', 'commit', 'delete', 'quit']),
37
+ ...ids('crtr.profile.menu', ['up', 'down', 'select', 'accept-default', 'abort', 'slot.1', 'slot.2', 'slot.3', 'slot.4', 'slot.5', 'slot.6', 'slot.7', 'slot.8', 'slot.9', 'create', 'search-existing', 'use-root', 'set-default', 'add-directory', 'unpin']),
38
+ ...ids('crtr.profile.search', ['up', 'down', 'pick', 'back', 'delete', 'abort']),
39
+ ...ids('crtr.setup', ['cancel', 'tab-next', 'tab-previous', 'open', 'list-down', 'list-up', 'toggle', 'details', 'advance']),
40
+ ...ids('crtr.setup.confirm', ['accept', 'back']),
41
+ ...ids('crtr.setup.keybindings', ['edit', 'commit-edit', 'cancel-edit', 'unbind', 'reset-row', 'reset-all', 'save', 'discard', 'page-up', 'page-down', 'filter', 'filter-delete', 'filter-clear']),
42
+ ];
43
+ let home;
44
+ let realHome;
45
+ let realCwd;
46
+ before(() => {
47
+ realHome = process.env['HOME'];
48
+ realCwd = process.cwd();
49
+ });
50
+ beforeEach(() => {
51
+ home = mkdtempSync(join(tmpdir(), 'crtr-keybindings-'));
52
+ process.env['HOME'] = home;
53
+ process.chdir(realCwd);
54
+ });
55
+ after(() => {
56
+ process.chdir(realCwd);
57
+ if (realHome === undefined)
58
+ delete process.env['HOME'];
59
+ else
60
+ process.env['HOME'] = realHome;
61
+ });
62
+ test.afterEach(() => rmSync(home, { recursive: true, force: true }));
63
+ test('catalog is closed, complete, ordered, and has valid collision-free defaults', () => {
64
+ const actualIds = BINDING_CATALOG.map((definition) => definition.id);
65
+ assert.deepEqual(actualIds, REQUIRED_IDS);
66
+ assert.equal(actualIds.length, REQUIRED_IDS.length);
67
+ assert.equal(new Set(actualIds).size, actualIds.length);
68
+ for (const definition of BINDING_CATALOG) {
69
+ assert.match(definition.id, /^crtr\.[a-z0-9.-]+$/);
70
+ assert.ok(definition.name.length > 0);
71
+ assert.ok(definition.description.length > 0);
72
+ assert.ok(definition.contexts.length > 0);
73
+ for (const gesture of definition.defaults)
74
+ assert.equal(normalizeGesture(gesture, definition), gesture);
75
+ }
76
+ const resolved = resolveKeybindings();
77
+ assert.equal(resolved.bindings.size, BINDING_CATALOG.length);
78
+ assert.deepEqual(resolved.diagnostics, []);
79
+ assert.ok(SAFETY_REQUIREMENTS.length > 0);
80
+ });
81
+ test('normalizes aliases, modifier order, uppercase printables, sequences, and duplicate gestures', () => {
82
+ assert.equal(normalizeGesture('M-C-X'), 'ctrl+alt+shift+x');
83
+ assert.equal(normalizeGesture('alt+ctrl+x'), 'ctrl+alt+x');
84
+ assert.equal(normalizeGesture('return'), 'enter');
85
+ assert.equal(normalizeGesture('esc'), 'escape');
86
+ assert.equal(normalizeGesture('meta+j'), 'alt+j');
87
+ assert.equal(normalizeGesture('+'), '+');
88
+ assert.equal(normalizeGesture('ctrl++'), 'ctrl++');
89
+ assert.equal(normalizeGesture('alt+>'), 'alt+>');
90
+ assert.equal(formatStroke('ctrl++'), 'Ctrl++');
91
+ assert.equal(canonicalTerminalStroke({ input: '+', ctrl: true }), 'ctrl++');
92
+ const resolved = resolveKeybindings({
93
+ 'crtr.attach.graph.toggle': ['M-X', 'alt+x'],
94
+ 'crtr.tmux.view.next': ['M-V > ]', 'alt+v > ]'],
95
+ 'crtr.tmux.menu.promote': ['space'],
96
+ });
97
+ assert.deepEqual(resolved.gestures('crtr.attach.graph.toggle'), ['alt+shift+x', 'alt+x']);
98
+ assert.deepEqual(resolved.gestures('crtr.tmux.view.next'), ['alt+shift+v > ]', 'alt+v > ]']);
99
+ assert.deepEqual(resolved.gestures('crtr.tmux.menu.promote'), ['space']);
100
+ });
101
+ test('terminal literal space matches named space bindings', () => {
102
+ const raw = { input: ' ' };
103
+ assert.equal(canonicalTerminalStroke(raw), 'space');
104
+ assert.equal(matchesTerminalInput(resolveKeybindings(), 'crtr.browse.toggle-fold', raw), true);
105
+ });
106
+ test('invalid values and unknown IDs diagnose precisely while valid overrides still apply', () => {
107
+ const resolved = resolveKeybindings({
108
+ 'crtr.unknown': ['x'],
109
+ 'crtr.graph.down': 'down',
110
+ 'crtr.attach.graph.toggle': ['alt+x'],
111
+ 'crtr.tmux.view.next': ['alt+v'],
112
+ });
113
+ assert.equal(resolved.gestures('crtr.graph.down')[0], 'j');
114
+ assert.deepEqual(resolved.gestures('crtr.attach.graph.toggle'), ['alt+x']);
115
+ assert.equal(resolved.gestures('crtr.tmux.view.next')[0], 'alt+v > ]');
116
+ assert.deepEqual(new Set(resolved.diagnostics.map((item) => item.code)), new Set(['unknown-binding', 'invalid-override']));
117
+ });
118
+ test('colliding overrides fall back without affecting unrelated contexts', () => {
119
+ const resolved = resolveKeybindings({
120
+ 'crtr.graph.down': ['k'],
121
+ 'crtr.view.canvas.quit': ['x'],
122
+ 'crtr.attach.graph.toggle': ['alt+c'],
123
+ 'crtr.attach.detach': ['j'],
124
+ 'crtr.setup.list-down': ['space'],
125
+ 'crtr.view.settings.cancel-edit': ['q'],
126
+ });
127
+ assert.equal(resolved.gestures('crtr.graph.down')[0], 'j');
128
+ assert.deepEqual(resolved.gestures('crtr.view.canvas.quit'), ['x']);
129
+ assert.deepEqual(resolved.gestures('crtr.attach.graph.toggle'), ['alt+g']);
130
+ assert.deepEqual(resolved.gestures('crtr.attach.detach'), ['ctrl+d']);
131
+ assert.deepEqual(resolved.gestures('crtr.setup.list-down'), ['j', 'down']);
132
+ assert.deepEqual(resolved.gestures('crtr.view.settings.cancel-edit'), ['escape']);
133
+ assert.equal(resolved.diagnostics.filter((item) => item.code === 'collision').length, 5);
134
+ });
135
+ test('tmux menu selectors collide only inside their own menu', () => {
136
+ const resolved = resolveKeybindings({ 'crtr.tmux.menu.open': ['o'] });
137
+ assert.deepEqual(resolved.gestures('crtr.tmux.menu.open'), ['o']);
138
+ assert.deepEqual(resolved.diagnostics, []);
139
+ });
140
+ test('reachability rejects deliberate unbinding that would trap a context', () => {
141
+ const graph = resolveKeybindings({
142
+ 'crtr.graph.dismiss': [],
143
+ 'crtr.graph.down': ['escape'],
144
+ });
145
+ assert.deepEqual(graph.gestures('crtr.graph.dismiss'), ['escape']);
146
+ assert.deepEqual(graph.gestures('crtr.graph.down'), ['j']);
147
+ assert.ok(graph.diagnostics.some((item) => item.code === 'unreachable'));
148
+ assert.ok(graph.diagnostics.some((item) => item.code === 'collision'));
149
+ const setup = resolveKeybindings({
150
+ 'crtr.setup.keybindings.discard': [],
151
+ 'crtr.setup.keybindings.save': [],
152
+ });
153
+ assert.equal(setup.diagnostics.filter((item) => item.code === 'unreachable').length, 1);
154
+ });
155
+ test('sparsification omits defaults, preserves explicit unbinds, and reset removes the override', () => {
156
+ assert.deepEqual(normalizeSparseOverrides({ 'crtr.attach.graph.toggle': ['alt+g'] }), {});
157
+ assert.deepEqual(normalizeSparseOverrides({ 'crtr.graph.close-subtree': [] }), {
158
+ 'crtr.graph.close-subtree': [],
159
+ });
160
+ assert.deepEqual(normalizeSparseOverrides({}), {});
161
+ assert.throws(() => normalizeSparseOverrides({ 'crtr.graph.down': ['k'] }), KeybindingValidationError);
162
+ });
163
+ test('user resolution ignores project keybindings', () => {
164
+ mkdirSync(join(home, '.crouter'), { recursive: true });
165
+ writeFileSync(join(home, '.crouter', 'config.json'), JSON.stringify({ keybindings: { 'crtr.graph.down': ['down'] } }));
166
+ const project = mkdtempSync(join(tmpdir(), 'crtr-keybindings-project-'));
167
+ mkdirSync(join(project, '.crouter'), { recursive: true });
168
+ writeFileSync(join(project, '.crouter', 'config.json'), JSON.stringify({ keybindings: { 'crtr.graph.down': ['up'] } }));
169
+ try {
170
+ process.chdir(project);
171
+ assert.deepEqual(resolveUserKeybindings().gestures('crtr.graph.down'), ['down']);
172
+ }
173
+ finally {
174
+ process.chdir(realCwd);
175
+ rmSync(project, { recursive: true, force: true });
176
+ }
177
+ });
178
+ test('config hard cut exposes only sparse keybindings and ignores unsupported raw fields', () => {
179
+ mkdirSync(join(home, '.crouter'), { recursive: true });
180
+ writeFileSync(join(home, '.crouter', 'config.json'), JSON.stringify({
181
+ canvasNav: { prefixKey: 'alt+x' },
182
+ keybindings: { 'crtr.graph.down': ['down'], bad: 3 },
183
+ }));
184
+ const config = readConfig('user');
185
+ assert.deepEqual(config.keybindings, { 'crtr.graph.down': ['down'] });
186
+ assert.equal(Object.hasOwn(config, 'canvasNav'), false);
187
+ });
188
+ test('atomic persistence preserves latest unrelated fields and writes only sparse normalized overrides', () => {
189
+ mkdirSync(join(home, '.crouter'), { recursive: true });
190
+ const path = join(home, '.crouter', 'config.json');
191
+ writeFileSync(path, JSON.stringify({
192
+ customFutureField: { retained: true },
193
+ max_panes_per_window: 4,
194
+ canvasNav: { prefixKey: 'alt+z' },
195
+ }));
196
+ persistUserKeybindings({
197
+ 'crtr.attach.graph.toggle': ['M-X'],
198
+ 'crtr.graph.close-subtree': [],
199
+ 'crtr.graph.down': ['j'],
200
+ });
201
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
202
+ assert.deepEqual(raw.customFutureField, { retained: true });
203
+ assert.equal(raw.max_panes_per_window, 4);
204
+ assert.deepEqual(raw.keybindings, {
205
+ 'crtr.attach.graph.toggle': ['alt+shift+x'],
206
+ 'crtr.graph.close-subtree': [],
207
+ });
208
+ assert.equal(raw.canvasNav, undefined);
209
+ });
210
+ test('reset-all removes the top-level member and concurrent keybinding edits are refused', () => {
211
+ mkdirSync(join(home, '.crouter'), { recursive: true });
212
+ const path = join(home, '.crouter', 'config.json');
213
+ const original = { 'crtr.graph.down': ['down'] };
214
+ writeFileSync(path, JSON.stringify({ keybindings: original, retained: 1 }));
215
+ persistUserKeybindings({}, { expectedKeybindings: original });
216
+ let raw = JSON.parse(readFileSync(path, 'utf8'));
217
+ assert.equal(raw.keybindings, undefined);
218
+ assert.equal(raw.retained, 1);
219
+ writeFileSync(path, JSON.stringify({ keybindings: { 'crtr.graph.up': ['up'] }, retained: 2 }));
220
+ assert.throws(() => persistUserKeybindings({}, { expectedKeybindings: original }), KeybindingConcurrentEditError);
221
+ raw = JSON.parse(readFileSync(path, 'utf8'));
222
+ assert.deepEqual(raw.keybindings, { 'crtr.graph.up': ['up'] });
223
+ assert.equal(raw.retained, 2);
224
+ });
@@ -0,0 +1,14 @@
1
+ import type { BindingDefinition, SafetyRequirement } from './types.js';
2
+ type TmuxMenuAction = 'open' | 'promote' | 'resume' | 'demote' | 'detach' | 'close-subtree' | 'context' | 'graph' | 'focus-manager' | 'issues';
3
+ type GraphAction = 'down' | 'up' | 'first' | 'last' | 'collapse-or-manager' | 'expand-or-child' | 'focus' | 'focus-manager' | 'close-subtree' | 'dismiss';
4
+ type InboxAction = 'down' | 'up' | 'open' | 'reply' | 'react' | 'filter' | 'refresh' | 'connect-linkedin' | 'connect-gmail' | 'quit' | 'submit-reply' | 'cancel' | 'reaction-previous' | 'reaction-next' | 'submit-reaction';
5
+ type LinkedinAction = Exclude<InboxAction, 'filter' | 'connect-linkedin' | 'connect-gmail'>;
6
+ type BrowseAction = 'quit' | 'down' | 'up' | 'first' | 'last' | 'expand-or-child' | 'collapse-or-parent' | 'toggle-fold' | 'tab-next' | 'tab-previous' | `tab.${1 | 2 | 3 | 4}` | 'close-subtree' | 'confirm-close' | 'sort' | 'scope' | 'residents' | 'preview' | 'search' | 'revive-now' | 'focus';
7
+ type ProfileMenuAction = 'up' | 'down' | 'select' | 'accept-default' | 'abort' | `slot.${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}` | 'create' | 'search-existing' | 'use-root' | 'set-default' | 'add-directory' | 'unpin';
8
+ type SetupAction = 'cancel' | 'tab-next' | 'tab-previous' | 'open' | 'list-down' | 'list-up' | 'toggle' | 'details' | 'advance';
9
+ type SetupKeybindingAction = 'edit' | 'commit-edit' | 'cancel-edit' | 'unbind' | 'reset-row' | 'reset-all' | 'save' | 'discard' | 'page-up' | 'page-down' | 'filter' | 'filter-delete' | 'filter-clear';
10
+ export type BindingId = `crtr.tmux.menu.${TmuxMenuAction}` | `crtr.tmux.menu.issue.${'idea' | 'bug'}` | `crtr.tmux.node.${'next' | 'previous'}` | `crtr.tmux.view.${'next' | 'previous'}` | `crtr.attach.${'detach' | 'clear-or-detach' | 'graph.toggle' | 'model-ladder.next'}` | 'crtr.mode.cycle' | `crtr.graph.${GraphAction}` | `crtr.graph.confirm.${'accept' | 'cancel'}` | `crtr.view.host.${'force-quit' | 'editor.backspace'}` | `crtr.view.canvas.${'down' | 'up' | 'refresh' | 'quit'}` | `crtr.view.chat.${'submit' | 'contextual-cancel' | 'reconnect' | 'request-control' | 'release-control' | 'quit'}` | `crtr.view.git-pr.${'down' | 'up' | 'refresh' | 'quit'}` | `crtr.view.inbox.${InboxAction}` | `crtr.view.linkedin.${LinkedinAction}` | `crtr.view.prompt-review.${'down' | 'up' | 'compare-previous' | 'compare-next' | 'toggle-mode' | 'expand' | 'export' | 'refresh' | 'quit'}` | `crtr.view.settings.${'down' | 'up' | 'cell-previous' | 'cell-next' | 'refresh' | 'quit' | 'edit-or-submit' | 'cancel-edit'}` | `crtr.view.workspace-sidebar.${'down' | 'up' | 'open' | 'refresh' | 'quit'}` | `crtr.view-picker.${'down' | 'up' | 'run' | 'quit'}` | `crtr.browse.${BrowseAction}` | `crtr.browse.search.${'cancel' | 'commit' | 'delete' | 'quit'}` | `crtr.profile.menu.${ProfileMenuAction}` | `crtr.profile.search.${'up' | 'down' | 'pick' | 'back' | 'delete' | 'abort'}` | `crtr.setup.${SetupAction}` | `crtr.setup.confirm.${'accept' | 'back'}` | `crtr.setup.keybindings.${SetupKeybindingAction}`;
11
+ export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, BindingDefinition<"crtr.tmux.menu.promote">, BindingDefinition<"crtr.tmux.menu.resume">, BindingDefinition<"crtr.tmux.menu.demote">, BindingDefinition<"crtr.tmux.menu.detach">, BindingDefinition<"crtr.tmux.menu.close-subtree">, BindingDefinition<"crtr.tmux.menu.context">, BindingDefinition<"crtr.tmux.menu.graph">, BindingDefinition<"crtr.tmux.menu.focus-manager">, BindingDefinition<"crtr.tmux.menu.issues">, BindingDefinition<"crtr.tmux.menu.issue.idea">, BindingDefinition<"crtr.tmux.menu.issue.bug">, BindingDefinition<"crtr.tmux.node.next">, BindingDefinition<"crtr.tmux.node.previous">, BindingDefinition<"crtr.tmux.view.next">, BindingDefinition<"crtr.tmux.view.previous">, BindingDefinition<"crtr.attach.detach">, BindingDefinition<"crtr.attach.clear-or-detach">, BindingDefinition<"crtr.attach.graph.toggle">, BindingDefinition<"crtr.attach.model-ladder.next">, BindingDefinition<"crtr.mode.cycle">, BindingDefinition<"crtr.graph.down">, BindingDefinition<"crtr.graph.up">, BindingDefinition<"crtr.graph.first">, BindingDefinition<"crtr.graph.last">, BindingDefinition<"crtr.graph.collapse-or-manager">, BindingDefinition<"crtr.graph.expand-or-child">, BindingDefinition<"crtr.graph.focus">, BindingDefinition<"crtr.graph.focus-manager">, BindingDefinition<"crtr.graph.close-subtree">, BindingDefinition<"crtr.graph.dismiss">, BindingDefinition<"crtr.graph.confirm.accept">, BindingDefinition<"crtr.graph.confirm.cancel">, BindingDefinition<"crtr.view.host.force-quit">, BindingDefinition<"crtr.view.host.editor.backspace">, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<"crtr.view.chat.submit">, BindingDefinition<"crtr.view.chat.contextual-cancel">, BindingDefinition<"crtr.view.chat.reconnect">, BindingDefinition<"crtr.view.chat.request-control">, BindingDefinition<"crtr.view.chat.release-control">, BindingDefinition<"crtr.view.chat.quit">, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.view.prompt-review.down"> | BindingDefinition<"crtr.view.prompt-review.up"> | BindingDefinition<"crtr.view.prompt-review.compare-previous"> | BindingDefinition<"crtr.view.prompt-review.compare-next"> | BindingDefinition<"crtr.view.prompt-review.toggle-mode"> | BindingDefinition<"crtr.view.prompt-review.expand"> | BindingDefinition<"crtr.view.prompt-review.export"> | BindingDefinition<"crtr.view.prompt-review.refresh"> | BindingDefinition<"crtr.view.prompt-review.quit"> | BindingDefinition<"crtr.view.settings.down"> | BindingDefinition<"crtr.view.settings.up"> | BindingDefinition<"crtr.view.settings.cell-previous"> | BindingDefinition<"crtr.view.settings.cell-next"> | BindingDefinition<"crtr.view.settings.refresh"> | BindingDefinition<"crtr.view.settings.quit"> | BindingDefinition<"crtr.view.settings.edit-or-submit"> | BindingDefinition<"crtr.view.settings.cancel-edit"> | BindingDefinition<"crtr.view.workspace-sidebar.down"> | BindingDefinition<"crtr.view.workspace-sidebar.up"> | BindingDefinition<"crtr.view.workspace-sidebar.open"> | BindingDefinition<"crtr.view.workspace-sidebar.refresh"> | BindingDefinition<"crtr.view.workspace-sidebar.quit"> | BindingDefinition<"crtr.view-picker.down"> | BindingDefinition<"crtr.view-picker.up"> | BindingDefinition<"crtr.view-picker.run"> | BindingDefinition<"crtr.view-picker.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.6" | "crtr.profile.menu.slot.7" | "crtr.profile.menu.slot.9">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">];
12
+ export declare const BINDING_IDS: ReadonlySet<string>;
13
+ export declare const SAFETY_REQUIREMENTS: readonly SafetyRequirement[];
14
+ export {};
@@ -0,0 +1,256 @@
1
+ function binding(id, group, name, description, defaults, inputKind, contexts, safety) {
2
+ return Object.freeze({
3
+ id,
4
+ group,
5
+ name,
6
+ description,
7
+ defaults: Object.freeze([...defaults]),
8
+ inputKind,
9
+ contexts: Object.freeze([...contexts]),
10
+ ...(safety === undefined ? {} : { safety }),
11
+ });
12
+ }
13
+ const b = binding;
14
+ const ATTACH_CONTEXTS = ['attach', 'graph', 'graph.confirm'];
15
+ const VIEW_CONTEXTS = [
16
+ 'view.canvas', 'view.chat', 'view.git-pr',
17
+ 'view.inbox.list', 'view.inbox.compose', 'view.inbox.react',
18
+ 'view.linkedin.list', 'view.linkedin.compose', 'view.linkedin.react',
19
+ 'view.prompt-review', 'view.settings', 'view.settings.edit',
20
+ 'view.workspace-sidebar',
21
+ ];
22
+ const SETUP_CANCEL_CONTEXTS = [
23
+ 'setup.login', 'setup.list', 'setup.input', 'setup.keybindings',
24
+ 'setup.keybindings.edit', 'setup.keybindings.filter',
25
+ ];
26
+ const SETUP_TAB_CONTEXTS = ['setup.login', 'setup.list', 'setup.keybindings'];
27
+ export const BINDING_CATALOG = Object.freeze([
28
+ b('crtr.tmux.menu.open', 'Global tmux', 'Open crouter menu', 'Open the crouter action menu.', ['alt+c'], 'host-global', ['tmux.root']),
29
+ b('crtr.tmux.menu.promote', 'Global tmux', 'Promote node', 'Promote the focused node to orchestrator.', ['o'], 'tmux-menu-selector', ['tmux.menu']),
30
+ b('crtr.tmux.menu.resume', 'Global tmux', 'Resume node', 'Open the node resume picker.', ['r'], 'tmux-menu-selector', ['tmux.menu']),
31
+ b('crtr.tmux.menu.demote', 'Global tmux', 'Demote node', 'Demote the focused node to terminal.', ['d'], 'tmux-menu-selector', ['tmux.menu']),
32
+ b('crtr.tmux.menu.detach', 'Global tmux', 'Detach node', 'Detach the focused node to the background.', ['shift+d'], 'tmux-menu-selector', ['tmux.menu']),
33
+ b('crtr.tmux.menu.close-subtree', 'Global tmux', 'Close subtree', 'Close the focused node and its subtree.', ['x'], 'tmux-menu-selector', ['tmux.menu']),
34
+ b('crtr.tmux.menu.context', 'Global tmux', 'Browse context', 'Browse the focused node context.', ['c'], 'tmux-menu-selector', ['tmux.menu']),
35
+ b('crtr.tmux.menu.graph', 'Global tmux', 'Open graph', 'Open the canvas graph.', ['g'], 'tmux-menu-selector', ['tmux.menu']),
36
+ b('crtr.tmux.menu.focus-manager', 'Global tmux', 'Focus manager', 'Focus the node manager.', ['m'], 'tmux-menu-selector', ['tmux.menu']),
37
+ b('crtr.tmux.menu.issues', 'Global tmux', 'Open issue menu', 'Open the issue type submenu.', ['i'], 'tmux-menu-selector', ['tmux.menu']),
38
+ b('crtr.tmux.menu.issue.idea', 'Global tmux', 'Create idea', 'Create an idea issue.', ['i'], 'tmux-menu-selector', ['tmux.menu.issue']),
39
+ b('crtr.tmux.menu.issue.bug', 'Global tmux', 'Create bug', 'Create a bug issue.', ['b'], 'tmux-menu-selector', ['tmux.menu.issue']),
40
+ b('crtr.tmux.node.next', 'Global tmux', 'Next node', 'Focus the next canvas node.', ['alt+]'], 'host-global', ['tmux.root']),
41
+ b('crtr.tmux.node.previous', 'Global tmux', 'Previous node', 'Focus the previous canvas node.', ['alt+['], 'host-global', ['tmux.root']),
42
+ b('crtr.tmux.view.next', 'Global tmux', 'Next view', 'Focus the next view.', ['alt+v > ]'], 'tmux-sequence', ['tmux.root']),
43
+ b('crtr.tmux.view.previous', 'Global tmux', 'Previous view', 'Focus the previous view.', ['alt+v > ['], 'tmux-sequence', ['tmux.root']),
44
+ b('crtr.attach.detach', 'Attach', 'Detach', 'Detach from the node.', ['ctrl+d'], 'host-global', ATTACH_CONTEXTS),
45
+ b('crtr.attach.clear-or-detach', 'Attach', 'Clear or detach', 'Clear input or detach when input is empty.', ['ctrl+c'], 'host-global', ATTACH_CONTEXTS),
46
+ b('crtr.attach.graph.toggle', 'Attach', 'Toggle graph', 'Open or close the graph overlay.', ['alt+g'], 'host-global', ATTACH_CONTEXTS),
47
+ b('crtr.attach.model-ladder.next', 'Attach', 'Next model ladder', 'Move to the next model ladder rung.', ['alt+shift+m'], 'host-global', ATTACH_CONTEXTS),
48
+ b('crtr.mode.cycle', 'Attach', 'Cycle mode', 'Cycle the active agent mode.', ['alt+m'], 'host-global', ATTACH_CONTEXTS),
49
+ b('crtr.graph.down', 'GRAPH', 'Move down', 'Move to the next graph row.', ['j'], 'terminal', ['graph']),
50
+ b('crtr.graph.up', 'GRAPH', 'Move up', 'Move to the previous graph row.', ['k'], 'terminal', ['graph']),
51
+ b('crtr.graph.first', 'GRAPH', 'First row', 'Move to the first graph row.', ['g'], 'terminal', ['graph']),
52
+ b('crtr.graph.last', 'GRAPH', 'Last row', 'Move to the last graph row.', ['shift+g'], 'terminal', ['graph']),
53
+ b('crtr.graph.collapse-or-manager', 'GRAPH', 'Collapse or manager', 'Collapse a node or move to its manager.', ['h'], 'terminal', ['graph']),
54
+ b('crtr.graph.expand-or-child', 'GRAPH', 'Expand or child', 'Expand a node or move to its child.', ['l'], 'terminal', ['graph']),
55
+ b('crtr.graph.focus', 'GRAPH', 'Focus node', 'Focus the selected node.', ['enter'], 'terminal', ['graph']),
56
+ b('crtr.graph.focus-manager', 'GRAPH', 'Focus manager', 'Focus the selected node manager.', ['m'], 'terminal', ['graph']),
57
+ b('crtr.graph.close-subtree', 'GRAPH', 'Close subtree', 'Ask to close the selected subtree.', ['x'], 'terminal', ['graph']),
58
+ b('crtr.graph.dismiss', 'GRAPH', 'Dismiss graph', 'Close the graph overlay.', ['escape'], 'terminal', ['graph'], 'dismiss-modal'),
59
+ b('crtr.graph.confirm.accept', 'GRAPH', 'Confirm close', 'Confirm closing the selected subtree.', ['y'], 'terminal', ['graph.confirm']),
60
+ b('crtr.graph.confirm.cancel', 'GRAPH', 'Cancel close', 'Cancel closing the selected subtree.', ['n', 'escape'], 'terminal', ['graph.confirm'], 'dismiss-modal'),
61
+ b('crtr.view.host.force-quit', 'Views', 'Force quit view', 'Immediately leave a hosted view.', ['ctrl+c'], 'host-global', VIEW_CONTEXTS, 'leave-screen'),
62
+ b('crtr.view.host.editor.backspace', 'Views', 'Delete captured text', 'Delete the previous captured character.', ['backspace'], 'host-global', ['view.chat', 'view.inbox.compose', 'view.linkedin.compose', 'view.settings.edit']),
63
+ ...viewNavigation('canvas', 'Canvas view'),
64
+ b('crtr.view.chat.submit', 'Views', 'Submit chat', 'Submit the current chat input.', ['enter'], 'terminal', ['view.chat']),
65
+ b('crtr.view.chat.contextual-cancel', 'Views', 'Cancel chat action', 'Cancel the active chat action.', ['escape'], 'terminal', ['view.chat']),
66
+ b('crtr.view.chat.reconnect', 'Views', 'Reconnect chat', 'Reconnect the chat stream.', ['r'], 'terminal', ['view.chat']),
67
+ b('crtr.view.chat.request-control', 'Views', 'Request control', 'Request control of the chat.', ['c'], 'terminal', ['view.chat']),
68
+ b('crtr.view.chat.release-control', 'Views', 'Release control', 'Release control of the chat.', ['shift+c', 'ctrl+r'], 'terminal', ['view.chat']),
69
+ b('crtr.view.chat.quit', 'Views', 'Quit chat', 'Leave the chat view.', ['q'], 'terminal', ['view.chat'], 'leave-screen'),
70
+ ...viewNavigation('git-pr', 'Git PR view'),
71
+ ...listView('inbox', 'Inbox', [
72
+ ['open', 'Open item', 'Open the selected inbox item.', ['enter']],
73
+ ['reply', 'Reply', 'Reply to the selected inbox item.', ['r']],
74
+ ['react', 'React', 'React to the selected inbox item.', ['e']],
75
+ ['filter', 'Filter', 'Filter inbox items.', ['f']],
76
+ ['refresh', 'Refresh', 'Refresh inbox items.', ['g']],
77
+ ['connect-linkedin', 'Connect LinkedIn', 'Connect the LinkedIn inbox.', ['shift+l']],
78
+ ['connect-gmail', 'Connect Gmail', 'Connect the Gmail inbox.', ['shift+g']],
79
+ ]),
80
+ ...composeAndReact('inbox', 'Inbox'),
81
+ ...listView('linkedin', 'LinkedIn', [
82
+ ['open', 'Open item', 'Open the selected LinkedIn item.', ['enter']],
83
+ ['reply', 'Reply', 'Reply to the selected LinkedIn item.', ['r']],
84
+ ['react', 'React', 'React to the selected LinkedIn item.', ['e']],
85
+ ['refresh', 'Refresh', 'Refresh LinkedIn items.', ['g']],
86
+ ]),
87
+ ...composeAndReact('linkedin', 'LinkedIn'),
88
+ b('crtr.view.prompt-review.down', 'Views', 'Move down', 'Move to the next prompt review row.', ['j', 'down'], 'terminal', ['view.prompt-review']),
89
+ b('crtr.view.prompt-review.up', 'Views', 'Move up', 'Move to the previous prompt review row.', ['k', 'up'], 'terminal', ['view.prompt-review']),
90
+ b('crtr.view.prompt-review.compare-previous', 'Views', 'Previous comparison', 'Select the previous comparison.', ['h', 'left'], 'terminal', ['view.prompt-review']),
91
+ b('crtr.view.prompt-review.compare-next', 'Views', 'Next comparison', 'Select the next comparison.', ['l', 'right'], 'terminal', ['view.prompt-review']),
92
+ b('crtr.view.prompt-review.toggle-mode', 'Views', 'Toggle mode', 'Toggle the prompt review mode.', ['tab'], 'terminal', ['view.prompt-review']),
93
+ b('crtr.view.prompt-review.expand', 'Views', 'Expand', 'Expand the selected prompt review.', ['enter'], 'terminal', ['view.prompt-review']),
94
+ b('crtr.view.prompt-review.export', 'Views', 'Export', 'Export the prompt review.', ['e'], 'terminal', ['view.prompt-review']),
95
+ b('crtr.view.prompt-review.refresh', 'Views', 'Refresh', 'Refresh the prompt review.', ['g'], 'terminal', ['view.prompt-review']),
96
+ b('crtr.view.prompt-review.quit', 'Views', 'Quit', 'Leave the prompt review.', ['q'], 'terminal', ['view.prompt-review'], 'leave-screen'),
97
+ b('crtr.view.settings.down', 'Views', 'Move down', 'Move to the next settings row.', ['j', 'down'], 'terminal', ['view.settings']),
98
+ b('crtr.view.settings.up', 'Views', 'Move up', 'Move to the previous settings row.', ['k', 'up'], 'terminal', ['view.settings']),
99
+ b('crtr.view.settings.cell-previous', 'Views', 'Previous cell', 'Select the previous settings cell.', ['h', 'left'], 'terminal', ['view.settings']),
100
+ b('crtr.view.settings.cell-next', 'Views', 'Next cell', 'Select the next settings cell.', ['l', 'right'], 'terminal', ['view.settings']),
101
+ b('crtr.view.settings.refresh', 'Views', 'Refresh', 'Refresh settings.', ['g'], 'terminal', ['view.settings']),
102
+ b('crtr.view.settings.quit', 'Views', 'Quit', 'Leave settings.', ['q'], 'terminal', ['view.settings', 'view.settings.edit'], 'leave-screen'),
103
+ b('crtr.view.settings.edit-or-submit', 'Views', 'Edit or submit', 'Edit or submit the selected setting.', ['enter'], 'terminal', ['view.settings']),
104
+ b('crtr.view.settings.cancel-edit', 'Views', 'Cancel edit', 'Cancel the active settings edit.', ['escape'], 'terminal', ['view.settings.edit'], 'dismiss-modal'),
105
+ b('crtr.view.workspace-sidebar.down', 'Views', 'Move down', 'Move to the next workspace.', ['j', 'down'], 'terminal', ['view.workspace-sidebar']),
106
+ b('crtr.view.workspace-sidebar.up', 'Views', 'Move up', 'Move to the previous workspace.', ['k', 'up'], 'terminal', ['view.workspace-sidebar']),
107
+ b('crtr.view.workspace-sidebar.open', 'Views', 'Open workspace', 'Open the selected workspace.', ['enter'], 'terminal', ['view.workspace-sidebar']),
108
+ b('crtr.view.workspace-sidebar.refresh', 'Views', 'Refresh', 'Refresh workspaces.', ['g', 'r'], 'terminal', ['view.workspace-sidebar']),
109
+ b('crtr.view.workspace-sidebar.quit', 'Views', 'Quit', 'Leave the workspace sidebar.', ['q'], 'terminal', ['view.workspace-sidebar'], 'leave-screen'),
110
+ b('crtr.view-picker.down', 'Browse and pickers', 'Move down', 'Move to the next view.', ['j', 'down'], 'terminal', ['view-picker']),
111
+ b('crtr.view-picker.up', 'Browse and pickers', 'Move up', 'Move to the previous view.', ['k', 'up'], 'terminal', ['view-picker']),
112
+ b('crtr.view-picker.run', 'Browse and pickers', 'Run view', 'Run the selected view.', ['enter'], 'terminal', ['view-picker']),
113
+ b('crtr.view-picker.quit', 'Browse and pickers', 'Quit picker', 'Leave the view picker.', ['q', 'escape', 'ctrl+c'], 'terminal', ['view-picker'], 'leave-screen'),
114
+ ...browseBindings(),
115
+ ...profileBindings(),
116
+ ...setupBindings(),
117
+ ]);
118
+ function viewNavigation(id, label) {
119
+ const context = `view.${id}`;
120
+ return [
121
+ b(`crtr.view.${id}.down`, 'Views', 'Move down', `Move to the next ${label.toLowerCase()} row.`, ['j', 'down'], 'terminal', [context]),
122
+ b(`crtr.view.${id}.up`, 'Views', 'Move up', `Move to the previous ${label.toLowerCase()} row.`, ['k', 'up'], 'terminal', [context]),
123
+ b(`crtr.view.${id}.refresh`, 'Views', 'Refresh', `Refresh the ${label.toLowerCase()}.`, ['g'], 'terminal', [context]),
124
+ b(`crtr.view.${id}.quit`, 'Views', 'Quit', `Leave the ${label.toLowerCase()}.`, ['q'], 'terminal', [context], 'leave-screen'),
125
+ ];
126
+ }
127
+ function listView(id, label, actions) {
128
+ const context = `view.${id}.list`;
129
+ return [
130
+ b(`crtr.view.${id}.down`, 'Views', 'Move down', `Move to the next ${label} item.`, ['j', 'down'], 'terminal', [context]),
131
+ b(`crtr.view.${id}.up`, 'Views', 'Move up', `Move to the previous ${label} item.`, ['k', 'up'], 'terminal', [context]),
132
+ ...actions.map(([action, name, description, defaults]) => b(`crtr.view.${id}.${action}`, 'Views', name, description, defaults, 'terminal', [context])),
133
+ b(`crtr.view.${id}.quit`, 'Views', 'Quit', `Leave ${label}.`, ['q'], 'terminal', [context], 'leave-screen'),
134
+ ];
135
+ }
136
+ function composeAndReact(id, label) {
137
+ return [
138
+ b(`crtr.view.${id}.submit-reply`, 'Views', 'Submit reply', `Submit the ${label} reply.`, ['enter'], 'terminal', [`view.${id}.compose`]),
139
+ b(`crtr.view.${id}.cancel`, 'Views', 'Cancel', `Cancel ${label} composition or reaction.`, ['escape'], 'terminal', [`view.${id}.compose`, `view.${id}.react`], 'dismiss-modal'),
140
+ b(`crtr.view.${id}.reaction-previous`, 'Views', 'Previous reaction', 'Select the previous reaction.', id === 'inbox' ? ['h', 'left'] : ['left', 'h'], 'terminal', [`view.${id}.react`]),
141
+ b(`crtr.view.${id}.reaction-next`, 'Views', 'Next reaction', 'Select the next reaction.', id === 'inbox' ? ['l', 'right'] : ['right', 'l'], 'terminal', [`view.${id}.react`]),
142
+ b(`crtr.view.${id}.submit-reaction`, 'Views', 'Submit reaction', `Submit the ${label} reaction.`, ['enter'], 'terminal', [`view.${id}.react`]),
143
+ ];
144
+ }
145
+ function browseBindings() {
146
+ const nav = 'browse';
147
+ return [
148
+ b('crtr.browse.quit', 'Browse and pickers', 'Quit browse', 'Leave canvas browse.', ['q', 'escape', 'ctrl+c'], 'terminal', [nav], 'leave-screen'),
149
+ b('crtr.browse.down', 'Browse and pickers', 'Move down', 'Move to the next row.', ['j', 'down'], 'terminal', [nav]),
150
+ b('crtr.browse.up', 'Browse and pickers', 'Move up', 'Move to the previous row.', ['k', 'up'], 'terminal', [nav]),
151
+ b('crtr.browse.first', 'Browse and pickers', 'First row', 'Move to the first row.', ['g'], 'terminal', [nav]),
152
+ b('crtr.browse.last', 'Browse and pickers', 'Last row', 'Move to the last row.', ['shift+g'], 'terminal', [nav]),
153
+ b('crtr.browse.expand-or-child', 'Browse and pickers', 'Expand or child', 'Expand a row or move to its child.', ['l', 'right'], 'terminal', [nav]),
154
+ b('crtr.browse.collapse-or-parent', 'Browse and pickers', 'Collapse or parent', 'Collapse a row or move to its parent.', ['h', 'left'], 'terminal', [nav]),
155
+ b('crtr.browse.toggle-fold', 'Browse and pickers', 'Toggle fold', 'Toggle the selected fold.', ['space'], 'terminal', [nav]),
156
+ b('crtr.browse.tab-next', 'Browse and pickers', 'Next tab', 'Select the next browse tab.', ['tab', ']'], 'terminal', [nav]),
157
+ b('crtr.browse.tab-previous', 'Browse and pickers', 'Previous tab', 'Select the previous browse tab.', ['shift+tab', '['], 'terminal', [nav]),
158
+ ...[1, 2, 3, 4].map((slot) => b(`crtr.browse.tab.${slot}`, 'Browse and pickers', `Tab ${slot}`, `Select browse tab ${slot}.`, [`${slot}`], 'terminal', [nav])),
159
+ b('crtr.browse.close-subtree', 'Browse and pickers', 'Close subtree', 'Ask to close the selected subtree.', ['x'], 'terminal', [nav]),
160
+ b('crtr.browse.confirm-close', 'Browse and pickers', 'Confirm close', 'Confirm closing the selected subtree.', ['y'], 'terminal', ['browse.confirm']),
161
+ b('crtr.browse.sort', 'Browse and pickers', 'Change sort', 'Change the browse sort.', ['s'], 'terminal', [nav]),
162
+ b('crtr.browse.scope', 'Browse and pickers', 'Change scope', 'Change the browse scope.', ['c'], 'terminal', [nav]),
163
+ b('crtr.browse.residents', 'Browse and pickers', 'Toggle residents', 'Toggle resident display.', ['r'], 'terminal', [nav]),
164
+ b('crtr.browse.preview', 'Browse and pickers', 'Toggle preview', 'Toggle the preview panel.', ['p'], 'terminal', [nav]),
165
+ b('crtr.browse.search', 'Browse and pickers', 'Search', 'Open browse search.', ['/'], 'terminal', [nav]),
166
+ b('crtr.browse.revive-now', 'Browse and pickers', 'Revive now', 'Revive the selected node now.', ['shift+k'], 'terminal', [nav]),
167
+ b('crtr.browse.focus', 'Browse and pickers', 'Focus node', 'Focus the selected node.', ['enter'], 'terminal', [nav]),
168
+ b('crtr.browse.search.cancel', 'Browse and pickers', 'Cancel search', 'Cancel browse search.', ['escape'], 'terminal', ['browse.search'], 'dismiss-modal'),
169
+ b('crtr.browse.search.commit', 'Browse and pickers', 'Commit search', 'Apply browse search.', ['enter'], 'terminal', ['browse.search']),
170
+ b('crtr.browse.search.delete', 'Browse and pickers', 'Delete search text', 'Delete the previous search character.', ['backspace'], 'terminal', ['browse.search']),
171
+ b('crtr.browse.search.quit', 'Browse and pickers', 'Quit browse', 'Leave canvas browse from search.', ['ctrl+c'], 'terminal', ['browse.search'], 'leave-screen'),
172
+ ];
173
+ }
174
+ function profileBindings() {
175
+ const menu = 'profile.menu';
176
+ const search = 'profile.search';
177
+ return [
178
+ b('crtr.profile.menu.up', 'Profiles', 'Move up', 'Move to the previous profile.', ['k', 'up'], 'terminal', [menu]),
179
+ b('crtr.profile.menu.down', 'Profiles', 'Move down', 'Move to the next profile.', ['j', 'down'], 'terminal', [menu]),
180
+ b('crtr.profile.menu.select', 'Profiles', 'Select profile', 'Select the highlighted profile.', ['enter'], 'terminal', [menu]),
181
+ b('crtr.profile.menu.accept-default', 'Profiles', 'Accept default', 'Accept the default profile.', ['escape'], 'terminal', [menu], 'leave-screen'),
182
+ b('crtr.profile.menu.abort', 'Profiles', 'Abort', 'Leave profile selection.', ['ctrl+c'], 'terminal', [menu], 'leave-screen'),
183
+ ...[1, 2, 3, 4, 5, 6, 7, 8, 9].map((slot) => b(`crtr.profile.menu.slot.${slot}`, 'Profiles', `Profile slot ${slot}`, `Select profile slot ${slot}.`, [`${slot}`], 'terminal', [menu])),
184
+ b('crtr.profile.menu.create', 'Profiles', 'Create profile', 'Create a profile.', ['c'], 'terminal', [menu]),
185
+ b('crtr.profile.menu.search-existing', 'Profiles', 'Search profiles', 'Search existing profiles.', ['s'], 'terminal', [menu]),
186
+ b('crtr.profile.menu.use-root', 'Profiles', 'Use root profile', 'Use the root profile.', ['r'], 'terminal', [menu]),
187
+ b('crtr.profile.menu.set-default', 'Profiles', 'Set default', 'Set the selected profile as default.', ['d'], 'terminal', [menu]),
188
+ b('crtr.profile.menu.add-directory', 'Profiles', 'Add directory', 'Add a directory to the profile.', ['a'], 'terminal', [menu]),
189
+ b('crtr.profile.menu.unpin', 'Profiles', 'Unpin directory', 'Unpin the selected directory.', ['u'], 'terminal', [menu]),
190
+ b('crtr.profile.search.up', 'Profiles', 'Move up', 'Move to the previous search result.', ['up', 'ctrl+p'], 'terminal', [search]),
191
+ b('crtr.profile.search.down', 'Profiles', 'Move down', 'Move to the next search result.', ['down', 'ctrl+n'], 'terminal', [search]),
192
+ b('crtr.profile.search.pick', 'Profiles', 'Pick profile', 'Pick the highlighted profile.', ['enter'], 'terminal', [search]),
193
+ b('crtr.profile.search.back', 'Profiles', 'Back', 'Return to profile selection.', ['escape'], 'terminal', [search], 'leave-screen'),
194
+ b('crtr.profile.search.delete', 'Profiles', 'Delete filter text', 'Delete the previous filter character.', ['backspace'], 'terminal', [search]),
195
+ b('crtr.profile.search.abort', 'Profiles', 'Abort', 'Leave profile search.', ['ctrl+c'], 'terminal', [search], 'leave-screen'),
196
+ ];
197
+ }
198
+ function setupBindings() {
199
+ return [
200
+ b('crtr.setup.cancel', 'Setup', 'Cancel setup', 'Leave the setup wizard.', ['q', 'ctrl+c', 'ctrl+d'], 'terminal', SETUP_CANCEL_CONTEXTS, 'leave-screen'),
201
+ b('crtr.setup.tab-next', 'Setup', 'Next tab', 'Select the next setup tab.', ['tab', 'right'], 'terminal', SETUP_TAB_CONTEXTS),
202
+ b('crtr.setup.tab-previous', 'Setup', 'Previous tab', 'Select the previous setup tab.', ['shift+tab', 'left'], 'terminal', SETUP_TAB_CONTEXTS),
203
+ b('crtr.setup.open', 'Setup', 'Open', 'Open the selected setup item.', ['enter'], 'terminal', ['setup.login']),
204
+ b('crtr.setup.list-down', 'Setup', 'Move down', 'Move to the next setup item.', ['j', 'down'], 'terminal', ['setup.list']),
205
+ b('crtr.setup.list-up', 'Setup', 'Move up', 'Move to the previous setup item.', ['k', 'up'], 'terminal', ['setup.list']),
206
+ b('crtr.setup.toggle', 'Setup', 'Toggle', 'Toggle the selected setup item.', ['space'], 'terminal', ['setup.list']),
207
+ b('crtr.setup.details', 'Setup', 'Show details', 'Show details for the selected item.', ['?'], 'terminal', ['setup.list']),
208
+ b('crtr.setup.advance', 'Setup', 'Advance', 'Advance the setup flow.', ['enter'], 'terminal', ['setup.list']),
209
+ b('crtr.setup.confirm.accept', 'Setup', 'Confirm', 'Accept the setup confirmation.', ['y', 'enter'], 'terminal', ['setup.confirm']),
210
+ b('crtr.setup.confirm.back', 'Setup', 'Go back', 'Cancel the setup confirmation.', ['n', 'escape', 'q'], 'terminal', ['setup.confirm'], 'dismiss-modal'),
211
+ b('crtr.setup.keybindings.edit', 'Setup', 'Edit binding', 'Edit the selected binding.', ['enter'], 'terminal', ['setup.keybindings']),
212
+ b('crtr.setup.keybindings.commit-edit', 'Setup', 'Commit edit', 'Commit the active binding edit.', ['enter'], 'terminal', ['setup.keybindings.edit']),
213
+ b('crtr.setup.keybindings.cancel-edit', 'Setup', 'Cancel edit', 'Cancel the active binding edit.', ['escape'], 'terminal', ['setup.keybindings.edit'], 'dismiss-modal'),
214
+ b('crtr.setup.keybindings.unbind', 'Setup', 'Unbind', 'Disable the selected binding.', ['delete'], 'terminal', ['setup.keybindings']),
215
+ b('crtr.setup.keybindings.reset-row', 'Setup', 'Reset binding', 'Reset the selected binding.', ['r'], 'terminal', ['setup.keybindings']),
216
+ b('crtr.setup.keybindings.reset-all', 'Setup', 'Reset all bindings', 'Reset all bindings to defaults.', ['shift+r'], 'terminal', ['setup.keybindings']),
217
+ b('crtr.setup.keybindings.save', 'Setup', 'Save bindings', 'Save keybinding changes.', ['s'], 'terminal', ['setup.keybindings'], 'save-keybindings'),
218
+ b('crtr.setup.keybindings.discard', 'Setup', 'Discard changes', 'Discard keybinding changes.', ['escape'], 'terminal', ['setup.keybindings'], 'leave-screen'),
219
+ b('crtr.setup.keybindings.page-up', 'Setup', 'Page up', 'Move up one keybinding page.', ['pageup'], 'terminal', ['setup.keybindings', 'setup.keybindings.edit']),
220
+ b('crtr.setup.keybindings.page-down', 'Setup', 'Page down', 'Move down one keybinding page.', ['pagedown'], 'terminal', ['setup.keybindings', 'setup.keybindings.edit']),
221
+ b('crtr.setup.keybindings.filter', 'Setup', 'Filter bindings', 'Open the keybinding filter.', ['/'], 'terminal', ['setup.keybindings']),
222
+ b('crtr.setup.keybindings.filter-delete', 'Setup', 'Delete filter text', 'Delete the previous filter character.', ['backspace'], 'terminal', ['setup.keybindings.filter']),
223
+ b('crtr.setup.keybindings.filter-clear', 'Setup', 'Clear filter', 'Clear the keybinding filter.', ['escape'], 'terminal', ['setup.keybindings.filter'], 'dismiss-modal'),
224
+ ];
225
+ }
226
+ export const BINDING_IDS = new Set(BINDING_CATALOG.map((definition) => definition.id));
227
+ export const SAFETY_REQUIREMENTS = Object.freeze([
228
+ { context: 'graph', capability: 'dismiss-modal' },
229
+ { context: 'graph.confirm', capability: 'dismiss-modal' },
230
+ { context: 'view.canvas', capability: 'leave-screen' },
231
+ { context: 'view.chat', capability: 'leave-screen' },
232
+ { context: 'view.git-pr', capability: 'leave-screen' },
233
+ { context: 'view.inbox.list', capability: 'leave-screen' },
234
+ { context: 'view.inbox.compose', capability: 'dismiss-modal' },
235
+ { context: 'view.inbox.react', capability: 'dismiss-modal' },
236
+ { context: 'view.linkedin.list', capability: 'leave-screen' },
237
+ { context: 'view.linkedin.compose', capability: 'dismiss-modal' },
238
+ { context: 'view.linkedin.react', capability: 'dismiss-modal' },
239
+ { context: 'view.prompt-review', capability: 'leave-screen' },
240
+ { context: 'view.settings', capability: 'leave-screen' },
241
+ { context: 'view.settings.edit', capability: 'dismiss-modal' },
242
+ { context: 'view.workspace-sidebar', capability: 'leave-screen' },
243
+ { context: 'view-picker', capability: 'leave-screen' },
244
+ { context: 'browse', capability: 'leave-screen' },
245
+ { context: 'browse.search', capability: 'dismiss-modal' },
246
+ { context: 'profile.menu', capability: 'leave-screen' },
247
+ { context: 'profile.search', capability: 'leave-screen' },
248
+ { context: 'setup.login', capability: 'leave-screen' },
249
+ { context: 'setup.list', capability: 'leave-screen' },
250
+ { context: 'setup.input', capability: 'leave-screen' },
251
+ { context: 'setup.confirm', capability: 'dismiss-modal' },
252
+ { context: 'setup.keybindings', capability: 'leave-screen' },
253
+ { context: 'setup.keybindings', capability: 'save-keybindings' },
254
+ { context: 'setup.keybindings.edit', capability: 'dismiss-modal' },
255
+ { context: 'setup.keybindings.filter', capability: 'dismiss-modal' },
256
+ ]);
@@ -0,0 +1,5 @@
1
+ export { BINDING_CATALOG, BINDING_IDS, SAFETY_REQUIREMENTS, type BindingId, } from './catalog.js';
2
+ export { GestureSyntaxError, KeybindingValidationError, normalizeGesture, normalizeSparseOverrides, normalizeStroke, resolveKeybindings, resolveUserKeybindings, } from './resolve.js';
3
+ export { canonicalTerminalStroke, formatBinding, formatGesture, formatStroke, matchesPiTuiInput, matchesTerminalInput, type PiTuiMatcher, type RawTerminalInput, } from './match.js';
4
+ export { KeybindingConcurrentEditError, persistUserKeybindings, type PersistedKeybindings, type PersistKeybindingsOptions, } from './persistence.js';
5
+ export type { BindingDefinition, BindingInputKind, BindingResolution, BindingSafetyCapability, EffectiveBinding, KeybindingDiagnostic, KeybindingDiagnosticCode, SafetyRequirement, SparseKeybindingOverrides, } from './types.js';
@@ -0,0 +1,4 @@
1
+ export { BINDING_CATALOG, BINDING_IDS, SAFETY_REQUIREMENTS, } from './catalog.js';
2
+ export { GestureSyntaxError, KeybindingValidationError, normalizeGesture, normalizeSparseOverrides, normalizeStroke, resolveKeybindings, resolveUserKeybindings, } from './resolve.js';
3
+ export { canonicalTerminalStroke, formatBinding, formatGesture, formatStroke, matchesPiTuiInput, matchesTerminalInput, } from './match.js';
4
+ export { KeybindingConcurrentEditError, persistUserKeybindings, } from './persistence.js';
@@ -0,0 +1,29 @@
1
+ import type { BindingId } from './catalog.js';
2
+ import type { BindingResolution } from './types.js';
3
+ export declare function formatStroke(stroke: string): string;
4
+ export declare function formatGesture(gesture: string): string;
5
+ export declare function formatBinding(bindingSet: BindingResolution<BindingId>, id: BindingId): string;
6
+ export interface RawTerminalInput {
7
+ readonly input?: string;
8
+ readonly upArrow?: boolean;
9
+ readonly downArrow?: boolean;
10
+ readonly leftArrow?: boolean;
11
+ readonly rightArrow?: boolean;
12
+ readonly return?: boolean;
13
+ readonly escape?: boolean;
14
+ readonly ctrl?: boolean;
15
+ readonly meta?: boolean;
16
+ readonly shift?: boolean;
17
+ readonly tab?: boolean;
18
+ readonly shiftTab?: boolean;
19
+ readonly backspace?: boolean;
20
+ readonly delete?: boolean;
21
+ readonly pageUp?: boolean;
22
+ readonly pageDown?: boolean;
23
+ readonly home?: boolean;
24
+ readonly end?: boolean;
25
+ }
26
+ export declare function canonicalTerminalStroke(raw: RawTerminalInput): string | null;
27
+ export declare function matchesTerminalInput(bindingSet: BindingResolution<BindingId>, id: BindingId, raw: RawTerminalInput): boolean;
28
+ export type PiTuiMatcher<Input> = (input: Input, gesture: string) => boolean;
29
+ export declare function matchesPiTuiInput<Input>(bindingSet: BindingResolution<BindingId>, id: BindingId, input: Input, matchesKey: PiTuiMatcher<Input>): boolean;