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,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ Trigger File Completion Module
|
|
3
|
+
* Provides file path auto-completion triggered by @ symbol
|
|
4
|
+
*
|
|
5
|
+
* Supported commands:
|
|
6
|
+
* - /upload @<local-path> @<remote-path>
|
|
7
|
+
* First @ completes local files, second @ completes remote files
|
|
8
|
+
*
|
|
9
|
+
* - /download @<remote-path> @<local-path>
|
|
10
|
+
* First @ completes remote files, second @ completes local files
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import {
|
|
16
|
+
hasDangerousShellChars,
|
|
17
|
+
shellEscapePosix,
|
|
18
|
+
findFirstUnescapedSpace,
|
|
19
|
+
escapeSpaces,
|
|
20
|
+
} from './remotePathSafety.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Maximum number of completion items to show
|
|
24
|
+
*/
|
|
25
|
+
const MAX_COMPLETIONS = 20;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse @ completion context from input buffer
|
|
29
|
+
* Determines if we're completing local (first @) or remote (second @) path
|
|
30
|
+
*
|
|
31
|
+
* @param {string} buffer - Current input buffer
|
|
32
|
+
* @param {number} cursorPos - Cursor position (defaults to end of buffer)
|
|
33
|
+
* @returns {Object|null} Context object or null if no @ completion needed
|
|
34
|
+
*/
|
|
35
|
+
export function parseAtContext(buffer, cursorPos = buffer.length) {
|
|
36
|
+
if (!buffer) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Support both /upload and /download commands
|
|
41
|
+
const isUploadCmd = buffer.startsWith('/upload');
|
|
42
|
+
const isDownloadCmd = buffer.startsWith('/download');
|
|
43
|
+
|
|
44
|
+
if (!isUploadCmd && !isDownloadCmd) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Find all @ positions in the buffer
|
|
49
|
+
const atPositions = [];
|
|
50
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
51
|
+
if (buffer[i] === '@') {
|
|
52
|
+
atPositions.push(i);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (atPositions.length === 0) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Find which @ the cursor is in (cursor is after @, before next space or end)
|
|
61
|
+
let activeAtIndex = -1;
|
|
62
|
+
let activeAtPosition = -1;
|
|
63
|
+
|
|
64
|
+
for (let i = atPositions.length - 1; i >= 0; i--) {
|
|
65
|
+
const atPos = atPositions[i];
|
|
66
|
+
if (atPos < cursorPos) {
|
|
67
|
+
// Check if cursor is still within this @ argument (no space between @ and cursor)
|
|
68
|
+
const textAfterAt = buffer.substring(atPos + 1, cursorPos);
|
|
69
|
+
if (findFirstUnescapedSpace(textAfterAt) === -1) {
|
|
70
|
+
activeAtIndex = i;
|
|
71
|
+
activeAtPosition = atPos;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (activeAtIndex === -1) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Extract partial path after @ (up to cursor position)
|
|
82
|
+
const afterAt = buffer.substring(activeAtPosition + 1);
|
|
83
|
+
const spaceIdx = findFirstUnescapedSpace(afterAt);
|
|
84
|
+
|
|
85
|
+
// Calculate the end of the current @ argument
|
|
86
|
+
const argEndInAfterAt = spaceIdx === -1 ? afterAt.length : spaceIdx;
|
|
87
|
+
const argEndInBuffer = activeAtPosition + 1 + argEndInAfterAt;
|
|
88
|
+
|
|
89
|
+
// Partial path is from @ to cursor (or end of argument if cursor is past it)
|
|
90
|
+
const cursorRelativeToAt = cursorPos - (activeAtPosition + 1);
|
|
91
|
+
const partialPathEnd = Math.min(cursorRelativeToAt, argEndInAfterAt);
|
|
92
|
+
const partialPath = afterAt.substring(0, partialPathEnd);
|
|
93
|
+
|
|
94
|
+
// For /upload: First @ = local file, second @ = remote file
|
|
95
|
+
// For /download: First @ = remote file, second @ = local file
|
|
96
|
+
const isLocal = isUploadCmd ? (activeAtIndex === 0) : (activeAtIndex === 1);
|
|
97
|
+
|
|
98
|
+
// Calculate replace range (from @ to end of argument)
|
|
99
|
+
const replaceStart = activeAtPosition;
|
|
100
|
+
const replaceEnd = argEndInBuffer;
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
atIndex: activeAtIndex,
|
|
104
|
+
atPosition: activeAtPosition,
|
|
105
|
+
isLocal,
|
|
106
|
+
partialPath,
|
|
107
|
+
replaceStart,
|
|
108
|
+
replaceEnd,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Fuzzy match filter - checks if query characters appear in order in target
|
|
114
|
+
* @param {string} target - String to search in
|
|
115
|
+
* @param {string} query - Query to match
|
|
116
|
+
* @returns {boolean}
|
|
117
|
+
*/
|
|
118
|
+
export function fuzzyMatch(target, query) {
|
|
119
|
+
if (!query) return true;
|
|
120
|
+
|
|
121
|
+
const lowerTarget = target.toLowerCase();
|
|
122
|
+
const lowerQuery = query.toLowerCase();
|
|
123
|
+
|
|
124
|
+
let queryIdx = 0;
|
|
125
|
+
for (let i = 0; i < lowerTarget.length && queryIdx < lowerQuery.length; i++) {
|
|
126
|
+
if (lowerTarget[i] === lowerQuery[queryIdx]) {
|
|
127
|
+
queryIdx++;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return queryIdx === lowerQuery.length;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Parse directory and file prefix from partial path
|
|
136
|
+
* @param {string} partialPath - Path like "src/comp" or "test/"
|
|
137
|
+
* @returns {{ dirPath: string, filePrefix: string }}
|
|
138
|
+
*/
|
|
139
|
+
export function parsePartialPath(partialPath) {
|
|
140
|
+
if (!partialPath) {
|
|
141
|
+
return { dirPath: '.', filePrefix: '' };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (partialPath.endsWith('/')) {
|
|
145
|
+
return { dirPath: partialPath, filePrefix: '' };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const lastSlash = partialPath.lastIndexOf('/');
|
|
149
|
+
if (lastSlash === -1) {
|
|
150
|
+
return { dirPath: '.', filePrefix: partialPath };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
dirPath: partialPath.substring(0, lastSlash + 1) || '.',
|
|
155
|
+
filePrefix: partialPath.substring(lastSlash + 1),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get local file completions
|
|
161
|
+
* @param {string} partialPath - Partial path entered after @
|
|
162
|
+
* @param {string} cwd - Current working directory
|
|
163
|
+
* @returns {Array<{label: string, value: string, description: string, isDir: boolean}>}
|
|
164
|
+
*/
|
|
165
|
+
export function getLocalCompletions(partialPath, cwd = process.cwd()) {
|
|
166
|
+
const { dirPath, filePrefix } = parsePartialPath(partialPath);
|
|
167
|
+
|
|
168
|
+
// Resolve directory path
|
|
169
|
+
let resolvedDir;
|
|
170
|
+
if (path.isAbsolute(dirPath)) {
|
|
171
|
+
resolvedDir = dirPath;
|
|
172
|
+
} else if (dirPath === '.') {
|
|
173
|
+
resolvedDir = cwd;
|
|
174
|
+
} else {
|
|
175
|
+
resolvedDir = path.resolve(cwd, dirPath);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const entries = fs.readdirSync(resolvedDir, { withFileTypes: true });
|
|
180
|
+
|
|
181
|
+
const results = [];
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
const name = entry.name;
|
|
184
|
+
|
|
185
|
+
// Skip hidden files unless user typed a dot
|
|
186
|
+
if (name.startsWith('.') && !filePrefix.startsWith('.')) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Apply fuzzy filter
|
|
191
|
+
if (!fuzzyMatch(name, filePrefix)) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const isDir = entry.isDirectory();
|
|
196
|
+
|
|
197
|
+
// Skip exact match for files - path is already complete
|
|
198
|
+
if (!isDir && name === filePrefix) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const displayName = isDir ? `${name}/` : name;
|
|
203
|
+
|
|
204
|
+
// Build the full value to replace
|
|
205
|
+
const valuePath = dirPath === '.'
|
|
206
|
+
? (isDir ? `${name}/` : name)
|
|
207
|
+
: (isDir ? `${dirPath}${name}/` : `${dirPath}${name}`);
|
|
208
|
+
|
|
209
|
+
results.push({
|
|
210
|
+
label: displayName,
|
|
211
|
+
value: `@${escapeSpaces(valuePath)}`,
|
|
212
|
+
description: isDir ? 'directory' : 'file',
|
|
213
|
+
isDir,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (results.length >= MAX_COMPLETIONS) {
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Sort: directories first, then alphabetically
|
|
222
|
+
results.sort((a, b) => {
|
|
223
|
+
if (a.isDir !== b.isDir) {
|
|
224
|
+
return a.isDir ? -1 : 1;
|
|
225
|
+
}
|
|
226
|
+
return a.label.localeCompare(b.label);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return results;
|
|
230
|
+
} catch (err) {
|
|
231
|
+
// Directory doesn't exist or not readable
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get remote file completions from sandbox
|
|
238
|
+
* @param {Object} sessionManager - Session manager for sandbox communication
|
|
239
|
+
* @param {string} partialPath - Partial path entered after @
|
|
240
|
+
* @returns {Promise<Array<{label: string, value: string, description: string, isDir: boolean}>>}
|
|
241
|
+
*/
|
|
242
|
+
export async function getRemoteCompletions(sessionManager, partialPath, options = {}) {
|
|
243
|
+
const signal = options && options.signal ? options.signal : null;
|
|
244
|
+
if (!sessionManager) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (signal?.aborted) {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (hasDangerousShellChars(partialPath)) {
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const { dirPath, filePrefix } = parsePartialPath(partialPath);
|
|
257
|
+
|
|
258
|
+
// Use absolute path for remote, default to /
|
|
259
|
+
const remoteDirPath = dirPath === '.' ? '/' : dirPath;
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
// Use ls -1Ap to get files with / suffix for directories
|
|
263
|
+
const result = await sessionManager.execute(`ls -1Ap -- ${shellEscapePosix(remoteDirPath)} 2>/dev/null`);
|
|
264
|
+
|
|
265
|
+
if (signal?.aborted) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!result.output) {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const entries = result.output
|
|
274
|
+
.split(/[\r\n]+/)
|
|
275
|
+
.filter(f => f.trim())
|
|
276
|
+
.slice(0, 100)
|
|
277
|
+
.map(name => ({
|
|
278
|
+
name: name.endsWith('/') ? name.slice(0, -1) : name,
|
|
279
|
+
isDir: name.endsWith('/'),
|
|
280
|
+
}));
|
|
281
|
+
|
|
282
|
+
const results = [];
|
|
283
|
+
for (const entry of entries) {
|
|
284
|
+
const name = entry.name;
|
|
285
|
+
|
|
286
|
+
// Skip hidden files unless user typed a dot
|
|
287
|
+
if (name.startsWith('.') && !filePrefix.startsWith('.')) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Apply fuzzy filter
|
|
292
|
+
if (!fuzzyMatch(name, filePrefix)) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Skip exact match for files - path is already complete
|
|
297
|
+
if (!entry.isDir && name === filePrefix) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const displayName = entry.isDir ? `${name}/` : name;
|
|
302
|
+
|
|
303
|
+
// Build the full value to replace
|
|
304
|
+
const valuePath = dirPath === '.'
|
|
305
|
+
? (entry.isDir ? `/${name}/` : `/${name}`)
|
|
306
|
+
: (entry.isDir ? `${remoteDirPath}${name}/` : `${remoteDirPath}${name}`);
|
|
307
|
+
|
|
308
|
+
results.push({
|
|
309
|
+
label: displayName,
|
|
310
|
+
value: `@${escapeSpaces(valuePath)}`,
|
|
311
|
+
description: entry.isDir ? 'remote directory' : 'remote file',
|
|
312
|
+
isDir: entry.isDir,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
if (results.length >= MAX_COMPLETIONS) {
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Sort: directories first, then alphabetically
|
|
321
|
+
results.sort((a, b) => {
|
|
322
|
+
if (a.isDir !== b.isDir) {
|
|
323
|
+
return a.isDir ? -1 : 1;
|
|
324
|
+
}
|
|
325
|
+
return a.label.localeCompare(b.label);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return results;
|
|
329
|
+
} catch (err) {
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Replace the @ path in buffer with selected value
|
|
336
|
+
* @param {string} buffer - Current buffer
|
|
337
|
+
* @param {Object} atContext - Context from parseAtContext
|
|
338
|
+
* @param {string} selectedValue - Selected completion value (including @)
|
|
339
|
+
* @returns {string} New buffer with replaced path
|
|
340
|
+
*/
|
|
341
|
+
export function replaceAtPath(buffer, atContext, selectedValue) {
|
|
342
|
+
const before = buffer.substring(0, atContext.replaceStart);
|
|
343
|
+
const after = buffer.substring(atContext.replaceEnd);
|
|
344
|
+
|
|
345
|
+
return before + selectedValue + after;
|
|
346
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clipboard utilities for terminal
|
|
3
|
+
* Uses native commands (pbcopy on macOS, xclip on Linux)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Copy text to system clipboard
|
|
11
|
+
* @param {string} text Text to copy
|
|
12
|
+
* @returns {Promise<boolean>} Success status
|
|
13
|
+
*/
|
|
14
|
+
export async function copyToClipboard(text) {
|
|
15
|
+
const platform = os.platform();
|
|
16
|
+
|
|
17
|
+
let command;
|
|
18
|
+
let args = [];
|
|
19
|
+
|
|
20
|
+
if (platform === 'darwin') {
|
|
21
|
+
command = 'pbcopy';
|
|
22
|
+
} else if (platform === 'linux') {
|
|
23
|
+
// Try xclip first, fallback to xsel
|
|
24
|
+
command = 'xclip';
|
|
25
|
+
args = ['-selection', 'clipboard'];
|
|
26
|
+
} else if (platform === 'win32') {
|
|
27
|
+
command = 'clip';
|
|
28
|
+
} else {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
try {
|
|
34
|
+
const proc = spawn(command, args, { stdio: ['pipe', 'ignore', 'ignore'] });
|
|
35
|
+
|
|
36
|
+
proc.stdin.write(text);
|
|
37
|
+
proc.stdin.end();
|
|
38
|
+
|
|
39
|
+
proc.on('close', (code) => {
|
|
40
|
+
resolve(code === 0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
proc.on('error', () => {
|
|
44
|
+
resolve(false);
|
|
45
|
+
});
|
|
46
|
+
} catch {
|
|
47
|
+
resolve(false);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console Logger - Global logger for InkREPL console panel
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { consoleLogger } from './utils/consoleLogger.js';
|
|
6
|
+
* consoleLogger.info('Message');
|
|
7
|
+
* consoleLogger.warn('Warning');
|
|
8
|
+
* consoleLogger.error('Error');
|
|
9
|
+
* consoleLogger.debug('Debug info');
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Store the setState function reference
|
|
13
|
+
let _setStateRef = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Initialize the console logger with setState function
|
|
17
|
+
* Called from InkREPL component
|
|
18
|
+
*/
|
|
19
|
+
export function initConsoleLogger(setState) {
|
|
20
|
+
_setStateRef = setState;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Clear the console logger reference
|
|
25
|
+
* Called when InkREPL unmounts
|
|
26
|
+
*/
|
|
27
|
+
export function clearConsoleLogger() {
|
|
28
|
+
_setStateRef = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Console height (visible lines) - should match Console component
|
|
32
|
+
const CONSOLE_HEIGHT = 7;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Add a log entry to the console
|
|
36
|
+
* Auto-scrolls to bottom to show latest logs
|
|
37
|
+
* @param {string} level - Log level
|
|
38
|
+
* @param {string} message - Display message (may be truncated)
|
|
39
|
+
* @param {string} fullMessage - Full message for copying (optional)
|
|
40
|
+
*/
|
|
41
|
+
function addLog(level, message, fullMessage = null) {
|
|
42
|
+
if (!_setStateRef) {
|
|
43
|
+
// Fallback to real console if not initialized
|
|
44
|
+
const method = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log';
|
|
45
|
+
console[method](`[ConsoleLogger] ${message}`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_setStateRef(state => {
|
|
50
|
+
const newLog = {
|
|
51
|
+
id: `log-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
52
|
+
level,
|
|
53
|
+
message: String(message),
|
|
54
|
+
fullMessage: fullMessage ? String(fullMessage) : String(message), // For copying
|
|
55
|
+
timestamp: new Date(),
|
|
56
|
+
};
|
|
57
|
+
const newLogs = [...state.consoleLogs, newLog];
|
|
58
|
+
// Auto-scroll to bottom: offset = total logs - visible lines
|
|
59
|
+
const newScrollOffset = Math.max(0, newLogs.length - CONSOLE_HEIGHT);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
...state,
|
|
63
|
+
consoleLogs: newLogs,
|
|
64
|
+
consoleScrollOffset: newScrollOffset,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Console Logger API
|
|
71
|
+
* Each method accepts optional fullMessage for copying
|
|
72
|
+
*/
|
|
73
|
+
export const consoleLogger = {
|
|
74
|
+
info: (message, fullMessage) => addLog('info', message, fullMessage),
|
|
75
|
+
warn: (message, fullMessage) => addLog('warn', message, fullMessage),
|
|
76
|
+
error: (message, fullMessage) => addLog('error', message, fullMessage),
|
|
77
|
+
debug: (message, fullMessage) => addLog('debug', message, fullMessage),
|
|
78
|
+
log: (message, fullMessage) => addLog('info', message, fullMessage),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default consoleLogger;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle abnormal exit codes with helpful messages.
|
|
3
|
+
*
|
|
4
|
+
* @param {number} exitCode - The exit code from command execution
|
|
5
|
+
* @param {string} output - The original output (can be empty)
|
|
6
|
+
* @returns {{ output: string, hint: string|null }} - Processed output and optional hint
|
|
7
|
+
*/
|
|
8
|
+
export function handleAbnormalExitCode(exitCode, output = '') {
|
|
9
|
+
let hint = null;
|
|
10
|
+
|
|
11
|
+
if (exitCode === -1) {
|
|
12
|
+
// -1 typically indicates process was killed or internal error
|
|
13
|
+
hint = 'Command terminated abnormally (exit code -1). This may indicate a timeout, signal termination, or internal error.';
|
|
14
|
+
if (!output) {
|
|
15
|
+
return { output: hint, hint };
|
|
16
|
+
}
|
|
17
|
+
return { output, hint };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (exitCode === 137) {
|
|
21
|
+
// 128 + 9 (SIGKILL) - often OOM killed
|
|
22
|
+
hint = '[Process killed (exit code 137). Possibly OOM or manually terminated.]';
|
|
23
|
+
return {
|
|
24
|
+
output: output ? `${output}\n${hint}` : hint,
|
|
25
|
+
hint
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (exitCode === 143) {
|
|
30
|
+
// 128 + 15 (SIGTERM) - graceful termination
|
|
31
|
+
hint = '[Process terminated (exit code 143). Received SIGTERM.]';
|
|
32
|
+
return {
|
|
33
|
+
output: output ? `${output}\n${hint}` : hint,
|
|
34
|
+
hint
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { output, hint: null };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if exit code indicates an abnormal termination.
|
|
43
|
+
*
|
|
44
|
+
* @param {number} exitCode - The exit code
|
|
45
|
+
* @returns {boolean} - True if abnormal
|
|
46
|
+
*/
|
|
47
|
+
export function isAbnormalExitCode(exitCode) {
|
|
48
|
+
return exitCode === -1 || exitCode === 137 || exitCode === 143;
|
|
49
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 退出码提示工具
|
|
3
|
+
* 根据退出码提供用户友好的提示信息
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const EXIT_CODE_TIPS = {
|
|
7
|
+
zh: {
|
|
8
|
+
0: null, // 成功无提示
|
|
9
|
+
'-1': '命令执行异常(超时/网络/会话失效)',
|
|
10
|
+
1: '命令执行失败,请检查参数或路径是否正确',
|
|
11
|
+
2: '命令用法错误,使用 --help 查看帮助',
|
|
12
|
+
126: '没有执行权限,尝试 chmod +x 或检查权限',
|
|
13
|
+
127: '命令不存在,请检查拼写或是否已安装',
|
|
14
|
+
128: '无效的参数,请检查命令语法',
|
|
15
|
+
130: '命令被用户中断',
|
|
16
|
+
137: '命令被强制终止(可能是 OOM)',
|
|
17
|
+
139: '程序发生段错误(Segmentation Fault)',
|
|
18
|
+
143: '命令被终止信号终止',
|
|
19
|
+
},
|
|
20
|
+
en: {
|
|
21
|
+
0: null,
|
|
22
|
+
'-1': 'Command execution failed (timeout/network/session expired)',
|
|
23
|
+
1: 'Command failed. Please check arguments or path',
|
|
24
|
+
2: 'Command usage error. Use --help for help',
|
|
25
|
+
126: 'No execute permission. Try chmod +x or check permissions',
|
|
26
|
+
127: 'Command not found. Check spelling or if installed',
|
|
27
|
+
128: 'Invalid argument. Check command syntax',
|
|
28
|
+
130: 'Command interrupted by user',
|
|
29
|
+
137: 'Command killed (possibly OOM)',
|
|
30
|
+
139: 'Segmentation fault',
|
|
31
|
+
143: 'Command terminated by signal',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 获取退出码对应的提示
|
|
37
|
+
* @param {number} exitCode - 退出码
|
|
38
|
+
* @param {string} locale - 'zh' | 'en'
|
|
39
|
+
* @returns {string|null}
|
|
40
|
+
*/
|
|
41
|
+
export function getExitCodeTips(exitCode, locale = 'zh') {
|
|
42
|
+
const tips = EXIT_CODE_TIPS[locale] || EXIT_CODE_TIPS.zh;
|
|
43
|
+
|
|
44
|
+
// 将 exitCode 转换为字符串进行查找(因为对象的键是字符串)
|
|
45
|
+
const exitCodeStr = String(exitCode);
|
|
46
|
+
|
|
47
|
+
if (tips[exitCodeStr] !== undefined) {
|
|
48
|
+
return tips[exitCodeStr];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 未知退出码
|
|
52
|
+
if (locale === 'en') {
|
|
53
|
+
return `Command exited with code: ${exitCode}`;
|
|
54
|
+
}
|
|
55
|
+
return `命令返回非零状态码: ${exitCode}`;
|
|
56
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format timestamp to HH:MM:SS
|
|
3
|
+
* @param {number} timestamp - Unix timestamp in milliseconds
|
|
4
|
+
* @returns {string} Formatted time string
|
|
5
|
+
*/
|
|
6
|
+
export function formatTimestamp(timestamp) {
|
|
7
|
+
const date = new Date(timestamp);
|
|
8
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
9
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
10
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
11
|
+
return `${hours}:${minutes}:${seconds}`;
|
|
12
|
+
}
|