lsh-framework 1.1.0 → 1.2.1

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 (73) hide show
  1. package/README.md +70 -4
  2. package/dist/cli.js +104 -486
  3. package/dist/commands/doctor.js +427 -0
  4. package/dist/commands/init.js +371 -0
  5. package/dist/constants/api.js +94 -0
  6. package/dist/constants/commands.js +64 -0
  7. package/dist/constants/config.js +56 -0
  8. package/dist/constants/database.js +21 -0
  9. package/dist/constants/errors.js +79 -0
  10. package/dist/constants/index.js +28 -0
  11. package/dist/constants/paths.js +28 -0
  12. package/dist/constants/ui.js +73 -0
  13. package/dist/constants/validation.js +124 -0
  14. package/dist/daemon/lshd.js +11 -32
  15. package/dist/lib/daemon-client-helper.js +7 -4
  16. package/dist/lib/daemon-client.js +9 -2
  17. package/dist/lib/format-utils.js +163 -0
  18. package/dist/lib/job-manager.js +2 -1
  19. package/dist/lib/platform-utils.js +211 -0
  20. package/dist/lib/secrets-manager.js +11 -1
  21. package/dist/lib/string-utils.js +128 -0
  22. package/dist/services/daemon/daemon-registrar.js +3 -2
  23. package/dist/services/secrets/secrets.js +154 -30
  24. package/package.json +10 -74
  25. package/dist/app.js +0 -33
  26. package/dist/cicd/analytics.js +0 -261
  27. package/dist/cicd/auth.js +0 -269
  28. package/dist/cicd/cache-manager.js +0 -172
  29. package/dist/cicd/data-retention.js +0 -305
  30. package/dist/cicd/performance-monitor.js +0 -224
  31. package/dist/cicd/webhook-receiver.js +0 -640
  32. package/dist/commands/api.js +0 -346
  33. package/dist/commands/theme.js +0 -261
  34. package/dist/commands/zsh-import.js +0 -240
  35. package/dist/components/App.js +0 -1
  36. package/dist/components/Divider.js +0 -29
  37. package/dist/components/REPL.js +0 -43
  38. package/dist/components/Terminal.js +0 -232
  39. package/dist/components/UserInput.js +0 -30
  40. package/dist/daemon/api-server.js +0 -316
  41. package/dist/daemon/monitoring-api.js +0 -220
  42. package/dist/lib/api-error-handler.js +0 -185
  43. package/dist/lib/associative-arrays.js +0 -285
  44. package/dist/lib/base-api-server.js +0 -290
  45. package/dist/lib/brace-expansion.js +0 -160
  46. package/dist/lib/builtin-commands.js +0 -439
  47. package/dist/lib/executors/builtin-executor.js +0 -52
  48. package/dist/lib/extended-globbing.js +0 -411
  49. package/dist/lib/extended-parameter-expansion.js +0 -227
  50. package/dist/lib/interactive-shell.js +0 -460
  51. package/dist/lib/job-builtins.js +0 -582
  52. package/dist/lib/pathname-expansion.js +0 -216
  53. package/dist/lib/script-runner.js +0 -226
  54. package/dist/lib/shell-executor.js +0 -2504
  55. package/dist/lib/shell-parser.js +0 -958
  56. package/dist/lib/shell-types.js +0 -6
  57. package/dist/lib/shell.lib.js +0 -40
  58. package/dist/lib/theme-manager.js +0 -476
  59. package/dist/lib/variable-expansion.js +0 -385
  60. package/dist/lib/zsh-compatibility.js +0 -659
  61. package/dist/lib/zsh-import-manager.js +0 -707
  62. package/dist/lib/zsh-options.js +0 -328
  63. package/dist/pipeline/job-tracker.js +0 -491
  64. package/dist/pipeline/mcli-bridge.js +0 -309
  65. package/dist/pipeline/pipeline-service.js +0 -1119
  66. package/dist/pipeline/workflow-engine.js +0 -870
  67. package/dist/services/api/api.js +0 -58
  68. package/dist/services/api/auth.js +0 -35
  69. package/dist/services/api/config.js +0 -7
  70. package/dist/services/api/file.js +0 -22
  71. package/dist/services/shell/shell.js +0 -28
  72. package/dist/services/zapier.js +0 -16
  73. package/dist/simple-api-server.js +0 -148
@@ -1,6 +0,0 @@
1
- /**
2
- * Shell Type Definitions
3
- * Shared types used across shell execution modules
4
- * Extracted from shell-executor.ts to break circular dependencies
5
- */
6
- export {};
@@ -1,40 +0,0 @@
1
- import { exec, spawn } from 'child_process';
2
- import { promisify } from 'util';
3
- const execAsync = promisify(exec);
4
- // Execute shell command and return result
5
- export async function shell_exec(command) {
6
- try {
7
- const { stdout, stderr } = await execAsync(command);
8
- return { stdout: stdout.trim(), stderr: stderr.trim() };
9
- }
10
- catch (error) {
11
- return {
12
- stdout: '',
13
- stderr: '',
14
- error: error.message
15
- };
16
- }
17
- }
18
- // Execute shell command with streaming output
19
- export function shell_spawn(command, args = []) {
20
- return new Promise((resolve) => {
21
- const [cmd, ...defaultArgs] = command.split(' ');
22
- const finalArgs = args.length > 0 ? args : defaultArgs;
23
- const child = spawn(cmd, finalArgs);
24
- let stdout = '';
25
- let stderr = '';
26
- child.stdout.on('data', (data) => {
27
- stdout += data.toString();
28
- });
29
- child.stderr.on('data', (data) => {
30
- stderr += data.toString();
31
- });
32
- child.on('close', (code) => {
33
- resolve({
34
- stdout: stdout.trim(),
35
- stderr: stderr.trim(),
36
- code: code || 0
37
- });
38
- });
39
- });
40
- }
@@ -1,476 +0,0 @@
1
- /**
2
- * Theme Manager
3
- * Import and apply ZSH themes (Oh-My-Zsh, Powerlevel10k, custom)
4
- */
5
- import * as fs from 'fs';
6
- import * as path from 'path';
7
- import * as os from 'os';
8
- import chalk from 'chalk';
9
- export class ThemeManager {
10
- themesPath;
11
- customThemesPath;
12
- currentTheme = null;
13
- constructor() {
14
- this.themesPath = path.join(os.homedir(), '.oh-my-zsh', 'themes');
15
- this.customThemesPath = path.join(os.homedir(), '.lsh', 'themes');
16
- // Ensure custom themes directory exists
17
- if (!fs.existsSync(this.customThemesPath)) {
18
- fs.mkdirSync(this.customThemesPath, { recursive: true });
19
- }
20
- }
21
- /**
22
- * List available themes
23
- */
24
- listThemes() {
25
- const ohmyzsh = [];
26
- const custom = [];
27
- // Oh-My-Zsh themes
28
- if (fs.existsSync(this.themesPath)) {
29
- ohmyzsh.push(...fs.readdirSync(this.themesPath)
30
- .filter(f => f.endsWith('.zsh-theme'))
31
- .map(f => f.replace('.zsh-theme', '')));
32
- }
33
- // Custom themes
34
- if (fs.existsSync(this.customThemesPath)) {
35
- custom.push(...fs.readdirSync(this.customThemesPath)
36
- .filter(f => f.endsWith('.lsh-theme'))
37
- .map(f => f.replace('.lsh-theme', '')));
38
- }
39
- // Built-in LSH themes
40
- const builtin = [
41
- 'default',
42
- 'minimal',
43
- 'powerline',
44
- 'simple',
45
- 'git',
46
- 'robbyrussell', // Popular Oh-My-Zsh theme
47
- 'agnoster',
48
- ];
49
- return { ohmyzsh, custom, builtin };
50
- }
51
- /**
52
- * Import Oh-My-Zsh theme
53
- */
54
- async importOhMyZshTheme(themeName) {
55
- const themePath = path.join(this.themesPath, `${themeName}.zsh-theme`);
56
- if (!fs.existsSync(themePath)) {
57
- throw new Error(`Theme not found: ${themeName}`);
58
- }
59
- const themeContent = fs.readFileSync(themePath, 'utf8');
60
- const parsed = this.parseZshTheme(themeName, themeContent);
61
- // Convert to LSH format and save
62
- this.saveAsLshTheme(parsed);
63
- return parsed;
64
- }
65
- /**
66
- * Parse ZSH theme file
67
- */
68
- parseZshTheme(name, content) {
69
- const theme = {
70
- name,
71
- colors: new Map(),
72
- prompts: { left: '' },
73
- variables: new Map(),
74
- hooks: [],
75
- dependencies: [],
76
- };
77
- const lines = content.split('\n');
78
- for (const line of lines) {
79
- let trimmed = line.trim();
80
- // Skip comments and empty lines
81
- if (trimmed.startsWith('#') || trimmed === '') {
82
- continue;
83
- }
84
- // Strip inline comments (but preserve # inside quotes)
85
- const inlineCommentMatch = trimmed.match(/^([^#]*?["'][^"']*["'])\s*#/);
86
- if (inlineCommentMatch) {
87
- trimmed = inlineCommentMatch[1].trim();
88
- }
89
- else if (trimmed.includes('#')) {
90
- // Simple inline comment without quotes
91
- const parts = trimmed.split('#');
92
- if (parts[0].includes('=')) {
93
- trimmed = parts[0].trim();
94
- }
95
- }
96
- // Parse color definitions
97
- const colorMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)=["']?%\{.*?%F\{(\d+|[a-z]+)\}.*?\}["']?/);
98
- if (colorMatch) {
99
- theme.colors.set(colorMatch[1], colorMatch[2]);
100
- continue;
101
- }
102
- // Parse PROMPT
103
- const promptMatch = trimmed.match(/^PROMPT=["'](.+)["']$/);
104
- if (promptMatch) {
105
- theme.prompts.left = promptMatch[1];
106
- continue;
107
- }
108
- // Parse RPROMPT
109
- const rpromptMatch = trimmed.match(/^RPROMPT=["'](.+)["']$/);
110
- if (rpromptMatch) {
111
- theme.prompts.right = rpromptMatch[1];
112
- continue;
113
- }
114
- // Parse PS2 (continuation)
115
- const ps2Match = trimmed.match(/^PS2=["'](.+)["']$/);
116
- if (ps2Match) {
117
- theme.prompts.continuation = ps2Match[1];
118
- continue;
119
- }
120
- // Parse PS3 (select)
121
- const ps3Match = trimmed.match(/^PS3=["'](.+)["']$/);
122
- if (ps3Match) {
123
- theme.prompts.select = ps3Match[1];
124
- continue;
125
- }
126
- // Parse git formats (custom formats)
127
- if (trimmed.includes('FMT_BRANCH')) {
128
- const fmtMatch = trimmed.match(/FMT_BRANCH=["'](.+)["']$/);
129
- if (fmtMatch) {
130
- if (!theme.gitFormats)
131
- theme.gitFormats = {};
132
- theme.gitFormats.branch = fmtMatch[1];
133
- }
134
- }
135
- if (trimmed.includes('FMT_UNSTAGED')) {
136
- const fmtMatch = trimmed.match(/FMT_UNSTAGED=["'](.+)["']$/);
137
- if (fmtMatch) {
138
- if (!theme.gitFormats)
139
- theme.gitFormats = {};
140
- theme.gitFormats.unstaged = fmtMatch[1];
141
- }
142
- }
143
- if (trimmed.includes('FMT_STAGED')) {
144
- const fmtMatch = trimmed.match(/FMT_STAGED=["'](.+)["']$/);
145
- if (fmtMatch) {
146
- if (!theme.gitFormats)
147
- theme.gitFormats = {};
148
- theme.gitFormats.staged = fmtMatch[1];
149
- }
150
- }
151
- // Parse Oh-My-Zsh git prompt variables
152
- const gitPromptVars = {
153
- 'ZSH_THEME_GIT_PROMPT_PREFIX': 'prefix',
154
- 'ZSH_THEME_GIT_PROMPT_SUFFIX': 'suffix',
155
- 'ZSH_THEME_GIT_PROMPT_DIRTY': 'dirty',
156
- 'ZSH_THEME_GIT_PROMPT_CLEAN': 'clean',
157
- 'ZSH_THEME_GIT_PROMPT_UNTRACKED': 'untracked',
158
- };
159
- for (const [varName, formatKey] of Object.entries(gitPromptVars)) {
160
- if (trimmed.startsWith(varName)) {
161
- const match = trimmed.match(new RegExp(`${varName}=["'](.+?)["']$`));
162
- if (match) {
163
- if (!theme.gitFormats)
164
- theme.gitFormats = {};
165
- // Construct branch format from prefix/suffix if we have them
166
- if (formatKey === 'prefix' || formatKey === 'suffix') {
167
- const prefix = formatKey === 'prefix' ? match[1] : theme.gitFormats.prefix || '';
168
- const suffix = formatKey === 'suffix' ? match[1] : theme.gitFormats.suffix || '';
169
- if (prefix || suffix) {
170
- theme.gitFormats.branch = `${prefix}%b${suffix}`;
171
- }
172
- // Store the raw values too
173
- theme.gitFormats[formatKey] = match[1];
174
- }
175
- else {
176
- theme.gitFormats[formatKey] = match[1];
177
- }
178
- }
179
- }
180
- }
181
- // Parse variables (skip prompts, formats, and git theme variables)
182
- const varMatch = trimmed.match(/^([A-Z_][A-Z0-9_]*)=["']?(.+?)["']?$/);
183
- if (varMatch &&
184
- !trimmed.includes('PROMPT') &&
185
- !trimmed.includes('FMT_') &&
186
- !trimmed.startsWith('ZSH_THEME_GIT_PROMPT_') &&
187
- !trimmed.startsWith('PS2') &&
188
- !trimmed.startsWith('PS3')) {
189
- theme.variables.set(varMatch[1], varMatch[2]);
190
- }
191
- // Parse hooks
192
- if (trimmed.includes('add-zsh-hook')) {
193
- const hookMatch = trimmed.match(/add-zsh-hook\s+(\w+)\s+(\w+)/);
194
- if (hookMatch) {
195
- theme.hooks.push(`${hookMatch[1]}:${hookMatch[2]}`);
196
- }
197
- }
198
- // Detect dependencies
199
- if (trimmed.includes('vcs_info')) {
200
- if (!theme.dependencies.includes('vcs_info')) {
201
- theme.dependencies.push('vcs_info');
202
- }
203
- }
204
- if (trimmed.includes('git_branch') || trimmed.includes('git_prompt_info') || trimmed.includes('$(git ')) {
205
- if (!theme.dependencies.includes('git')) {
206
- theme.dependencies.push('git');
207
- }
208
- }
209
- if (trimmed.includes('virtualenv_prompt_info')) {
210
- if (!theme.dependencies.includes('virtualenv')) {
211
- theme.dependencies.push('virtualenv');
212
- }
213
- }
214
- if (trimmed.includes('ruby_prompt_info')) {
215
- if (!theme.dependencies.includes('ruby')) {
216
- theme.dependencies.push('ruby');
217
- }
218
- }
219
- }
220
- // Post-processing: Check prompts for dependencies
221
- const allPrompts = [
222
- theme.prompts.left,
223
- theme.prompts.right,
224
- theme.prompts.continuation,
225
- theme.prompts.select
226
- ].filter(Boolean).join(' ');
227
- if (allPrompts.includes('git_branch') || allPrompts.includes('git_prompt_info') || allPrompts.includes('$(git ')) {
228
- if (!theme.dependencies.includes('git')) {
229
- theme.dependencies.push('git');
230
- }
231
- }
232
- if (allPrompts.includes('virtualenv_prompt_info')) {
233
- if (!theme.dependencies.includes('virtualenv')) {
234
- theme.dependencies.push('virtualenv');
235
- }
236
- }
237
- if (allPrompts.includes('vcs_info')) {
238
- if (!theme.dependencies.includes('vcs_info')) {
239
- theme.dependencies.push('vcs_info');
240
- }
241
- }
242
- return theme;
243
- }
244
- /**
245
- * Convert ZSH prompt format to LSH format
246
- */
247
- convertPromptToLsh(zshPrompt, colors) {
248
- let lshPrompt = zshPrompt;
249
- // Convert color variables
250
- for (const [name, color] of colors.entries()) {
251
- // Map ZSH color codes to chalk colors
252
- const chalkColor = this.mapColorToChalk(color);
253
- lshPrompt = lshPrompt.replace(new RegExp(`\\$\\{${name}\\}`, 'g'), chalkColor);
254
- }
255
- // Convert ZSH prompt escapes to LSH equivalents
256
- const conversions = [
257
- [/%n/g, process.env.USER || 'user'], // username
258
- [/%m/g, os.hostname().split('.')[0]], // hostname (short)
259
- [/%M/g, os.hostname()], // hostname (full)
260
- [/%~|%d/g, '$(pwd | sed "s|^$HOME|~|")'], // current directory
261
- [/%\//g, '$(pwd)'], // current directory (full)
262
- [/%c/g, '$(basename "$(pwd)")'], // current directory (basename)
263
- [/%D/g, '$(date +"%m/%d/%y")'], // date
264
- [/%T/g, '$(date +"%H:%M")'], // time (24h)
265
- [/%t/g, '$(date +"%I:%M %p")'], // time (12h)
266
- [/%#/g, '\\$'], // # for root, $ for user
267
- [/%\{.*?reset_color.*?\}/g, chalk.reset('')], // reset color
268
- [/%\{.*?\}/g, ''], // remove other ZSH escapes
269
- ];
270
- for (const [pattern, replacement] of conversions) {
271
- lshPrompt = lshPrompt.replace(pattern, replacement);
272
- }
273
- // Handle git info
274
- if (lshPrompt.includes('$vcs_info_msg_0_')) {
275
- lshPrompt = lshPrompt.replace(/\$vcs_info_msg_0_/g, '$(git_prompt_info)');
276
- }
277
- // Handle virtualenv
278
- if (lshPrompt.includes('$(virtualenv_prompt_info)')) {
279
- lshPrompt = lshPrompt.replace(/\$\(virtualenv_prompt_info\)/g, '$([ -n "$VIRTUAL_ENV" ] && echo " ($(basename $VIRTUAL_ENV))")');
280
- }
281
- return lshPrompt;
282
- }
283
- /**
284
- * Map ZSH color code to chalk color
285
- */
286
- mapColorToChalk(zshColor) {
287
- // 256 color codes
288
- const colorMap = {
289
- '81': 'cyan',
290
- '166': 'yellow',
291
- '135': 'magenta',
292
- '161': 'red',
293
- '118': 'green',
294
- '208': 'yellow',
295
- '39': 'blue',
296
- '214': 'yellow',
297
- // Standard colors
298
- 'black': 'black',
299
- 'red': 'red',
300
- 'green': 'green',
301
- 'yellow': 'yellow',
302
- 'blue': 'blue',
303
- 'magenta': 'magenta',
304
- 'cyan': 'cyan',
305
- 'white': 'white',
306
- };
307
- const mapped = colorMap[zshColor] || 'white';
308
- return `\\[\\033[${this.getAnsiCode(mapped)}m\\]`;
309
- }
310
- /**
311
- * Get ANSI code for color name
312
- */
313
- getAnsiCode(color) {
314
- const codes = {
315
- 'black': '30',
316
- 'red': '31',
317
- 'green': '32',
318
- 'yellow': '33',
319
- 'blue': '34',
320
- 'magenta': '35',
321
- 'cyan': '36',
322
- 'white': '37',
323
- 'reset': '0',
324
- 'bold': '1',
325
- 'dim': '2',
326
- 'italic': '3',
327
- 'underline': '4',
328
- };
329
- return codes[color] || '37';
330
- }
331
- /**
332
- * Save theme in LSH format
333
- */
334
- saveAsLshTheme(theme) {
335
- try {
336
- const lshThemePath = path.join(this.customThemesPath, `${theme.name}.lsh-theme`);
337
- const lshThemeContent = {
338
- name: theme.name,
339
- prompts: {
340
- left: theme.prompts.left,
341
- right: theme.prompts.right,
342
- continuation: theme.prompts.continuation,
343
- select: theme.prompts.select,
344
- },
345
- colors: Object.fromEntries(theme.colors),
346
- gitFormats: theme.gitFormats,
347
- variables: Object.fromEntries(theme.variables),
348
- hooks: theme.hooks,
349
- dependencies: theme.dependencies,
350
- };
351
- fs.writeFileSync(lshThemePath, JSON.stringify(lshThemeContent, null, 2), 'utf8');
352
- }
353
- catch (error) {
354
- // Gracefully handle save errors (e.g., permission issues, invalid paths)
355
- console.error(`Failed to save theme ${theme.name}:`, error);
356
- }
357
- }
358
- /**
359
- * Apply theme
360
- */
361
- applyTheme(theme) {
362
- this.currentTheme = theme;
363
- // Generate prompt setting commands
364
- const commands = [];
365
- // Set main prompt
366
- if (theme.prompts.left) {
367
- const lshPrompt = this.convertPromptToLsh(theme.prompts.left, theme.colors);
368
- commands.push(`export LSH_PROMPT='${lshPrompt}'`);
369
- }
370
- // Set right prompt
371
- if (theme.prompts.right) {
372
- const lshRPrompt = this.convertPromptToLsh(theme.prompts.right, theme.colors);
373
- commands.push(`export LSH_RPROMPT='${lshRPrompt}'`);
374
- }
375
- return commands.join('\n');
376
- }
377
- /**
378
- * Get built-in theme
379
- */
380
- getBuiltinTheme(name) {
381
- const themes = {
382
- 'default': {
383
- name: 'default',
384
- colors: new Map([['blue', '34'], ['green', '32'], ['reset', '0']]),
385
- prompts: {
386
- left: chalk.blue('%n@%m') + ':' + chalk.green('%~') + '$ ',
387
- },
388
- variables: new Map(),
389
- hooks: [],
390
- dependencies: [],
391
- },
392
- 'minimal': {
393
- name: 'minimal',
394
- colors: new Map([['cyan', '36'], ['reset', '0']]),
395
- prompts: {
396
- left: chalk.cyan('%~') + ' ❯ ',
397
- },
398
- variables: new Map(),
399
- hooks: [],
400
- dependencies: [],
401
- },
402
- 'powerline': {
403
- name: 'powerline',
404
- colors: new Map([
405
- ['blue', '34'],
406
- ['white', '37'],
407
- ['green', '32'],
408
- ['yellow', '33'],
409
- ]),
410
- prompts: {
411
- left: chalk.bgBlue.white(' %n ') + '' + chalk.bgGreen.black(' %~ ') + '' + ' ',
412
- },
413
- variables: new Map(),
414
- hooks: [],
415
- dependencies: ['git'],
416
- },
417
- 'robbyrussell': {
418
- name: 'robbyrussell',
419
- colors: new Map([['cyan', '36'], ['red', '31'], ['green', '32']]),
420
- prompts: {
421
- left: chalk.cyan('➜ ') + chalk.cyan('%~') + ' $(git_prompt_info)',
422
- },
423
- gitFormats: {
424
- branch: chalk.red(' git:') + chalk.green('(%b)'),
425
- },
426
- variables: new Map(),
427
- hooks: [],
428
- dependencies: ['git'],
429
- },
430
- 'simple': {
431
- name: 'simple',
432
- colors: new Map([['reset', '0']]),
433
- prompts: {
434
- left: '%~ $ ',
435
- },
436
- variables: new Map(),
437
- hooks: [],
438
- dependencies: [],
439
- },
440
- };
441
- if (!themes[name]) {
442
- throw new Error(`Theme not found: ${name}`);
443
- }
444
- return themes[name];
445
- }
446
- /**
447
- * Preview theme without applying
448
- */
449
- previewTheme(theme) {
450
- console.log(chalk.bold(`\n📦 Theme: ${theme.name}\n`));
451
- // Show prompt
452
- console.log(chalk.dim('Prompt:'));
453
- const examplePrompt = this.convertPromptToLsh(theme.prompts.left, theme.colors)
454
- .replace('$(pwd | sed "s|^$HOME|~|")', '~/projects/my-app')
455
- .replace('$(git_prompt_info)', ' git:(main)');
456
- console.log(' ' + examplePrompt);
457
- if (theme.prompts.right) {
458
- console.log(chalk.dim('\nRight Prompt:'));
459
- console.log(' ' + this.convertPromptToLsh(theme.prompts.right, theme.colors));
460
- }
461
- // Show colors
462
- if (theme.colors.size > 0) {
463
- console.log(chalk.dim('\nColors:'));
464
- for (const [name, code] of theme.colors.entries()) {
465
- const color = this.mapColorToChalk(code);
466
- console.log(` ${name}: ${color}${name}${chalk.reset('')}`);
467
- }
468
- }
469
- // Show dependencies
470
- if (theme.dependencies.length > 0) {
471
- console.log(chalk.dim('\nDependencies:'));
472
- theme.dependencies.forEach(dep => console.log(` - ${dep}`));
473
- }
474
- console.log('');
475
- }
476
- }