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,108 @@
1
+ import { Show } from 'solid-js';
2
+ import { useTheme } from '../contexts/ThemeContext.tsx';
3
+ import type { OutputItem } from '../contexts/ReplContext.tsx';
4
+
5
+ const MAX_OUTPUT_LINES = 50;
6
+
7
+ function formatTime(ts: number): string {
8
+ const d = new Date(ts);
9
+ const hh = String(d.getHours()).padStart(2, '0');
10
+ const mm = String(d.getMinutes()).padStart(2, '0');
11
+ const ss = String(d.getSeconds()).padStart(2, '0');
12
+ return `${hh}:${mm}:${ss}`;
13
+ }
14
+
15
+ function truncateOutput(output: string, maxLines: number): { lines: string[]; truncated: number } {
16
+ if (!output) return { lines: [], truncated: 0 };
17
+ const allLines = output.split('\n');
18
+ if (allLines.length <= maxLines) {
19
+ return { lines: allLines, truncated: 0 };
20
+ }
21
+ return {
22
+ lines: allLines.slice(0, maxLines),
23
+ truncated: allLines.length - maxLines,
24
+ };
25
+ }
26
+
27
+ export function OutputBlock(props: { item: OutputItem }) {
28
+ const theme = useTheme();
29
+
30
+ // 判断是否显示错误样式:exitCode 不为 0 且不为 null,或者 exitCode 为 null 但有 tips(超时/网络错误)
31
+ const showErrorStyle =
32
+ (props.item.exitCode !== 0 && props.item.exitCode !== null) ||
33
+ (props.item.exitCode === null && !!props.item.tips);
34
+
35
+ // 判断是否显示 tips:有 tips 且(exitCode 不为 0 或 exitCode 为 null)
36
+ const showTips = props.item.tips && (props.item.exitCode !== 0 || props.item.exitCode === null);
37
+
38
+ return (
39
+ <Show when={!props.item.isWelcome}>
40
+ <box
41
+ flexDirection="column"
42
+ marginLeft={2}
43
+ marginRight={2}
44
+ borderStyle="single"
45
+ border
46
+ borderColor={showErrorStyle ? theme.colors.danger : theme.colors.border}
47
+ paddingLeft={1}
48
+ paddingRight={1}
49
+ paddingTop={0}
50
+ paddingBottom={0}
51
+ >
52
+ {/* Command line */}
53
+ <text>
54
+ <span style={{ fg: theme.colors.textSecondary }}>[{formatTime(props.item.timestamp)}] </span>
55
+ <span style={{ fg: theme.colors.prompt }}>{props.item.prompt || '$ '}</span>
56
+ <span style={{ fg: theme.colors.textPrimary }}>{props.item.command}</span>
57
+ </text>
58
+
59
+ {/* Output - only this part is selectable */}
60
+ <Show when={props.item.output !== null && props.item.output !== undefined}>
61
+ {(() => {
62
+ const { lines, truncated } = truncateOutput(props.item.output, MAX_OUTPUT_LINES);
63
+ return (
64
+ <box flexDirection="column">
65
+ {/* Output content */}
66
+ <text>
67
+ <span
68
+ style={{
69
+ fg: showErrorStyle ? theme.colors.danger : theme.colors.textPrimary,
70
+ }}
71
+ >
72
+ {lines.join('\n')}
73
+ </span>
74
+ </text>
75
+ {/* Truncation hint - not selectable */}
76
+ <Show when={truncated > 0}>
77
+ <text>
78
+ <span style={{ fg: theme.colors.warning }}>
79
+ ... {truncated} more lines (use Ctrl+V to view full output)
80
+ </span>
81
+ </text>
82
+ </Show>
83
+
84
+ {/* Meta info (exit code) - displayed in gray when command fails */}
85
+ <Show when={props.item.metaInfo && showErrorStyle}>
86
+ <text>
87
+ <span style={{ fg: theme.colors.textSecondary, dim: true }}>
88
+ {props.item.metaInfo}
89
+ </span>
90
+ </text>
91
+ </Show>
92
+
93
+ {/* Tips - helpful hints when command fails */}
94
+ <Show when={showTips}>
95
+ <text>
96
+ <span style={{ fg: theme.colors.warning }}>
97
+ 💡 {props.item.tips}
98
+ </span>
99
+ </text>
100
+ </Show>
101
+ </box>
102
+ );
103
+ })()}
104
+ </Show>
105
+ </box>
106
+ </Show>
107
+ );
108
+ }
@@ -0,0 +1,109 @@
1
+ import { Show, createMemo } from 'solid-js';
2
+ import { useTheme } from '../contexts/ThemeContext.tsx';
3
+ import { useRepl } from '../contexts/ReplContext.tsx';
4
+ import type { InputRenderable } from '@opentui/core';
5
+
6
+ // Import paramHint from ink-repl (shared logic)
7
+ import { getPlaceholderText } from '../../ink-repl/utils/paramHint.js';
8
+
9
+ /**
10
+ * Input area with separator lines (like Ink InputArea / Claude Code style)
11
+ *
12
+ * Layout:
13
+ * ────────────────────────────────
14
+ * prompt> user input here█ @<param>
15
+ * ────────────────────────────────
16
+ *
17
+ * Shows parameter hints for /upload and /download commands.
18
+ * Input is always visible even during execution (spinner is separate).
19
+ */
20
+ export function PromptInput(props: {
21
+ onSubmit?: (value: string) => void;
22
+ onInputChange?: (value: string) => void;
23
+ onInputRef?: (ref: InputRenderable) => void;
24
+ }) {
25
+ const theme = useTheme();
26
+ const [state, setState] = useRepl();
27
+ let inputRef: InputRenderable | undefined;
28
+
29
+ function handleSubmit(value: string) {
30
+ const trimmed = value.trim();
31
+ if (!trimmed) return;
32
+
33
+ // Clear input
34
+ setState('buffer', '');
35
+ if (inputRef) {
36
+ inputRef.value = '';
37
+ }
38
+
39
+ if (props.onSubmit) {
40
+ props.onSubmit(trimmed);
41
+ }
42
+ }
43
+
44
+ function handleInput(value: string) {
45
+ setState('buffer', value);
46
+ if (props.onInputChange) {
47
+ props.onInputChange(value);
48
+ }
49
+ }
50
+
51
+ // Calculate parameter hint text for /upload and /download commands
52
+ // Use createMemo for reactivity - recalculates when state.buffer changes
53
+ const hintText = createMemo(() => getPlaceholderText(state.buffer, state.buffer.length));
54
+
55
+ return (
56
+ <box
57
+ flexShrink={0}
58
+ flexDirection="column"
59
+ marginLeft={2}
60
+ marginRight={2}
61
+ borderStyle="single"
62
+ border
63
+ borderColor="#4a90d9"
64
+ paddingLeft={1}
65
+ paddingRight={1}
66
+ paddingTop={1}
67
+ paddingBottom={1}
68
+ >
69
+ {/* Input line - always visible */}
70
+ <box flexDirection="row" width="100%" overflow="hidden" position="relative">
71
+ {/* Prompt label: never shrink so long input won't cover it */}
72
+ <box flexShrink={0}>
73
+ <text selectable={false}>
74
+ <span style={{ fg: theme.colors.accent, bold: true }}>{state.shellPrompt}</span>
75
+ </text>
76
+ </box>
77
+ {/* Input container */}
78
+ <box flexGrow={1} overflow="hidden" minWidth={0}>
79
+ <input
80
+ ref={(el) => {
81
+ inputRef = el;
82
+ if (props.onInputRef && el) {
83
+ props.onInputRef(el);
84
+ }
85
+ }}
86
+ width="100%"
87
+ focused={state.viewMode === 'repl' && !state.consoleVisible}
88
+ value={state.buffer}
89
+ placeholder="Enter command..."
90
+ textColor={theme.colors.textPrimary}
91
+ focusedTextColor={theme.colors.textPrimary}
92
+ backgroundColor={theme.colors.background}
93
+ focusedBackgroundColor={theme.colors.background}
94
+ onSubmit={handleSubmit}
95
+ onInput={handleInput}
96
+ />
97
+ </box>
98
+ {/* Parameter hint - overlay positioned after input content */}
99
+ <Show when={hintText()}>
100
+ <box position="absolute" left={state.shellPrompt.length + state.buffer.length + 1}>
101
+ <text selectable={false}>
102
+ <span style={{ fg: theme.colors.textHint }}> {hintText()}</span>
103
+ </text>
104
+ </box>
105
+ </Show>
106
+ </box>
107
+ </box>
108
+ );
109
+ }
@@ -0,0 +1,63 @@
1
+ import { Show } from 'solid-js';
2
+ import { useTheme } from '../contexts/ThemeContext.tsx';
3
+ import { useSession } from '../contexts/SessionContext.tsx';
4
+ import { useRepl } from '../contexts/ReplContext.tsx';
5
+
6
+ function formatResources(resources: any): string {
7
+ if (!resources) return '';
8
+ const parts: string[] = [];
9
+ if (resources.cpu) parts.push(`CPU ${resources.cpu}`);
10
+ if (resources.load) parts.push(`LOAD ${resources.load}`);
11
+ if (resources.memory) parts.push(`MEM ${resources.memory.used}/${resources.memory.total}`);
12
+ if (resources.disk) parts.push(`DISK ${resources.disk.used}/${resources.disk.total}`);
13
+ return parts.join(' ');
14
+ }
15
+
16
+ export function StatusBar() {
17
+ const theme = useTheme();
18
+ const session = useSession();
19
+ const [state] = useRepl();
20
+
21
+ function getKeyHints(): string {
22
+ if (state.isExecuting) return 'ctrl+c cancel';
23
+ if (state.menuVisible) return '↑↓ select enter confirm esc cancel';
24
+ return '/ commands ↑↓ history ctrl+c×2 exit';
25
+ }
26
+
27
+ return (
28
+ <box flexShrink={0} flexDirection="column">
29
+ <Show when={state.exitPending}>
30
+ <box paddingLeft={2}>
31
+ <text selectable={false}>
32
+ <span style={{ fg: theme.colors.warning, bold: true }}>Press Ctrl+C again to exit</span>
33
+ </text>
34
+ </box>
35
+ </Show>
36
+ <box flexDirection="row" paddingLeft={2} paddingRight={2}>
37
+ {/* Left: sandbox info */}
38
+ <box flexGrow={1} flexBasis={0}>
39
+ <text selectable={false}>
40
+ <span style={{ fg: theme.colors.accent }}>● {session.sandboxId}</span>
41
+ <Show when={session.hostIp}>
42
+ <span style={{ fg: theme.colors.textSecondary }}> ({session.hostIp})</span>
43
+ </Show>
44
+ </text>
45
+ </box>
46
+
47
+ {/* Center: resources */}
48
+ <box flexGrow={1} flexBasis={0} justifyContent="center">
49
+ <text selectable={false}>
50
+ <span style={{ fg: theme.colors.warning }}>{formatResources(state.resources)}</span>
51
+ </text>
52
+ </box>
53
+
54
+ {/* Right: key hints */}
55
+ <box flexGrow={1} flexBasis={0} alignItems="flex-end">
56
+ <text selectable={false}>
57
+ <span style={{ fg: theme.colors.textSecondary }}>{getKeyHints()}</span>
58
+ </text>
59
+ </box>
60
+ </box>
61
+ </box>
62
+ );
63
+ }
@@ -0,0 +1,65 @@
1
+ import { Show } from 'solid-js';
2
+ import { useTerminalDimensions } from '@opentui/solid';
3
+ import { useTheme } from '../contexts/ThemeContext.tsx';
4
+ import { useToast } from '../contexts/ToastContext.tsx';
5
+
6
+ /**
7
+ * Variant-to-theme-color mapping.
8
+ */
9
+ const VARIANT_COLOR_MAP = {
10
+ info: 'accent',
11
+ success: 'success',
12
+ warning: 'warning',
13
+ error: 'danger',
14
+ } as const;
15
+
16
+ /**
17
+ * Floating toast notification component.
18
+ *
19
+ * Renders in the top-right corner of the terminal as an
20
+ * absolute-positioned overlay. Auto-dismisses based on the
21
+ * duration set by ToastContext.
22
+ */
23
+ export function Toast() {
24
+ const toast = useToast();
25
+ const theme = useTheme();
26
+ const dimensions = useTerminalDimensions();
27
+
28
+ return (
29
+ <Show when={toast.currentToast}>
30
+ {(current) => {
31
+ const borderColor = () => {
32
+ const colorKey = VARIANT_COLOR_MAP[current().variant];
33
+ return theme.colors[colorKey];
34
+ };
35
+
36
+ return (
37
+ <box
38
+ position="absolute"
39
+ justifyContent="center"
40
+ alignItems="flex-start"
41
+ top={2}
42
+ right={2}
43
+ maxWidth={Math.min(50, dimensions().width - 6)}
44
+ paddingLeft={2}
45
+ paddingRight={2}
46
+ paddingTop={1}
47
+ paddingBottom={1}
48
+ backgroundColor={theme.colors.surface}
49
+ borderColor={borderColor()}
50
+ border={['left', 'right']}
51
+ >
52
+ <Show when={current().title}>
53
+ <text marginBottom={1} fg={theme.colors.textPrimary}>
54
+ <span style={{ bold: true }}>{current().title}</span>
55
+ </text>
56
+ </Show>
57
+ <text fg={theme.colors.textPrimary} wrapMode="word" width="100%">
58
+ {current().message}
59
+ </text>
60
+ </box>
61
+ );
62
+ }}
63
+ </Show>
64
+ );
65
+ }
@@ -0,0 +1,41 @@
1
+ import { For } from 'solid-js';
2
+ import { useTheme } from '../contexts/ThemeContext.tsx';
3
+ import i18n from '../../../../utils/i18n.js';
4
+ import { getLogoLines, getGradientColors } from '../../../../utils/asciiArt.js';
5
+ const { t } = i18n;
6
+
7
+ export function WelcomeBanner() {
8
+ const theme = useTheme();
9
+ const gradient = getGradientColors();
10
+ const ASCII_LINES = getLogoLines();
11
+
12
+ const TIPS = [
13
+ t('welcome.tip.commands'),
14
+ t('welcome.tip.execute'),
15
+ t('welcome.tip.console'),
16
+ t('welcome.tip.exit'),
17
+ ];
18
+
19
+ return (
20
+ <box flexDirection="column" alignItems="center" paddingTop={1} paddingBottom={1} width="100%">
21
+ <box flexDirection="column" alignItems="center">
22
+ <For each={ASCII_LINES}>
23
+ {(line, i) => (
24
+ <text selectable={false}>
25
+ <span style={{ fg: gradient[i() % gradient.length], bold: true }}>{line}</span>
26
+ </text>
27
+ )}
28
+ </For>
29
+ </box>
30
+ <box marginTop={1} flexDirection="column">
31
+ <For each={TIPS}>
32
+ {(tip) => (
33
+ <text selectable={false}>
34
+ <span style={{ fg: theme.colors.textSecondary }}>{tip}</span>
35
+ </text>
36
+ )}
37
+ </For>
38
+ </box>
39
+ </box>
40
+ );
41
+ }
@@ -0,0 +1,137 @@
1
+ import { createContext, useContext } from 'solid-js';
2
+ import { createStore, type SetStoreFunction } from 'solid-js/store';
3
+
4
+ export interface OutputItem {
5
+ id: string;
6
+ command: string;
7
+ output: string;
8
+ exitCode: number | null;
9
+ timestamp: number;
10
+ prompt: string;
11
+ isWelcome?: boolean;
12
+ metaInfo?: string | null;
13
+ tips?: string | null;
14
+ }
15
+
16
+ export interface ConsoleLog {
17
+ id: string;
18
+ timestamp: string;
19
+ level: 'info' | 'warn' | 'error' | 'debug';
20
+ message: string;
21
+ }
22
+
23
+ export interface ReplState {
24
+ outputs: OutputItem[];
25
+ buffer: string;
26
+ isExecuting: boolean;
27
+ executingCommand: string;
28
+ lastExecutionDuration: number | null;
29
+ executionStartTime: number | null;
30
+ historyIndex: number;
31
+ commandHistory: string[];
32
+ savedBuffer: string;
33
+ shellPrompt: string;
34
+ exitPending: boolean;
35
+ resources: {
36
+ cpu: string;
37
+ load: string;
38
+ memory: { used: string; total: string };
39
+ disk: { used: string; total: string };
40
+ } | null;
41
+ menuVisible: boolean;
42
+ menuItems: { label: string; value: string; description: string }[];
43
+ selectedIndex: number;
44
+ menuType: 'slash' | 'path' | 'at' | null;
45
+ atContext: any;
46
+ viewMode: 'repl' | 'detail';
47
+ detailContent: string | null;
48
+ consoleVisible: boolean;
49
+ consoleLogs: ConsoleLog[];
50
+ }
51
+
52
+ let _nextId = 0;
53
+ function nextId(): string {
54
+ return `out-${++_nextId}`;
55
+ }
56
+
57
+ export function createInitialState(shellPrompt: string): ReplState {
58
+ return {
59
+ outputs: [
60
+ {
61
+ id: nextId(),
62
+ command: '',
63
+ output: '',
64
+ exitCode: 0,
65
+ timestamp: Date.now(),
66
+ prompt: shellPrompt,
67
+ isWelcome: true,
68
+ },
69
+ ],
70
+ buffer: '',
71
+ isExecuting: false,
72
+ executingCommand: '',
73
+ lastExecutionDuration: null,
74
+ executionStartTime: null,
75
+ historyIndex: -1,
76
+ commandHistory: [],
77
+ savedBuffer: '',
78
+ shellPrompt,
79
+ exitPending: false,
80
+ resources: null,
81
+ menuVisible: false,
82
+ menuItems: [],
83
+ selectedIndex: 0,
84
+ menuType: null,
85
+ atContext: null,
86
+ viewMode: 'repl',
87
+ detailContent: null,
88
+ consoleVisible: false,
89
+ consoleLogs: [],
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Add a log entry to the console
95
+ */
96
+ function createAddLog(setState: SetStoreFunction<ReplState>) {
97
+ return (level: ConsoleLog['level'], message: string) => {
98
+ const timestamp = new Date().toTimeString().slice(0, 8);
99
+ const newLog: ConsoleLog = {
100
+ id: `log-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
101
+ timestamp,
102
+ level,
103
+ message,
104
+ };
105
+ setState('consoleLogs', (logs) => [...logs, newLog]);
106
+ };
107
+ }
108
+
109
+ export interface ReplActions {
110
+ nextId: () => string;
111
+ addLog: (level: ConsoleLog['level'], message: string) => void;
112
+ }
113
+
114
+ type ReplContextValue = [ReplState, SetStoreFunction<ReplState>, ReplActions];
115
+
116
+ const ReplContext = createContext<ReplContextValue>();
117
+
118
+ export function ReplProvider(props: { shellPrompt: string; children: any }) {
119
+ const [state, setState] = createStore<ReplState>(createInitialState(props.shellPrompt));
120
+
121
+ const actions: ReplActions = {
122
+ nextId,
123
+ addLog: createAddLog(setState),
124
+ };
125
+
126
+ return (
127
+ <ReplContext.Provider value={[state, setState, actions]}>
128
+ {props.children}
129
+ </ReplContext.Provider>
130
+ );
131
+ }
132
+
133
+ export function useRepl(): ReplContextValue {
134
+ const ctx = useContext(ReplContext);
135
+ if (!ctx) throw new Error('useRepl must be used within ReplProvider');
136
+ return ctx;
137
+ }
@@ -0,0 +1,32 @@
1
+ import { createContext, useContext } from 'solid-js';
2
+
3
+ export interface SessionInfo {
4
+ sandboxId: string;
5
+ hostname: string;
6
+ hostIp: string | null;
7
+ user: string;
8
+ version: string;
9
+ client: any;
10
+ sessionManager: any;
11
+ historyManager: any;
12
+ initialPrompt: string;
13
+ triggers: any[];
14
+ onExit?: () => void;
15
+ onThemeChange?: (name: string) => void;
16
+ }
17
+
18
+ const SessionContext = createContext<SessionInfo>();
19
+
20
+ export function SessionProvider(props: { session: SessionInfo; children: any }) {
21
+ return (
22
+ <SessionContext.Provider value={props.session}>
23
+ {props.children}
24
+ </SessionContext.Provider>
25
+ );
26
+ }
27
+
28
+ export function useSession(): SessionInfo {
29
+ const ctx = useContext(SessionContext);
30
+ if (!ctx) throw new Error('useSession must be used within SessionProvider');
31
+ return ctx;
32
+ }
@@ -0,0 +1,70 @@
1
+ import { createContext, useContext } from 'solid-js';
2
+
3
+ const defaultGradient = ['#2563EB', '#3B82F6', '#4F46E5', '#6366F1', '#7C3AED', '#8B5CF6'];
4
+
5
+ export const themes = {
6
+ 'rock-dark': {
7
+ name: 'rock-dark',
8
+ type: 'dark',
9
+ colors: {
10
+ background: '#000000',
11
+ surface: '#181825',
12
+ surfaceHighlight: '#11111b',
13
+ border: '#313244',
14
+ textPrimary: '#cdd6f4',
15
+ textSecondary: '#a6adc8',
16
+ textHint: '#6c7086',
17
+ accent: '#89b4fa',
18
+ accentMuted: '#b4befe',
19
+ warning: '#f9e2af',
20
+ success: '#a6e3a1',
21
+ danger: '#f38ba8',
22
+ prompt: '#89dceb',
23
+ cursor: '#cdd6f4',
24
+ gradient: defaultGradient,
25
+ },
26
+ },
27
+ 'rock-light': {
28
+ name: 'rock-light',
29
+ type: 'light',
30
+ colors: {
31
+ background: '#eff1f5',
32
+ surface: '#e6e9ef',
33
+ surfaceHighlight: '#dce0e8',
34
+ border: '#9ca0b0',
35
+ textPrimary: '#4c4f69',
36
+ textSecondary: '#6c6f85',
37
+ textHint: '#9ca0b0',
38
+ accent: '#1e66f5',
39
+ accentMuted: '#7287fd',
40
+ warning: '#df8e1d',
41
+ success: '#40a02b',
42
+ danger: '#d20f39',
43
+ prompt: '#04a5e5',
44
+ cursor: '#4c4f69',
45
+ gradient: defaultGradient,
46
+ },
47
+ },
48
+ };
49
+
50
+ export type ThemeColors = typeof themes['rock-dark']['colors'];
51
+ export type Theme = typeof themes['rock-dark'];
52
+
53
+ const ThemeContext = createContext<Theme>(themes['rock-dark']);
54
+
55
+ export function ThemeProvider(props: { theme?: string; children: any }) {
56
+ const themeName = props.theme && themes[props.theme as keyof typeof themes]
57
+ ? (props.theme as keyof typeof themes)
58
+ : 'rock-dark';
59
+ const theme = themes[themeName];
60
+
61
+ return (
62
+ <ThemeContext.Provider value={theme}>
63
+ {props.children}
64
+ </ThemeContext.Provider>
65
+ );
66
+ }
67
+
68
+ export function useTheme(): Theme {
69
+ return useContext(ThemeContext);
70
+ }
@@ -0,0 +1,69 @@
1
+ import { createContext, useContext, type ParentProps } from 'solid-js';
2
+ import { createStore } from 'solid-js/store';
3
+
4
+ export interface ToastOptions {
5
+ title?: string;
6
+ message: string;
7
+ variant: 'info' | 'success' | 'warning' | 'error';
8
+ duration?: number;
9
+ }
10
+
11
+ export type ToastData = Omit<ToastOptions, 'duration'>;
12
+
13
+ const DEFAULT_DURATION = 3000;
14
+
15
+ function createToastState() {
16
+ const [store, setStore] = createStore({
17
+ currentToast: null as ToastData | null,
18
+ });
19
+
20
+ let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
21
+
22
+ const toast = {
23
+ show(options: ToastOptions) {
24
+ const { duration, ...toastData } = options;
25
+ const dismissAfter = duration != null ? duration : DEFAULT_DURATION;
26
+
27
+ setStore('currentToast', toastData);
28
+
29
+ if (timeoutHandle) clearTimeout(timeoutHandle);
30
+ timeoutHandle = setTimeout(() => {
31
+ setStore('currentToast', null);
32
+ timeoutHandle = null;
33
+ }, dismissAfter);
34
+ },
35
+
36
+ error(err: unknown) {
37
+ if (err instanceof Error) {
38
+ toast.show({ variant: 'error', message: err.message });
39
+ } else if (typeof err === 'string') {
40
+ toast.show({ variant: 'error', message: err });
41
+ } else {
42
+ toast.show({ variant: 'error', message: 'An unknown error occurred' });
43
+ }
44
+ },
45
+
46
+ get currentToast(): ToastData | null {
47
+ return store.currentToast;
48
+ },
49
+ };
50
+
51
+ return toast;
52
+ }
53
+
54
+ export type ToastContext = ReturnType<typeof createToastState>;
55
+
56
+ const ToastCtx = createContext<ToastContext>();
57
+
58
+ export function ToastProvider(props: ParentProps) {
59
+ const value = createToastState();
60
+ return <ToastCtx.Provider value={value}>{props.children}</ToastCtx.Provider>;
61
+ }
62
+
63
+ export function useToast(): ToastContext {
64
+ const value = useContext(ToastCtx);
65
+ if (!value) {
66
+ throw new Error('useToast must be used within a ToastProvider');
67
+ }
68
+ return value;
69
+ }