bunosh 0.1.5 → 0.2.3

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
@@ -1,33 +1,45 @@
1
- const { Command } = require("commander");
1
+ import { Command } from "commander";
2
2
  import babelParser from "@babel/parser";
3
- import traverse from "@babel/traverse";
3
+ import traverseDefault from "@babel/traverse";
4
+ const traverse = traverseDefault.default || traverseDefault;
4
5
  import color from "chalk";
5
6
  import fs from 'fs';
6
7
  import openEditor from 'open-editor';
7
- import banner from '../templates/banner';
8
+ import { yell } from './io.js';
9
+ import cprint from "./font.js";
10
+ import { handleCompletion, detectCurrentShell, installCompletion, getCompletionPaths } from './completion.js';
11
+ import { upgradeExecutable, isExecutable, getCurrentVersion } from './upgrade.js';
8
12
 
9
- export const BUNOSHFILE = `bunosh.tasks.js`;
13
+ export const BUNOSHFILE = `Bunoshfile.js`;
10
14
 
11
- export { banner };
15
+ export const banner = () => {
16
+ console.log(cprint('Bunosh', { symbol: '⯀' }));
17
+ console.log(color.gray('🍲 Your exceptional task runner'));
18
+ console.log();
19
+ };
12
20
 
13
21
  export default function bunosh(commands, source) {
14
22
  const program = new Command();
23
+ program.option('--bunoshfile <path>', 'Path to the Bunoshfile');
15
24
 
16
25
  const internalCommands = [];
17
26
 
27
+ // Load npm scripts from package.json
28
+ const npmScripts = loadNpmScripts();
29
+
18
30
  program.configureHelp({
19
- commandDescription: _cmd => `${banner}\n Commands are loaded from exported functions in ${color.bold(BUNOSHFILE)}`,
31
+ commandDescription: _cmd => {
32
+ // Show banner and description
33
+ banner();
34
+ return ` Commands are loaded from exported functions in ${color.bold(BUNOSHFILE)}`;
35
+ },
20
36
  commandUsage: usg => 'bunosh <command> <args> [options]',
21
37
  showGlobalOptions: false,
22
- // visibleArguments: cmd => cmd.registeredArguments,
23
38
  visibleGlobalOptions: _opt => [],
24
- // Bunosh has no default options
25
39
  visibleOptions: _opt => [],
26
40
  visibleCommands: cmd => cmd.commands.filter(c => !internalCommands.includes(c)),
27
- // commandDescription: _opt => '',
28
- // argumentTerm: (arg) => color.gray("aaa"),
29
- subcommandTerm: (cmd) => pickColorForColorName(cmd.name()),
30
- subcommandDescription: (cmd) => cmd.description(),
41
+ subcommandTerm: (cmd) => color.white.bold(cmd.name()),
42
+ subcommandDescription: (cmd) => color.gray(cmd.description()),
31
43
  });
32
44
 
33
45
  program.showHelpAfterError();
@@ -36,7 +48,6 @@ export default function bunosh(commands, source) {
36
48
 
37
49
  const completeAst = babelParser.parse(source, {
38
50
  sourceType: "module",
39
- plugins: ["jsx"],
40
51
  ranges: true,
41
52
  tokens: true,
42
53
  comments: true,
@@ -45,150 +56,189 @@ export default function bunosh(commands, source) {
45
56
 
46
57
  const comments = fetchComments();
47
58
 
59
+ // Collect all commands (bunosh + npm scripts) and sort them
60
+ const allCommands = [];
61
+
62
+ // Add bunosh commands
48
63
  Object.keys(commands).forEach((fnName) => {
49
- const fnBody = commands[fnName].toString();
64
+ allCommands.push({ type: 'bunosh', name: fnName, data: commands[fnName] });
65
+ });
50
66
 
51
- const ast = fetchFnAst();
52
- const args = parseArgs();
53
- const opts = parseOpts();
67
+ // Add npm scripts
68
+ Object.entries(npmScripts).forEach(([scriptName, scriptCommand]) => {
69
+ allCommands.push({ type: 'npm', name: `npm:${scriptName}`, data: { scriptName, scriptCommand } });
70
+ });
54
71
 
55
- const comment = comments[fnName];
72
+ // Sort all commands alphabetically by name
73
+ allCommands.sort((a, b) => a.name.localeCompare(b.name));
56
74
 
57
- const commandName = prepareCommandName(fnName);
75
+ // Process all commands in sorted order
76
+ allCommands.forEach((cmdData) => {
77
+ if (cmdData.type === 'bunosh') {
78
+ const fnName = cmdData.name;
79
+ const fnBody = commands[fnName].toString();
58
80
 
59
- const command = program.command(commandName);
60
- command.hook('preAction', (_thisCommand) => {
61
- process.env.BUNOSH_COMMAND_STARTED = true;
62
- })
63
-
64
- let argsAndOptsDescription = [];
81
+ const ast = fetchFnAst();
82
+ const args = parseArgs();
83
+ const opts = parseOpts();
65
84
 
66
- Object.entries(args).forEach(([arg, value]) => {
67
- if (value === undefined) {
68
- argsAndOptsDescription.push(`<${arg}>`);
69
- return command.argument(`<${arg}>`);
70
- }
85
+ const comment = comments[fnName];
71
86
 
72
- if (value === null) {
73
- argsAndOptsDescription.push(`[${arg}]`);
74
- return command.argument(`[${arg}]`, '', null);
75
- }
76
-
77
- argsAndOptsDescription.push(`[${arg}=${value}]`);
78
- command.argument(`[${arg}]`, ``, value);
79
- });
87
+ const commandName = prepareCommandName(fnName);
80
88
 
81
- Object.entries(opts).forEach(([opt, value]) => {
82
- if (value === false || value === null) {
83
- argsAndOptsDescription.push(`--${opt}`);
84
- return command.option(`--${opt}`);
85
- }
86
-
87
- argsAndOptsDescription.push(`--${opt}=${value}`);
88
- command.option(`--${opt} [${opt}]`, "", value);
89
+ const command = program.command(commandName);
90
+ command.hook('preAction', (_thisCommand) => {
91
+ process.env.BUNOSH_COMMAND_STARTED = true;
92
+ })
89
93
 
90
- });
94
+ let argsAndOptsDescription = [];
91
95
 
92
- let description = comment?.split('\n')[0] || '';
96
+ Object.entries(args).forEach(([arg, value]) => {
97
+ if (value === undefined) {
98
+ argsAndOptsDescription.push(`<${arg}>`);
99
+ return command.argument(`<${arg}>`);
100
+ }
93
101
 
102
+ if (value === null) {
103
+ argsAndOptsDescription.push(`[${arg}]`);
104
+ return command.argument(`[${arg}]`, '', null);
105
+ }
94
106
 
95
- if (comment && argsAndOptsDescription.length) description += `\n ${color.gray(`bunosh ${commandName}`)} ${color.blue(argsAndOptsDescription.join(' ').trim())}`;
107
+ argsAndOptsDescription.push(`[${arg}=${value}]`);
108
+ command.argument(`[${arg}]`, ``, value);
109
+ });
96
110
 
97
- command.description(description);
98
- command.action(commands[fnName].bind(commands));
111
+ Object.entries(opts).forEach(([opt, value]) => {
112
+ if (value === false || value === null) {
113
+ argsAndOptsDescription.push(`--${opt}`);
114
+ return command.option(`--${opt}`);
115
+ }
99
116
 
100
- // We either take the ast from the file or we parse the function body
101
- function fetchFnAst() {
102
- let hasFnInSource = false;
117
+ argsAndOptsDescription.push(`--${opt}=${value}`);
118
+ command.option(`--${opt} [${opt}]`, "", value);
103
119
 
104
- traverse(completeAst, {
105
- FunctionDeclaration(path) {
106
- if (path.node.id.name == fnName) {
107
- hasFnInSource = true;
108
- return;
109
- }
110
- },
111
120
  });
112
121
 
113
- if (hasFnInSource) return completeAst;
122
+ let description = comment?.split('\n')[0] || '';
114
123
 
115
- return babelParser.parse(fnBody, { comment: true, tokens: true });
116
- }
124
+ if (comment && argsAndOptsDescription.length) description += `\n ${color.gray(`bunosh ${commandName}`)} ${color.blue(argsAndOptsDescription.join(' ').trim())}`;
117
125
 
118
- // We parse command args from function args
119
- function parseArgs() {
120
- const functionArguments = {};
121
-
122
- traverse(ast, {
123
- FunctionDeclaration(path) {
124
- if (path.node.id.name !== fnName) return;
125
-
126
- const params = path.node.params
127
- .filter((node) => {
128
- return node?.right?.type !== "ObjectExpression";
129
- })
130
- .forEach((param) => {
131
- if (param.type === "AssignmentPattern") {
132
- functionArguments[param.left.name] = param.right.value;
133
- return;
134
- }
135
- if (!param.name) return;
126
+ command.description(description);
127
+ command.action(commands[fnName].bind(commands));
136
128
 
137
- return functionArguments[param.name] = null;
138
- });
129
+ function fetchFnAst() {
130
+ let hasFnInSource = false;
139
131
 
140
- },
141
- });
132
+ traverse(completeAst, {
133
+ FunctionDeclaration(path) {
134
+ if (path.node.id.name == fnName) {
135
+ hasFnInSource = true;
136
+ return;
137
+ }
138
+ },
139
+ });
142
140
 
143
- return functionArguments;
144
- }
141
+ if (hasFnInSource) return completeAst;
145
142
 
146
- // We parse command options from the object of last function args
147
- function parseOpts() {
148
- let functionOpts = {};
143
+ return babelParser.parse(fnBody, { comment: true, tokens: true });
144
+ }
149
145
 
150
- traverse(ast, {
151
- FunctionDeclaration(path) {
152
- if (path.node.id.name !== fnName) return;
146
+ function parseArgs() {
147
+ const functionArguments = {};
153
148
 
154
- const node = path.node.params.pop();
155
- if (!node) return;
156
- if (
157
- !node.type === "AssignmentPattern" &&
158
- node.right.type === "ObjectExpression"
159
- )
160
- return;
149
+ traverse(ast, {
150
+ FunctionDeclaration(path) {
151
+ if (path.node.id.name !== fnName) return;
161
152
 
153
+ const params = path.node.params
154
+ .filter((node) => {
155
+ return node?.right?.type !== "ObjectExpression";
156
+ })
157
+ .forEach((param) => {
158
+ if (param.type === "AssignmentPattern") {
159
+ functionArguments[param.left.name] = param.right.value;
160
+ return;
161
+ }
162
+ if (!param.name) return;
162
163
 
163
- node?.right?.properties?.forEach((p) => {
164
+ return functionArguments[param.name] = null;
165
+ });
166
+
167
+ },
168
+ });
169
+
170
+ return functionArguments;
171
+ }
172
+
173
+ function parseOpts() {
174
+ let functionOpts = {};
175
+
176
+ traverse(ast, {
177
+ FunctionDeclaration(path) {
178
+ if (path.node.id.name !== fnName) return;
179
+
180
+ const node = path.node.params.pop();
181
+ if (!node) return;
164
182
  if (
165
- ["NumericLiteral", "StringLiteral", "BooleanLiteral"].includes(
166
- p.value.type,
167
- )
168
- ) {
169
- functionOpts[camelToDasherize(p.key.name)] = p.value.value;
183
+ !node.type === "AssignmentPattern" &&
184
+ node.right.type === "ObjectExpression"
185
+ )
170
186
  return;
171
- }
172
187
 
173
- if (p.value.type === "NullLiteral") {
174
- functionOpts[camelToDasherize(p.key.name)] = null;
175
- return;
176
- }
188
+ node?.right?.properties?.forEach((p) => {
189
+ if (
190
+ ["NumericLiteral", "StringLiteral", "BooleanLiteral"].includes(
191
+ p.value.type,
192
+ )
193
+ ) {
194
+ functionOpts[camelToDasherize(p.key.name)] = p.value.value;
195
+ return;
196
+ }
177
197
 
178
- if (p.value.type == "UnaryExpression" && p.value.operator == "!") {
179
- functionOpts[camelToDasherize(p.key.name)] =
180
- !p.value.argument.value;
181
- return;
182
- }
183
- // ignore other options for now
184
- });
185
- },
186
- });
198
+ if (p.value.type === "NullLiteral") {
199
+ functionOpts[camelToDasherize(p.key.name)] = null;
200
+ return;
201
+ }
187
202
 
188
- return functionOpts;
203
+ if (p.value.type == "UnaryExpression" && p.value.operator == "!") {
204
+ functionOpts[camelToDasherize(p.key.name)] =
205
+ !p.value.argument.value;
206
+ return;
207
+ }
208
+ });
209
+ },
210
+ });
211
+
212
+ return functionOpts;
213
+ }
214
+ } else if (cmdData.type === 'npm') {
215
+ // Handle npm scripts
216
+ const { scriptName, scriptCommand } = cmdData.data;
217
+ const commandName = `npm:${scriptName}`;
218
+ const command = program.command(commandName);
219
+ command.description(color.gray(scriptCommand)); // Use script command as description
220
+
221
+ // Create action with proper closure to capture scriptName
222
+ command.action(createNpmScriptAction(scriptName));
189
223
  }
190
224
  });
191
225
 
226
+ // Helper function to create npm script action with proper closure
227
+ function createNpmScriptAction(scriptName) {
228
+ return async () => {
229
+ // Execute npm script using Bunosh's exec task
230
+ const { exec } = await import('../index.js');
231
+ try {
232
+ // Call exec with proper template literal simulation
233
+ const result = await exec(['npm run ', ''], scriptName);
234
+ return result;
235
+ } catch (error) {
236
+ console.error(`Failed to run npm script: ${scriptName}`);
237
+ process.exit(1);
238
+ }
239
+ };
240
+ }
241
+
192
242
  const editCmd = program.command('edit')
193
243
  .description('Open the bunosh file in your editor. $EDITOR or \'code\' is used.')
194
244
  .action(() => {
@@ -204,20 +254,185 @@ export default function bunosh(commands, source) {
204
254
  const exoprtCmd = program.command('export:scripts')
205
255
  .description('Export commands to "scripts" section of package.json.')
206
256
  .action(() => {
207
-
208
257
  exportFn(Object.keys(commands));
209
258
  });
210
-
211
- internalCommands.push(exoprtCmd);
259
+
260
+ internalCommands.push(exoprtCmd);
261
+
262
+ const completionCmd = program.command('completion <shell>')
263
+ .description('Generate shell completion scripts')
264
+ .argument('<shell>', 'Shell type: bash, zsh, or fish')
265
+ .action((shell) => {
266
+ try {
267
+ const completionScript = handleCompletion(shell);
268
+ console.log(completionScript);
269
+ } catch (error) {
270
+ console.error(error.message);
271
+ process.exit(1);
272
+ }
273
+ });
274
+
275
+ internalCommands.push(completionCmd);
276
+
277
+ const setupCompletionCmd = program.command('setup-completion')
278
+ .description('Automatically setup shell completion for your current shell')
279
+ .option('-f, --force', 'Overwrite existing completion setup')
280
+ .option('-s, --shell <shell>', 'Specify shell instead of auto-detection (bash, zsh, fish)')
281
+ .action((options) => {
282
+ try {
283
+ // Detect current shell or use specified shell
284
+ const shell = options.shell || detectCurrentShell();
285
+
286
+ if (!shell) {
287
+ console.error('❌ Could not detect your shell. Please specify one:');
288
+ console.log(' bunosh setup-completion --shell bash');
289
+ console.log(' bunosh setup-completion --shell zsh');
290
+ console.log(' bunosh setup-completion --shell fish');
291
+ process.exit(1);
292
+ }
293
+
294
+ console.log(`🐚 Detected shell: ${color.bold(shell)}`);
295
+ console.log();
296
+
297
+ // Get paths for this shell
298
+ const paths = getCompletionPaths(shell);
299
+
300
+ // Check if already installed
301
+ if (!options.force && fs.existsSync(paths.completionFile)) {
302
+ console.log(`⚠️ Completion already installed at: ${paths.completionFile}`);
303
+ console.log(' Use --force to overwrite, or run:');
304
+ console.log(` ${color.dim('rm')} ${paths.completionFile}`);
305
+ process.exit(0);
306
+ }
307
+
308
+ // Install completion
309
+ console.log('🔧 Installing completion...');
310
+ const result = installCompletion(shell);
311
+
312
+ // Report success
313
+ console.log(`✅ Completion installed: ${color.green(paths.completionFile)}`);
314
+
315
+ if (result.configFile && result.added) {
316
+ console.log(`📝 Updated shell config: ${color.green(result.configFile)}`);
317
+ console.log();
318
+ console.log(`💡 ${color.bold('Restart your terminal')} or run:`);
319
+ if (shell === 'bash') {
320
+ console.log(` ${color.dim('source ~/.bashrc')}`);
321
+ } else if (shell === 'zsh') {
322
+ console.log(` ${color.dim('source ~/.zshrc')}`);
323
+ }
324
+ } else if (shell === 'fish') {
325
+ console.log('🐟 Fish completion is ready! No restart needed.');
326
+ } else if (result.configFile && !result.added) {
327
+ console.log(`ℹ️ Shell config already has completion setup: ${result.configFile}`);
328
+ console.log(' Restart your terminal if completion isn\'t working.');
329
+ }
330
+
331
+ console.log();
332
+ console.log('🎯 Test completion by typing: ' + color.bold('bunosh <TAB>'));
333
+
334
+ } catch (error) {
335
+ console.error(`❌ Setup failed: ${error.message}`);
336
+ process.exit(1);
337
+ }
338
+ });
339
+
340
+ internalCommands.push(setupCompletionCmd);
341
+
342
+ const upgradeCmd = program.command('upgrade')
343
+ .description('Upgrade bunosh to the latest version (single executable only)')
344
+ .option('-f, --force', 'Force upgrade even if already on latest version')
345
+ .option('--check', 'Check for updates without upgrading')
346
+ .action(async (options) => {
347
+ try {
348
+ if (!isExecutable()) {
349
+ console.log('📦 Bunosh is installed via npm.');
350
+ console.log('To upgrade, run: ' + color.bold('npm update -g bunosh'));
351
+ process.exit(0);
352
+ }
353
+
354
+ const currentVersion = getCurrentVersion();
355
+ console.log(`📍 Current version: ${color.bold(currentVersion)}`);
356
+
357
+ if (options.check) {
358
+ console.log('🔍 Checking for updates...');
359
+ try {
360
+ const { getLatestRelease, isNewerVersion } = await import('./upgrade.js');
361
+ const release = await getLatestRelease();
362
+ const latestVersion = release.tag_name;
363
+
364
+ console.log(`📦 Latest version: ${color.bold(latestVersion)}`);
365
+
366
+ if (isNewerVersion(latestVersion, currentVersion)) {
367
+ console.log(`✨ ${color.green('Update available!')} ${currentVersion} → ${latestVersion}`);
368
+ console.log('Run ' + color.bold('bunosh upgrade') + ' to update.');
369
+ } else {
370
+ console.log(`✅ ${color.green('You are on the latest version!')}`);
371
+ }
372
+ } catch (error) {
373
+ console.error(`❌ Failed to check for updates: ${error.message}`);
374
+ process.exit(1);
375
+ }
376
+ return;
377
+ }
378
+
379
+ console.log('⬆️ Starting upgrade process...');
380
+ console.log();
381
+
382
+ let lastMessage = '';
383
+ const result = await upgradeExecutable({
384
+ force: options.force,
385
+ onProgress: (message) => {
386
+ if (message !== lastMessage) {
387
+ console.log(` ${message}`);
388
+ lastMessage = message;
389
+ }
390
+ }
391
+ });
392
+
393
+ console.log();
394
+ if (result.updated) {
395
+ console.log(`🎉 ${color.green('Upgrade successful!')}`);
396
+ console.log(` ${result.currentVersion} → ${color.bold(result.latestVersion)}`);
397
+ console.log();
398
+ console.log(`💡 Run ${color.bold('bunosh --version')} to verify the new version.`);
399
+ } else {
400
+ console.log(`✅ ${color.green(result.message)}`);
401
+ if (!options.force) {
402
+ console.log(` Use ${color.bold('--force')} to reinstall the current version.`);
403
+ }
404
+ }
405
+
406
+ } catch (error) {
407
+ console.error(`❌ Upgrade failed: ${error.message}`);
408
+
409
+ if (error.message.includes('Unsupported platform')) {
410
+ console.log();
411
+ console.log('💡 Supported platforms:');
412
+ console.log(' • Linux x64');
413
+ console.log(' • macOS ARM64 (Apple Silicon)');
414
+ console.log(' • Windows x64');
415
+ } else if (error.message.includes('GitHub API')) {
416
+ console.log();
417
+ console.log('💡 Try again later or check your internet connection.');
418
+ }
419
+
420
+ process.exit(1);
421
+ }
422
+ });
423
+
424
+ internalCommands.push(upgradeCmd);
212
425
 
213
426
  program.addHelpText('after', `
214
427
 
215
428
  Special Commands:
216
429
  📝 Edit bunosh file: ${color.bold('bunosh edit')}
217
430
  📥 Export scripts to package.json: ${color.bold('bunosh export:scripts')}
431
+ 🔤 Generate shell completion: ${color.bold('bunosh completion bash|zsh|fish')}
432
+ ⚡ Auto-setup completion: ${color.bold('bunosh setup-completion')}
433
+ ⬆️ Upgrade bunosh: ${color.bold('bunosh upgrade')}
218
434
  `);
219
435
 
220
-
221
436
  program.on("command:*", (cmd) => {
222
437
  console.log(`\nUnknown command ${cmd}\n`);
223
438
  program.outputHelp();
@@ -244,16 +459,18 @@ Special Commands:
244
459
 
245
460
  if (matches && matches[1]) {
246
461
  comments[functionName] = matches[1]
247
- .replace(/^\s*\*\s*/gm, "") // remove * chars
248
- .replace(/\s*\*\*\s*$/gm, "") // remove * chars
462
+ .replace(/^\s*\*\s*/gm, "")
463
+ .replace(/\s*\*\*\s*$/gm, "")
249
464
  .trim()
250
- .replace(/^@.*$/gm, "") // remove params from description
465
+ .replace(/^@.*$/gm, "")
251
466
  .trim();
252
467
  } else {
253
- const innerComments = path.node?.body?.innerComments;
468
+ // Check for comments attached to the first statement in the function body
469
+ const firstStatement = path.node?.body?.body?.[0];
470
+ const leadingComments = firstStatement?.leadingComments;
254
471
 
255
- if (innerComments && innerComments.length > 0) {
256
- comments[functionName] = innerComments[0].value.trim();
472
+ if (leadingComments && leadingComments.length > 0) {
473
+ comments[functionName] = leadingComments[0].value.trim();
257
474
  }
258
475
  }
259
476
 
@@ -277,25 +494,6 @@ function camelToDasherize(camelCaseString) {
277
494
  return camelCaseString.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
278
495
  }
279
496
 
280
- function pickColorForColorName(commandName) {
281
- const colors = [
282
- color.red,
283
- color.green,
284
- color.yellow,
285
- color.blue,
286
- color.magenta,
287
- color.cyan,
288
- ];
289
-
290
- const prefixName = camelToDasherize(commandName).split("-")[0];
291
-
292
- const index =
293
- prefixName.split("").reduce((acc, char) => {
294
- return acc + char.charCodeAt(0);
295
- }, 0) % colors.length;
296
-
297
- return color.bold(colors[index](commandName));
298
- }
299
497
 
300
498
  function parseDocBlock(funcName, code) {
301
499
  const regex = new RegExp(
@@ -304,7 +502,6 @@ function parseDocBlock(funcName, code) {
304
502
  const match = code.match(regex);
305
503
 
306
504
  if (match && match[1]) {
307
- // Remove leading asterisks and trim the result
308
505
  return match[1]
309
506
  .replace(/^\s*\*\s*/gm, "")
310
507
  .split("\n")[0]
@@ -329,8 +526,7 @@ function exportFn(commands) {
329
526
  if (!pkg.scripts) {
330
527
  pkg.scripts = {};
331
528
  }
332
-
333
- // cleanup from previously exported
529
+
334
530
  for (let s in pkg.scripts ) {
335
531
  if (pkg[s] && pkg[s].startsWith('bunosh')) delete pkg[s];
336
532
  }
@@ -351,3 +547,27 @@ function exportFn(commands) {
351
547
  console.log(`${Object.keys(scripts).length} scripts exported`);
352
548
  return true;
353
549
  }
550
+
551
+ function loadNpmScripts() {
552
+ try {
553
+ if (!fs.existsSync('package.json')) {
554
+ return {};
555
+ }
556
+
557
+ const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
558
+ const scripts = pkg.scripts || {};
559
+
560
+ // Filter out bunosh scripts (scripts that contain "bunosh")
561
+ const npmScripts = {};
562
+ Object.entries(scripts).forEach(([name, command]) => {
563
+ if (!command.includes('bunosh')) {
564
+ npmScripts[name] = command;
565
+ }
566
+ });
567
+
568
+ return npmScripts;
569
+ } catch (error) {
570
+ console.warn('Warning: Could not load npm scripts from package.json:', error.message);
571
+ return {};
572
+ }
573
+ }