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,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';
|