rl-rockcli 0.0.9 → 0.0.11
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.
- package/commands/attach/basic-repl.js +212 -0
- package/commands/attach/cleanup-history.js +189 -0
- package/commands/attach/cleanup-manager.js +163 -0
- package/commands/attach/copy-ui/copyRepl.js +195 -0
- package/commands/attach/copy-ui/index.js +7 -0
- package/commands/attach/copy-ui/render/outputBlock.js +25 -0
- package/commands/attach/copy-ui/viewport/viewport.js +23 -0
- package/commands/attach/copy-ui/viewport/wheel.js +14 -0
- package/commands/attach/history-manager.js +507 -0
- package/commands/attach/history-session.js +48 -0
- package/commands/attach/ink-repl/InkREPL.js +1507 -0
- package/commands/attach/ink-repl/builtinCommands.js +1253 -0
- package/commands/attach/ink-repl/components/ConnectingScreen.js +76 -0
- package/commands/attach/ink-repl/components/Console.js +191 -0
- package/commands/attach/ink-repl/components/DetailView.js +148 -0
- package/commands/attach/ink-repl/components/DropdownMenu.js +86 -0
- package/commands/attach/ink-repl/components/InputArea.js +125 -0
- package/commands/attach/ink-repl/components/InputLine.js +18 -0
- package/commands/attach/ink-repl/components/OutputArea.js +22 -0
- package/commands/attach/ink-repl/components/OutputItem.js +96 -0
- package/commands/attach/ink-repl/components/ShellLayout.js +61 -0
- package/commands/attach/ink-repl/components/Spinner.js +79 -0
- package/commands/attach/ink-repl/components/StatusBar.js +106 -0
- package/commands/attach/ink-repl/components/WelcomeBanner.js +48 -0
- package/commands/attach/ink-repl/contexts/LayoutContext.js +12 -0
- package/commands/attach/ink-repl/contexts/ThemeContext.js +43 -0
- package/commands/attach/ink-repl/hooks/useFunctionKeys.js +70 -0
- package/commands/attach/ink-repl/hooks/useMouse.js +162 -0
- package/commands/attach/ink-repl/hooks/useResources.js +132 -0
- package/commands/attach/ink-repl/hooks/useSpinner.js +49 -0
- package/commands/attach/ink-repl/index.js +112 -0
- package/commands/attach/ink-repl/package.json +3 -0
- package/commands/attach/ink-repl/replState.js +947 -0
- package/commands/attach/ink-repl/shortcuts/defaultKeybindings.js +138 -0
- package/commands/attach/ink-repl/shortcuts/index.js +332 -0
- package/commands/attach/ink-repl/themes/defaultDark.js +18 -0
- package/commands/attach/ink-repl/themes/defaultLight.js +18 -0
- package/commands/attach/ink-repl/themes/index.js +4 -0
- package/commands/attach/ink-repl/themes/themeManager.js +45 -0
- package/commands/attach/ink-repl/themes/themeTokens.js +15 -0
- package/commands/attach/ink-repl/utils/atCompletion.js +346 -0
- package/commands/attach/ink-repl/utils/clipboard.js +50 -0
- package/commands/attach/ink-repl/utils/consoleLogger.js +81 -0
- package/commands/attach/ink-repl/utils/exitCodeHandler.js +49 -0
- package/commands/attach/ink-repl/utils/exitCodeTips.js +56 -0
- package/commands/attach/ink-repl/utils/formatTime.js +12 -0
- package/commands/attach/ink-repl/utils/outputSelection.js +120 -0
- package/commands/attach/ink-repl/utils/outputViewport.js +77 -0
- package/commands/attach/ink-repl/utils/paginatedFileLoading.js +76 -0
- package/commands/attach/ink-repl/utils/paramHint.js +60 -0
- package/commands/attach/ink-repl/utils/parseError.js +174 -0
- package/commands/attach/ink-repl/utils/pathCompletion.js +167 -0
- package/commands/attach/ink-repl/utils/remotePathSafety.js +56 -0
- package/commands/attach/ink-repl/utils/replSelection.js +205 -0
- package/commands/attach/ink-repl/utils/responseFormatter.js +127 -0
- package/commands/attach/ink-repl/utils/textWrap.js +117 -0
- package/commands/attach/ink-repl/utils/truncate.js +115 -0
- package/commands/attach/opentui-repl/App.tsx +891 -0
- package/commands/attach/opentui-repl/builtinCommands.ts +80 -0
- package/commands/attach/opentui-repl/components/ConfirmDialog.tsx +116 -0
- package/commands/attach/opentui-repl/components/ConnectingScreen.tsx +131 -0
- package/commands/attach/opentui-repl/components/Console.tsx +73 -0
- package/commands/attach/opentui-repl/components/DetailView.tsx +45 -0
- package/commands/attach/opentui-repl/components/DropdownMenu.tsx +130 -0
- package/commands/attach/opentui-repl/components/ExecutionStatus.tsx +66 -0
- package/commands/attach/opentui-repl/components/Header.tsx +24 -0
- package/commands/attach/opentui-repl/components/OutputArea.tsx +25 -0
- package/commands/attach/opentui-repl/components/OutputBlock.tsx +108 -0
- package/commands/attach/opentui-repl/components/PromptInput.tsx +109 -0
- package/commands/attach/opentui-repl/components/StatusBar.tsx +63 -0
- package/commands/attach/opentui-repl/components/Toast.tsx +65 -0
- package/commands/attach/opentui-repl/components/WelcomeBanner.tsx +41 -0
- package/commands/attach/opentui-repl/contexts/ReplContext.tsx +137 -0
- package/commands/attach/opentui-repl/contexts/SessionContext.tsx +32 -0
- package/commands/attach/opentui-repl/contexts/ThemeContext.tsx +70 -0
- package/commands/attach/opentui-repl/contexts/ToastContext.tsx +69 -0
- package/commands/attach/opentui-repl/contexts/toast-logic.js +71 -0
- package/commands/attach/opentui-repl/hooks/useResources.ts +102 -0
- package/commands/attach/opentui-repl/hooks/useSpinner.ts +46 -0
- package/commands/attach/opentui-repl/index.js +99 -0
- package/commands/attach/opentui-repl/keybindings.ts +39 -0
- package/commands/attach/opentui-repl/package.json +3 -0
- package/commands/attach/opentui-repl/render.tsx +72 -0
- package/commands/attach/opentui-repl/tsconfig.json +12 -0
- package/commands/attach/repl.js +791 -0
- package/commands/attach/sandbox-id-resolver.js +56 -0
- package/commands/attach/session-manager.js +307 -0
- package/commands/attach/ui-mode.js +146 -0
- package/commands/attach.js +186 -0
- package/package.json +1 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default keybindings for attach REPL
|
|
3
|
+
*
|
|
4
|
+
* This module defines the default key bindings and their display labels.
|
|
5
|
+
* Users can override these in ~/.rock/settings.json under attach.keybindings
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Default keybindings configuration
|
|
10
|
+
* Each action maps to an array of key combinations
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_KEYBINDINGS = {
|
|
13
|
+
// Global actions
|
|
14
|
+
exit: ['ctrl+c', 'ctrl+d'],
|
|
15
|
+
cancelExecution: ['escape'],
|
|
16
|
+
toggleConsole: ['f12'],
|
|
17
|
+
// toggleMouseCapture: ['f11'], // REMOVED - mouse always enabled
|
|
18
|
+
viewOutput: ['ctrl+v'],
|
|
19
|
+
copyLastOutput: ['ctrl+y'],
|
|
20
|
+
|
|
21
|
+
// History navigation
|
|
22
|
+
historyUp: ['up'],
|
|
23
|
+
historyDown: ['down'],
|
|
24
|
+
|
|
25
|
+
// Editing
|
|
26
|
+
deleteWord: ['alt+backspace', 'meta+backspace'],
|
|
27
|
+
|
|
28
|
+
// Completion
|
|
29
|
+
autoComplete: ['tab'],
|
|
30
|
+
|
|
31
|
+
// Command submission
|
|
32
|
+
submit: ['enter'],
|
|
33
|
+
|
|
34
|
+
// Menu actions
|
|
35
|
+
closeMenu: ['escape'],
|
|
36
|
+
menuConfirm: ['enter', 'tab'],
|
|
37
|
+
menuUp: ['up'],
|
|
38
|
+
menuDown: ['down'],
|
|
39
|
+
|
|
40
|
+
// Detail view actions
|
|
41
|
+
detailScrollUp: ['up', 'k'],
|
|
42
|
+
detailScrollDown: ['down', 'j'],
|
|
43
|
+
detailExit: ['escape'],
|
|
44
|
+
|
|
45
|
+
// Console actions
|
|
46
|
+
consoleScrollUp: ['pageup'],
|
|
47
|
+
consoleScrollDown: ['pagedown'],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Display labels for key names
|
|
52
|
+
* Used to generate human-readable hints in StatusBar
|
|
53
|
+
*/
|
|
54
|
+
export const KEY_LABELS = {
|
|
55
|
+
'ctrl+c': 'ctrl+c',
|
|
56
|
+
'ctrl+d': 'ctrl+d',
|
|
57
|
+
'ctrl+v': 'ctrl+v',
|
|
58
|
+
'ctrl+y': 'ctrl+y',
|
|
59
|
+
'alt+backspace': 'alt+⌫',
|
|
60
|
+
'meta+backspace': 'opt+⌫',
|
|
61
|
+
'pageup': 'PgUp',
|
|
62
|
+
'pagedown': 'PgDn',
|
|
63
|
+
'f1': 'F1',
|
|
64
|
+
'f2': 'F2',
|
|
65
|
+
'f3': 'F3',
|
|
66
|
+
'f4': 'F4',
|
|
67
|
+
'f5': 'F5',
|
|
68
|
+
'f6': 'F6',
|
|
69
|
+
'f7': 'F7',
|
|
70
|
+
'f8': 'F8',
|
|
71
|
+
'f9': 'F9',
|
|
72
|
+
'f10': 'F10',
|
|
73
|
+
'f11': 'F11',
|
|
74
|
+
'f12': 'F12',
|
|
75
|
+
'enter': '⏎',
|
|
76
|
+
'tab': '⇥',
|
|
77
|
+
'escape': 'esc',
|
|
78
|
+
'up': '↑',
|
|
79
|
+
'down': '↓',
|
|
80
|
+
'left': '←',
|
|
81
|
+
'right': '→',
|
|
82
|
+
'space': 'space',
|
|
83
|
+
'backspace': '⌫',
|
|
84
|
+
'delete': '⌦',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Function key escape sequences (xterm/VT style)
|
|
89
|
+
* Maps raw terminal escape sequences to key names
|
|
90
|
+
*/
|
|
91
|
+
export const FN_KEY_SEQUENCES = {
|
|
92
|
+
'\x1b[11~': 'f1',
|
|
93
|
+
'\x1b[12~': 'f2',
|
|
94
|
+
'\x1b[13~': 'f3',
|
|
95
|
+
'\x1b[14~': 'f4',
|
|
96
|
+
'\x1b[15~': 'f5',
|
|
97
|
+
'\x1b[17~': 'f6',
|
|
98
|
+
'\x1b[18~': 'f7',
|
|
99
|
+
'\x1b[19~': 'f8',
|
|
100
|
+
'\x1b[20~': 'f9',
|
|
101
|
+
'\x1b[21~': 'f10',
|
|
102
|
+
'\x1b[23~': 'f11',
|
|
103
|
+
'\x1b[24~': 'f12',
|
|
104
|
+
// Alternative sequences (some terminals)
|
|
105
|
+
'\x1bOP': 'f1',
|
|
106
|
+
'\x1bOQ': 'f2',
|
|
107
|
+
'\x1bOR': 'f3',
|
|
108
|
+
'\x1bOS': 'f4',
|
|
109
|
+
// Page Up/Down
|
|
110
|
+
'\x1b[5~': 'pageup',
|
|
111
|
+
'\x1b[6~': 'pagedown',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Action descriptions for help text
|
|
116
|
+
*/
|
|
117
|
+
export const ACTION_DESCRIPTIONS = {
|
|
118
|
+
exit: 'Exit',
|
|
119
|
+
cancelExecution: 'Cancel execution',
|
|
120
|
+
toggleConsole: 'Toggle console',
|
|
121
|
+
// toggleMouseCapture: 'Toggle mouse capture', // REMOVED - always enabled
|
|
122
|
+
viewOutput: 'View full output',
|
|
123
|
+
copyLastOutput: 'Copy last output',
|
|
124
|
+
historyUp: 'Previous command',
|
|
125
|
+
historyDown: 'Next command',
|
|
126
|
+
deleteWord: 'Delete word',
|
|
127
|
+
autoComplete: 'Auto-complete',
|
|
128
|
+
submit: 'Submit command',
|
|
129
|
+
closeMenu: 'Close menu',
|
|
130
|
+
menuConfirm: 'Confirm selection',
|
|
131
|
+
menuUp: 'Move up',
|
|
132
|
+
menuDown: 'Move down',
|
|
133
|
+
detailScrollUp: 'Scroll up',
|
|
134
|
+
detailScrollDown: 'Scroll down',
|
|
135
|
+
detailExit: 'Exit detail view',
|
|
136
|
+
consoleScrollUp: 'Scroll console up',
|
|
137
|
+
consoleScrollDown: 'Scroll console down',
|
|
138
|
+
};
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keybinding Manager for attach REPL
|
|
3
|
+
*
|
|
4
|
+
* Provides configurable keyboard shortcuts with user customization support.
|
|
5
|
+
* Settings are stored in ~/.rock/settings.json under attach.keybindings
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs-extra';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import os from 'os';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
DEFAULT_KEYBINDINGS,
|
|
14
|
+
KEY_LABELS,
|
|
15
|
+
FN_KEY_SEQUENCES,
|
|
16
|
+
} from './defaultKeybindings.js';
|
|
17
|
+
|
|
18
|
+
const CONFIG_PATH = path.join(os.homedir(), '.rock', 'settings.json');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* KeybindingManager class
|
|
22
|
+
* Manages keybindings with support for user customization
|
|
23
|
+
*/
|
|
24
|
+
export class KeybindingManager {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.keybindings = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Load keybindings from config file, merged with defaults
|
|
31
|
+
* @returns {Object} Merged keybindings
|
|
32
|
+
*/
|
|
33
|
+
loadKeybindings() {
|
|
34
|
+
if (this.keybindings) {
|
|
35
|
+
return this.keybindings;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let userBindings = {};
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
42
|
+
const config = fs.readJsonSync(CONFIG_PATH);
|
|
43
|
+
if (config.attach && config.attach.keybindings) {
|
|
44
|
+
userBindings = config.attach.keybindings;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// Ignore config read errors, use defaults
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Merge: user bindings override defaults
|
|
52
|
+
this.keybindings = {
|
|
53
|
+
...DEFAULT_KEYBINDINGS,
|
|
54
|
+
...userBindings,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return this.keybindings;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get keybindings (load if not already loaded)
|
|
62
|
+
* @returns {Object} Current keybindings
|
|
63
|
+
*/
|
|
64
|
+
getKeybindings() {
|
|
65
|
+
if (!this.keybindings) {
|
|
66
|
+
this.loadKeybindings();
|
|
67
|
+
}
|
|
68
|
+
return this.keybindings;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Normalize a key string to lowercase
|
|
73
|
+
* @param {string} key Key string
|
|
74
|
+
* @returns {string} Normalized key
|
|
75
|
+
*/
|
|
76
|
+
normalizeKey(key) {
|
|
77
|
+
return key.toLowerCase();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parse ink useInput (input, key) into a normalized key string
|
|
82
|
+
* @param {string} input The input character
|
|
83
|
+
* @param {Object} key The key object from useInput
|
|
84
|
+
* @returns {string} Normalized key string like 'ctrl+c', 'up', 'enter'
|
|
85
|
+
*/
|
|
86
|
+
parseInkKey(input, key) {
|
|
87
|
+
// Handle special keys
|
|
88
|
+
if (key.return) return 'enter';
|
|
89
|
+
if (key.escape) return 'escape';
|
|
90
|
+
if (key.tab) return 'tab';
|
|
91
|
+
if (key.upArrow) return 'up';
|
|
92
|
+
if (key.downArrow) return 'down';
|
|
93
|
+
if (key.leftArrow) return 'left';
|
|
94
|
+
if (key.rightArrow) return 'right';
|
|
95
|
+
|
|
96
|
+
// Handle meta/alt + backspace (Option+Delete on macOS)
|
|
97
|
+
if ((key.meta || key.alt) && (key.backspace || key.delete)) {
|
|
98
|
+
return 'meta+backspace';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Handle regular backspace
|
|
102
|
+
if (key.backspace || key.delete) return 'backspace';
|
|
103
|
+
|
|
104
|
+
// Handle Ctrl+D (EOT character)
|
|
105
|
+
if (input === '\u0004') return 'ctrl+d';
|
|
106
|
+
|
|
107
|
+
// Handle ctrl/meta combinations
|
|
108
|
+
if (key.ctrl && input) {
|
|
109
|
+
return `ctrl+${input.toLowerCase()}`;
|
|
110
|
+
}
|
|
111
|
+
if ((key.meta || key.alt) && input) {
|
|
112
|
+
return `meta+${input.toLowerCase()}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Regular character
|
|
116
|
+
if (input) {
|
|
117
|
+
return input.toLowerCase();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Check if useInput matches an action
|
|
125
|
+
* @param {string} action Action name (e.g., 'exit', 'submit')
|
|
126
|
+
* @param {string} input The input from useInput
|
|
127
|
+
* @param {Object} key The key object from useInput
|
|
128
|
+
* @returns {boolean} True if matches
|
|
129
|
+
*/
|
|
130
|
+
matchesAction(action, input, key) {
|
|
131
|
+
const bindings = this.getKeybindings();
|
|
132
|
+
const actionKeys = bindings[action];
|
|
133
|
+
|
|
134
|
+
if (!actionKeys || !Array.isArray(actionKeys)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const parsedKey = this.parseInkKey(input, key);
|
|
139
|
+
return actionKeys.some(k => this.normalizeKey(k) === parsedKey);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Parse raw terminal input for function keys
|
|
144
|
+
* @param {string} rawInput Raw terminal input
|
|
145
|
+
* @returns {string|null} Function key name or null
|
|
146
|
+
*/
|
|
147
|
+
parseFunctionKey(rawInput) {
|
|
148
|
+
return FN_KEY_SEQUENCES[rawInput] || null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Check if a function key matches an action
|
|
153
|
+
* @param {string} action Action name
|
|
154
|
+
* @param {string} rawInput Raw terminal input
|
|
155
|
+
* @returns {boolean} True if matches
|
|
156
|
+
*/
|
|
157
|
+
matchesFunctionKeyAction(action, rawInput) {
|
|
158
|
+
const bindings = this.getKeybindings();
|
|
159
|
+
const actionKeys = bindings[action];
|
|
160
|
+
|
|
161
|
+
if (!actionKeys || !Array.isArray(actionKeys)) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const fnKey = this.parseFunctionKey(rawInput);
|
|
166
|
+
if (!fnKey) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return actionKeys.some(k => this.normalizeKey(k) === fnKey);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get the display label for a key
|
|
175
|
+
* @param {string} key Key string
|
|
176
|
+
* @returns {string} Display label
|
|
177
|
+
*/
|
|
178
|
+
getKeyLabel(key) {
|
|
179
|
+
const normalized = this.normalizeKey(key);
|
|
180
|
+
return KEY_LABELS[normalized] || key.toUpperCase();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get display labels for an action's first key
|
|
185
|
+
* @param {string} action Action name
|
|
186
|
+
* @returns {string} Display label for the first bound key
|
|
187
|
+
*/
|
|
188
|
+
getActionKeyLabel(action) {
|
|
189
|
+
const bindings = this.getKeybindings();
|
|
190
|
+
const actionKeys = bindings[action];
|
|
191
|
+
|
|
192
|
+
if (!actionKeys || actionKeys.length === 0) {
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return this.getKeyLabel(actionKeys[0]);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get keyboard hints for StatusBar based on current mode and state
|
|
201
|
+
* @param {string} mode Current mode ('repl' or 'detail')
|
|
202
|
+
* @param {Object} state Current state object
|
|
203
|
+
* @returns {string} Formatted hint string
|
|
204
|
+
*/
|
|
205
|
+
getKeyHints(mode, state = {}) {
|
|
206
|
+
const { menuVisible, isExecuting, consoleVisible } = state;
|
|
207
|
+
|
|
208
|
+
if (mode === 'detail') {
|
|
209
|
+
const upKey = this.getActionKeyLabel('detailScrollUp');
|
|
210
|
+
const downKey = this.getActionKeyLabel('detailScrollDown');
|
|
211
|
+
const exitKey = this.getActionKeyLabel('detailExit');
|
|
212
|
+
const consoleKey = this.getActionKeyLabel('toggleConsole');
|
|
213
|
+
return `${upKey}${downKey}/wheel scroll ${exitKey} back ${consoleKey} console`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (isExecuting) {
|
|
217
|
+
const exitKey = this.getActionKeyLabel('exit');
|
|
218
|
+
const consoleKey = this.getActionKeyLabel('toggleConsole');
|
|
219
|
+
return `${exitKey} cancel ${consoleKey} console`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (menuVisible) {
|
|
223
|
+
const upKey = this.getActionKeyLabel('menuUp');
|
|
224
|
+
const downKey = this.getActionKeyLabel('menuDown');
|
|
225
|
+
const confirmKey = this.getActionKeyLabel('menuConfirm');
|
|
226
|
+
const closeKey = this.getActionKeyLabel('closeMenu');
|
|
227
|
+
return `${upKey}${downKey} select ${confirmKey} confirm ${closeKey} close`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (consoleVisible) {
|
|
231
|
+
const consoleKey = this.getActionKeyLabel('toggleConsole');
|
|
232
|
+
const exitKey = this.getActionKeyLabel('exit');
|
|
233
|
+
return `${consoleKey} hide console ${exitKey} exit`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Default REPL mode
|
|
237
|
+
const historyUpKey = this.getActionKeyLabel('historyUp');
|
|
238
|
+
const historyDownKey = this.getActionKeyLabel('historyDown');
|
|
239
|
+
const copyKey = this.getActionKeyLabel('copyLastOutput');
|
|
240
|
+
const consoleKey = this.getActionKeyLabel('toggleConsole');
|
|
241
|
+
const scrollUpKey = this.getActionKeyLabel('consoleScrollUp');
|
|
242
|
+
const scrollDownKey = this.getActionKeyLabel('consoleScrollDown');
|
|
243
|
+
const exitKey = this.getActionKeyLabel('exit');
|
|
244
|
+
return `/ cmds ${historyUpKey}${historyDownKey} history ${copyKey} copy ${consoleKey} console ${scrollUpKey}${scrollDownKey} scroll/wheel ${exitKey} exit`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Save custom keybindings to config file
|
|
249
|
+
* @param {Object} keybindings Keybindings to save
|
|
250
|
+
* @returns {boolean} Success status
|
|
251
|
+
*/
|
|
252
|
+
saveKeybindings(keybindings) {
|
|
253
|
+
try {
|
|
254
|
+
let config = {};
|
|
255
|
+
|
|
256
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
257
|
+
config = fs.readJsonSync(CONFIG_PATH);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!config.attach) {
|
|
261
|
+
config.attach = {};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
config.attach.keybindings = keybindings;
|
|
265
|
+
|
|
266
|
+
// Ensure directory exists
|
|
267
|
+
const configDir = path.dirname(CONFIG_PATH);
|
|
268
|
+
if (!fs.existsSync(configDir)) {
|
|
269
|
+
fs.mkdirpSync(configDir);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
fs.writeJsonSync(CONFIG_PATH, config, { spaces: 2 });
|
|
273
|
+
|
|
274
|
+
// Update cached keybindings
|
|
275
|
+
this.keybindings = {
|
|
276
|
+
...DEFAULT_KEYBINDINGS,
|
|
277
|
+
...keybindings,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
return true;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Reset keybindings to defaults (removes user customizations)
|
|
288
|
+
* @returns {boolean} Success status
|
|
289
|
+
*/
|
|
290
|
+
resetToDefaults() {
|
|
291
|
+
try {
|
|
292
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
293
|
+
const config = fs.readJsonSync(CONFIG_PATH);
|
|
294
|
+
|
|
295
|
+
if (config.attach && config.attach.keybindings) {
|
|
296
|
+
delete config.attach.keybindings;
|
|
297
|
+
|
|
298
|
+
// Clean up empty attach object
|
|
299
|
+
if (Object.keys(config.attach).length === 0) {
|
|
300
|
+
delete config.attach;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
fs.writeJsonSync(CONFIG_PATH, config, { spaces: 2 });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Reset cached keybindings to defaults
|
|
308
|
+
this.keybindings = { ...DEFAULT_KEYBINDINGS };
|
|
309
|
+
|
|
310
|
+
return true;
|
|
311
|
+
} catch (error) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Reload keybindings from config (useful after external changes)
|
|
318
|
+
* @returns {Object} Reloaded keybindings
|
|
319
|
+
*/
|
|
320
|
+
reloadKeybindings() {
|
|
321
|
+
this.keybindings = null;
|
|
322
|
+
return this.loadKeybindings();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Singleton instance
|
|
327
|
+
export const keybindingManager = new KeybindingManager();
|
|
328
|
+
|
|
329
|
+
export default keybindingManager;
|
|
330
|
+
|
|
331
|
+
// Re-export constants for convenience
|
|
332
|
+
export { DEFAULT_KEYBINDINGS, KEY_LABELS, FN_KEY_SEQUENCES } from './defaultKeybindings.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createTheme, defaultGradient } from './themeTokens.js';
|
|
2
|
+
|
|
3
|
+
export const DefaultDark = createTheme('rock-dark', {
|
|
4
|
+
background: '#1e1e2e',
|
|
5
|
+
surface: '#181825',
|
|
6
|
+
surfaceHighlight: '#11111b',
|
|
7
|
+
border: '#313244',
|
|
8
|
+
textPrimary: '#cdd6f4',
|
|
9
|
+
textSecondary: '#a6adc8',
|
|
10
|
+
accent: '#89b4fa',
|
|
11
|
+
accentMuted: '#b4befe',
|
|
12
|
+
warning: '#f9e2af',
|
|
13
|
+
success: '#a6e3a1',
|
|
14
|
+
danger: '#f38ba8',
|
|
15
|
+
prompt: '#89dceb',
|
|
16
|
+
cursor: '#cdd6f4',
|
|
17
|
+
gradient: defaultGradient,
|
|
18
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createTheme, defaultGradient } from './themeTokens.js';
|
|
2
|
+
|
|
3
|
+
export const DefaultLight = createTheme('rock-light', {
|
|
4
|
+
background: '#fafafa',
|
|
5
|
+
surface: '#ffffff',
|
|
6
|
+
surfaceHighlight: '#f3f4f6',
|
|
7
|
+
border: '#d0d7de',
|
|
8
|
+
textPrimary: '#1f2328',
|
|
9
|
+
textSecondary: '#4c566a',
|
|
10
|
+
accent: '#4796e4',
|
|
11
|
+
accentMuted: '#c3677f',
|
|
12
|
+
warning: '#bf6500',
|
|
13
|
+
success: '#1a7f37',
|
|
14
|
+
danger: '#b62324',
|
|
15
|
+
prompt: '#0a3069',
|
|
16
|
+
cursor: '#1f2328',
|
|
17
|
+
gradient: defaultGradient,
|
|
18
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { DEFAULT_THEME_NAME } from './themeTokens.js';
|
|
2
|
+
import { DefaultDark } from './defaultDark.js';
|
|
3
|
+
import { DefaultLight } from './defaultLight.js';
|
|
4
|
+
|
|
5
|
+
const BUILTIN_THEMES = [DefaultDark, DefaultLight];
|
|
6
|
+
|
|
7
|
+
class ThemeManager {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.themes = new Map();
|
|
10
|
+
BUILTIN_THEMES.forEach(theme => {
|
|
11
|
+
this.themes.set(theme.name, theme);
|
|
12
|
+
});
|
|
13
|
+
this.activeThemeName = DEFAULT_THEME_NAME;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getActiveTheme() {
|
|
17
|
+
return this.themes.get(this.activeThemeName) || DefaultDark;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getTheme(name) {
|
|
21
|
+
return this.themes.get(name);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
listThemes() {
|
|
25
|
+
return Array.from(this.themes.values()).map(theme => ({
|
|
26
|
+
name: theme.name,
|
|
27
|
+
type: theme.type,
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
registerTheme(theme) {
|
|
32
|
+
if (!theme || !theme.name) return;
|
|
33
|
+
this.themes.set(theme.name, theme);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setActiveTheme(name) {
|
|
37
|
+
if (this.themes.has(name)) {
|
|
38
|
+
this.activeThemeName = name;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const themeManager = new ThemeManager();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const DEFAULT_THEME_NAME = 'rock-dark';
|
|
2
|
+
|
|
3
|
+
export function createTheme(name, colors, type = 'default') {
|
|
4
|
+
return {
|
|
5
|
+
name,
|
|
6
|
+
type,
|
|
7
|
+
colors,
|
|
8
|
+
semantic: {
|
|
9
|
+
banner: colors.gradient || defaultGradient,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Logo/banner gradient aligned with web (blue → indigo → purple).
|
|
15
|
+
export const defaultGradient = ['#2563EB', '#4F46E5', '#7C3AED'];
|