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.
Files changed (90) 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/attach.js +186 -0
  90. package/package.json +1 -1
@@ -0,0 +1,132 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+
3
+ export const REFRESH_INTERVAL = 10000; // 10 seconds (to avoid too many shell commands)
4
+
5
+ /**
6
+ * Format bytes to human readable
7
+ */
8
+ export function formatBytes(bytes) {
9
+ if (bytes === 0) return '0B';
10
+ const k = 1024;
11
+ const sizes = ['B', 'K', 'M', 'G', 'T'];
12
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
13
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + sizes[i];
14
+ }
15
+
16
+ /**
17
+ * Parse resource info from shell command output
18
+ * Executes: cat /proc/loadavg; top -bn1 | head -3; free -b | head -2; df -B1 / | tail -1
19
+ */
20
+ export function parseResourceOutput(output) {
21
+ if (!output) return null;
22
+
23
+ const resources = {};
24
+ const lines = output.trim().split('\n');
25
+
26
+ for (const line of lines) {
27
+ // Parse CPU load from /proc/loadavg
28
+ // Format: "0.00 0.01 0.05 1/123 4567" (1min 5min 15min running/total lastpid)
29
+ if (/^\d+\.\d+\s+\d+\.\d+\s+\d+\.\d+/.test(line)) {
30
+ const parts = line.split(/\s+/);
31
+ if (parts.length >= 1) {
32
+ const load1min = parseFloat(parts[0]);
33
+ if (!isNaN(load1min)) {
34
+ resources.load = load1min.toFixed(2);
35
+ }
36
+ }
37
+ }
38
+
39
+ // Parse CPU usage from top output
40
+ // Format: "%Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, ..."
41
+ if (line.includes('Cpu') && line.includes('id')) {
42
+ const idleMatch = line.match(/(\d+\.?\d*)\s*id/);
43
+ if (idleMatch) {
44
+ const idle = parseFloat(idleMatch[1]);
45
+ const cpuUsage = (100 - idle).toFixed(0);
46
+ resources.cpu = `${cpuUsage}%`;
47
+ }
48
+ }
49
+
50
+ // Parse memory from 'free -b' output
51
+ // Format: "Mem: total used free shared buff/cache available"
52
+ if (line.startsWith('Mem:')) {
53
+ const parts = line.split(/\s+/);
54
+ if (parts.length >= 3) {
55
+ const total = parseInt(parts[1], 10);
56
+ const used = parseInt(parts[2], 10);
57
+ if (!isNaN(total) && !isNaN(used)) {
58
+ resources.memory = {
59
+ used: formatBytes(used),
60
+ total: formatBytes(total),
61
+ };
62
+ }
63
+ }
64
+ }
65
+
66
+ // Parse disk from 'df' output
67
+ // Format: "Filesystem 1B-blocks Used Available Use% Mounted"
68
+ if (line.includes('/') && !line.startsWith('Filesystem') && !line.includes('loadavg')) {
69
+ const parts = line.split(/\s+/);
70
+ if (parts.length >= 4) {
71
+ const total = parseInt(parts[1], 10);
72
+ const used = parseInt(parts[2], 10);
73
+ if (!isNaN(total) && !isNaN(used)) {
74
+ resources.disk = {
75
+ used: formatBytes(used),
76
+ total: formatBytes(total),
77
+ };
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ return Object.keys(resources).length > 0 ? resources : null;
84
+ }
85
+
86
+ /**
87
+ * Hook for monitoring sandbox resources via shell commands
88
+ * Uses client.exec() instead of sessionManager.execute() to avoid interfering with user session
89
+ * @param {Object} client - Sandbox client for executing commands
90
+ * @param {boolean} enabled - Whether to enable monitoring
91
+ * @returns {{ resources: Object|null, error: Error|null, refresh: Function }}
92
+ */
93
+ export function useResources(client, enabled = true) {
94
+ const [resources, setResources] = useState(null);
95
+ const [error, setError] = useState(null);
96
+
97
+ const refresh = useCallback(async () => {
98
+ if (!client || !enabled) return;
99
+
100
+ try {
101
+ // Use client.execute() to run in an isolated context (not in user's session)
102
+ // This avoids output interference with user commands
103
+ const result = await client.execute('cat /proc/loadavg 2>/dev/null; top -bn1 2>/dev/null | head -3; free -b 2>/dev/null | head -2; df -B1 / 2>/dev/null | tail -1');
104
+ // client.execute() returns { stdout, stderr, exit_code }
105
+ if (result && result.stdout) {
106
+ const parsed = parseResourceOutput(result.stdout);
107
+ setResources(parsed);
108
+ }
109
+ setError(null);
110
+ } catch (err) {
111
+ setError(err);
112
+ // Don't clear resources on error, keep last known values
113
+ }
114
+ }, [client, enabled]);
115
+
116
+ useEffect(() => {
117
+ if (!enabled || !client) return;
118
+
119
+ // Initial fetch after a short delay (let REPL initialize first)
120
+ const initialTimer = setTimeout(refresh, 1000);
121
+
122
+ // Set up interval
123
+ const timer = setInterval(refresh, REFRESH_INTERVAL);
124
+
125
+ return () => {
126
+ clearTimeout(initialTimer);
127
+ clearInterval(timer);
128
+ };
129
+ }, [client, enabled, refresh]);
130
+
131
+ return { resources, error, refresh };
132
+ }
@@ -0,0 +1,49 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ export const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
4
+ export const SPINNER_INTERVAL = 120; // ms (slower to reduce flicker)
5
+
6
+ /**
7
+ * Format spinner text with command
8
+ * @param {string} frame - Current spinner frame
9
+ * @param {string} command - Command being executed
10
+ * @param {number} maxLength - Maximum total length
11
+ * @returns {string} Formatted spinner text
12
+ */
13
+ export function formatSpinnerText(frame, command, maxLength = 60) {
14
+ const prefix = `${frame} `;
15
+ const availableLength = maxLength - prefix.length;
16
+
17
+ if (command.length <= availableLength) {
18
+ return `${prefix}${command}`;
19
+ }
20
+
21
+ return `${prefix}${command.slice(0, availableLength - 3)}...`;
22
+ }
23
+
24
+ /**
25
+ * Hook for animated spinner
26
+ * @param {boolean} isActive - Whether spinner should animate
27
+ * @returns {{ frame: string, frameIndex: number }}
28
+ */
29
+ export function useSpinner(isActive = false) {
30
+ const [frameIndex, setFrameIndex] = useState(0);
31
+
32
+ useEffect(() => {
33
+ if (!isActive) {
34
+ setFrameIndex(0);
35
+ return;
36
+ }
37
+
38
+ const timer = setInterval(() => {
39
+ setFrameIndex(prev => (prev + 1) % SPINNER_FRAMES.length);
40
+ }, SPINNER_INTERVAL);
41
+
42
+ return () => clearInterval(timer);
43
+ }, [isActive]);
44
+
45
+ return {
46
+ frame: SPINNER_FRAMES[frameIndex],
47
+ frameIndex,
48
+ };
49
+ }
@@ -0,0 +1,112 @@
1
+ import React from 'react';
2
+ import { render } from 'ink';
3
+ import { InkREPL } from './InkREPL.js';
4
+ import { ThemeProvider } from './contexts/ThemeContext.js';
5
+
6
+ const h = React.createElement;
7
+
8
+ /**
9
+ * Start the Ink-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 {Array} options.triggers - Trigger definitions
21
+ * @param {Function} options.onExit - Callback when REPL exits
22
+ * @returns {Promise<void>}
23
+ */
24
+ export async function startInkREPL(options) {
25
+ const {
26
+ sandboxId,
27
+ hostname,
28
+ hostIp,
29
+ user,
30
+ client,
31
+ sessionManager,
32
+ historyManager,
33
+ initialPrompt = '$ ',
34
+ version,
35
+ triggers = [],
36
+ onExit,
37
+ theme,
38
+ onThemeChange,
39
+ } = options;
40
+
41
+ let instance = null;
42
+ let cleaned = false;
43
+
44
+ const cleanup = () => {
45
+ if (cleaned) return;
46
+ cleaned = true;
47
+ if (onExit) onExit();
48
+ };
49
+
50
+ try {
51
+ instance = render(
52
+ h(ThemeProvider, { initialTheme: theme, onThemeChange },
53
+ h(InkREPL, {
54
+ sandboxId,
55
+ hostname,
56
+ hostIp,
57
+ user,
58
+ client,
59
+ sessionManager,
60
+ historyManager,
61
+ initialPrompt,
62
+ version,
63
+ triggers,
64
+ onExit: cleanup,
65
+ })
66
+ ),
67
+ {
68
+ // Disable patchConsole to prevent console output from interfering
69
+ patchConsole: false,
70
+ // We handle Ctrl+C ourselves in useInput
71
+ exitOnCtrlC: false,
72
+ // Use stdout directly for better performance
73
+ stdout: process.stdout,
74
+ stdin: process.stdin,
75
+ // Use Ink's built-in alternate buffer for flicker-free full-screen UI
76
+ alternateBuffer: true,
77
+ // Enable incremental rendering - only updates changed lines
78
+ incrementalRendering: true,
79
+ }
80
+ );
81
+
82
+ // Handle SIGTERM
83
+ process.on('SIGTERM', cleanup);
84
+
85
+ // Wait for the Ink instance to finish (user exits)
86
+ await instance.waitUntilExit();
87
+
88
+ // Ensure cleanup is called
89
+ cleanup();
90
+ } catch (error) {
91
+ throw error;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Create a slash command trigger
97
+ */
98
+ export function createSlashTrigger(commands) {
99
+ return {
100
+ symbol: '/',
101
+ getItems: (prefix) => {
102
+ return commands.map(cmd => ({
103
+ label: `/${cmd.name}`,
104
+ value: `/${cmd.name}`,
105
+ description: cmd.description || '',
106
+ }));
107
+ },
108
+ };
109
+ }
110
+
111
+ export { InkREPL };
112
+ export { consoleLogger } from './utils/consoleLogger.js';
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }