bunosh 0.4.13 → 0.5.0

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/src/program.js CHANGED
@@ -5,6 +5,7 @@ const traverse = traverseDefault.default || traverseDefault;
5
5
  import color from "chalk";
6
6
  import { readFileSync, existsSync, writeFileSync } from 'fs';
7
7
  import { yell } from './io.js';
8
+ import { formatError } from './error-formatter.js';
8
9
  import cprint from "./font.js";
9
10
  import { handleCompletion, detectCurrentShell, installCompletion, getCompletionPaths } from './completion.js';
10
11
  import { upgradeCommand } from './upgrade.js';
@@ -32,30 +33,25 @@ export const banner = () => {
32
33
  } catch (e) {
33
34
  }
34
35
  console.log(color.gray('🍲 ', color.yellowBright.bold('BUNOSH'), color.yellow(version)));
35
-
36
36
  console.log();
37
37
  };
38
38
 
39
39
  function createGradientAscii(asciiArt) {
40
40
  const lines = asciiArt.split('\n');
41
41
 
42
- // Yellow RGB (255, 220, 0) to Brown RGB (139, 69, 19)
43
42
  const startColor = { r: 255, g: 220, b: 0 };
44
43
  const endColor = { r: 139, g: 69, b: 19 };
45
44
 
46
45
  return lines.map((line, index) => {
47
- // Block characters should always be brown
48
46
  if (line.includes('░') || line.includes('▒') || line.includes('▓')) {
49
47
  return `\x1b[38;2;139;69;19m${line}\x1b[0m`;
50
48
  }
51
49
 
52
- // Create smooth gradient for other characters
53
50
  const progress = index / (lines.length - 1);
54
51
  const r = Math.round(startColor.r + (endColor.r - startColor.r) * progress);
55
52
  const g = Math.round(startColor.g + (endColor.g - startColor.g) * progress);
56
53
  const b = Math.round(startColor.b + (endColor.b - startColor.b) * progress);
57
54
 
58
- // Use true color escape sequence
59
55
  return `\x1b[38;2;${r};${g};${b}m${line}\x1b[0m`;
60
56
  }).join('\n');
61
57
  }
@@ -67,10 +63,9 @@ export default async function bunosh(commands, sources) {
67
63
 
68
64
  const internalCommands = [];
69
65
 
70
-
66
+
71
67
  program.configureHelp({
72
68
  commandDescription: _cmd => {
73
- // Show banner and description
74
69
  banner();
75
70
  return ' ';
76
71
  },
@@ -79,28 +74,23 @@ export default async function bunosh(commands, sources) {
79
74
  visibleGlobalOptions: _opt => [],
80
75
  visibleOptions: _opt => [],
81
76
  visibleCommands: cmd => {
82
- // Hide all commands from default listing - we'll show them in custom sections
83
77
  return [];
84
78
  },
85
79
  subcommandTerm: (cmd) => color.white.bold(cmd.name()),
86
80
  subcommandDescription: (cmd) => color.gray(cmd.description()),
87
81
  });
88
82
 
89
- // Don't show global help after error - individual commands will show their own help
90
83
  // program.showHelpAfterError();
91
84
  program.showSuggestionAfterError(true);
92
85
  program.addHelpCommand(false);
93
86
 
94
- // Override the program's error output formatting
95
87
  program.configureOutput({
96
88
  writeErr: (str) => {
97
- // Replace "error:" with red "Error:" in error output
98
89
  process.stderr.write(str.replace(/^error:/g, color.red('Error') + ':'));
99
90
  },
100
91
  writeOut: (str) => process.stdout.write(str)
101
92
  });
102
93
 
103
- // Parse AST and comments for each source
104
94
  const comments = {};
105
95
  const namespaceSources = {};
106
96
 
@@ -115,7 +105,6 @@ export default async function bunosh(commands, sources) {
115
105
  attachComment: true,
116
106
  });
117
107
 
118
- // Store AST for this command
119
108
  if (!namespaceSources[cmdInfo.namespace || '']) {
120
109
  namespaceSources[cmdInfo.namespace || ''] = {
121
110
  ast: ast,
@@ -123,27 +112,22 @@ export default async function bunosh(commands, sources) {
123
112
  };
124
113
  }
125
114
 
126
- // Extract comments for this command
127
115
  const fnName = cmdInfo.namespace ? cmdName.split(':')[1] : cmdName;
128
116
  if (fnName) {
129
117
  comments[cmdName] = extractCommentForFunction(ast, cmdInfo.source, fnName);
130
118
  }
131
119
  } catch (parseError) {
132
- // Re-throw with more specific error information
133
120
  parseError.code = 'BABEL_PARSER_SYNTAX_ERROR';
134
121
  throw parseError;
135
122
  }
136
123
  }
137
124
  }
138
125
 
139
- // Collect all commands (bunosh + namespace commands + npm scripts) and sort them
140
126
  const allCommands = [];
141
127
 
142
- // Add bunosh commands (including namespaced ones)
143
128
  Object.keys(commands).forEach((cmdName) => {
144
129
  const sourceInfo = sources[cmdName];
145
130
  if (sourceInfo && sourceInfo.namespace) {
146
- // This is a namespaced command
147
131
  allCommands.push({
148
132
  type: 'namespace',
149
133
  name: cmdName,
@@ -151,26 +135,21 @@ export default async function bunosh(commands, sources) {
151
135
  data: commands[cmdName]
152
136
  });
153
137
  } else {
154
- // Regular bunosh command
155
138
  allCommands.push({ type: 'bunosh', name: cmdName, data: commands[cmdName] });
156
139
  }
157
140
  });
158
141
 
159
142
 
160
-
161
- // Sort all commands alphabetically by name
143
+
162
144
  allCommands.sort((a, b) => a.name.localeCompare(b.name));
163
145
 
164
- // Organize commands by namespace for help display
165
146
  const commandsByNamespace = {
166
- '': [], // Main commands (no namespace)
167
- 'dev': [], // Dev commands
147
+ '': [],
148
+ 'dev': [],
168
149
  };
169
150
 
170
- // Categorize commands by namespace
171
151
  allCommands.forEach(cmd => {
172
152
  if (cmd.type === 'namespace' && cmd.namespace) {
173
- // Group by namespace, defaulting to 'dev' for unknown namespaces
174
153
  const namespace = cmd.namespace || 'dev';
175
154
  if (!commandsByNamespace[namespace]) {
176
155
  commandsByNamespace[namespace] = [];
@@ -181,170 +160,111 @@ export default async function bunosh(commands, sources) {
181
160
  }
182
161
  });
183
162
 
184
- // Process all commands in sorted order
185
163
  allCommands.forEach((cmdData) => {
186
- if (cmdData.type === 'bunosh' || (cmdData.type === 'namespace' && !cmdData.namespace)) {
187
- // Handle main bunosh commands (no namespace)
188
- const fnName = cmdData.name;
189
- const fnBody = commands[fnName].toString();
190
- const sourceInfo = sources[fnName];
191
- const namespaceSource = namespaceSources[''];
192
-
193
- const ast = namespaceSource?.ast || babelParser.parse(fnBody, { comment: true, tokens: true });
194
- const args = parseArgs(ast, fnName);
195
- const opts = parseOpts(ast, fnName);
196
- const comment = comments[fnName];
197
-
198
- const commandName = prepareCommandName(fnName);
199
-
200
- const command = program.command(commandName);
201
- if (comment) {
202
- command.description(comment);
203
- }
204
- command.hook('preAction', (_thisCommand) => {
205
- process.env.BUNOSH_COMMAND_STARTED = true;
206
- })
207
-
208
- let argsAndOptsDescription = [];
209
-
210
- Object.entries(args).forEach(([arg, value]) => {
211
- if (value === undefined) {
212
- argsAndOptsDescription.push(`<${arg}>`);
213
- return command.argument(`<${arg}>`);
214
- }
215
-
216
- if (value === null) {
217
- argsAndOptsDescription.push(`[${arg}]`);
218
- return command.argument(`[${arg}]`, '', null);
219
- }
164
+ const isNamespaced = cmdData.type === 'namespace' && cmdData.namespace;
165
+ const fnName = isNamespaced
166
+ ? (sources[cmdData.name].originalFnName || cmdData.name.split(':')[1])
167
+ : cmdData.name;
168
+ const namespace = isNamespaced ? cmdData.namespace : '';
169
+ const fnBody = commands[isNamespaced ? cmdData.name : fnName].toString();
170
+ const namespaceSource = namespaceSources[namespace];
220
171
 
221
- argsAndOptsDescription.push(`[${arg}=${value}]`);
222
- command.argument(`[${arg}]`, ``, value);
223
- });
172
+ const ast = namespaceSource?.ast || babelParser.parse(fnBody, { comment: true, tokens: true });
173
+ const args = parseArgs(ast, fnName);
174
+ const opts = parseOpts(ast, fnName);
175
+ const comment = comments[isNamespaced ? cmdData.name : fnName];
224
176
 
225
- Object.entries(opts).forEach(([opt, value]) => {
226
- if (value === false || value === null) {
227
- argsAndOptsDescription.push(`--${opt}`);
228
- return command.option(`--${opt}`);
229
- }
177
+ let commandName;
178
+ if (isNamespaced && cmdData.name.includes(':')) {
179
+ commandName = cmdData.name.split(':')[0] + ':' + toKebabCase(cmdData.name.split(':')[1]).replace(/^[^:]+:/, '');
180
+ } else {
181
+ commandName = prepareCommandName(isNamespaced ? cmdData.name : fnName);
182
+ }
230
183
 
231
- argsAndOptsDescription.push(`--${opt}=${value}`);
232
- command.option(`--${opt} [${opt}]`, "", value);
184
+ const command = program.command(commandName);
185
+ if (comment) {
186
+ command.description(comment);
187
+ }
188
+ command.hook('preAction', (_thisCommand) => {
189
+ process.env.BUNOSH_COMMAND_STARTED = true;
233
190
 
234
- });
191
+ const isBun = typeof Bun !== 'undefined';
192
+ const runtime = isBun ? 'Bun' : 'Node.js';
193
+ const runtimeColor = isBun ? color.red : color.green;
235
194
 
236
- let description = comment?.split('\n')[0] || '';
195
+ let runtimeVersion;
196
+ if (isBun) {
197
+ runtimeVersion = Bun.version;
198
+ } else {
199
+ runtimeVersion = process.version;
200
+ }
237
201
 
238
- if (comment && argsAndOptsDescription.length) description += `\n ▹ ${color.gray(`bunosh ${commandName}`)} ${color.blue(argsAndOptsDescription.join(' ').trim())}`;
202
+ console.log(color.gray(`Runtime: `, runtimeColor.bold(runtime), color.gray(` (${runtimeVersion})`)));
203
+ console.log();
204
+ })
239
205
 
240
- command.description(description);
206
+ let argsAndOptsDescription = [];
241
207
 
242
- // Custom error handling for missing arguments - show command help without banner
243
- command.configureHelp({
244
- commandDescription: () => comment || '',
245
- commandUsage: cmd => `bunosh ${cmd.name()}${argsAndOptsDescription.length ? ' ' + argsAndOptsDescription.join(' ').trim() : ''}`,
246
- showGlobalOptions: false,
247
- visibleGlobalOptions: () => [],
248
- visibleOptions: () => [],
249
- visibleCommands: () => []
250
- });
251
- command.showHelpAfterError();
252
-
253
- command.action(createCommandAction(commands[fnName], args, opts));
254
- } else if (cmdData.type === 'namespace') {
255
- // Handle namespaced commands
256
- const sourceInfo = sources[cmdData.name];
257
- const originalFnName = sourceInfo.originalFnName || cmdData.name.split(':')[1]; // Get original function name
258
- const namespace = cmdData.namespace;
259
- const fnBody = commands[cmdData.name].toString();
260
- const namespaceSource = namespaceSources[namespace];
261
-
262
- const ast = namespaceSource?.ast || babelParser.parse(fnBody, { comment: true, tokens: true });
263
- const args = parseArgs(ast, originalFnName);
264
- const opts = parseOpts(ast, originalFnName);
265
- const comment = comments[cmdData.name];
266
-
267
- // For namespaced commands, only transform the function part to kebab-case
268
- const commandName = cmdData.name.includes(':')
269
- ? cmdData.name.split(':')[0] + ':' + toKebabCase(cmdData.name.split(':')[1]).replace(/^[^:]+:/, '')
270
- : prepareCommandName(cmdData.name);
271
-
272
- const command = program.command(commandName);
273
- if (comment) {
274
- command.description(comment);
208
+ Object.entries(args).forEach(([arg, value]) => {
209
+ if (value === undefined) {
210
+ argsAndOptsDescription.push(`<${arg}>`);
211
+ return command.argument(`<${arg}>`);
275
212
  }
276
- command.hook('preAction', (_thisCommand) => {
277
- process.env.BUNOSH_COMMAND_STARTED = true;
278
- })
279
-
280
- let argsAndOptsDescription = [];
281
-
282
- Object.entries(args).forEach(([arg, value]) => {
283
- if (value === undefined) {
284
- argsAndOptsDescription.push(`<${arg}>`);
285
- return command.argument(`<${arg}>`);
286
- }
287
213
 
288
- if (value === null) {
289
- argsAndOptsDescription.push(`[${arg}]`);
290
- return command.argument(`[${arg}]`, '', null);
291
- }
214
+ if (value === null) {
215
+ argsAndOptsDescription.push(`[${arg}]`);
216
+ return command.argument(`[${arg}]`, '', null);
217
+ }
292
218
 
293
- argsAndOptsDescription.push(`[${arg}=${value}]`);
294
- command.argument(`[${arg}]`, ``, value);
295
- });
219
+ argsAndOptsDescription.push(`[${arg}=${value}]`);
220
+ command.argument(`[${arg}]`, ``, value);
221
+ });
296
222
 
297
- Object.entries(opts).forEach(([opt, value]) => {
298
- if (value === false || value === null) {
299
- argsAndOptsDescription.push(`--${opt}`);
300
- return command.option(`--${opt}`);
301
- }
223
+ Object.entries(opts).forEach(([opt, value]) => {
224
+ if (value === false || value === null) {
225
+ argsAndOptsDescription.push(`--${opt}`);
226
+ return command.option(`--${opt}`);
227
+ }
302
228
 
303
- argsAndOptsDescription.push(`--${opt}=${value}`);
304
- command.option(`--${opt} [${opt}]`, "", value);
305
- });
229
+ argsAndOptsDescription.push(`--${opt}=${value}`);
230
+ command.option(`--${opt} [${opt}]`, "", value);
231
+ });
306
232
 
307
- let description = comment?.split('\n')[0] || '';
233
+ let description = comment?.split('\n')[0] || '';
308
234
 
309
- if (comment && argsAndOptsDescription.length) {
310
- description += `\n ${color.gray(`bunosh ${commandName}`)} ${color.blue(argsAndOptsDescription.join(' ').trim())}`;
311
- }
235
+ if (comment && argsAndOptsDescription.length) {
236
+ const separator = isNamespaced ? '\n ' : '\n ';
237
+ description += `${separator}${color.gray(`bunosh ${commandName}`)} ${color.blue(argsAndOptsDescription.join(' ').trim())}`;
238
+ }
312
239
 
313
- command.description(description);
240
+ command.description(description);
314
241
 
315
- // Custom error handling for missing arguments - show command help without banner
316
- command.configureHelp({
317
- commandDescription: () => comment || '',
318
- commandUsage: cmd => `bunosh ${cmd.name()}${argsAndOptsDescription.length ? ' ' + argsAndOptsDescription.join(' ').trim() : ''}`,
319
- showGlobalOptions: false,
320
- visibleGlobalOptions: () => [],
321
- visibleOptions: () => [],
322
- visibleCommands: () => []
323
- });
324
- command.showHelpAfterError();
242
+ command.configureHelp({
243
+ commandDescription: () => comment || '',
244
+ commandUsage: cmd => `bunosh ${cmd.name()}${argsAndOptsDescription.length ? ' ' + argsAndOptsDescription.join(' ').trim() : ''}`,
245
+ showGlobalOptions: false,
246
+ visibleGlobalOptions: () => [],
247
+ visibleOptions: () => [],
248
+ visibleCommands: () => []
249
+ });
250
+ command.showHelpAfterError();
325
251
 
326
- command.action(createCommandAction(commands[cmdData.name], args, opts));
327
- }
252
+ command.action(createCommandAction(commands[isNamespaced ? cmdData.name : fnName], args, opts));
328
253
  });
329
254
 
330
- // Helper function to create command action with proper argument transformation
331
255
  function createCommandAction(commandFn, args, opts) {
332
256
  return async (...commanderArgs) => {
333
- // Transform Commander.js arguments to match function signature
334
257
  const transformedArgs = [];
335
258
  let argIndex = 0;
336
259
 
337
- // Add positional arguments
338
260
  Object.keys(args).forEach((argName) => {
339
- if (argIndex < commanderArgs.length - 1) { // -1 because last arg is options object
261
+ if (argIndex < commanderArgs.length - 1) {
340
262
  transformedArgs.push(commanderArgs[argIndex++]);
341
263
  } else {
342
- // Use default value if not provided
343
264
  transformedArgs.push(args[argName]);
344
265
  }
345
266
  });
346
267
 
347
- // Handle options object
348
268
  const optionsObj = commanderArgs[commanderArgs.length - 1];
349
269
  if (optionsObj && typeof optionsObj === 'object') {
350
270
  Object.keys(opts).forEach((optName) => {
@@ -352,27 +272,21 @@ export default async function bunosh(commands, sources) {
352
272
  if (optionsObj[dasherizedOpt] !== undefined) {
353
273
  transformedArgs.push(optionsObj[dasherizedOpt]);
354
274
  } else {
355
- // Use default value
356
275
  transformedArgs.push(opts[optName]);
357
276
  }
358
277
  });
359
278
  }
360
279
 
361
- // Call the original function with transformed arguments
362
280
  try {
363
281
  return await commandFn(...transformedArgs);
364
282
  } catch (error) {
365
- // Handle errors thrown from functions properly
366
- console.error(`\n❌ Error: ${error.message}`);
367
- if (error.stack && process.env.BUNOSH_DEBUG) {
368
- console.error(error.stack);
369
- }
283
+ console.error('\n' + formatError(error));
370
284
  process.exit(1);
371
285
  }
372
286
  };
373
287
  }
374
288
 
375
-
289
+
376
290
  const editCmd = program.command('edit')
377
291
  .description('Open the bunosh file in your editor. $EDITOR or \'code\' is used.')
378
292
  .action(async () => {
@@ -417,7 +331,6 @@ export default async function bunosh(commands, sources) {
417
331
  .option('-s, --shell <shell>', 'Specify shell instead of auto-detection (bash, zsh, fish)')
418
332
  .action((options) => {
419
333
  try {
420
- // Detect current shell or use specified shell
421
334
  const shell = options.shell || detectCurrentShell();
422
335
 
423
336
  if (!shell) {
@@ -431,10 +344,8 @@ export default async function bunosh(commands, sources) {
431
344
  console.log(`🐚 Detected shell: ${color.bold(shell)}`);
432
345
  console.log();
433
346
 
434
- // Get paths for this shell
435
347
  const paths = getCompletionPaths(shell);
436
348
 
437
- // Check if already installed
438
349
  if (!options.force && existsSync(paths.completionFile)) {
439
350
  console.log(`⚠️ Completion already installed at: ${paths.completionFile}`);
440
351
  console.log(' Use --force to overwrite, or run:');
@@ -442,11 +353,9 @@ export default async function bunosh(commands, sources) {
442
353
  process.exit(0);
443
354
  }
444
355
 
445
- // Install completion
446
356
  console.log('🔧 Installing completion...');
447
357
  const result = installCompletion(shell);
448
358
 
449
- // Report success
450
359
  console.log(`✅ Completion installed: ${color.green(paths.completionFile)}`);
451
360
 
452
361
  if (result.configFile && result.added) {
@@ -487,10 +396,8 @@ export default async function bunosh(commands, sources) {
487
396
  internalCommands.push(upgradeCmd);
488
397
 
489
398
 
490
- // Add organized command help sections
491
399
  let helpText = '';
492
400
 
493
- // Main Commands (no namespace)
494
401
  if (commandsByNamespace[''].length > 0) {
495
402
  const mainCommands = commandsByNamespace[''].map(cmd => {
496
403
  const processedName = cmd.type === 'bunosh' ? toKebabCase(cmd.name) : cmd.name;
@@ -502,7 +409,6 @@ export default async function bunosh(commands, sources) {
502
409
  return ` ${color.white.bold(paddedName)}`;
503
410
  }
504
411
 
505
- // Handle multi-line descriptions with proper indentation
506
412
  const lines = description.split('\n');
507
413
  const firstLine = ` ${color.white.bold(paddedName)} ${lines[0]}`;
508
414
  const indentedLines = lines.slice(1).map(line =>
@@ -517,24 +423,19 @@ ${mainCommands}
517
423
  `;
518
424
  }
519
425
 
520
- // Dev Commands (dev namespace)
521
426
  if (commandsByNamespace.dev.length > 0) {
522
427
  const devCommands = commandsByNamespace.dev.map(cmd => {
523
428
  let processedName;
524
429
  if (cmd.type === 'namespace') {
525
- // For namespaced commands, handle the name properly
526
430
  if (cmd.name.includes(':')) {
527
- // If cmd.name already has namespace (like 'dev:devFn'), only process the part after the colon
528
431
  const [namespace, functionName] = cmd.name.split(':');
529
432
  processedName = `${namespace}:${toKebabCase(functionName).replace(/^[^:]+:/, '')}`;
530
433
  } else {
531
- // If cmd.name doesn't have namespace, use toKebabCase which will add it
532
434
  processedName = toKebabCase(cmd.name);
533
435
  }
534
436
  } else {
535
437
  processedName = cmd.name;
536
438
  }
537
- // Debug removed
538
439
  const cmdObj = program.commands.find(c => c.name() === processedName);
539
440
  const description = cmdObj ? cmdObj.description() : '';
540
441
  const paddedName = processedName.padEnd(22);
@@ -543,7 +444,6 @@ ${mainCommands}
543
444
  return ` ${color.white.bold(paddedName)}`;
544
445
  }
545
446
 
546
- // Handle multi-line descriptions with proper indentation
547
447
  const lines = description.split('\n');
548
448
  const firstLine = ` ${color.white.bold(paddedName)} ${lines[0]}`;
549
449
  const indentedLines = lines.slice(1).map(line =>
@@ -558,7 +458,6 @@ ${devCommands}
558
458
  `;
559
459
  }
560
460
 
561
- // Add other namespace sections dynamically
562
461
  Object.keys(commandsByNamespace).forEach(namespace => {
563
462
  if (namespace && namespace !== 'dev' && commandsByNamespace[namespace].length > 0) {
564
463
  const namespaceName = namespace.charAt(0).toUpperCase() + namespace.slice(1) + ' Commands';
@@ -571,7 +470,6 @@ ${devCommands}
571
470
  return ` ${color.white.bold(paddedName)}`;
572
471
  }
573
472
 
574
- // Handle multi-line descriptions with proper indentation
575
473
  const lines = description.split('\n');
576
474
  const firstLine = ` ${color.white.bold(paddedName)} ${lines[0]}`;
577
475
  const indentedLines = lines.slice(1).map(line =>
@@ -587,8 +485,6 @@ ${namespaceCommands}
587
485
  }
588
486
  });
589
487
 
590
-
591
- // Special Commands
592
488
  helpText += color.dim(`Special Commands:
593
489
  ${color.bold('bunosh edit')} 📝 Edit bunosh file with $EDITOR
594
490
  ${color.bold('bunosh export:scripts')} 📥 Export commands to package.json
@@ -606,28 +502,7 @@ ${namespaceCommands}
606
502
  process.exit(1);
607
503
  });
608
504
 
609
-
610
- // Handle --version option before parsing
611
- if (process.argv.includes('--version')) {
612
- let version = '';
613
- // For compiled binaries, check if version is embedded at build time
614
- if (typeof BUNOSH_VERSION !== 'undefined') {
615
- version = BUNOSH_VERSION;
616
- } else {
617
- // For development, try to read from package.json
618
- try {
619
- const pkgPath = new URL('../package.json', import.meta.url);
620
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
621
- version = pkg.version;
622
- } catch (e) {
623
- version = '0.1.5'; // fallback to current version
624
- }
625
- }
626
- console.log(version);
627
- process.exit(0);
628
- }
629
505
 
630
- // Show help if no command provided
631
506
  if (process.argv.length === 2) {
632
507
  program.outputHelp();
633
508
  return program;
@@ -636,50 +511,7 @@ ${namespaceCommands}
636
511
  program.parse(process.argv);
637
512
  }
638
513
 
639
- function fetchComments() {
640
- const comments = {};
641
-
642
- let startFromLine = 0;
643
-
644
- traverse(completeAst, {
645
- FunctionDeclaration(path) {
646
- const functionName = path.node.id && path.node.id.name;
647
-
648
- const commentSource = source
649
- .split("\n")
650
- .slice(startFromLine, path.node?.loc?.start?.line)
651
- .join("\n");
652
- const matches = commentSource.match(
653
- /\/\*\*\s([\s\S]*)\\*\/\s*export/,
654
- );
655
-
656
- if (matches && matches[1]) {
657
- comments[functionName] = matches[1]
658
- .replace(/^\s*\*\s*/gm, "")
659
- .replace(/\s*\*\*\s*$/gm, "")
660
- .trim()
661
- .replace(/^@.*$/gm, "")
662
- .trim();
663
- } else {
664
- // Check for comments attached to the first statement in the function body
665
- const firstStatement = path.node?.body?.body?.[0];
666
- const leadingComments = firstStatement?.leadingComments;
667
-
668
- if (leadingComments && leadingComments.length > 0) {
669
- comments[functionName] = leadingComments[0].value.trim();
670
- }
671
- }
672
-
673
- startFromLine = path.node?.loc?.end?.line;
674
- },
675
- });
676
-
677
- return comments;
678
- }
679
-
680
514
  function prepareCommandName(name) {
681
- // name is already the final command name (could be namespaced or not)
682
- // For namespaced commands, only transform the function part (after the last colon)
683
515
  const lastColonIndex = name.lastIndexOf(':');
684
516
  if (lastColonIndex !== -1) {
685
517
  const namespace = name.substring(0, lastColonIndex);
@@ -687,21 +519,18 @@ function prepareCommandName(name) {
687
519
  return `${namespace}:${toKebabCase(commandPart)}`;
688
520
  }
689
521
 
690
- // For non-namespaced commands, just convert to kebab-case
691
522
  return toKebabCase(name);
692
523
  }
693
524
 
694
525
  function toKebabCase(name) {
695
526
  const parts = name.split(/(?=[A-Z])/);
696
527
 
697
- // If there are multiple parts, treat first part as namespace with colon
698
528
  if (parts.length > 1) {
699
529
  const namespace = parts[0].toLowerCase();
700
530
  const command = parts.slice(1).join("-").toLowerCase();
701
531
  return `${namespace}:${command}`;
702
532
  }
703
533
 
704
- // Single word, just return lowercase
705
534
  return name.toLowerCase();
706
535
  }
707
536
 
@@ -710,22 +539,6 @@ function camelToDasherize(camelCaseString) {
710
539
  }
711
540
 
712
541
 
713
- function parseDocBlock(funcName, code) {
714
- const regex = new RegExp(
715
- `\\/\\*\\*([\\s\\S]*?)\\*\\/\\s*export\\s+function\\s+${funcName}\\s*\\(`,
716
- );
717
- const match = code.match(regex);
718
-
719
- if (match && match[1]) {
720
- return match[1]
721
- .replace(/^\s*\*\s*/gm, "")
722
- .split("\n")[0]
723
- .trim();
724
- }
725
-
726
- return null;
727
- }
728
-
729
542
  function exportFn(commands) {
730
543
  if (!existsSync(BUNOSHFILE)) {
731
544
  console.error(`${BUNOSHFILE} file not found, can\'t export its commands.`);
@@ -773,19 +586,17 @@ function extractCommentForFunction(ast, source, fnName) {
773
586
 
774
587
  const functionStartLine = path.node.loc.start.line;
775
588
 
776
- // Find the JSDoc comment that immediately precedes this function
777
589
  if (ast.comments) {
778
590
  for (const astComment of ast.comments) {
779
591
  if (astComment.type === 'CommentBlock' && astComment.value.startsWith('*')) {
780
592
  const commentEndLine = astComment.loc.end.line;
781
593
 
782
- // Check if this comment is immediately before the function
783
594
  if (commentEndLine === functionStartLine - 1) {
784
595
  comment = astComment.value
785
- .replace(/^\s*\*\s*/gm, '') // Remove leading * and spaces
786
- .replace(/^\s*@.*$/gm, '') // Remove @param and other @ tags
787
- .replace(/\n\s*\n/g, '\n') // Remove excessive empty lines
788
- .replace(/^\*\s*/, '') // Remove any remaining leading *
596
+ .replace(/^\s*\*\s*/gm, '')
597
+ .replace(/^\s*@.*$/gm, '')
598
+ .replace(/\n\s*\n/g, '\n')
599
+ .replace(/^\*\s*/, '')
789
600
  .trim();
790
601
  break;
791
602
  }
@@ -793,7 +604,6 @@ function extractCommentForFunction(ast, source, fnName) {
793
604
  }
794
605
  }
795
606
 
796
- // If no JSDoc comment found, check for comments attached to the first statement in the function body
797
607
  if (!comment) {
798
608
  const firstStatement = path.node?.body?.body?.[0];
799
609
  const statementLeadingComments = firstStatement?.leadingComments;
@@ -813,7 +623,7 @@ function parseArgs(ast, fnName) {
813
623
 
814
624
  traverse(ast, {
815
625
  FunctionDeclaration(path) {
816
- if (path.node.id.name !== fnName) return;
626
+ if (path.node.id?.name !== fnName) return;
817
627
 
818
628
  const params = path.node.params
819
629
  .filter((node) => {
@@ -840,7 +650,7 @@ function parseOpts(ast, fnName) {
840
650
 
841
651
  traverse(ast, {
842
652
  FunctionDeclaration(path) {
843
- if (path.node.id.name !== fnName) return;
653
+ if (path.node.id?.name !== fnName) return;
844
654
 
845
655
  const node = path.node.params.pop();
846
656
  if (!node) return;
@@ -877,9 +687,6 @@ function parseOpts(ast, fnName) {
877
687
  return functionOpts;
878
688
  }
879
689
 
880
- /**
881
- * Represents a parsed Bunosh command with all its metadata
882
- */
883
690
  export class BunoshCommand {
884
691
  constructor(name, namespace, args, opts, comment, fn) {
885
692
  this.name = name;
@@ -890,16 +697,10 @@ export class BunoshCommand {
890
697
  this.function = fn;
891
698
  }
892
699
 
893
- /**
894
- * Get the full command name (namespace:name if namespace exists)
895
- */
896
700
  get fullName() {
897
701
  return this.namespace ? `${this.namespace}:${this.name}` : this.name;
898
702
  }
899
703
 
900
- /**
901
- * Get the command name in kebab-case for CLI usage
902
- */
903
704
  get cliName() {
904
705
  if (this.namespace) {
905
706
  return `${this.namespace}:${camelToDasherize(this.name)}`;
@@ -907,32 +708,18 @@ export class BunoshCommand {
907
708
  return camelToDasherize(this.name);
908
709
  }
909
710
 
910
- /**
911
- * Get all parameter names (args + opts)
912
- */
913
711
  get allParams() {
914
712
  return [...Object.keys(this.args), ...Object.keys(this.opts)];
915
713
  }
916
714
 
917
- /**
918
- * Get required parameter names
919
- */
920
715
  get requiredParams() {
921
716
  return Object.keys(this.args).filter(arg => this.args[arg] === undefined);
922
717
  }
923
718
  }
924
719
 
925
- /**
926
- * Process commands and sources to extract structured command information
927
- * This reuses all the existing parsing logic from the main bunosh function
928
- * @param {Object} commands - Commands object from Bunoshfile
929
- * @param {Object} sources - Sources object containing comments and metadata
930
- * @returns {Array<BunoshCommand>} Array of parsed BunoshCommand objects
931
- */
932
720
  export function processCommands(commands, sources) {
933
721
  const parsedCommands = [];
934
722
 
935
- // Parse AST and comments for each source (same as in main bunosh function)
936
723
  const comments = {};
937
724
  const namespaceSources = {};
938
725
 
@@ -947,7 +734,6 @@ export function processCommands(commands, sources) {
947
734
  attachComment: true,
948
735
  });
949
736
 
950
- // Store AST for this command
951
737
  if (!namespaceSources[cmdInfo.namespace || '']) {
952
738
  namespaceSources[cmdInfo.namespace || ''] = {
953
739
  ast: ast,
@@ -955,7 +741,6 @@ export function processCommands(commands, sources) {
955
741
  };
956
742
  }
957
743
 
958
- // Extract comments for this command
959
744
  const fnName = cmdInfo.namespace ? cmdName.split(':')[1] : cmdName;
960
745
  if (fnName) {
961
746
  comments[cmdName] = extractCommentForFunction(ast, cmdInfo.source, fnName);
@@ -967,7 +752,6 @@ export function processCommands(commands, sources) {
967
752
  }
968
753
  }
969
754
 
970
- // Process each command using the same logic as the main bunosh function
971
755
  Object.entries(commands).forEach(([cmdName, cmdFn]) => {
972
756
  const sourceInfo = sources[cmdName];
973
757
  const originalFnName = sourceInfo?.originalFnName || cmdName.split(':')[1] || cmdName;
@@ -975,13 +759,11 @@ export function processCommands(commands, sources) {
975
759
  const namespaceSource = namespaceSources[namespace];
976
760
  const comment = comments[cmdName];
977
761
 
978
- // Parse function using the same logic as the main bunosh function
979
762
  const fnBody = cmdFn.toString();
980
763
  const ast = namespaceSource?.ast || babelParser.parse(fnBody, { comment: true, tokens: true });
981
764
  const args = parseArgs(ast, originalFnName);
982
765
  const opts = parseOpts(ast, originalFnName);
983
766
 
984
- // Extract the actual command name without namespace
985
767
  const commandName = originalFnName;
986
768
 
987
769
  parsedCommands.push(new BunoshCommand(
@@ -997,5 +779,4 @@ export function processCommands(commands, sources) {
997
779
  return parsedCommands;
998
780
  }
999
781
 
1000
- // Export parsing functions for use in MCP server
1001
782
  export { parseArgs, parseOpts, extractCommentForFunction };