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.
Files changed (162) hide show
  1. package/commands/attach/basic-repl.js +212 -0
  2. package/commands/attach/cleanup-history.js +189 -0
  3. package/commands/attach/cleanup-manager.js +163 -0
  4. package/commands/attach/copy-ui/copyRepl.js +195 -0
  5. package/commands/attach/copy-ui/index.js +7 -0
  6. package/commands/attach/copy-ui/render/outputBlock.js +25 -0
  7. package/commands/attach/copy-ui/viewport/viewport.js +23 -0
  8. package/commands/attach/copy-ui/viewport/wheel.js +14 -0
  9. package/commands/attach/history-manager.js +507 -0
  10. package/commands/attach/history-session.js +48 -0
  11. package/commands/attach/ink-repl/InkREPL.js +1507 -0
  12. package/commands/attach/ink-repl/builtinCommands.js +1253 -0
  13. package/commands/attach/ink-repl/components/ConnectingScreen.js +76 -0
  14. package/commands/attach/ink-repl/components/Console.js +191 -0
  15. package/commands/attach/ink-repl/components/DetailView.js +148 -0
  16. package/commands/attach/ink-repl/components/DropdownMenu.js +86 -0
  17. package/commands/attach/ink-repl/components/InputArea.js +125 -0
  18. package/commands/attach/ink-repl/components/InputLine.js +18 -0
  19. package/commands/attach/ink-repl/components/OutputArea.js +22 -0
  20. package/commands/attach/ink-repl/components/OutputItem.js +96 -0
  21. package/commands/attach/ink-repl/components/ShellLayout.js +61 -0
  22. package/commands/attach/ink-repl/components/Spinner.js +79 -0
  23. package/commands/attach/ink-repl/components/StatusBar.js +106 -0
  24. package/commands/attach/ink-repl/components/WelcomeBanner.js +48 -0
  25. package/commands/attach/ink-repl/contexts/LayoutContext.js +12 -0
  26. package/commands/attach/ink-repl/contexts/ThemeContext.js +43 -0
  27. package/commands/attach/ink-repl/hooks/useFunctionKeys.js +70 -0
  28. package/commands/attach/ink-repl/hooks/useMouse.js +162 -0
  29. package/commands/attach/ink-repl/hooks/useResources.js +132 -0
  30. package/commands/attach/ink-repl/hooks/useSpinner.js +49 -0
  31. package/commands/attach/ink-repl/index.js +112 -0
  32. package/commands/attach/ink-repl/package.json +3 -0
  33. package/commands/attach/ink-repl/replState.js +947 -0
  34. package/commands/attach/ink-repl/shortcuts/defaultKeybindings.js +138 -0
  35. package/commands/attach/ink-repl/shortcuts/index.js +332 -0
  36. package/commands/attach/ink-repl/themes/defaultDark.js +18 -0
  37. package/commands/attach/ink-repl/themes/defaultLight.js +18 -0
  38. package/commands/attach/ink-repl/themes/index.js +4 -0
  39. package/commands/attach/ink-repl/themes/themeManager.js +45 -0
  40. package/commands/attach/ink-repl/themes/themeTokens.js +15 -0
  41. package/commands/attach/ink-repl/utils/atCompletion.js +346 -0
  42. package/commands/attach/ink-repl/utils/clipboard.js +50 -0
  43. package/commands/attach/ink-repl/utils/consoleLogger.js +81 -0
  44. package/commands/attach/ink-repl/utils/exitCodeHandler.js +49 -0
  45. package/commands/attach/ink-repl/utils/exitCodeTips.js +56 -0
  46. package/commands/attach/ink-repl/utils/formatTime.js +12 -0
  47. package/commands/attach/ink-repl/utils/outputSelection.js +120 -0
  48. package/commands/attach/ink-repl/utils/outputViewport.js +77 -0
  49. package/commands/attach/ink-repl/utils/paginatedFileLoading.js +76 -0
  50. package/commands/attach/ink-repl/utils/paramHint.js +60 -0
  51. package/commands/attach/ink-repl/utils/parseError.js +174 -0
  52. package/commands/attach/ink-repl/utils/pathCompletion.js +167 -0
  53. package/commands/attach/ink-repl/utils/remotePathSafety.js +56 -0
  54. package/commands/attach/ink-repl/utils/replSelection.js +205 -0
  55. package/commands/attach/ink-repl/utils/responseFormatter.js +127 -0
  56. package/commands/attach/ink-repl/utils/textWrap.js +117 -0
  57. package/commands/attach/ink-repl/utils/truncate.js +115 -0
  58. package/commands/attach/opentui-repl/App.tsx +891 -0
  59. package/commands/attach/opentui-repl/builtinCommands.ts +80 -0
  60. package/commands/attach/opentui-repl/components/ConfirmDialog.tsx +116 -0
  61. package/commands/attach/opentui-repl/components/ConnectingScreen.tsx +131 -0
  62. package/commands/attach/opentui-repl/components/Console.tsx +73 -0
  63. package/commands/attach/opentui-repl/components/DetailView.tsx +45 -0
  64. package/commands/attach/opentui-repl/components/DropdownMenu.tsx +130 -0
  65. package/commands/attach/opentui-repl/components/ExecutionStatus.tsx +66 -0
  66. package/commands/attach/opentui-repl/components/Header.tsx +24 -0
  67. package/commands/attach/opentui-repl/components/OutputArea.tsx +25 -0
  68. package/commands/attach/opentui-repl/components/OutputBlock.tsx +108 -0
  69. package/commands/attach/opentui-repl/components/PromptInput.tsx +109 -0
  70. package/commands/attach/opentui-repl/components/StatusBar.tsx +63 -0
  71. package/commands/attach/opentui-repl/components/Toast.tsx +65 -0
  72. package/commands/attach/opentui-repl/components/WelcomeBanner.tsx +41 -0
  73. package/commands/attach/opentui-repl/contexts/ReplContext.tsx +137 -0
  74. package/commands/attach/opentui-repl/contexts/SessionContext.tsx +32 -0
  75. package/commands/attach/opentui-repl/contexts/ThemeContext.tsx +70 -0
  76. package/commands/attach/opentui-repl/contexts/ToastContext.tsx +69 -0
  77. package/commands/attach/opentui-repl/contexts/toast-logic.js +71 -0
  78. package/commands/attach/opentui-repl/hooks/useResources.ts +102 -0
  79. package/commands/attach/opentui-repl/hooks/useSpinner.ts +46 -0
  80. package/commands/attach/opentui-repl/index.js +99 -0
  81. package/commands/attach/opentui-repl/keybindings.ts +39 -0
  82. package/commands/attach/opentui-repl/package.json +3 -0
  83. package/commands/attach/opentui-repl/render.tsx +72 -0
  84. package/commands/attach/opentui-repl/tsconfig.json +12 -0
  85. package/commands/attach/repl.js +791 -0
  86. package/commands/attach/sandbox-id-resolver.js +56 -0
  87. package/commands/attach/session-manager.js +307 -0
  88. package/commands/attach/ui-mode.js +146 -0
  89. package/commands/log/core/constants.js +237 -0
  90. package/commands/log/core/display.js +370 -0
  91. package/commands/log/core/search.js +330 -0
  92. package/commands/log/core/tail.js +216 -0
  93. package/commands/log/core/utils.js +424 -0
  94. package/commands/log.js +298 -0
  95. package/commands/sandbox/core/log-bridge.js +119 -0
  96. package/commands/sandbox/core/replay/analyzer.js +311 -0
  97. package/commands/sandbox/core/replay/batch-orchestrator.js +536 -0
  98. package/commands/sandbox/core/replay/batch-task.js +369 -0
  99. package/commands/sandbox/core/replay/concurrent-display.js +70 -0
  100. package/commands/sandbox/core/replay/concurrent-orchestrator.js +170 -0
  101. package/commands/sandbox/core/replay/data-source.js +86 -0
  102. package/commands/sandbox/core/replay/display.js +231 -0
  103. package/commands/sandbox/core/replay/executor.js +634 -0
  104. package/commands/sandbox/core/replay/history-fetcher.js +124 -0
  105. package/commands/sandbox/core/replay/index.js +338 -0
  106. package/commands/sandbox/core/replay/loghouse-data-source.js +177 -0
  107. package/commands/sandbox/core/replay/pid-mapping.js +26 -0
  108. package/commands/sandbox/core/replay/request.js +109 -0
  109. package/commands/sandbox/core/replay/worker.js +166 -0
  110. package/commands/sandbox/core/session.js +346 -0
  111. package/commands/sandbox/log-bridge.js +2 -0
  112. package/commands/sandbox/ray.js +2 -0
  113. package/commands/sandbox/replay/analyzer.js +311 -0
  114. package/commands/sandbox/replay/batch-orchestrator.js +536 -0
  115. package/commands/sandbox/replay/batch-task.js +369 -0
  116. package/commands/sandbox/replay/concurrent-display.js +70 -0
  117. package/commands/sandbox/replay/concurrent-orchestrator.js +170 -0
  118. package/commands/sandbox/replay/display.js +231 -0
  119. package/commands/sandbox/replay/executor.js +634 -0
  120. package/commands/sandbox/replay/history-fetcher.js +118 -0
  121. package/commands/sandbox/replay/index.js +338 -0
  122. package/commands/sandbox/replay/pid-mapping.js +26 -0
  123. package/commands/sandbox/replay/request.js +109 -0
  124. package/commands/sandbox/replay/worker.js +166 -0
  125. package/commands/sandbox/replay.js +2 -0
  126. package/commands/sandbox/session.js +2 -0
  127. package/commands/sandbox-original.js +1393 -0
  128. package/commands/sandbox.js +499 -0
  129. package/help/help.json +1071 -0
  130. package/help/middleware.js +71 -0
  131. package/help/renderer.js +800 -0
  132. package/index.js +5 -15
  133. package/lib/plugin-context.js +40 -0
  134. package/package.json +2 -2
  135. package/sdks/sandbox/core/client.js +845 -0
  136. package/sdks/sandbox/core/config.js +70 -0
  137. package/sdks/sandbox/core/types.js +74 -0
  138. package/sdks/sandbox/httpLogger.js +251 -0
  139. package/sdks/sandbox/index.js +9 -0
  140. package/utils/asciiArt.js +138 -0
  141. package/utils/bun-compat.js +59 -0
  142. package/utils/ciPipelines.js +138 -0
  143. package/utils/cli.js +17 -0
  144. package/utils/command-router.js +79 -0
  145. package/utils/configManager.js +503 -0
  146. package/utils/dependency-resolver.js +135 -0
  147. package/utils/eagleeye_traceid.js +151 -0
  148. package/utils/envDetector.js +78 -0
  149. package/utils/execution_logger.js +415 -0
  150. package/utils/featureManager.js +68 -0
  151. package/utils/firstTimeTip.js +44 -0
  152. package/utils/hook-manager.js +125 -0
  153. package/utils/http-logger.js +264 -0
  154. package/utils/i18n.js +139 -0
  155. package/utils/image-progress.js +159 -0
  156. package/utils/logger.js +154 -0
  157. package/utils/plugin-loader.js +124 -0
  158. package/utils/plugin-manager.js +348 -0
  159. package/utils/ray_cli_wrapper.js +746 -0
  160. package/utils/sandbox-client.js +419 -0
  161. package/utils/terminal.js +32 -0
  162. 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,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -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
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "jsx": "preserve",
4
+ "jsxImportSource": "@opentui/solid",
5
+ "target": "ESNext",
6
+ "module": "ESNext",
7
+ "moduleResolution": "bundler",
8
+ "strict": false,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true
11
+ }
12
+ }