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,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab Completion System Implementation
|
|
3
|
+
* Provides ZSH-compatible completion functionality
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
export class CompletionSystem {
|
|
8
|
+
completionFunctions = new Map();
|
|
9
|
+
defaultCompletions = [];
|
|
10
|
+
isEnabled = true;
|
|
11
|
+
constructor() {
|
|
12
|
+
this.setupDefaultCompletions();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Register a completion function for a specific command
|
|
16
|
+
*/
|
|
17
|
+
registerCompletion(command, func) {
|
|
18
|
+
this.completionFunctions.set(command, func);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Register a default completion function
|
|
22
|
+
*/
|
|
23
|
+
registerDefaultCompletion(func) {
|
|
24
|
+
this.defaultCompletions.push(func);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get completions for the current context
|
|
28
|
+
*/
|
|
29
|
+
async getCompletions(context) {
|
|
30
|
+
if (!this.isEnabled)
|
|
31
|
+
return [];
|
|
32
|
+
const candidates = [];
|
|
33
|
+
// Try command-specific completion first
|
|
34
|
+
const commandFunc = this.completionFunctions.get(context.command);
|
|
35
|
+
if (commandFunc) {
|
|
36
|
+
try {
|
|
37
|
+
const commandCompletions = await commandFunc(context);
|
|
38
|
+
candidates.push(...commandCompletions);
|
|
39
|
+
}
|
|
40
|
+
catch (_error) {
|
|
41
|
+
// Continue with default completions if command-specific fails
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// If no command-specific completions, try default completions
|
|
45
|
+
if (candidates.length === 0) {
|
|
46
|
+
for (const defaultFunc of this.defaultCompletions) {
|
|
47
|
+
try {
|
|
48
|
+
const defaultCompletions = await defaultFunc(context);
|
|
49
|
+
candidates.push(...defaultCompletions);
|
|
50
|
+
}
|
|
51
|
+
catch (_error) {
|
|
52
|
+
// Continue with other default completions
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Filter and sort candidates
|
|
57
|
+
return this.filterAndSortCandidates(candidates, context.currentWord);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Enable/disable completion
|
|
61
|
+
*/
|
|
62
|
+
setEnabled(enabled) {
|
|
63
|
+
this.isEnabled = enabled;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Setup default completion functions
|
|
67
|
+
*/
|
|
68
|
+
setupDefaultCompletions() {
|
|
69
|
+
// File and directory completion
|
|
70
|
+
this.registerDefaultCompletion(async (context) => {
|
|
71
|
+
return this.completeFilesAndDirectories(context);
|
|
72
|
+
});
|
|
73
|
+
// Command completion
|
|
74
|
+
this.registerDefaultCompletion(async (context) => {
|
|
75
|
+
if (context.wordIndex === 0) {
|
|
76
|
+
return this.completeCommands(context);
|
|
77
|
+
}
|
|
78
|
+
return [];
|
|
79
|
+
});
|
|
80
|
+
// Variable completion
|
|
81
|
+
this.registerDefaultCompletion(async (context) => {
|
|
82
|
+
if (context.currentWord.startsWith('$')) {
|
|
83
|
+
return this.completeVariables(context);
|
|
84
|
+
}
|
|
85
|
+
return [];
|
|
86
|
+
});
|
|
87
|
+
// Built-in command completions
|
|
88
|
+
this.setupBuiltinCompletions();
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Complete files and directories
|
|
92
|
+
*/
|
|
93
|
+
async completeFilesAndDirectories(context) {
|
|
94
|
+
const candidates = [];
|
|
95
|
+
const currentWord = context.currentWord;
|
|
96
|
+
// Determine search directory
|
|
97
|
+
let searchDir = context.cwd;
|
|
98
|
+
let pattern = currentWord;
|
|
99
|
+
if (currentWord.includes('/')) {
|
|
100
|
+
const lastSlash = currentWord.lastIndexOf('/');
|
|
101
|
+
searchDir = path.resolve(context.cwd, currentWord.substring(0, lastSlash + 1));
|
|
102
|
+
pattern = currentWord.substring(lastSlash + 1);
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const entries = await fs.promises.readdir(searchDir, { withFileTypes: true });
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
// Skip hidden files unless explicitly requested
|
|
108
|
+
if (!pattern.startsWith('.') && entry.name.startsWith('.')) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// Check if entry matches pattern
|
|
112
|
+
if (this.matchesPattern(entry.name, pattern)) {
|
|
113
|
+
const fullPath = path.join(searchDir, entry.name);
|
|
114
|
+
const relativePath = path.relative(context.cwd, fullPath);
|
|
115
|
+
candidates.push({
|
|
116
|
+
word: entry.isDirectory() ? relativePath + '/' : relativePath,
|
|
117
|
+
type: entry.isDirectory() ? 'directory' : 'file',
|
|
118
|
+
description: entry.isDirectory() ? 'Directory' : 'File',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (_error) {
|
|
124
|
+
// Directory doesn't exist or not readable
|
|
125
|
+
}
|
|
126
|
+
return candidates;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Complete commands from PATH
|
|
130
|
+
*/
|
|
131
|
+
async completeCommands(context) {
|
|
132
|
+
const candidates = [];
|
|
133
|
+
const pattern = context.currentWord;
|
|
134
|
+
const pathDirs = (context.env.PATH || '').split(':').filter(dir => dir);
|
|
135
|
+
// Add built-in commands
|
|
136
|
+
const builtins = [
|
|
137
|
+
'cd', 'pwd', 'echo', 'printf', 'test', '[', 'export', 'unset', 'set',
|
|
138
|
+
'eval', 'exec', 'return', 'shift', 'local', 'jobs', 'fg', 'bg', 'wait',
|
|
139
|
+
'read', 'getopts', 'trap', 'true', 'false', 'exit'
|
|
140
|
+
];
|
|
141
|
+
for (const builtin of builtins) {
|
|
142
|
+
if (this.matchesPattern(builtin, pattern)) {
|
|
143
|
+
candidates.push({
|
|
144
|
+
word: builtin,
|
|
145
|
+
type: 'command',
|
|
146
|
+
description: 'Built-in command',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Search PATH for executables
|
|
151
|
+
for (const dir of pathDirs) {
|
|
152
|
+
try {
|
|
153
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
if (entry.isFile() && this.isExecutable(path.join(dir, entry.name))) {
|
|
156
|
+
if (this.matchesPattern(entry.name, pattern)) {
|
|
157
|
+
candidates.push({
|
|
158
|
+
word: entry.name,
|
|
159
|
+
type: 'command',
|
|
160
|
+
description: `Command in ${dir}`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (_error) {
|
|
167
|
+
// Directory doesn't exist or not readable
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return candidates;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Complete variables
|
|
174
|
+
*/
|
|
175
|
+
async completeVariables(context) {
|
|
176
|
+
const candidates = [];
|
|
177
|
+
const pattern = context.currentWord.substring(1); // Remove $
|
|
178
|
+
// Complete environment variables
|
|
179
|
+
for (const [name, value] of Object.entries(context.env)) {
|
|
180
|
+
if (this.matchesPattern(name, pattern)) {
|
|
181
|
+
candidates.push({
|
|
182
|
+
word: `$${name}`,
|
|
183
|
+
type: 'variable',
|
|
184
|
+
description: `Environment variable: ${value}`,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return candidates;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Setup built-in command completions
|
|
192
|
+
*/
|
|
193
|
+
setupBuiltinCompletions() {
|
|
194
|
+
// cd completion
|
|
195
|
+
this.registerCompletion('cd', async (context) => {
|
|
196
|
+
return this.completeDirectories(context);
|
|
197
|
+
});
|
|
198
|
+
// export completion
|
|
199
|
+
this.registerCompletion('export', async (context) => {
|
|
200
|
+
if (context.wordIndex === 1) {
|
|
201
|
+
return this.completeVariables(context);
|
|
202
|
+
}
|
|
203
|
+
return [];
|
|
204
|
+
});
|
|
205
|
+
// unset completion
|
|
206
|
+
this.registerCompletion('unset', async (context) => {
|
|
207
|
+
if (context.wordIndex === 1) {
|
|
208
|
+
return this.completeVariables(context);
|
|
209
|
+
}
|
|
210
|
+
return [];
|
|
211
|
+
});
|
|
212
|
+
// test completion
|
|
213
|
+
this.registerCompletion('test', async (context) => {
|
|
214
|
+
return this.completeTestOptions(context);
|
|
215
|
+
});
|
|
216
|
+
// Job management completions
|
|
217
|
+
this.registerCompletion('job-start', async (context) => {
|
|
218
|
+
return this.completeJobIds(context);
|
|
219
|
+
});
|
|
220
|
+
this.registerCompletion('job-stop', async (context) => {
|
|
221
|
+
return this.completeJobIds(context);
|
|
222
|
+
});
|
|
223
|
+
this.registerCompletion('job-show', async (context) => {
|
|
224
|
+
return this.completeJobIds(context);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Complete directories only
|
|
229
|
+
*/
|
|
230
|
+
async completeDirectories(context) {
|
|
231
|
+
const candidates = [];
|
|
232
|
+
const currentWord = context.currentWord;
|
|
233
|
+
let searchDir = context.cwd;
|
|
234
|
+
let pattern = currentWord;
|
|
235
|
+
if (currentWord.includes('/')) {
|
|
236
|
+
const lastSlash = currentWord.lastIndexOf('/');
|
|
237
|
+
searchDir = path.resolve(context.cwd, currentWord.substring(0, lastSlash + 1));
|
|
238
|
+
pattern = currentWord.substring(lastSlash + 1);
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const entries = await fs.promises.readdir(searchDir, { withFileTypes: true });
|
|
242
|
+
for (const entry of entries) {
|
|
243
|
+
if (entry.isDirectory() && this.matchesPattern(entry.name, pattern)) {
|
|
244
|
+
const fullPath = path.join(searchDir, entry.name);
|
|
245
|
+
const relativePath = path.relative(context.cwd, fullPath);
|
|
246
|
+
candidates.push({
|
|
247
|
+
word: relativePath + '/',
|
|
248
|
+
type: 'directory',
|
|
249
|
+
description: 'Directory',
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch (_error) {
|
|
255
|
+
// Directory doesn't exist or not readable
|
|
256
|
+
}
|
|
257
|
+
return candidates;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Complete test command options
|
|
261
|
+
*/
|
|
262
|
+
async completeTestOptions(context) {
|
|
263
|
+
const testOptions = [
|
|
264
|
+
{ word: '-f', description: 'File exists and is regular file' },
|
|
265
|
+
{ word: '-d', description: 'File exists and is directory' },
|
|
266
|
+
{ word: '-e', description: 'File exists' },
|
|
267
|
+
{ word: '-r', description: 'File exists and is readable' },
|
|
268
|
+
{ word: '-w', description: 'File exists and is writable' },
|
|
269
|
+
{ word: '-x', description: 'File exists and is executable' },
|
|
270
|
+
{ word: '-s', description: 'File exists and has size > 0' },
|
|
271
|
+
{ word: '-z', description: 'String is empty' },
|
|
272
|
+
{ word: '-n', description: 'String is not empty' },
|
|
273
|
+
{ word: '=', description: 'Strings are equal' },
|
|
274
|
+
{ word: '!=', description: 'Strings are not equal' },
|
|
275
|
+
{ word: '-eq', description: 'Numbers are equal' },
|
|
276
|
+
{ word: '-ne', description: 'Numbers are not equal' },
|
|
277
|
+
{ word: '-lt', description: 'Number is less than' },
|
|
278
|
+
{ word: '-le', description: 'Number is less than or equal' },
|
|
279
|
+
{ word: '-gt', description: 'Number is greater than' },
|
|
280
|
+
{ word: '-ge', description: 'Number is greater than or equal' },
|
|
281
|
+
];
|
|
282
|
+
return testOptions.filter(opt => this.matchesPattern(opt.word, context.currentWord)).map(opt => ({
|
|
283
|
+
word: opt.word,
|
|
284
|
+
type: 'option',
|
|
285
|
+
description: opt.description,
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Complete job IDs (placeholder - would integrate with job manager)
|
|
290
|
+
*/
|
|
291
|
+
async completeJobIds(_context) {
|
|
292
|
+
// This would integrate with the job manager to get actual job IDs
|
|
293
|
+
return [
|
|
294
|
+
{ word: '1', description: 'Job ID 1' },
|
|
295
|
+
{ word: '2', description: 'Job ID 2' },
|
|
296
|
+
{ word: '3', description: 'Job ID 3' },
|
|
297
|
+
];
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Check if a pattern matches a string
|
|
301
|
+
*/
|
|
302
|
+
matchesPattern(str, pattern) {
|
|
303
|
+
if (!pattern)
|
|
304
|
+
return true;
|
|
305
|
+
// Simple case-insensitive prefix matching
|
|
306
|
+
return str.toLowerCase().startsWith(pattern.toLowerCase());
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Check if a file is executable
|
|
310
|
+
*/
|
|
311
|
+
isExecutable(filePath) {
|
|
312
|
+
try {
|
|
313
|
+
fs.accessSync(filePath, fs.constants.X_OK);
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Filter and sort completion candidates
|
|
322
|
+
*/
|
|
323
|
+
filterAndSortCandidates(candidates, _currentWord) {
|
|
324
|
+
// Remove duplicates
|
|
325
|
+
const unique = new Map();
|
|
326
|
+
for (const candidate of candidates) {
|
|
327
|
+
if (!unique.has(candidate.word)) {
|
|
328
|
+
unique.set(candidate.word, candidate);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Sort by type priority and alphabetically
|
|
332
|
+
const sorted = Array.from(unique.values()).sort((a, b) => {
|
|
333
|
+
const typeOrder = { directory: 0, file: 1, command: 2, variable: 3, function: 4, option: 5 };
|
|
334
|
+
const aOrder = typeOrder[a.type || 'file'];
|
|
335
|
+
const bOrder = typeOrder[b.type || 'file'];
|
|
336
|
+
if (aOrder !== bOrder) {
|
|
337
|
+
return aOrder - bOrder;
|
|
338
|
+
}
|
|
339
|
+
return a.word.localeCompare(b.word);
|
|
340
|
+
});
|
|
341
|
+
return sorted;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
export default CompletionSystem;
|