rl-rockcli 0.0.8 → 0.0.10
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/log/core/constants.js +237 -0
- package/commands/log/core/display.js +370 -0
- package/commands/log/core/search.js +330 -0
- package/commands/log/core/tail.js +216 -0
- package/commands/log/core/utils.js +424 -0
- package/commands/log.js +298 -0
- package/commands/sandbox/core/log-bridge.js +119 -0
- package/commands/sandbox/core/replay/analyzer.js +311 -0
- package/commands/sandbox/core/replay/batch-orchestrator.js +536 -0
- package/commands/sandbox/core/replay/batch-task.js +369 -0
- package/commands/sandbox/core/replay/concurrent-display.js +70 -0
- package/commands/sandbox/core/replay/concurrent-orchestrator.js +170 -0
- package/commands/sandbox/core/replay/data-source.js +86 -0
- package/commands/sandbox/core/replay/display.js +231 -0
- package/commands/sandbox/core/replay/executor.js +634 -0
- package/commands/sandbox/core/replay/history-fetcher.js +124 -0
- package/commands/sandbox/core/replay/index.js +338 -0
- package/commands/sandbox/core/replay/loghouse-data-source.js +177 -0
- package/commands/sandbox/core/replay/pid-mapping.js +26 -0
- package/commands/sandbox/core/replay/request.js +109 -0
- package/commands/sandbox/core/replay/worker.js +166 -0
- package/commands/sandbox/core/session.js +346 -0
- package/commands/sandbox/log-bridge.js +2 -0
- package/commands/sandbox/ray.js +2 -0
- package/commands/sandbox/replay/analyzer.js +311 -0
- package/commands/sandbox/replay/batch-orchestrator.js +536 -0
- package/commands/sandbox/replay/batch-task.js +369 -0
- package/commands/sandbox/replay/concurrent-display.js +70 -0
- package/commands/sandbox/replay/concurrent-orchestrator.js +170 -0
- package/commands/sandbox/replay/display.js +231 -0
- package/commands/sandbox/replay/executor.js +634 -0
- package/commands/sandbox/replay/history-fetcher.js +118 -0
- package/commands/sandbox/replay/index.js +338 -0
- package/commands/sandbox/replay/pid-mapping.js +26 -0
- package/commands/sandbox/replay/request.js +109 -0
- package/commands/sandbox/replay/worker.js +166 -0
- package/commands/sandbox/replay.js +2 -0
- package/commands/sandbox/session.js +2 -0
- package/commands/sandbox-original.js +1393 -0
- package/commands/sandbox.js +499 -0
- package/help/help.json +1071 -0
- package/help/middleware.js +71 -0
- package/help/renderer.js +800 -0
- package/index.js +5 -15
- package/lib/plugin-context.js +40 -0
- package/package.json +2 -2
- package/sdks/sandbox/core/client.js +845 -0
- package/sdks/sandbox/core/config.js +70 -0
- package/sdks/sandbox/core/types.js +74 -0
- package/sdks/sandbox/httpLogger.js +251 -0
- package/sdks/sandbox/index.js +9 -0
- package/utils/asciiArt.js +138 -0
- package/utils/bun-compat.js +59 -0
- package/utils/ciPipelines.js +138 -0
- package/utils/cli.js +17 -0
- package/utils/command-router.js +79 -0
- package/utils/configManager.js +503 -0
- package/utils/dependency-resolver.js +135 -0
- package/utils/eagleeye_traceid.js +151 -0
- package/utils/envDetector.js +78 -0
- package/utils/execution_logger.js +415 -0
- package/utils/featureManager.js +68 -0
- package/utils/firstTimeTip.js +44 -0
- package/utils/hook-manager.js +125 -0
- package/utils/http-logger.js +264 -0
- package/utils/i18n.js +139 -0
- package/utils/image-progress.js +159 -0
- package/utils/logger.js +154 -0
- package/utils/plugin-loader.js +124 -0
- package/utils/plugin-manager.js +348 -0
- package/utils/ray_cli_wrapper.js +746 -0
- package/utils/sandbox-client.js +419 -0
- package/utils/terminal.js +32 -0
- package/utils/tips.js +106 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toast notification state management logic.
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic: no SolidJS dependency.
|
|
5
|
+
* Used by ToastContext.tsx for SolidJS integration,
|
|
6
|
+
* and directly testable via Jest.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_DURATION = 3000;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create a toast manager instance.
|
|
13
|
+
* Manages currentToast state and auto-dismiss timers.
|
|
14
|
+
*
|
|
15
|
+
* @returns {object} Toast manager with show(), error(), and currentToast
|
|
16
|
+
*/
|
|
17
|
+
export function createToastManager() {
|
|
18
|
+
let _currentToast = null;
|
|
19
|
+
let _timeoutHandle = null;
|
|
20
|
+
|
|
21
|
+
const manager = {
|
|
22
|
+
/**
|
|
23
|
+
* Show a toast notification.
|
|
24
|
+
* @param {object} options
|
|
25
|
+
* @param {string} options.message - Toast message (required)
|
|
26
|
+
* @param {'info'|'success'|'warning'|'error'} options.variant - Toast variant (required)
|
|
27
|
+
* @param {string} [options.title] - Optional title
|
|
28
|
+
* @param {number} [options.duration=3000] - Auto-dismiss duration in ms
|
|
29
|
+
*/
|
|
30
|
+
show(options) {
|
|
31
|
+
const { duration, ...toastData } = options;
|
|
32
|
+
const dismissAfter = duration != null ? duration : DEFAULT_DURATION;
|
|
33
|
+
|
|
34
|
+
_currentToast = toastData;
|
|
35
|
+
|
|
36
|
+
// Clear previous timer
|
|
37
|
+
if (_timeoutHandle) {
|
|
38
|
+
clearTimeout(_timeoutHandle);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Set auto-dismiss timer
|
|
42
|
+
_timeoutHandle = setTimeout(() => {
|
|
43
|
+
_currentToast = null;
|
|
44
|
+
_timeoutHandle = null;
|
|
45
|
+
}, dismissAfter);
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Show an error toast from an Error, string, or unknown value.
|
|
50
|
+
* @param {*} err
|
|
51
|
+
*/
|
|
52
|
+
error(err) {
|
|
53
|
+
if (err instanceof Error) {
|
|
54
|
+
manager.show({ variant: 'error', message: err.message });
|
|
55
|
+
} else if (typeof err === 'string') {
|
|
56
|
+
manager.show({ variant: 'error', message: err });
|
|
57
|
+
} else {
|
|
58
|
+
manager.show({ variant: 'error', message: 'An unknown error occurred' });
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Current toast or null.
|
|
64
|
+
*/
|
|
65
|
+
get currentToast() {
|
|
66
|
+
return _currentToast;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return manager;
|
|
71
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { onCleanup } from 'solid-js';
|
|
2
|
+
import { useRepl } from '../contexts/ReplContext.tsx';
|
|
3
|
+
import { useSession } from '../contexts/SessionContext.tsx';
|
|
4
|
+
|
|
5
|
+
const REFRESH_INTERVAL = 10000;
|
|
6
|
+
const INITIAL_DELAY = 1000;
|
|
7
|
+
const RESOURCE_COMMAND =
|
|
8
|
+
"cat /proc/loadavg; top -bn1 | head -3; free -b | head -2; df -B1 / | tail -1";
|
|
9
|
+
|
|
10
|
+
function formatBytes(bytes: number): string {
|
|
11
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
12
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}K`;
|
|
13
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
|
|
14
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseResourceOutput(output: string) {
|
|
18
|
+
try {
|
|
19
|
+
const lines = output.split('\n');
|
|
20
|
+
const result: any = {};
|
|
21
|
+
|
|
22
|
+
// Parse load average from /proc/loadavg (first line)
|
|
23
|
+
const loadLine = lines[0];
|
|
24
|
+
if (loadLine) {
|
|
25
|
+
const loadParts = loadLine.split(/\s+/);
|
|
26
|
+
result.load = loadParts[0] || '?';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Parse CPU from top output
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
const cpuMatch = line.match(/(\d+[\.,]\d+)\s*(?:id|idle)/i);
|
|
32
|
+
if (cpuMatch) {
|
|
33
|
+
const idle = parseFloat(cpuMatch[1].replace(',', '.'));
|
|
34
|
+
result.cpu = `${(100 - idle).toFixed(0)}%`;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Parse memory from free -b
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
const memMatch = line.match(/^Mem:\s+(\d+)\s+(\d+)/);
|
|
42
|
+
if (memMatch) {
|
|
43
|
+
const total = parseInt(memMatch[1], 10);
|
|
44
|
+
const used = parseInt(memMatch[2], 10);
|
|
45
|
+
result.memory = {
|
|
46
|
+
used: formatBytes(used),
|
|
47
|
+
total: formatBytes(total),
|
|
48
|
+
};
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Parse disk from df
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
const dfMatch = line.match(/\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s+\/$/);
|
|
56
|
+
if (dfMatch) {
|
|
57
|
+
const total = parseInt(dfMatch[1], 10);
|
|
58
|
+
const used = parseInt(dfMatch[2], 10);
|
|
59
|
+
result.disk = {
|
|
60
|
+
used: formatBytes(used),
|
|
61
|
+
total: formatBytes(total),
|
|
62
|
+
};
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return result;
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function useResources() {
|
|
74
|
+
const [, setState] = useRepl();
|
|
75
|
+
const session = useSession();
|
|
76
|
+
|
|
77
|
+
async function fetchResources() {
|
|
78
|
+
try {
|
|
79
|
+
// Use client.execute() instead of sessionManager.execute() to avoid
|
|
80
|
+
// resource monitoring output mixing into the user's command output.
|
|
81
|
+
// client.execute() runs commands via a separate exec API, not the shared session.
|
|
82
|
+
const result = await session.client.execute(RESOURCE_COMMAND);
|
|
83
|
+
if (result?.stdout) {
|
|
84
|
+
const parsed = parseResourceOutput(result.stdout);
|
|
85
|
+
if (parsed) {
|
|
86
|
+
setState('resources', parsed);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Keep last known values
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Initial fetch after delay
|
|
95
|
+
const initialTimer = setTimeout(fetchResources, INITIAL_DELAY);
|
|
96
|
+
const intervalTimer = setInterval(fetchResources, REFRESH_INTERVAL);
|
|
97
|
+
|
|
98
|
+
onCleanup(() => {
|
|
99
|
+
clearTimeout(initialTimer);
|
|
100
|
+
clearInterval(intervalTimer);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createSignal, onCleanup } from 'solid-js';
|
|
2
|
+
|
|
3
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
4
|
+
const SPINNER_INTERVAL = 120;
|
|
5
|
+
|
|
6
|
+
export function useSpinner(isActive: () => boolean) {
|
|
7
|
+
const [frame, setFrame] = createSignal(0);
|
|
8
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
9
|
+
|
|
10
|
+
function start() {
|
|
11
|
+
if (timer) return;
|
|
12
|
+
setFrame(0);
|
|
13
|
+
timer = setInterval(() => {
|
|
14
|
+
setFrame((prev) => (prev + 1) % SPINNER_FRAMES.length);
|
|
15
|
+
}, SPINNER_INTERVAL);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function stop() {
|
|
19
|
+
if (timer) {
|
|
20
|
+
clearInterval(timer);
|
|
21
|
+
timer = null;
|
|
22
|
+
}
|
|
23
|
+
setFrame(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Watch isActive reactively
|
|
27
|
+
// We use a simple polling approach via createEffect equivalent
|
|
28
|
+
const checkTimer = setInterval(() => {
|
|
29
|
+
if (isActive() && !timer) {
|
|
30
|
+
start();
|
|
31
|
+
} else if (!isActive() && timer) {
|
|
32
|
+
stop();
|
|
33
|
+
}
|
|
34
|
+
}, 50);
|
|
35
|
+
|
|
36
|
+
onCleanup(() => {
|
|
37
|
+
stop();
|
|
38
|
+
clearInterval(checkTimer);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return () => SPINNER_FRAMES[frame()];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function formatSpinnerText(frame: string, command: string): string {
|
|
45
|
+
return `${frame} ${command}`;
|
|
46
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTUI REPL entry point
|
|
3
|
+
*
|
|
4
|
+
* Provides the same interface as ink-repl/index.js but renders
|
|
5
|
+
* using @opentui/solid (SolidJS) instead of React/Ink.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Start the OpenTUI-based REPL
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} options
|
|
12
|
+
* @param {string} options.sandboxId - Sandbox ID
|
|
13
|
+
* @param {string} options.hostname - Hostname for prompt
|
|
14
|
+
* @param {string} options.hostIp - Host IP address
|
|
15
|
+
* @param {string} options.user - Current user
|
|
16
|
+
* @param {Object} options.client - Sandbox client
|
|
17
|
+
* @param {Object} options.sessionManager - Session manager
|
|
18
|
+
* @param {Object} options.historyManager - History manager
|
|
19
|
+
* @param {string} options.initialPrompt - Initial shell prompt
|
|
20
|
+
* @param {string} options.version - CLI version
|
|
21
|
+
* @param {Array} options.triggers - Trigger definitions
|
|
22
|
+
* @param {string} options.theme - Initial theme name
|
|
23
|
+
* @param {Function} options.onThemeChange - Theme change callback
|
|
24
|
+
* @param {Function} options.onExit - Callback when REPL exits
|
|
25
|
+
* @returns {Promise<void>}
|
|
26
|
+
*/
|
|
27
|
+
export async function startOpenTuiREPL(options) {
|
|
28
|
+
const {
|
|
29
|
+
sandboxId,
|
|
30
|
+
hostname,
|
|
31
|
+
hostIp,
|
|
32
|
+
user,
|
|
33
|
+
client,
|
|
34
|
+
sessionManager,
|
|
35
|
+
historyManager,
|
|
36
|
+
initialPrompt = '$ ',
|
|
37
|
+
version,
|
|
38
|
+
triggers = [],
|
|
39
|
+
onExit,
|
|
40
|
+
theme,
|
|
41
|
+
onThemeChange,
|
|
42
|
+
} = options;
|
|
43
|
+
|
|
44
|
+
const session = {
|
|
45
|
+
sandboxId,
|
|
46
|
+
hostname,
|
|
47
|
+
hostIp,
|
|
48
|
+
user,
|
|
49
|
+
version,
|
|
50
|
+
client,
|
|
51
|
+
sessionManager,
|
|
52
|
+
historyManager,
|
|
53
|
+
initialPrompt,
|
|
54
|
+
triggers,
|
|
55
|
+
onExit,
|
|
56
|
+
onThemeChange,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
let cleaned = false;
|
|
60
|
+
const cleanup = async () => {
|
|
61
|
+
if (cleaned) return;
|
|
62
|
+
cleaned = true;
|
|
63
|
+
if (onExit) {
|
|
64
|
+
await onExit();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// First, show connecting screen using the existing ANSI-based approach
|
|
70
|
+
// but integrate it better with the OpenTUI flow
|
|
71
|
+
|
|
72
|
+
// Connection is already handled by repl.js before this function is called
|
|
73
|
+
// So we can start the main REPL directly
|
|
74
|
+
const { renderApp } = await import('./render.tsx');
|
|
75
|
+
await renderApp({ session, theme });
|
|
76
|
+
|
|
77
|
+
// Call onExit AFTER exiting alternate screen, so banner shows on main screen
|
|
78
|
+
await cleanup();
|
|
79
|
+
} catch (error) {
|
|
80
|
+
await cleanup();
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create a slash command trigger (shared interface with ink-repl)
|
|
87
|
+
*/
|
|
88
|
+
export function createSlashTrigger(commands) {
|
|
89
|
+
return {
|
|
90
|
+
symbol: '/',
|
|
91
|
+
getItems: (prefix) => {
|
|
92
|
+
return commands.map((cmd) => ({
|
|
93
|
+
label: `/${cmd.name}`,
|
|
94
|
+
value: `/${cmd.name}`,
|
|
95
|
+
description: cmd.description || '',
|
|
96
|
+
}));
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard shortcut definitions for the OpenTUI REPL.
|
|
3
|
+
*
|
|
4
|
+
* These mirror the ink-repl/shortcuts/defaultKeybindings.js but are
|
|
5
|
+
* expressed as OpenTUI KeyEvent matchers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface KeyBinding {
|
|
9
|
+
name: string;
|
|
10
|
+
key: string;
|
|
11
|
+
ctrl?: boolean;
|
|
12
|
+
shift?: boolean;
|
|
13
|
+
meta?: boolean;
|
|
14
|
+
description: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const keybindings: KeyBinding[] = [
|
|
18
|
+
{ name: 'exit', key: 'c', ctrl: true, description: 'Exit (press twice)' },
|
|
19
|
+
{ name: 'cancel', key: 'escape', description: 'Cancel / close menu / back from detail' },
|
|
20
|
+
{ name: 'historyUp', key: 'up', description: 'Previous command in history' },
|
|
21
|
+
{ name: 'historyDown', key: 'down', description: 'Next command in history' },
|
|
22
|
+
{ name: 'detailView', key: 'v', ctrl: true, description: 'View full output of last command' },
|
|
23
|
+
{ name: 'copy', key: 'y', ctrl: true, description: 'Copy last output to clipboard' },
|
|
24
|
+
{ name: 'menuUp', key: 'up', description: 'Move up in dropdown menu' },
|
|
25
|
+
{ name: 'menuDown', key: 'down', description: 'Move down in dropdown menu' },
|
|
26
|
+
{ name: 'menuConfirm', key: 'return', description: 'Confirm menu selection' },
|
|
27
|
+
{ name: 'menuConfirmTab', key: 'tab', description: 'Confirm menu selection (Tab)' },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export function matchKeybinding(
|
|
31
|
+
event: { name: string; ctrl?: boolean; shift?: boolean; meta?: boolean },
|
|
32
|
+
binding: KeyBinding
|
|
33
|
+
): boolean {
|
|
34
|
+
if (event.name !== binding.key) return false;
|
|
35
|
+
if (binding.ctrl && !event.ctrl) return false;
|
|
36
|
+
if (binding.shift && !event.shift) return false;
|
|
37
|
+
if (binding.meta && !event.meta) return false;
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { render } from '@opentui/solid';
|
|
2
|
+
import { App } from './App.tsx';
|
|
3
|
+
import type { SessionInfo } from './contexts/SessionContext.tsx';
|
|
4
|
+
|
|
5
|
+
function copyToClipboard(text: string) {
|
|
6
|
+
try {
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
if (process.platform === 'darwin') {
|
|
9
|
+
execSync('pbcopy', { input: text });
|
|
10
|
+
} else {
|
|
11
|
+
// Try multiple Linux clipboard tools
|
|
12
|
+
const clipboardTools = [
|
|
13
|
+
'xclip -selection clipboard',
|
|
14
|
+
'xsel -b',
|
|
15
|
+
'wl-copy',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
let copied = false;
|
|
19
|
+
for (const tool of clipboardTools) {
|
|
20
|
+
try {
|
|
21
|
+
execSync(tool, { input: text });
|
|
22
|
+
copied = true;
|
|
23
|
+
break;
|
|
24
|
+
} catch {
|
|
25
|
+
// Try next tool
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!copied) {
|
|
30
|
+
// Fallback: save to temp file and show hint
|
|
31
|
+
const fs = require('fs');
|
|
32
|
+
const os = require('os');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const tmpFile = path.join(os.tmpdir(), 'rock-clipboard-' + Date.now() + '.txt');
|
|
35
|
+
fs.writeFileSync(tmpFile, text);
|
|
36
|
+
// Silently fail - user can still select and copy manually
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// Clipboard not available
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function renderApp(options: {
|
|
45
|
+
session: SessionInfo;
|
|
46
|
+
theme?: string;
|
|
47
|
+
onExit?: () => void;
|
|
48
|
+
}) {
|
|
49
|
+
// Use Promise pattern like opencode: resolve when exit is triggered
|
|
50
|
+
// Note: onExit callback should be called AFTER the renderer exits (outside alternate screen)
|
|
51
|
+
return new Promise<void>((resolve) => {
|
|
52
|
+
const handleExit = () => {
|
|
53
|
+
// Just resolve - onExit will be called after renderer exits
|
|
54
|
+
resolve();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
render(() => <App session={options.session} theme={options.theme} onExit={handleExit} />, {
|
|
58
|
+
exitOnCtrlC: false,
|
|
59
|
+
useAlternateScreen: true,
|
|
60
|
+
targetFps: 30,
|
|
61
|
+
// Enable mouse for OpenTUI internal text selection (selectable property)
|
|
62
|
+
// OpenTUI captures mouse escape sequences at framework level,
|
|
63
|
+
// so they won't appear as garbled text in input components.
|
|
64
|
+
consoleOptions: {
|
|
65
|
+
keyBindings: [{ name: 'y', ctrl: true, action: 'copy-selection' }],
|
|
66
|
+
onCopySelection: (text: string) => {
|
|
67
|
+
copyToClipboard(text);
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|