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,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging Framework
|
|
3
|
+
* Centralized logging utility with support for different log levels,
|
|
4
|
+
* structured logging, and environment-based configuration.
|
|
5
|
+
*/
|
|
6
|
+
export var LogLevel;
|
|
7
|
+
(function (LogLevel) {
|
|
8
|
+
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
|
9
|
+
LogLevel[LogLevel["INFO"] = 1] = "INFO";
|
|
10
|
+
LogLevel[LogLevel["WARN"] = 2] = "WARN";
|
|
11
|
+
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
|
|
12
|
+
LogLevel[LogLevel["NONE"] = 4] = "NONE";
|
|
13
|
+
})(LogLevel || (LogLevel = {}));
|
|
14
|
+
/**
|
|
15
|
+
* ANSI color codes for terminal output
|
|
16
|
+
*/
|
|
17
|
+
const colors = {
|
|
18
|
+
reset: '\x1b[0m',
|
|
19
|
+
bright: '\x1b[1m',
|
|
20
|
+
dim: '\x1b[2m',
|
|
21
|
+
// Foreground colors
|
|
22
|
+
black: '\x1b[30m',
|
|
23
|
+
red: '\x1b[31m',
|
|
24
|
+
green: '\x1b[32m',
|
|
25
|
+
yellow: '\x1b[33m',
|
|
26
|
+
blue: '\x1b[34m',
|
|
27
|
+
magenta: '\x1b[35m',
|
|
28
|
+
cyan: '\x1b[36m',
|
|
29
|
+
white: '\x1b[37m',
|
|
30
|
+
// Background colors
|
|
31
|
+
bgBlack: '\x1b[40m',
|
|
32
|
+
bgRed: '\x1b[41m',
|
|
33
|
+
bgGreen: '\x1b[42m',
|
|
34
|
+
bgYellow: '\x1b[43m',
|
|
35
|
+
bgBlue: '\x1b[44m',
|
|
36
|
+
bgMagenta: '\x1b[45m',
|
|
37
|
+
bgCyan: '\x1b[46m',
|
|
38
|
+
bgWhite: '\x1b[47m',
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Get log level from environment variable
|
|
42
|
+
*/
|
|
43
|
+
function getLogLevelFromEnv() {
|
|
44
|
+
const level = process.env.LSH_LOG_LEVEL?.toUpperCase();
|
|
45
|
+
switch (level) {
|
|
46
|
+
case 'DEBUG':
|
|
47
|
+
return LogLevel.DEBUG;
|
|
48
|
+
case 'INFO':
|
|
49
|
+
return LogLevel.INFO;
|
|
50
|
+
case 'WARN':
|
|
51
|
+
return LogLevel.WARN;
|
|
52
|
+
case 'ERROR':
|
|
53
|
+
return LogLevel.ERROR;
|
|
54
|
+
case 'NONE':
|
|
55
|
+
return LogLevel.NONE;
|
|
56
|
+
default:
|
|
57
|
+
return process.env.NODE_ENV === 'production' ? LogLevel.INFO : LogLevel.DEBUG;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Logger class for structured logging
|
|
62
|
+
*/
|
|
63
|
+
export class Logger {
|
|
64
|
+
config;
|
|
65
|
+
constructor(config) {
|
|
66
|
+
this.config = {
|
|
67
|
+
level: config?.level ?? getLogLevelFromEnv(),
|
|
68
|
+
enableTimestamp: config?.enableTimestamp ?? true,
|
|
69
|
+
enableColors: config?.enableColors ?? (!process.env.NO_COLOR && process.stdout.isTTY),
|
|
70
|
+
enableJSON: config?.enableJSON ?? (process.env.LSH_LOG_FORMAT === 'json'),
|
|
71
|
+
context: config?.context,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create a child logger with a specific context
|
|
76
|
+
*/
|
|
77
|
+
child(context) {
|
|
78
|
+
return new Logger({
|
|
79
|
+
...this.config,
|
|
80
|
+
context: this.config.context ? `${this.config.context}:${context}` : context,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Set log level dynamically
|
|
85
|
+
*/
|
|
86
|
+
setLevel(level) {
|
|
87
|
+
this.config.level = level;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if a log level is enabled
|
|
91
|
+
*/
|
|
92
|
+
isLevelEnabled(level) {
|
|
93
|
+
return level >= this.config.level;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Format timestamp
|
|
97
|
+
*/
|
|
98
|
+
formatTimestamp() {
|
|
99
|
+
const now = new Date();
|
|
100
|
+
return now.toISOString();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get level name string
|
|
104
|
+
*/
|
|
105
|
+
getLevelName(level) {
|
|
106
|
+
switch (level) {
|
|
107
|
+
case LogLevel.DEBUG:
|
|
108
|
+
return 'DEBUG';
|
|
109
|
+
case LogLevel.INFO:
|
|
110
|
+
return 'INFO';
|
|
111
|
+
case LogLevel.WARN:
|
|
112
|
+
return 'WARN';
|
|
113
|
+
case LogLevel.ERROR:
|
|
114
|
+
return 'ERROR';
|
|
115
|
+
default:
|
|
116
|
+
return 'UNKNOWN';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get color for log level
|
|
121
|
+
*/
|
|
122
|
+
getLevelColor(level) {
|
|
123
|
+
switch (level) {
|
|
124
|
+
case LogLevel.DEBUG:
|
|
125
|
+
return colors.cyan;
|
|
126
|
+
case LogLevel.INFO:
|
|
127
|
+
return colors.green;
|
|
128
|
+
case LogLevel.WARN:
|
|
129
|
+
return colors.yellow;
|
|
130
|
+
case LogLevel.ERROR:
|
|
131
|
+
return colors.red;
|
|
132
|
+
default:
|
|
133
|
+
return colors.white;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Format log entry as JSON
|
|
138
|
+
*/
|
|
139
|
+
formatJSON(entry) {
|
|
140
|
+
const obj = {
|
|
141
|
+
timestamp: entry.timestamp.toISOString(),
|
|
142
|
+
level: this.getLevelName(entry.level),
|
|
143
|
+
message: entry.message,
|
|
144
|
+
};
|
|
145
|
+
if (entry.context) {
|
|
146
|
+
obj.context = entry.context;
|
|
147
|
+
}
|
|
148
|
+
if (entry.metadata && Object.keys(entry.metadata).length > 0) {
|
|
149
|
+
obj.metadata = entry.metadata;
|
|
150
|
+
}
|
|
151
|
+
if (entry.error) {
|
|
152
|
+
obj.error = {
|
|
153
|
+
name: entry.error.name,
|
|
154
|
+
message: entry.error.message,
|
|
155
|
+
stack: entry.error.stack,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return JSON.stringify(obj);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Format log entry as text
|
|
162
|
+
*/
|
|
163
|
+
formatText(entry) {
|
|
164
|
+
const parts = [];
|
|
165
|
+
// Timestamp
|
|
166
|
+
if (this.config.enableTimestamp) {
|
|
167
|
+
const timestamp = this.formatTimestamp();
|
|
168
|
+
parts.push(this.config.enableColors ? `${colors.dim}${timestamp}${colors.reset}` : timestamp);
|
|
169
|
+
}
|
|
170
|
+
// Level
|
|
171
|
+
const levelName = this.getLevelName(entry.level);
|
|
172
|
+
const levelColor = this.getLevelColor(entry.level);
|
|
173
|
+
const formattedLevel = this.config.enableColors
|
|
174
|
+
? `${levelColor}${levelName.padEnd(5)}${colors.reset}`
|
|
175
|
+
: levelName.padEnd(5);
|
|
176
|
+
parts.push(formattedLevel);
|
|
177
|
+
// Context
|
|
178
|
+
if (entry.context) {
|
|
179
|
+
const formattedContext = this.config.enableColors
|
|
180
|
+
? `${colors.magenta}[${entry.context}]${colors.reset}`
|
|
181
|
+
: `[${entry.context}]`;
|
|
182
|
+
parts.push(formattedContext);
|
|
183
|
+
}
|
|
184
|
+
// Message
|
|
185
|
+
parts.push(entry.message);
|
|
186
|
+
// Metadata
|
|
187
|
+
if (entry.metadata && Object.keys(entry.metadata).length > 0) {
|
|
188
|
+
const metadataStr = JSON.stringify(entry.metadata);
|
|
189
|
+
const formattedMetadata = this.config.enableColors
|
|
190
|
+
? `${colors.dim}${metadataStr}${colors.reset}`
|
|
191
|
+
: metadataStr;
|
|
192
|
+
parts.push(formattedMetadata);
|
|
193
|
+
}
|
|
194
|
+
return parts.join(' ');
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Core logging method
|
|
198
|
+
*/
|
|
199
|
+
log(level, message, metadata, error) {
|
|
200
|
+
if (!this.isLevelEnabled(level)) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const entry = {
|
|
204
|
+
timestamp: new Date(),
|
|
205
|
+
level,
|
|
206
|
+
message,
|
|
207
|
+
context: this.config.context,
|
|
208
|
+
metadata,
|
|
209
|
+
error,
|
|
210
|
+
};
|
|
211
|
+
const output = this.config.enableJSON
|
|
212
|
+
? this.formatJSON(entry)
|
|
213
|
+
: this.formatText(entry);
|
|
214
|
+
// Output to appropriate stream
|
|
215
|
+
if (level >= LogLevel.ERROR) {
|
|
216
|
+
console.error(output);
|
|
217
|
+
if (error?.stack) {
|
|
218
|
+
console.error(error.stack);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else if (level >= LogLevel.WARN) {
|
|
222
|
+
console.warn(output);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
// INFO and DEBUG go to stdout
|
|
226
|
+
// Using console.log here is intentional for the logger itself
|
|
227
|
+
// eslint-disable-next-line no-console
|
|
228
|
+
console.log(output);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Debug level logging
|
|
233
|
+
*/
|
|
234
|
+
debug(message, metadata) {
|
|
235
|
+
this.log(LogLevel.DEBUG, message, metadata);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Info level logging
|
|
239
|
+
*/
|
|
240
|
+
info(message, metadata) {
|
|
241
|
+
this.log(LogLevel.INFO, message, metadata);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Warning level logging
|
|
245
|
+
*/
|
|
246
|
+
warn(message, metadata) {
|
|
247
|
+
this.log(LogLevel.WARN, message, metadata);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Error level logging
|
|
251
|
+
*/
|
|
252
|
+
error(message, error, metadata) {
|
|
253
|
+
const err = error instanceof Error ? error : undefined;
|
|
254
|
+
const errorMetadata = error && !(error instanceof Error) ? { error } : undefined;
|
|
255
|
+
this.log(LogLevel.ERROR, message, { ...metadata, ...errorMetadata }, err);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Default logger instance
|
|
260
|
+
*/
|
|
261
|
+
export const logger = new Logger();
|
|
262
|
+
/**
|
|
263
|
+
* Create a logger with a specific context
|
|
264
|
+
*/
|
|
265
|
+
export function createLogger(context, config) {
|
|
266
|
+
return new Logger({
|
|
267
|
+
...config,
|
|
268
|
+
context,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Export for default usage
|
|
273
|
+
*/
|
|
274
|
+
export default logger;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .lshrc Initialization
|
|
3
|
+
* Creates and manages the user's .lshrc configuration file
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
export class LshrcManager {
|
|
12
|
+
lshrcPath;
|
|
13
|
+
constructor(lshrcPath) {
|
|
14
|
+
this.lshrcPath = lshrcPath || path.join(os.homedir(), '.lshrc');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Initialize .lshrc if it doesn't exist
|
|
18
|
+
*/
|
|
19
|
+
initialize(options = {}) {
|
|
20
|
+
try {
|
|
21
|
+
// Check if .lshrc already exists
|
|
22
|
+
if (fs.existsSync(this.lshrcPath)) {
|
|
23
|
+
return false; // Already exists, no action needed
|
|
24
|
+
}
|
|
25
|
+
if (!options.createIfMissing) {
|
|
26
|
+
return false; // Don't create if not requested
|
|
27
|
+
}
|
|
28
|
+
// Copy template to user's home directory
|
|
29
|
+
const templatePath = path.join(__dirname, '../../templates/.lshrc.template');
|
|
30
|
+
if (!fs.existsSync(templatePath)) {
|
|
31
|
+
// Template not found, create basic .lshrc
|
|
32
|
+
this.createBasicLshrc(options);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
// Copy template
|
|
36
|
+
let content = fs.readFileSync(templatePath, 'utf8');
|
|
37
|
+
// Enable auto-import if requested
|
|
38
|
+
if (options.autoImportZsh) {
|
|
39
|
+
const importCmd = `zsh-source${options.importOptions ? ' ' + options.importOptions.join(' ') : ''}`;
|
|
40
|
+
content = content.replace('# zsh-source', importCmd);
|
|
41
|
+
}
|
|
42
|
+
fs.writeFileSync(this.lshrcPath, content, 'utf8');
|
|
43
|
+
console.log(`✅ Created ${this.lshrcPath}`);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error(`Failed to initialize .lshrc: ${error.message}`);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create basic .lshrc without template
|
|
53
|
+
*/
|
|
54
|
+
createBasicLshrc(options) {
|
|
55
|
+
const content = `# LSH Configuration File
|
|
56
|
+
# Location: ${this.lshrcPath}
|
|
57
|
+
|
|
58
|
+
${options.autoImportZsh ? `# Auto-import ZSH configurations
|
|
59
|
+
zsh-source${options.importOptions ? ' ' + options.importOptions.join(' ') : ''}
|
|
60
|
+
` : '# Uncomment to auto-import ZSH configurations\n# zsh-source\n'}
|
|
61
|
+
|
|
62
|
+
# Add your aliases, functions, and environment variables here
|
|
63
|
+
`;
|
|
64
|
+
fs.writeFileSync(this.lshrcPath, content, 'utf8');
|
|
65
|
+
console.log(`✅ Created ${this.lshrcPath}`);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if .lshrc exists
|
|
69
|
+
*/
|
|
70
|
+
exists() {
|
|
71
|
+
return fs.existsSync(this.lshrcPath);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Source .lshrc commands
|
|
75
|
+
*/
|
|
76
|
+
async source(_executor) {
|
|
77
|
+
if (!this.exists()) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const content = fs.readFileSync(this.lshrcPath, 'utf8');
|
|
82
|
+
const commands = [];
|
|
83
|
+
for (const line of content.split('\n')) {
|
|
84
|
+
const trimmed = line.trim();
|
|
85
|
+
// Skip comments and empty lines
|
|
86
|
+
if (trimmed.startsWith('#') || trimmed === '') {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
commands.push(trimmed);
|
|
90
|
+
}
|
|
91
|
+
return commands;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.error(`Failed to source .lshrc: ${error.message}`);
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Enable auto-import in existing .lshrc
|
|
100
|
+
*/
|
|
101
|
+
enableAutoImport(options = []) {
|
|
102
|
+
try {
|
|
103
|
+
if (!this.exists()) {
|
|
104
|
+
// Create new .lshrc with auto-import enabled
|
|
105
|
+
this.initialize({ autoImportZsh: true, importOptions: options, createIfMissing: true });
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
let content = fs.readFileSync(this.lshrcPath, 'utf8');
|
|
109
|
+
// Check if auto-import is already enabled
|
|
110
|
+
if (content.includes('zsh-source') && !content.match(/^#.*zsh-source/m)) {
|
|
111
|
+
console.log('Auto-import is already enabled in .lshrc');
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// Add auto-import configuration
|
|
115
|
+
const autoImportBlock = `
|
|
116
|
+
# ZSH Auto-Import (added by LSH)
|
|
117
|
+
zsh-source${options.length > 0 ? ' ' + options.join(' ') : ''}
|
|
118
|
+
`;
|
|
119
|
+
// Find the ZSH import section or add at the top
|
|
120
|
+
if (content.includes('# ZSH IMPORT CONFIGURATION')) {
|
|
121
|
+
content = content.replace(/# zsh-source/, `zsh-source${options.length > 0 ? ' ' + options.join(' ') : ''}`);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
content = autoImportBlock + '\n' + content;
|
|
125
|
+
}
|
|
126
|
+
fs.writeFileSync(this.lshrcPath, content, 'utf8');
|
|
127
|
+
console.log('✅ Auto-import enabled in .lshrc');
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error(`Failed to enable auto-import: ${error.message}`);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Disable auto-import in .lshrc
|
|
137
|
+
*/
|
|
138
|
+
disableAutoImport() {
|
|
139
|
+
try {
|
|
140
|
+
if (!this.exists()) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
let content = fs.readFileSync(this.lshrcPath, 'utf8');
|
|
144
|
+
// Comment out zsh-source lines
|
|
145
|
+
content = content.replace(/^(zsh-source.*)$/gm, '# $1');
|
|
146
|
+
// Remove auto-import blocks
|
|
147
|
+
content = content.replace(/# ZSH Auto-Import[\s\S]*?(?=\n#|\n\n|$)/g, '');
|
|
148
|
+
fs.writeFileSync(this.lshrcPath, content, 'utf8');
|
|
149
|
+
console.log('✅ Auto-import disabled in .lshrc');
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error(`Failed to disable auto-import: ${error.message}`);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get .lshrc path
|
|
159
|
+
*/
|
|
160
|
+
getPath() {
|
|
161
|
+
return this.lshrcPath;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Initialize .lshrc on first run
|
|
166
|
+
*/
|
|
167
|
+
export function initializeLshrc(options = {}) {
|
|
168
|
+
const manager = new LshrcManager();
|
|
169
|
+
return manager.initialize(options);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Check if .lshrc exists
|
|
173
|
+
*/
|
|
174
|
+
export function lshrcExists() {
|
|
175
|
+
const manager = new LshrcManager();
|
|
176
|
+
return manager.exists();
|
|
177
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POSIX Pathname Expansion Implementation
|
|
3
|
+
* Implements POSIX.1-2017 Section 2.13 Pathname Expansion (Globbing)
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
export class PathnameExpander {
|
|
8
|
+
cwd;
|
|
9
|
+
constructor(cwd = process.cwd()) {
|
|
10
|
+
this.cwd = cwd;
|
|
11
|
+
}
|
|
12
|
+
async expandPathnames(pattern, options = {}) {
|
|
13
|
+
// If no glob characters, return as-is
|
|
14
|
+
if (!this.containsGlobChars(pattern)) {
|
|
15
|
+
return [pattern];
|
|
16
|
+
}
|
|
17
|
+
// Handle tilde expansion first
|
|
18
|
+
const expandedPattern = this.expandTilde(pattern);
|
|
19
|
+
// Perform pathname expansion
|
|
20
|
+
return this.glob(expandedPattern, options);
|
|
21
|
+
}
|
|
22
|
+
containsGlobChars(str) {
|
|
23
|
+
// Check for unescaped glob characters
|
|
24
|
+
return /[*?[\]{}~]/.test(str);
|
|
25
|
+
}
|
|
26
|
+
expandTilde(pattern) {
|
|
27
|
+
if (pattern.startsWith('~/')) {
|
|
28
|
+
const homeDir = process.env.HOME || '/';
|
|
29
|
+
return path.join(homeDir, pattern.slice(2));
|
|
30
|
+
}
|
|
31
|
+
if (pattern === '~') {
|
|
32
|
+
return process.env.HOME || '/';
|
|
33
|
+
}
|
|
34
|
+
return pattern;
|
|
35
|
+
}
|
|
36
|
+
async glob(pattern, options) {
|
|
37
|
+
const baseCwd = options.cwd || this.cwd;
|
|
38
|
+
// Handle absolute vs relative paths
|
|
39
|
+
const isAbsolute = path.isAbsolute(pattern);
|
|
40
|
+
const searchBase = isAbsolute ? '/' : baseCwd;
|
|
41
|
+
const relativePath = isAbsolute ? pattern.slice(1) : pattern;
|
|
42
|
+
// Split pattern into segments
|
|
43
|
+
const segments = relativePath.split('/').filter(seg => seg.length > 0);
|
|
44
|
+
if (segments.length === 0) {
|
|
45
|
+
return [searchBase];
|
|
46
|
+
}
|
|
47
|
+
// Start recursive matching
|
|
48
|
+
const results = await this.matchSegments(searchBase, segments, options);
|
|
49
|
+
// Sort results for consistent output
|
|
50
|
+
return results.sort();
|
|
51
|
+
}
|
|
52
|
+
async matchSegments(currentPath, remainingSegments, options) {
|
|
53
|
+
if (remainingSegments.length === 0) {
|
|
54
|
+
return [currentPath];
|
|
55
|
+
}
|
|
56
|
+
const [currentSegment, ...restSegments] = remainingSegments;
|
|
57
|
+
const results = [];
|
|
58
|
+
try {
|
|
59
|
+
const entries = await fs.promises.readdir(currentPath, { withFileTypes: true });
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
// Skip hidden files unless explicitly included
|
|
62
|
+
if (!options.includeHidden && entry.name.startsWith('.')) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Check if this entry matches the current segment pattern
|
|
66
|
+
if (this.matchSegment(entry.name, currentSegment)) {
|
|
67
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
68
|
+
if (restSegments.length === 0) {
|
|
69
|
+
// This is the final segment, add to results
|
|
70
|
+
results.push(fullPath);
|
|
71
|
+
}
|
|
72
|
+
else if (entry.isDirectory()) {
|
|
73
|
+
// Recurse into directory for remaining segments
|
|
74
|
+
const subResults = await this.matchSegments(fullPath, restSegments, options);
|
|
75
|
+
results.push(...subResults);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (_error) {
|
|
81
|
+
// Directory doesn't exist or not readable, return empty
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
matchSegment(filename, pattern) {
|
|
87
|
+
// Convert glob pattern to regex
|
|
88
|
+
const regex = this.patternToRegex(pattern);
|
|
89
|
+
return regex.test(filename);
|
|
90
|
+
}
|
|
91
|
+
patternToRegex(pattern) {
|
|
92
|
+
let regexStr = '';
|
|
93
|
+
let i = 0;
|
|
94
|
+
while (i < pattern.length) {
|
|
95
|
+
const char = pattern[i];
|
|
96
|
+
switch (char) {
|
|
97
|
+
case '*':
|
|
98
|
+
regexStr += '.*';
|
|
99
|
+
break;
|
|
100
|
+
case '?':
|
|
101
|
+
regexStr += '.';
|
|
102
|
+
break;
|
|
103
|
+
case '[': {
|
|
104
|
+
// Character class
|
|
105
|
+
const closeIdx = this.findClosingBracket(pattern, i);
|
|
106
|
+
if (closeIdx === -1) {
|
|
107
|
+
// Invalid bracket, treat as literal
|
|
108
|
+
regexStr += '\\[';
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
let charClass = pattern.slice(i + 1, closeIdx);
|
|
112
|
+
// Handle negation
|
|
113
|
+
if (charClass.startsWith('!') || charClass.startsWith('^')) {
|
|
114
|
+
charClass = '^' + charClass.slice(1);
|
|
115
|
+
}
|
|
116
|
+
// Handle character ranges and classes
|
|
117
|
+
charClass = this.processCharacterClass(charClass);
|
|
118
|
+
regexStr += '[' + charClass + ']';
|
|
119
|
+
i = closeIdx;
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case '{': {
|
|
124
|
+
// Brace expansion - simplified implementation
|
|
125
|
+
const braceEnd = this.findClosingBrace(pattern, i);
|
|
126
|
+
if (braceEnd === -1) {
|
|
127
|
+
regexStr += '\\{';
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const braceContent = pattern.slice(i + 1, braceEnd);
|
|
131
|
+
const alternatives = braceContent.split(',');
|
|
132
|
+
regexStr += '(' + alternatives.map(alt => this.escapeRegex(alt)).join('|') + ')';
|
|
133
|
+
i = braceEnd;
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case '\\':
|
|
138
|
+
// Escape next character
|
|
139
|
+
if (i + 1 < pattern.length) {
|
|
140
|
+
regexStr += '\\' + pattern[i + 1];
|
|
141
|
+
i++;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
regexStr += '\\\\';
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
default:
|
|
148
|
+
// Literal character - escape regex special chars
|
|
149
|
+
regexStr += this.escapeRegex(char);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
i++;
|
|
153
|
+
}
|
|
154
|
+
return new RegExp('^' + regexStr + '$');
|
|
155
|
+
}
|
|
156
|
+
findClosingBracket(str, startIdx) {
|
|
157
|
+
let depth = 1;
|
|
158
|
+
for (let i = startIdx + 1; i < str.length; i++) {
|
|
159
|
+
if (str[i] === '[')
|
|
160
|
+
depth++;
|
|
161
|
+
else if (str[i] === ']') {
|
|
162
|
+
depth--;
|
|
163
|
+
if (depth === 0)
|
|
164
|
+
return i;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return -1;
|
|
168
|
+
}
|
|
169
|
+
findClosingBrace(str, startIdx) {
|
|
170
|
+
let depth = 1;
|
|
171
|
+
for (let i = startIdx + 1; i < str.length; i++) {
|
|
172
|
+
if (str[i] === '{')
|
|
173
|
+
depth++;
|
|
174
|
+
else if (str[i] === '}') {
|
|
175
|
+
depth--;
|
|
176
|
+
if (depth === 0)
|
|
177
|
+
return i;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return -1;
|
|
181
|
+
}
|
|
182
|
+
processCharacterClass(charClass) {
|
|
183
|
+
// Handle POSIX character classes like [:alpha:]
|
|
184
|
+
charClass = charClass.replace(/\[:alpha:\]/g, 'a-zA-Z');
|
|
185
|
+
charClass = charClass.replace(/\[:digit:\]/g, '0-9');
|
|
186
|
+
charClass = charClass.replace(/\[:alnum:\]/g, 'a-zA-Z0-9');
|
|
187
|
+
charClass = charClass.replace(/\[:lower:\]/g, 'a-z');
|
|
188
|
+
charClass = charClass.replace(/\[:upper:\]/g, 'A-Z');
|
|
189
|
+
charClass = charClass.replace(/\[:space:\]/g, ' \\t\\n\\r\\f\\v');
|
|
190
|
+
charClass = charClass.replace(/\[:blank:\]/g, ' \\t');
|
|
191
|
+
charClass = charClass.replace(/\[:punct:\]/g, '!-/:-@\\[-`{-~');
|
|
192
|
+
charClass = charClass.replace(/\[:cntrl:\]/g, '\\x00-\\x1F\\x7F');
|
|
193
|
+
charClass = charClass.replace(/\[:print:\]/g, '\\x20-\\x7E');
|
|
194
|
+
charClass = charClass.replace(/\[:graph:\]/g, '\\x21-\\x7E');
|
|
195
|
+
charClass = charClass.replace(/\[:xdigit:\]/g, '0-9A-Fa-f');
|
|
196
|
+
return charClass;
|
|
197
|
+
}
|
|
198
|
+
escapeRegex(str) {
|
|
199
|
+
return str.replace(/[.+^$()|[\]{}\\]/g, '\\$&');
|
|
200
|
+
}
|
|
201
|
+
// Utility method for expanding multiple patterns
|
|
202
|
+
async expandMultiplePatterns(patterns, options = {}) {
|
|
203
|
+
const results = [];
|
|
204
|
+
for (const pattern of patterns) {
|
|
205
|
+
const expanded = await this.expandPathnames(pattern, options);
|
|
206
|
+
results.push(...expanded);
|
|
207
|
+
}
|
|
208
|
+
// Remove duplicates and sort
|
|
209
|
+
return [...new Set(results)].sort();
|
|
210
|
+
}
|
|
211
|
+
// Check if a pattern would match any files (useful for error reporting)
|
|
212
|
+
async hasMatches(pattern, options = {}) {
|
|
213
|
+
const matches = await this.expandPathnames(pattern, options);
|
|
214
|
+
return matches.length > 0 && matches[0] !== pattern;
|
|
215
|
+
}
|
|
216
|
+
}
|