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.
Files changed (90) hide show
  1. package/.env.example +51 -0
  2. package/README.md +399 -0
  3. package/dist/app.js +33 -0
  4. package/dist/cicd/analytics.js +261 -0
  5. package/dist/cicd/auth.js +269 -0
  6. package/dist/cicd/cache-manager.js +172 -0
  7. package/dist/cicd/data-retention.js +305 -0
  8. package/dist/cicd/performance-monitor.js +224 -0
  9. package/dist/cicd/webhook-receiver.js +634 -0
  10. package/dist/cli.js +500 -0
  11. package/dist/commands/api.js +343 -0
  12. package/dist/commands/self.js +318 -0
  13. package/dist/commands/theme.js +257 -0
  14. package/dist/commands/zsh-import.js +240 -0
  15. package/dist/components/App.js +1 -0
  16. package/dist/components/Divider.js +29 -0
  17. package/dist/components/REPL.js +43 -0
  18. package/dist/components/Terminal.js +232 -0
  19. package/dist/components/UserInput.js +30 -0
  20. package/dist/daemon/api-server.js +315 -0
  21. package/dist/daemon/job-registry.js +554 -0
  22. package/dist/daemon/lshd.js +822 -0
  23. package/dist/daemon/monitoring-api.js +220 -0
  24. package/dist/examples/supabase-integration.js +106 -0
  25. package/dist/lib/api-error-handler.js +183 -0
  26. package/dist/lib/associative-arrays.js +285 -0
  27. package/dist/lib/base-api-server.js +290 -0
  28. package/dist/lib/base-command-registrar.js +286 -0
  29. package/dist/lib/base-job-manager.js +293 -0
  30. package/dist/lib/brace-expansion.js +160 -0
  31. package/dist/lib/builtin-commands.js +439 -0
  32. package/dist/lib/cloud-config-manager.js +347 -0
  33. package/dist/lib/command-validator.js +190 -0
  34. package/dist/lib/completion-system.js +344 -0
  35. package/dist/lib/cron-job-manager.js +364 -0
  36. package/dist/lib/daemon-client-helper.js +141 -0
  37. package/dist/lib/daemon-client.js +501 -0
  38. package/dist/lib/database-persistence.js +638 -0
  39. package/dist/lib/database-schema.js +259 -0
  40. package/dist/lib/enhanced-history-system.js +246 -0
  41. package/dist/lib/env-validator.js +265 -0
  42. package/dist/lib/executors/builtin-executor.js +52 -0
  43. package/dist/lib/extended-globbing.js +411 -0
  44. package/dist/lib/extended-parameter-expansion.js +227 -0
  45. package/dist/lib/floating-point-arithmetic.js +256 -0
  46. package/dist/lib/history-system.js +245 -0
  47. package/dist/lib/interactive-shell.js +460 -0
  48. package/dist/lib/job-builtins.js +580 -0
  49. package/dist/lib/job-manager.js +386 -0
  50. package/dist/lib/job-storage-database.js +156 -0
  51. package/dist/lib/job-storage-memory.js +73 -0
  52. package/dist/lib/logger.js +274 -0
  53. package/dist/lib/lshrc-init.js +177 -0
  54. package/dist/lib/pathname-expansion.js +216 -0
  55. package/dist/lib/prompt-system.js +328 -0
  56. package/dist/lib/script-runner.js +226 -0
  57. package/dist/lib/secrets-manager.js +193 -0
  58. package/dist/lib/shell-executor.js +2504 -0
  59. package/dist/lib/shell-parser.js +958 -0
  60. package/dist/lib/shell-types.js +6 -0
  61. package/dist/lib/shell.lib.js +40 -0
  62. package/dist/lib/supabase-client.js +58 -0
  63. package/dist/lib/theme-manager.js +476 -0
  64. package/dist/lib/variable-expansion.js +385 -0
  65. package/dist/lib/zsh-compatibility.js +658 -0
  66. package/dist/lib/zsh-import-manager.js +699 -0
  67. package/dist/lib/zsh-options.js +328 -0
  68. package/dist/pipeline/job-tracker.js +491 -0
  69. package/dist/pipeline/mcli-bridge.js +302 -0
  70. package/dist/pipeline/pipeline-service.js +1116 -0
  71. package/dist/pipeline/workflow-engine.js +867 -0
  72. package/dist/services/api/api.js +58 -0
  73. package/dist/services/api/auth.js +35 -0
  74. package/dist/services/api/config.js +7 -0
  75. package/dist/services/api/file.js +22 -0
  76. package/dist/services/cron/cron-registrar.js +235 -0
  77. package/dist/services/cron/cron.js +9 -0
  78. package/dist/services/daemon/daemon-registrar.js +565 -0
  79. package/dist/services/daemon/daemon.js +9 -0
  80. package/dist/services/lib/lib.js +86 -0
  81. package/dist/services/log-file-extractor.js +170 -0
  82. package/dist/services/secrets/secrets.js +94 -0
  83. package/dist/services/shell/shell.js +28 -0
  84. package/dist/services/supabase/supabase-registrar.js +367 -0
  85. package/dist/services/supabase/supabase.js +9 -0
  86. package/dist/services/zapier.js +16 -0
  87. package/dist/simple-api-server.js +148 -0
  88. package/dist/store/store.js +31 -0
  89. package/dist/util/lib.util.js +11 -0
  90. package/package.json +144 -0
@@ -0,0 +1,343 @@
1
+ /**
2
+ * API Server Commands for LSH
3
+ */
4
+ import chalk from 'chalk';
5
+ import crypto from 'crypto';
6
+ import DaemonClient from '../lib/daemon-client.js';
7
+ export function registerApiCommands(program) {
8
+ const api = program
9
+ .command('api')
10
+ .description('API server management and configuration');
11
+ // Start daemon with API server enabled
12
+ api
13
+ .command('start')
14
+ .description('Start daemon with API server enabled')
15
+ .option('-p, --port <port>', 'API port', '3030')
16
+ .option('-k, --api-key <key>', 'API key (generated if not provided)')
17
+ .option('--webhooks', 'Enable webhook support', false)
18
+ .action(async (options) => {
19
+ try {
20
+ const apiKey = options.apiKey || crypto.randomBytes(32).toString('hex');
21
+ const daemonClient = new DaemonClient();
22
+ // Check if daemon is already running
23
+ if (daemonClient.isDaemonRunning()) {
24
+ console.log(chalk.yellow('⚠️ Daemon is already running. Restarting with API enabled...'));
25
+ // Stop existing daemon
26
+ await daemonClient.connect();
27
+ await daemonClient.stopDaemon();
28
+ await new Promise(resolve => setTimeout(resolve, 1000));
29
+ }
30
+ // Write API configuration to environment or a config file
31
+ process.env.LSH_API_ENABLED = 'true';
32
+ process.env.LSH_API_PORT = options.port;
33
+ process.env.LSH_API_KEY = apiKey;
34
+ process.env.LSH_ENABLE_WEBHOOKS = options.webhooks ? 'true' : 'false';
35
+ // Restart the daemon which will pick up the environment variables
36
+ await daemonClient.restartDaemon();
37
+ console.log(chalk.green('✅ Daemon started with API server'));
38
+ console.log(chalk.blue(`\n📡 API Server: http://localhost:${options.port}`));
39
+ console.log(chalk.yellow(`🔑 API Key: ${apiKey}`));
40
+ console.log(chalk.gray('\nStore this API key securely. You will need it to authenticate API requests.'));
41
+ console.log(chalk.gray(`\nExample usage:`));
42
+ console.log(chalk.gray(` curl -H "X-API-Key: ${apiKey}" http://localhost:${options.port}/api/status`));
43
+ }
44
+ catch (error) {
45
+ console.error(chalk.red(`❌ Failed to start API server: ${error.message}`));
46
+ process.exit(1);
47
+ }
48
+ });
49
+ // Generate API key
50
+ api
51
+ .command('key')
52
+ .description('Generate a new API key')
53
+ .action(() => {
54
+ const apiKey = crypto.randomBytes(32).toString('hex');
55
+ console.log(chalk.green('🔑 Generated API Key:'));
56
+ console.log(apiKey);
57
+ console.log(chalk.gray('\nSet this as LSH_API_KEY environment variable:'));
58
+ console.log(chalk.gray(` export LSH_API_KEY="${apiKey}"`));
59
+ });
60
+ // Test API connection
61
+ api
62
+ .command('test')
63
+ .description('Test API server connection')
64
+ .option('-p, --port <port>', 'API port', '3030')
65
+ .option('-k, --api-key <key>', 'API key')
66
+ .action(async (options) => {
67
+ try {
68
+ const apiKey = options.apiKey || process.env.LSH_API_KEY;
69
+ if (!apiKey) {
70
+ console.error(chalk.red('❌ API key required. Use --api-key or set LSH_API_KEY environment variable'));
71
+ process.exit(1);
72
+ }
73
+ const response = await fetch(`http://localhost:${options.port}/api/status`, {
74
+ headers: {
75
+ 'X-API-Key': apiKey
76
+ }
77
+ });
78
+ if (response.ok) {
79
+ const status = await response.json();
80
+ console.log(chalk.green('✅ API server is running'));
81
+ console.log('Status:', JSON.stringify(status, null, 2));
82
+ }
83
+ else {
84
+ console.error(chalk.red(`❌ API server returned ${response.status}: ${response.statusText}`));
85
+ }
86
+ }
87
+ catch (error) {
88
+ console.error(chalk.red(`❌ Failed to connect to API server: ${error.message}`));
89
+ console.log(chalk.yellow('Make sure the daemon is running with API enabled:'));
90
+ console.log(chalk.gray(' lsh api start'));
91
+ }
92
+ });
93
+ // Configure webhooks
94
+ api
95
+ .command('webhook')
96
+ .description('Configure webhook endpoints')
97
+ .argument('<action>', 'Action: add, list, remove')
98
+ .argument('[endpoint]', 'Webhook endpoint URL')
99
+ .option('-p, --port <port>', 'API port', '3030')
100
+ .option('-k, --api-key <key>', 'API key')
101
+ .action(async (action, endpoint, options) => {
102
+ try {
103
+ const apiKey = options.apiKey || process.env.LSH_API_KEY;
104
+ if (!apiKey) {
105
+ console.error(chalk.red('❌ API key required'));
106
+ process.exit(1);
107
+ }
108
+ const baseUrl = `http://localhost:${options.port}`;
109
+ switch (action) {
110
+ case 'add': {
111
+ if (!endpoint) {
112
+ console.error(chalk.red('❌ Endpoint URL required'));
113
+ process.exit(1);
114
+ }
115
+ const addResponse = await fetch(`${baseUrl}/api/webhooks`, {
116
+ method: 'POST',
117
+ headers: {
118
+ 'X-API-Key': apiKey,
119
+ 'Content-Type': 'application/json'
120
+ },
121
+ body: JSON.stringify({ endpoint })
122
+ });
123
+ if (addResponse.ok) {
124
+ const result = await addResponse.json();
125
+ console.log(chalk.green('✅ Webhook added successfully'));
126
+ console.log('Endpoints:', result.endpoints);
127
+ }
128
+ else {
129
+ console.error(chalk.red('❌ Failed to add webhook'));
130
+ }
131
+ break;
132
+ }
133
+ case 'list': {
134
+ const listResponse = await fetch(`${baseUrl}/api/webhooks`, {
135
+ headers: { 'X-API-Key': apiKey }
136
+ });
137
+ if (listResponse.ok) {
138
+ const webhooks = await listResponse.json();
139
+ console.log(chalk.blue('📮 Webhook Configuration:'));
140
+ console.log(`Enabled: ${webhooks.enabled}`);
141
+ console.log('Endpoints:', webhooks.endpoints);
142
+ }
143
+ break;
144
+ }
145
+ default:
146
+ console.error(chalk.red(`❌ Unknown action: ${action}`));
147
+ console.log('Valid actions: add, list, remove');
148
+ }
149
+ }
150
+ catch (error) {
151
+ console.error(chalk.red(`❌ Failed: ${error.message}`));
152
+ }
153
+ });
154
+ // Example client code generator
155
+ api
156
+ .command('example')
157
+ .description('Generate example client code')
158
+ .option('-l, --language <lang>', 'Language (js, python, curl)', 'js')
159
+ .option('-p, --port <port>', 'API port', '3030')
160
+ .action((options) => {
161
+ const apiKey = process.env.LSH_API_KEY || 'YOUR_API_KEY';
162
+ switch (options.language) {
163
+ case 'js':
164
+ console.log(chalk.blue('// JavaScript Example Client\n'));
165
+ console.log(`const LSHClient = {
166
+ baseURL: 'http://localhost:${options.port}',
167
+ apiKey: '${apiKey}',
168
+
169
+ async request(path, options = {}) {
170
+ const response = await fetch(\`\${this.baseURL}\${path}\`, {
171
+ ...options,
172
+ headers: {
173
+ 'X-API-Key': this.apiKey,
174
+ 'Content-Type': 'application/json',
175
+ ...options.headers
176
+ }
177
+ });
178
+
179
+ if (!response.ok) {
180
+ throw new Error(\`API error: \${response.statusText}\`);
181
+ }
182
+
183
+ return response.json();
184
+ },
185
+
186
+ // Get daemon status
187
+ async getStatus() {
188
+ return this.request('/api/status');
189
+ },
190
+
191
+ // List all jobs
192
+ async listJobs() {
193
+ return this.request('/api/jobs');
194
+ },
195
+
196
+ // Create a new job
197
+ async createJob(jobSpec) {
198
+ return this.request('/api/jobs', {
199
+ method: 'POST',
200
+ body: JSON.stringify(jobSpec)
201
+ });
202
+ },
203
+
204
+ // Trigger job execution
205
+ async triggerJob(jobId) {
206
+ return this.request(\`/api/jobs/\${jobId}/trigger\`, {
207
+ method: 'POST'
208
+ });
209
+ },
210
+
211
+ // Stream events using EventSource
212
+ streamEvents() {
213
+ const eventSource = new EventSource(\`\${this.baseURL}/api/events\`);
214
+
215
+ eventSource.onmessage = (event) => {
216
+ const data = JSON.parse(event.data);
217
+ console.log('Event:', data);
218
+ };
219
+
220
+ eventSource.onerror = (error) => {
221
+ console.error('EventSource error:', error);
222
+ };
223
+
224
+ return eventSource;
225
+ }
226
+ };
227
+
228
+ // Example usage
229
+ (async () => {
230
+ try {
231
+ const status = await LSHClient.getStatus();
232
+ console.log('Daemon status:', status);
233
+
234
+ // Create a job
235
+ const job = await LSHClient.createJob({
236
+ name: 'Example Job',
237
+ command: 'echo "Hello from API"',
238
+ type: 'shell'
239
+ });
240
+ console.log('Created job:', job);
241
+
242
+ // Trigger the job
243
+ const result = await LSHClient.triggerJob(job.id);
244
+ console.log('Job result:', result);
245
+
246
+ } catch (error) {
247
+ console.error('Error:', error);
248
+ }
249
+ })();`);
250
+ break;
251
+ case 'python':
252
+ console.log(chalk.blue('# Python Example Client\n'));
253
+ console.log(`import requests
254
+ import json
255
+ from typing import Dict, Any, Optional
256
+
257
+ class LSHClient:
258
+ def __init__(self, base_url: str = "http://localhost:${options.port}", api_key: str = "${apiKey}"):
259
+ self.base_url = base_url
260
+ self.api_key = api_key
261
+ self.headers = {
262
+ "X-API-Key": api_key,
263
+ "Content-Type": "application/json"
264
+ }
265
+
266
+ def request(self, method: str, path: str, data: Optional[Dict] = None) -> Dict[str, Any]:
267
+ """Make an API request"""
268
+ url = f"{self.base_url}{path}"
269
+ response = requests.request(method, url, headers=self.headers, json=data)
270
+ response.raise_for_status()
271
+ return response.json()
272
+
273
+ def get_status(self) -> Dict[str, Any]:
274
+ """Get daemon status"""
275
+ return self.request("GET", "/api/status")
276
+
277
+ def list_jobs(self) -> list:
278
+ """List all jobs"""
279
+ return self.request("GET", "/api/jobs")
280
+
281
+ def create_job(self, job_spec: Dict[str, Any]) -> Dict[str, Any]:
282
+ """Create a new job"""
283
+ return self.request("POST", "/api/jobs", job_spec)
284
+
285
+ def trigger_job(self, job_id: str) -> Dict[str, Any]:
286
+ """Trigger job execution"""
287
+ return self.request("POST", f"/api/jobs/{job_id}/trigger")
288
+
289
+ # Example usage
290
+ if __name__ == "__main__":
291
+ client = LSHClient()
292
+
293
+ # Get status
294
+ status = client.get_status()
295
+ print(f"Daemon status: {json.dumps(status, indent=2)}")
296
+
297
+ # Create a job
298
+ job = client.create_job({
299
+ "name": "Example Job",
300
+ "command": "echo 'Hello from Python'",
301
+ "type": "shell"
302
+ })
303
+ print(f"Created job: {job}")
304
+
305
+ # Trigger the job
306
+ result = client.trigger_job(job["id"])
307
+ print(f"Job result: {result}")`);
308
+ break;
309
+ case 'curl':
310
+ console.log(chalk.blue('# cURL Examples\n'));
311
+ console.log(`# Get daemon status
312
+ curl -H "X-API-Key: ${apiKey}" \\
313
+ http://localhost:${options.port}/api/status
314
+
315
+ # List all jobs
316
+ curl -H "X-API-Key: ${apiKey}" \\
317
+ http://localhost:${options.port}/api/jobs
318
+
319
+ # Create a new job
320
+ curl -X POST \\
321
+ -H "X-API-Key: ${apiKey}" \\
322
+ -H "Content-Type: application/json" \\
323
+ -d '{
324
+ "name": "Example Job",
325
+ "command": "echo Hello World",
326
+ "type": "shell"
327
+ }' \\
328
+ http://localhost:${options.port}/api/jobs
329
+
330
+ # Trigger a job
331
+ curl -X POST \\
332
+ -H "X-API-Key: ${apiKey}" \\
333
+ http://localhost:${options.port}/api/jobs/JOB_ID/trigger
334
+
335
+ # Stream events
336
+ curl -H "X-API-Key: ${apiKey}" \\
337
+ -H "Accept: text/event-stream" \\
338
+ http://localhost:${options.port}/api/events`);
339
+ break;
340
+ }
341
+ });
342
+ }
343
+ export default registerApiCommands;
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Self-management commands for LSH
3
+ * Provides utilities for updating and maintaining the CLI
4
+ */
5
+ import { Command } from 'commander';
6
+ import * as https from 'https';
7
+ import { spawn } from 'child_process';
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import chalk from 'chalk';
11
+ import { fileURLToPath } from 'url';
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ const selfCommand = new Command('self');
15
+ selfCommand.description('Manage and update the LSH application');
16
+ /**
17
+ * Parse version string to tuple for comparison
18
+ */
19
+ function parseVersion(version) {
20
+ return version
21
+ .replace(/^v/, '')
22
+ .split('.')
23
+ .map(x => parseInt(x) || 0);
24
+ }
25
+ /**
26
+ * Compare two version strings
27
+ * Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2
28
+ */
29
+ function compareVersions(v1, v2) {
30
+ const parts1 = parseVersion(v1);
31
+ const parts2 = parseVersion(v2);
32
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
33
+ const p1 = parts1[i] || 0;
34
+ const p2 = parts2[i] || 0;
35
+ if (p1 < p2)
36
+ return -1;
37
+ if (p1 > p2)
38
+ return 1;
39
+ }
40
+ return 0;
41
+ }
42
+ /**
43
+ * Get current version from package.json
44
+ */
45
+ function getCurrentVersion() {
46
+ try {
47
+ const packageJsonPath = path.join(__dirname, '../../package.json');
48
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
49
+ return packageJson.version || 'unknown';
50
+ }
51
+ catch {
52
+ return 'unknown';
53
+ }
54
+ }
55
+ /**
56
+ * Fetch latest version from npm registry
57
+ */
58
+ async function fetchLatestVersion() {
59
+ return new Promise((resolve, reject) => {
60
+ const options = {
61
+ hostname: 'registry.npmjs.org',
62
+ port: 443,
63
+ path: '/gwicho38-lsh',
64
+ method: 'GET',
65
+ headers: {
66
+ 'User-Agent': 'lsh-cli',
67
+ },
68
+ };
69
+ https.get(options, (res) => {
70
+ let data = '';
71
+ res.on('data', (chunk) => {
72
+ data += chunk;
73
+ });
74
+ res.on('end', () => {
75
+ try {
76
+ if (res.statusCode === 200) {
77
+ const npmData = JSON.parse(data);
78
+ const latestVersion = npmData['dist-tags']?.latest;
79
+ if (latestVersion) {
80
+ const publishedAt = npmData.time?.[latestVersion];
81
+ resolve({
82
+ version: latestVersion,
83
+ publishedAt: publishedAt || undefined,
84
+ });
85
+ }
86
+ else {
87
+ resolve(null);
88
+ }
89
+ }
90
+ else {
91
+ console.error(chalk.red(`✗ npm registry returned status ${res.statusCode}`));
92
+ resolve(null);
93
+ }
94
+ }
95
+ catch (error) {
96
+ reject(error);
97
+ }
98
+ });
99
+ }).on('error', (error) => {
100
+ reject(error);
101
+ });
102
+ });
103
+ }
104
+ /**
105
+ * Check GitHub Actions CI status for a specific version tag
106
+ */
107
+ async function checkCIStatus(_version) {
108
+ return new Promise((resolve) => {
109
+ const options = {
110
+ hostname: 'api.github.com',
111
+ port: 443,
112
+ path: `/repos/gwicho38/lsh/actions/runs?per_page=5`,
113
+ method: 'GET',
114
+ headers: {
115
+ 'User-Agent': 'lsh-cli',
116
+ 'Accept': 'application/vnd.github.v3+json',
117
+ },
118
+ };
119
+ https.get(options, (res) => {
120
+ let data = '';
121
+ res.on('data', (chunk) => {
122
+ data += chunk;
123
+ });
124
+ res.on('end', () => {
125
+ try {
126
+ if (res.statusCode === 200) {
127
+ const ghData = JSON.parse(data);
128
+ const runs = ghData.workflow_runs || [];
129
+ // Find the most recent workflow run for main branch
130
+ const mainRuns = runs.filter((run) => run.head_branch === 'main' && run.status === 'completed');
131
+ if (mainRuns.length > 0) {
132
+ const latestRun = mainRuns[0];
133
+ const passing = latestRun.conclusion === 'success';
134
+ resolve({
135
+ passing,
136
+ url: latestRun.html_url,
137
+ });
138
+ }
139
+ else {
140
+ // No completed runs found, assume passing
141
+ resolve({ passing: true });
142
+ }
143
+ }
144
+ else {
145
+ // If we can't check CI, don't block the update
146
+ resolve({ passing: true });
147
+ }
148
+ }
149
+ catch (_error) {
150
+ // On error, don't block the update
151
+ resolve({ passing: true });
152
+ }
153
+ });
154
+ }).on('error', () => {
155
+ // On network error, don't block the update
156
+ resolve({ passing: true });
157
+ });
158
+ });
159
+ }
160
+ /**
161
+ * Update command - check for and install updates from npm
162
+ */
163
+ selfCommand
164
+ .command('update')
165
+ .description('Check for and install LSH updates from npm')
166
+ .option('--check', 'Only check for updates, don\'t install')
167
+ .option('-y, --yes', 'Skip confirmation prompt')
168
+ .option('--skip-ci-check', 'Skip CI status check and install anyway')
169
+ .action(async (options) => {
170
+ try {
171
+ const currentVersion = getCurrentVersion();
172
+ console.log(chalk.cyan('Current version:'), currentVersion);
173
+ console.log(chalk.cyan('Checking npm for updates...'));
174
+ // Fetch latest version from npm
175
+ const latestInfo = await fetchLatestVersion();
176
+ if (!latestInfo) {
177
+ console.log(chalk.red('✗ Failed to fetch version information from npm'));
178
+ console.log(chalk.yellow('⚠ Make sure you have internet connectivity'));
179
+ return;
180
+ }
181
+ const { version: latestVersion, publishedAt } = latestInfo;
182
+ console.log(chalk.cyan('Latest version:'), latestVersion);
183
+ if (publishedAt) {
184
+ const date = new Date(publishedAt);
185
+ console.log(chalk.dim(` Published: ${date.toLocaleDateString()}`));
186
+ }
187
+ // Compare versions
188
+ const comparison = compareVersions(currentVersion, latestVersion);
189
+ if (comparison === 0) {
190
+ console.log(chalk.green('✓ You\'re already on the latest version!'));
191
+ return;
192
+ }
193
+ if (comparison > 0) {
194
+ console.log(chalk.green(`✓ Your version (${currentVersion}) is newer than npm`));
195
+ console.log(chalk.dim(' You may be using a development version'));
196
+ return;
197
+ }
198
+ // Update available
199
+ console.log(chalk.yellow(`⬆ Update available: ${currentVersion} → ${latestVersion}`));
200
+ if (options.check) {
201
+ console.log(chalk.cyan('ℹ Run \'lsh self update\' to install the update'));
202
+ return;
203
+ }
204
+ // Ask for confirmation unless --yes flag is used
205
+ if (!options.yes) {
206
+ const readline = await import('readline');
207
+ const rl = readline.createInterface({
208
+ input: process.stdin,
209
+ output: process.stdout,
210
+ });
211
+ const answer = await new Promise((resolve) => {
212
+ rl.question(chalk.yellow(`Install lsh ${latestVersion}? (y/N) `), (ans) => {
213
+ rl.close();
214
+ resolve(ans);
215
+ });
216
+ });
217
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
218
+ console.log(chalk.yellow('Update cancelled'));
219
+ return;
220
+ }
221
+ }
222
+ // Check CI status before installing (unless skipped)
223
+ if (!options.skipCiCheck) {
224
+ console.log(chalk.cyan('🔍 Checking CI status...'));
225
+ const ciStatus = await checkCIStatus(latestVersion);
226
+ if (!ciStatus.passing) {
227
+ console.log(chalk.red('✗ CI build is failing for the latest version'));
228
+ if (ciStatus.url) {
229
+ console.log(chalk.yellow(` View CI status: ${ciStatus.url}`));
230
+ }
231
+ console.log(chalk.yellow('⚠ Update blocked to prevent installing a broken version'));
232
+ console.log(chalk.dim(' Use --skip-ci-check to install anyway (not recommended)'));
233
+ return;
234
+ }
235
+ else {
236
+ console.log(chalk.green('✓ CI build is passing'));
237
+ }
238
+ }
239
+ // Install update
240
+ console.log(chalk.cyan(`📦 Installing lsh ${latestVersion}...`));
241
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
242
+ const updateProcess = spawn(npmCmd, ['install', '-g', 'gwicho38-lsh@latest'], {
243
+ stdio: 'inherit',
244
+ });
245
+ updateProcess.on('close', (code) => {
246
+ if (code === 0) {
247
+ console.log(chalk.green(`✓ Successfully updated to lsh ${latestVersion}!`));
248
+ console.log(chalk.yellow('ℹ Restart your terminal or run \'hash -r\' to use the new version'));
249
+ }
250
+ else {
251
+ console.log(chalk.red('✗ Update failed'));
252
+ console.log(chalk.yellow('ℹ Try running with sudo: sudo npm install -g gwicho38-lsh@latest'));
253
+ }
254
+ });
255
+ }
256
+ catch (error) {
257
+ console.error(chalk.red('✗ Error during update:'), error);
258
+ }
259
+ });
260
+ /**
261
+ * Version command - show detailed version information
262
+ */
263
+ selfCommand
264
+ .command('version')
265
+ .description('Show detailed version information')
266
+ .action(() => {
267
+ const currentVersion = getCurrentVersion();
268
+ console.log(chalk.cyan('╔════════════════════════════════════╗'));
269
+ console.log(chalk.cyan('║ LSH Version Info ║'));
270
+ console.log(chalk.cyan('╚════════════════════════════════════╝'));
271
+ console.log();
272
+ console.log(chalk.cyan('Version:'), currentVersion);
273
+ console.log(chalk.cyan('Node:'), process.version);
274
+ console.log(chalk.cyan('Platform:'), `${process.platform} (${process.arch})`);
275
+ console.log();
276
+ console.log(chalk.dim('Run \'lsh self update --check\' to check for updates'));
277
+ });
278
+ /**
279
+ * Info command - show installation and configuration info
280
+ */
281
+ selfCommand
282
+ .command('info')
283
+ .description('Show installation and configuration information')
284
+ .action(() => {
285
+ const currentVersion = getCurrentVersion();
286
+ console.log(chalk.cyan('╔════════════════════════════════════╗'));
287
+ console.log(chalk.cyan('║ LSH Installation Info ║'));
288
+ console.log(chalk.cyan('╚════════════════════════════════════╝'));
289
+ console.log();
290
+ // Version info
291
+ console.log(chalk.yellow('Version Information:'));
292
+ console.log(' LSH Version:', currentVersion);
293
+ console.log(' Node.js:', process.version);
294
+ console.log(' Platform:', `${process.platform} (${process.arch})`);
295
+ console.log();
296
+ // Installation paths
297
+ console.log(chalk.yellow('Installation:'));
298
+ console.log(' Executable:', process.execPath);
299
+ console.log(' Working Dir:', process.cwd());
300
+ console.log();
301
+ // Environment
302
+ console.log(chalk.yellow('Environment:'));
303
+ console.log(' NODE_ENV:', process.env.NODE_ENV || 'not set');
304
+ console.log(' HOME:', process.env.HOME || 'not set');
305
+ console.log(' USER:', process.env.USER || 'not set');
306
+ console.log();
307
+ // Configuration
308
+ const envFile = path.join(process.cwd(), '.env');
309
+ const envExists = fs.existsSync(envFile);
310
+ console.log(chalk.yellow('Configuration:'));
311
+ console.log(' .env file:', envExists ? chalk.green('Found') : chalk.red('Not found'));
312
+ if (!envExists) {
313
+ console.log(chalk.dim(' Copy .env.example to .env to configure'));
314
+ }
315
+ console.log();
316
+ console.log(chalk.dim('For more info, visit: https://github.com/gwicho38/lsh'));
317
+ });
318
+ export default selfCommand;