lsh-framework 0.5.4
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/.env.example +51 -0
- package/README.md +399 -0
- package/dist/app.js +33 -0
- package/dist/cicd/analytics.js +261 -0
- package/dist/cicd/auth.js +269 -0
- package/dist/cicd/cache-manager.js +172 -0
- package/dist/cicd/data-retention.js +305 -0
- package/dist/cicd/performance-monitor.js +224 -0
- package/dist/cicd/webhook-receiver.js +634 -0
- package/dist/cli.js +500 -0
- package/dist/commands/api.js +343 -0
- package/dist/commands/self.js +318 -0
- package/dist/commands/theme.js +257 -0
- package/dist/commands/zsh-import.js +240 -0
- package/dist/components/App.js +1 -0
- package/dist/components/Divider.js +29 -0
- package/dist/components/REPL.js +43 -0
- package/dist/components/Terminal.js +232 -0
- package/dist/components/UserInput.js +30 -0
- package/dist/daemon/api-server.js +315 -0
- package/dist/daemon/job-registry.js +554 -0
- package/dist/daemon/lshd.js +822 -0
- package/dist/daemon/monitoring-api.js +220 -0
- package/dist/examples/supabase-integration.js +106 -0
- package/dist/lib/api-error-handler.js +183 -0
- package/dist/lib/associative-arrays.js +285 -0
- package/dist/lib/base-api-server.js +290 -0
- package/dist/lib/base-command-registrar.js +286 -0
- package/dist/lib/base-job-manager.js +293 -0
- package/dist/lib/brace-expansion.js +160 -0
- package/dist/lib/builtin-commands.js +439 -0
- package/dist/lib/cloud-config-manager.js +347 -0
- package/dist/lib/command-validator.js +190 -0
- package/dist/lib/completion-system.js +344 -0
- package/dist/lib/cron-job-manager.js +364 -0
- package/dist/lib/daemon-client-helper.js +141 -0
- package/dist/lib/daemon-client.js +501 -0
- package/dist/lib/database-persistence.js +638 -0
- package/dist/lib/database-schema.js +259 -0
- package/dist/lib/enhanced-history-system.js +246 -0
- package/dist/lib/env-validator.js +265 -0
- package/dist/lib/executors/builtin-executor.js +52 -0
- package/dist/lib/extended-globbing.js +411 -0
- package/dist/lib/extended-parameter-expansion.js +227 -0
- package/dist/lib/floating-point-arithmetic.js +256 -0
- package/dist/lib/history-system.js +245 -0
- package/dist/lib/interactive-shell.js +460 -0
- package/dist/lib/job-builtins.js +580 -0
- package/dist/lib/job-manager.js +386 -0
- package/dist/lib/job-storage-database.js +156 -0
- package/dist/lib/job-storage-memory.js +73 -0
- package/dist/lib/logger.js +274 -0
- package/dist/lib/lshrc-init.js +177 -0
- package/dist/lib/pathname-expansion.js +216 -0
- package/dist/lib/prompt-system.js +328 -0
- package/dist/lib/script-runner.js +226 -0
- package/dist/lib/secrets-manager.js +193 -0
- package/dist/lib/shell-executor.js +2504 -0
- package/dist/lib/shell-parser.js +958 -0
- package/dist/lib/shell-types.js +6 -0
- package/dist/lib/shell.lib.js +40 -0
- package/dist/lib/supabase-client.js +58 -0
- package/dist/lib/theme-manager.js +476 -0
- package/dist/lib/variable-expansion.js +385 -0
- package/dist/lib/zsh-compatibility.js +658 -0
- package/dist/lib/zsh-import-manager.js +699 -0
- package/dist/lib/zsh-options.js +328 -0
- package/dist/pipeline/job-tracker.js +491 -0
- package/dist/pipeline/mcli-bridge.js +302 -0
- package/dist/pipeline/pipeline-service.js +1116 -0
- package/dist/pipeline/workflow-engine.js +867 -0
- package/dist/services/api/api.js +58 -0
- package/dist/services/api/auth.js +35 -0
- package/dist/services/api/config.js +7 -0
- package/dist/services/api/file.js +22 -0
- package/dist/services/cron/cron-registrar.js +235 -0
- package/dist/services/cron/cron.js +9 -0
- package/dist/services/daemon/daemon-registrar.js +565 -0
- package/dist/services/daemon/daemon.js +9 -0
- package/dist/services/lib/lib.js +86 -0
- package/dist/services/log-file-extractor.js +170 -0
- package/dist/services/secrets/secrets.js +94 -0
- package/dist/services/shell/shell.js +28 -0
- package/dist/services/supabase/supabase-registrar.js +367 -0
- package/dist/services/supabase/supabase.js +9 -0
- package/dist/services/zapier.js +16 -0
- package/dist/simple-api-server.js +148 -0
- package/dist/store/store.js +31 -0
- package/dist/util/lib.util.js +11 -0
- package/package.json +144 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Prompt System Implementation
|
|
3
|
+
* Provides ZSH-compatible prompt customization
|
|
4
|
+
*/
|
|
5
|
+
import * as os from 'os';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
export class PromptSystem {
|
|
8
|
+
themes = new Map();
|
|
9
|
+
currentTheme = 'default';
|
|
10
|
+
context;
|
|
11
|
+
constructor() {
|
|
12
|
+
this.context = this.createDefaultContext();
|
|
13
|
+
this.setupDefaultThemes();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Expand prompt string with ZSH-style sequences
|
|
17
|
+
*/
|
|
18
|
+
expandPrompt(prompt, context) {
|
|
19
|
+
const ctx = { ...this.context, ...context };
|
|
20
|
+
let result = prompt;
|
|
21
|
+
// Basic sequences
|
|
22
|
+
result = result.replace(/%n/g, ctx.user); // Username
|
|
23
|
+
result = result.replace(/%m/g, ctx.host); // Hostname
|
|
24
|
+
result = result.replace(/%M/g, ctx.host.split('.')[0]); // Hostname without domain
|
|
25
|
+
result = result.replace(/%~/g, this.formatPath(ctx.cwd, ctx.home)); // Current directory
|
|
26
|
+
result = result.replace(/%d/g, ctx.cwd); // Full path
|
|
27
|
+
result = result.replace(/%c/g, path.basename(ctx.cwd)); // Basename of current directory
|
|
28
|
+
result = result.replace(/%C/g, path.basename(ctx.cwd)); // Same as %c
|
|
29
|
+
result = result.replace(/%h/g, ctx.jobCount.toString()); // Number of jobs
|
|
30
|
+
result = result.replace(/%j/g, ctx.jobCount.toString()); // Number of jobs
|
|
31
|
+
result = result.replace(/%L/g, ctx.exitCode.toString()); // Exit code of last command
|
|
32
|
+
result = result.replace(/%\?/g, ctx.exitCode.toString()); // Exit code of last command
|
|
33
|
+
result = result.replace(/%#/g, ctx.user === 'root' ? '#' : '$'); // Prompt character
|
|
34
|
+
result = result.replace(/%\$/g, ctx.user === 'root' ? '#' : '$'); // Prompt character
|
|
35
|
+
result = result.replace(/%%/g, '%'); // Literal %
|
|
36
|
+
// Time sequences
|
|
37
|
+
result = result.replace(/%T/g, this.formatTime(ctx.time, 'HH:MM')); // 24-hour time
|
|
38
|
+
result = result.replace(/%t/g, this.formatTime(ctx.time, 'h:mm')); // 12-hour time
|
|
39
|
+
result = result.replace(/%D/g, this.formatTime(ctx.time, 'yyyy-MM-dd')); // Date
|
|
40
|
+
result = result.replace(/%w/g, this.formatTime(ctx.time, 'EEE')); // Day of week
|
|
41
|
+
result = result.replace(/%W/g, this.formatTime(ctx.time, 'MM/dd')); // Date
|
|
42
|
+
// Git sequences
|
|
43
|
+
if (ctx.git) {
|
|
44
|
+
result = result.replace(/%b/g, ctx.git.branch); // Git branch
|
|
45
|
+
result = result.replace(/%B/g, ctx.git.branch); // Git branch
|
|
46
|
+
result = result.replace(/%r/g, this.formatGitStatus(ctx.git.status)); // Git status
|
|
47
|
+
}
|
|
48
|
+
// Conditional sequences
|
|
49
|
+
result = this.expandConditionalSequences(result, ctx);
|
|
50
|
+
// Color sequences
|
|
51
|
+
result = this.expandColorSequences(result);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Set current theme
|
|
56
|
+
*/
|
|
57
|
+
setTheme(themeName) {
|
|
58
|
+
if (this.themes.has(themeName)) {
|
|
59
|
+
this.currentTheme = themeName;
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get current theme
|
|
66
|
+
*/
|
|
67
|
+
getCurrentTheme() {
|
|
68
|
+
return this.currentTheme;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get current prompt
|
|
72
|
+
*/
|
|
73
|
+
getCurrentPrompt(context) {
|
|
74
|
+
const theme = this.themes.get(this.currentTheme);
|
|
75
|
+
if (!theme)
|
|
76
|
+
return '$ ';
|
|
77
|
+
return this.expandPrompt(theme.prompt, context);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get current right prompt
|
|
81
|
+
*/
|
|
82
|
+
getCurrentRPrompt(context) {
|
|
83
|
+
const theme = this.themes.get(this.currentTheme);
|
|
84
|
+
if (!theme || !theme.rprompt)
|
|
85
|
+
return '';
|
|
86
|
+
return this.expandPrompt(theme.rprompt, context);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Add a custom theme
|
|
90
|
+
*/
|
|
91
|
+
addTheme(theme) {
|
|
92
|
+
this.themes.set(theme.name, theme);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get all available themes
|
|
96
|
+
*/
|
|
97
|
+
getAvailableThemes() {
|
|
98
|
+
return Array.from(this.themes.keys());
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get theme information
|
|
102
|
+
*/
|
|
103
|
+
getThemeInfo(themeName) {
|
|
104
|
+
return this.themes.get(themeName);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Update prompt context
|
|
108
|
+
*/
|
|
109
|
+
updateContext(updates) {
|
|
110
|
+
this.context = { ...this.context, ...updates };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Create default context
|
|
114
|
+
*/
|
|
115
|
+
createDefaultContext() {
|
|
116
|
+
return {
|
|
117
|
+
user: os.userInfo().username,
|
|
118
|
+
host: os.hostname(),
|
|
119
|
+
cwd: process.cwd(),
|
|
120
|
+
home: os.homedir(),
|
|
121
|
+
exitCode: 0,
|
|
122
|
+
jobCount: 0,
|
|
123
|
+
time: new Date(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Setup default themes
|
|
128
|
+
*/
|
|
129
|
+
setupDefaultThemes() {
|
|
130
|
+
// Default theme
|
|
131
|
+
this.addTheme({
|
|
132
|
+
name: 'default',
|
|
133
|
+
description: 'Simple default prompt',
|
|
134
|
+
prompt: '%n@%m:%~$ ',
|
|
135
|
+
});
|
|
136
|
+
// Minimal theme
|
|
137
|
+
this.addTheme({
|
|
138
|
+
name: 'minimal',
|
|
139
|
+
description: 'Minimal prompt',
|
|
140
|
+
prompt: '$ ',
|
|
141
|
+
});
|
|
142
|
+
// Detailed theme
|
|
143
|
+
this.addTheme({
|
|
144
|
+
name: 'detailed',
|
|
145
|
+
description: 'Detailed prompt with time and exit code',
|
|
146
|
+
prompt: '[%T] %n@%m:%~ %?$ ',
|
|
147
|
+
rprompt: '%h jobs',
|
|
148
|
+
});
|
|
149
|
+
// Git theme
|
|
150
|
+
this.addTheme({
|
|
151
|
+
name: 'git',
|
|
152
|
+
description: 'Git-aware prompt',
|
|
153
|
+
prompt: '%n@%m:%~ %b$ ',
|
|
154
|
+
rprompt: '%r',
|
|
155
|
+
});
|
|
156
|
+
// Powerline-style theme
|
|
157
|
+
this.addTheme({
|
|
158
|
+
name: 'powerline',
|
|
159
|
+
description: 'Powerline-style prompt',
|
|
160
|
+
prompt: '%n@%m %~ %# ',
|
|
161
|
+
colors: {
|
|
162
|
+
user: '32', // Green
|
|
163
|
+
host: '34', // Blue
|
|
164
|
+
path: '35', // Magenta
|
|
165
|
+
git: '33', // Yellow
|
|
166
|
+
error: '31', // Red
|
|
167
|
+
success: '32', // Green
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
// Oh My Zsh-style theme
|
|
171
|
+
this.addTheme({
|
|
172
|
+
name: 'ohmyzsh',
|
|
173
|
+
description: 'Oh My Zsh-style prompt',
|
|
174
|
+
prompt: '%n@%m %~ %# ',
|
|
175
|
+
rprompt: '%T',
|
|
176
|
+
});
|
|
177
|
+
// Fish-style theme
|
|
178
|
+
this.addTheme({
|
|
179
|
+
name: 'fish',
|
|
180
|
+
description: 'Fish shell-style prompt',
|
|
181
|
+
prompt: '%n@%m %~ > ',
|
|
182
|
+
});
|
|
183
|
+
// Bash-style theme
|
|
184
|
+
this.addTheme({
|
|
185
|
+
name: 'bash',
|
|
186
|
+
description: 'Bash-style prompt',
|
|
187
|
+
prompt: '\\u@\\h:\\w\\$ ',
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Format path with tilde expansion
|
|
192
|
+
*/
|
|
193
|
+
formatPath(cwd, home) {
|
|
194
|
+
if (cwd.startsWith(home)) {
|
|
195
|
+
return '~' + cwd.substring(home.length);
|
|
196
|
+
}
|
|
197
|
+
return cwd;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Format time according to pattern
|
|
201
|
+
*/
|
|
202
|
+
formatTime(time, pattern) {
|
|
203
|
+
const hours = time.getHours();
|
|
204
|
+
const minutes = time.getMinutes();
|
|
205
|
+
const seconds = time.getSeconds();
|
|
206
|
+
const day = time.getDate();
|
|
207
|
+
const month = time.getMonth() + 1;
|
|
208
|
+
const year = time.getFullYear();
|
|
209
|
+
const dayOfWeek = time.getDay();
|
|
210
|
+
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
211
|
+
return pattern
|
|
212
|
+
.replace(/HH/g, hours.toString().padStart(2, '0'))
|
|
213
|
+
.replace(/H/g, hours.toString())
|
|
214
|
+
.replace(/h/g, (hours % 12 || 12).toString())
|
|
215
|
+
.replace(/mm/g, minutes.toString().padStart(2, '0'))
|
|
216
|
+
.replace(/m/g, minutes.toString())
|
|
217
|
+
.replace(/ss/g, seconds.toString().padStart(2, '0'))
|
|
218
|
+
.replace(/s/g, seconds.toString())
|
|
219
|
+
.replace(/dd/g, day.toString().padStart(2, '0'))
|
|
220
|
+
.replace(/d/g, day.toString())
|
|
221
|
+
.replace(/MM/g, month.toString().padStart(2, '0'))
|
|
222
|
+
.replace(/M/g, month.toString())
|
|
223
|
+
.replace(/yyyy/g, year.toString())
|
|
224
|
+
.replace(/yy/g, year.toString().slice(-2))
|
|
225
|
+
.replace(/EEE/g, dayNames[dayOfWeek]);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Format git status
|
|
229
|
+
*/
|
|
230
|
+
formatGitStatus(status) {
|
|
231
|
+
switch (status) {
|
|
232
|
+
case 'clean': return '✓';
|
|
233
|
+
case 'dirty': return '●';
|
|
234
|
+
case 'ahead': return '↑';
|
|
235
|
+
case 'behind': return '↓';
|
|
236
|
+
case 'diverged': return '↕';
|
|
237
|
+
default: return '';
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Expand conditional sequences
|
|
242
|
+
*/
|
|
243
|
+
expandConditionalSequences(prompt, context) {
|
|
244
|
+
// Handle %(condition.true-text.false-text)
|
|
245
|
+
const conditionalRegex = /%\(([^)]+)\)/g;
|
|
246
|
+
return prompt.replace(conditionalRegex, (match, condition) => {
|
|
247
|
+
const parts = condition.split('.');
|
|
248
|
+
if (parts.length < 2)
|
|
249
|
+
return match;
|
|
250
|
+
const [cond, trueText, falseText = ''] = parts;
|
|
251
|
+
const isTrue = this.evaluateCondition(cond, context);
|
|
252
|
+
return isTrue ? trueText : falseText;
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Evaluate condition for conditional sequences
|
|
257
|
+
*/
|
|
258
|
+
evaluateCondition(condition, context) {
|
|
259
|
+
switch (condition) {
|
|
260
|
+
case '?': return context.exitCode === 0;
|
|
261
|
+
case '!': return context.exitCode !== 0;
|
|
262
|
+
case 'j': return context.jobCount > 0;
|
|
263
|
+
case 'n': return context.user !== 'root';
|
|
264
|
+
case 'root': return context.user === 'root';
|
|
265
|
+
case 'git': return context.git !== undefined;
|
|
266
|
+
default: return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Expand color sequences
|
|
271
|
+
*/
|
|
272
|
+
expandColorSequences(prompt) {
|
|
273
|
+
// Handle %F{color} and %f
|
|
274
|
+
const colorRegex = /%F\{([^}]+)\}/g;
|
|
275
|
+
prompt = prompt.replace(colorRegex, (match, color) => {
|
|
276
|
+
return this.getColorCode(color);
|
|
277
|
+
});
|
|
278
|
+
prompt = prompt.replace(/%f/g, '\x1b[0m'); // Reset color
|
|
279
|
+
// Handle %K{color} and %k (background colors)
|
|
280
|
+
const bgColorRegex = /%K\{([^}]+)\}/g;
|
|
281
|
+
prompt = prompt.replace(bgColorRegex, (match, color) => {
|
|
282
|
+
return this.getBackgroundColorCode(color);
|
|
283
|
+
});
|
|
284
|
+
prompt = prompt.replace(/%k/g, '\x1b[49m'); // Reset background
|
|
285
|
+
// Handle %B and %b (bold)
|
|
286
|
+
prompt = prompt.replace(/%B/g, '\x1b[1m');
|
|
287
|
+
prompt = prompt.replace(/%b/g, '\x1b[22m');
|
|
288
|
+
// Handle %U and %u (underline)
|
|
289
|
+
prompt = prompt.replace(/%U/g, '\x1b[4m');
|
|
290
|
+
prompt = prompt.replace(/%u/g, '\x1b[24m');
|
|
291
|
+
return prompt;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Get color code for color name
|
|
295
|
+
*/
|
|
296
|
+
getColorCode(color) {
|
|
297
|
+
const colors = {
|
|
298
|
+
black: '\x1b[30m',
|
|
299
|
+
red: '\x1b[31m',
|
|
300
|
+
green: '\x1b[32m',
|
|
301
|
+
yellow: '\x1b[33m',
|
|
302
|
+
blue: '\x1b[34m',
|
|
303
|
+
magenta: '\x1b[35m',
|
|
304
|
+
cyan: '\x1b[36m',
|
|
305
|
+
white: '\x1b[37m',
|
|
306
|
+
default: '\x1b[39m',
|
|
307
|
+
};
|
|
308
|
+
return colors[color.toLowerCase()] || '\x1b[39m';
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Get background color code for color name
|
|
312
|
+
*/
|
|
313
|
+
getBackgroundColorCode(color) {
|
|
314
|
+
const colors = {
|
|
315
|
+
black: '\x1b[40m',
|
|
316
|
+
red: '\x1b[41m',
|
|
317
|
+
green: '\x1b[42m',
|
|
318
|
+
yellow: '\x1b[43m',
|
|
319
|
+
blue: '\x1b[44m',
|
|
320
|
+
magenta: '\x1b[45m',
|
|
321
|
+
cyan: '\x1b[46m',
|
|
322
|
+
white: '\x1b[47m',
|
|
323
|
+
default: '\x1b[49m',
|
|
324
|
+
};
|
|
325
|
+
return colors[color.toLowerCase()] || '\x1b[49m';
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
export default PromptSystem;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell Script Runner
|
|
3
|
+
* Executes shell scripts with LSH
|
|
4
|
+
*/
|
|
5
|
+
import { ShellExecutor } from './shell-executor.js';
|
|
6
|
+
import { parseShellCommand } from './shell-parser.js';
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
export class ScriptRunner {
|
|
9
|
+
executor;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.executor = new ShellExecutor({
|
|
12
|
+
cwd: options?.cwd || process.cwd(),
|
|
13
|
+
env: {
|
|
14
|
+
...Object.fromEntries(Object.entries(process.env).filter(([_, v]) => v !== undefined)),
|
|
15
|
+
...options?.env
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Execute a shell script file
|
|
21
|
+
*/
|
|
22
|
+
async executeScript(scriptPath, options = {}) {
|
|
23
|
+
try {
|
|
24
|
+
// Read script file
|
|
25
|
+
const scriptContent = fs.readFileSync(scriptPath, 'utf8');
|
|
26
|
+
// Set up script context
|
|
27
|
+
if (options.args) {
|
|
28
|
+
this.executor.setPositionalParams(options.args);
|
|
29
|
+
}
|
|
30
|
+
// Parse and execute script
|
|
31
|
+
const ast = parseShellCommand(scriptContent);
|
|
32
|
+
const result = await this.executor.execute(ast);
|
|
33
|
+
return {
|
|
34
|
+
success: result.success,
|
|
35
|
+
exitCode: result.exitCode,
|
|
36
|
+
output: result.stdout,
|
|
37
|
+
errors: result.stderr,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
exitCode: 1,
|
|
44
|
+
output: '',
|
|
45
|
+
errors: `Script execution failed: ${error.message}`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Execute shell commands from string
|
|
51
|
+
*/
|
|
52
|
+
async executeCommands(commands, options = {}) {
|
|
53
|
+
try {
|
|
54
|
+
// Set up context
|
|
55
|
+
if (options.args) {
|
|
56
|
+
this.executor.setPositionalParams(options.args);
|
|
57
|
+
}
|
|
58
|
+
// Parse and execute commands
|
|
59
|
+
const ast = parseShellCommand(commands);
|
|
60
|
+
const result = await this.executor.execute(ast);
|
|
61
|
+
return {
|
|
62
|
+
success: result.success,
|
|
63
|
+
exitCode: result.exitCode,
|
|
64
|
+
output: result.stdout,
|
|
65
|
+
errors: result.stderr,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
exitCode: 1,
|
|
72
|
+
output: '',
|
|
73
|
+
errors: `Command execution failed: ${error.message}`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Execute script with shebang detection
|
|
79
|
+
*/
|
|
80
|
+
async executeWithShebang(scriptPath, options = {}) {
|
|
81
|
+
try {
|
|
82
|
+
const scriptContent = fs.readFileSync(scriptPath, 'utf8');
|
|
83
|
+
// Check for shebang
|
|
84
|
+
const lines = scriptContent.split('\n');
|
|
85
|
+
const firstLine = lines[0];
|
|
86
|
+
if (firstLine.startsWith('#!')) {
|
|
87
|
+
const interpreter = firstLine.substring(2).trim();
|
|
88
|
+
// Handle different interpreters
|
|
89
|
+
if (interpreter.includes('sh') || interpreter.includes('bash') || interpreter.includes('zsh')) {
|
|
90
|
+
// Execute as shell script
|
|
91
|
+
return await this.executeScript(scriptPath, options);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// For other interpreters, delegate to system
|
|
95
|
+
return await this.executeSystemScript(scriptPath, options);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// No shebang, execute as shell script
|
|
100
|
+
return await this.executeScript(scriptPath, options);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
exitCode: 1,
|
|
107
|
+
output: '',
|
|
108
|
+
errors: `Script execution failed: ${error.message}`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Execute system script (fallback)
|
|
114
|
+
*/
|
|
115
|
+
async executeSystemScript(scriptPath, options) {
|
|
116
|
+
const { spawn } = await import('child_process');
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
const child = spawn('sh', [scriptPath], {
|
|
119
|
+
cwd: options.cwd || process.cwd(),
|
|
120
|
+
env: { ...process.env, ...options.env },
|
|
121
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
122
|
+
});
|
|
123
|
+
let stdout = '';
|
|
124
|
+
let stderr = '';
|
|
125
|
+
child.stdout?.on('data', (data) => {
|
|
126
|
+
stdout += data.toString();
|
|
127
|
+
});
|
|
128
|
+
child.stderr?.on('data', (data) => {
|
|
129
|
+
stderr += data.toString();
|
|
130
|
+
});
|
|
131
|
+
child.on('close', (code) => {
|
|
132
|
+
resolve({
|
|
133
|
+
success: code === 0,
|
|
134
|
+
exitCode: code || 0,
|
|
135
|
+
output: stdout,
|
|
136
|
+
errors: stderr,
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
child.on('error', (error) => {
|
|
140
|
+
resolve({
|
|
141
|
+
success: false,
|
|
142
|
+
exitCode: 1,
|
|
143
|
+
output: '',
|
|
144
|
+
errors: error.message,
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Validate shell script syntax
|
|
151
|
+
*/
|
|
152
|
+
validateScript(scriptPath) {
|
|
153
|
+
try {
|
|
154
|
+
const scriptContent = fs.readFileSync(scriptPath, 'utf8');
|
|
155
|
+
const _ast = parseShellCommand(scriptContent);
|
|
156
|
+
// Basic validation - if parsing succeeds, syntax is valid
|
|
157
|
+
return { valid: true, errors: [] };
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
return { valid: false, errors: [error.message] };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get script information
|
|
165
|
+
*/
|
|
166
|
+
getScriptInfo(scriptPath) {
|
|
167
|
+
try {
|
|
168
|
+
const stats = fs.statSync(scriptPath);
|
|
169
|
+
const content = fs.readFileSync(scriptPath, 'utf8');
|
|
170
|
+
const firstLine = content.split('\n')[0];
|
|
171
|
+
let shebang;
|
|
172
|
+
let interpreter;
|
|
173
|
+
if (firstLine.startsWith('#!')) {
|
|
174
|
+
shebang = firstLine;
|
|
175
|
+
interpreter = firstLine.substring(2).trim();
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
exists: true,
|
|
179
|
+
executable: stats.mode & 0o111 ? true : false,
|
|
180
|
+
size: stats.size,
|
|
181
|
+
shebang,
|
|
182
|
+
interpreter,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return {
|
|
187
|
+
exists: false,
|
|
188
|
+
executable: false,
|
|
189
|
+
size: 0,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Make script executable
|
|
195
|
+
*/
|
|
196
|
+
makeExecutable(scriptPath) {
|
|
197
|
+
try {
|
|
198
|
+
const stats = fs.statSync(scriptPath);
|
|
199
|
+
fs.chmodSync(scriptPath, stats.mode | 0o111);
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Create a simple shell script
|
|
208
|
+
*/
|
|
209
|
+
createScript(scriptPath, content, makeExecutable = true) {
|
|
210
|
+
try {
|
|
211
|
+
// Add shebang if not present
|
|
212
|
+
if (!content.startsWith('#!')) {
|
|
213
|
+
content = '#!/bin/sh\n' + content;
|
|
214
|
+
}
|
|
215
|
+
fs.writeFileSync(scriptPath, content, 'utf8');
|
|
216
|
+
if (makeExecutable) {
|
|
217
|
+
this.makeExecutable(scriptPath);
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
export default ScriptRunner;
|