bunosh 0.2.3 → 0.3.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.
package/bunosh.js CHANGED
@@ -3,6 +3,7 @@ import program, { BUNOSHFILE, banner } from "./src/program.js";
3
3
  import { existsSync, readFileSync, statSync } from "fs";
4
4
  import init from "./src/init.js";
5
5
  import path from "path";
6
+ import color from "chalk";
6
7
  import './index.js';
7
8
 
8
9
  // Parse --bunoshfile flag before importing tasks
@@ -49,8 +50,93 @@ if (!existsSync(tasksFile)) {
49
50
  }
50
51
 
51
52
  import(tasksFile).then((tasks) => {
52
- program(tasks, readFileSync(tasksFile, "utf-8"));
53
- }).catch((e) => {
54
- console.error(`Error loading: ${tasksFile}`);
55
- console.error(e);
53
+ try {
54
+ const source = readFileSync(tasksFile, "utf-8");
55
+ program(tasks, source);
56
+ } catch (error) {
57
+ handleBunoshfileError(error, tasksFile);
58
+ }
59
+ }).catch((error) => {
60
+ handleBunoshfileError(error, tasksFile);
56
61
  });
62
+
63
+ function handleBunoshfileError(error, filePath) {
64
+ banner();
65
+ console.log();
66
+
67
+ // Check for Babel parser syntax errors
68
+ if (error.code === 'BABEL_PARSER_SYNTAX_ERROR' ||
69
+ (error.reasonCode && error.loc) ||
70
+ error.constructor.name === 'SyntaxError') {
71
+
72
+ console.error(`❌ Syntax Error in ${path.basename(filePath)}:`);
73
+ console.log();
74
+
75
+ if (error.loc) {
76
+ console.error(` Line ${error.loc.line}, Column ${error.loc.column}:`);
77
+
78
+ // Provide specific error messages based on reasonCode
79
+ if (error.reasonCode === 'VarRedeclaration') {
80
+ console.error(` Variable redeclaration - '${error.message}' is already declared`);
81
+ } else if (error.reasonCode && error.reasonCode.includes('Unexpected')) {
82
+ console.error(` ${error.reasonCode}: ${error.message || 'Unexpected token'}`);
83
+ } else {
84
+ console.error(` ${error.message || error.reasonCode || 'Invalid syntax'}`);
85
+ }
86
+ } else {
87
+ console.error(` ${error.message || 'Invalid JavaScript syntax'}`);
88
+ }
89
+
90
+ console.log();
91
+ console.log('💡 Common issues:');
92
+ console.log(' â€ĸ Missing semicolons or commas');
93
+ console.log(' â€ĸ Unclosed brackets, parentheses, or quotes');
94
+ console.log(' â€ĸ Invalid variable declarations');
95
+ console.log(' â€ĸ Mixing import/export with require/module.exports');
96
+ console.log();
97
+ console.log(`📝 Edit your Bunoshfile: ${color.blue('bunosh edit')}`);
98
+ console.log(`🔧 Validate syntax: ${color.blue(`bun --check ${path.basename(filePath)}`)}`);
99
+
100
+ } else if (error.message && error.message.includes('SyntaxError')) {
101
+ console.error(`❌ JavaScript Syntax Error in ${path.basename(filePath)}:`);
102
+ console.log();
103
+ console.error(` ${error.message}`);
104
+ console.log();
105
+ console.log(`💡 Try running: ${color.blue('bun --check Bunoshfile.js')}`);
106
+ console.log(`📝 Edit your Bunoshfile: ${color.blue('bunosh edit')}`);
107
+
108
+ } else if (error.code === 'MODULE_NOT_FOUND' ||
109
+ error.message?.includes('Cannot resolve') ||
110
+ error.message?.includes('Could not resolve')) {
111
+ console.error(`❌ Module Import Error in ${path.basename(filePath)}:`);
112
+ console.log();
113
+ console.error(` ${error.message}`);
114
+ console.log();
115
+ console.log('💡 Common solutions:');
116
+ console.log(` â€ĸ Run: ${color.blue('bun install')}`);
117
+ console.log(' â€ĸ Check import paths are correct');
118
+ console.log(' â€ĸ Ensure dependencies are listed in package.json');
119
+
120
+ } else {
121
+ console.error(`❌ Error loading ${path.basename(filePath)}:`);
122
+ console.log();
123
+ console.error(` ${error.message || error.toString()}`);
124
+
125
+ // Add stack trace for debugging if available
126
+ if (process.env.BUNOSH_DEBUG) {
127
+ console.log();
128
+ console.log('🐛 Debug stack trace:');
129
+ console.log(error.stack || 'No stack trace available');
130
+ }
131
+
132
+ console.log();
133
+ console.log('💡 Try:');
134
+ console.log(` â€ĸ Check the file exists: ${color.blue(`ls -la ${path.basename(filePath)}`)}`);
135
+ console.log(` â€ĸ Validate syntax: ${color.blue(`bun --check ${path.basename(filePath)}`)}`);
136
+ console.log(` â€ĸ Edit the file: ${color.blue('bunosh edit')}`);
137
+ console.log(` â€ĸ Run with debug: ${color.blue('BUNOSH_DEBUG=1 bunosh')}`);
138
+ }
139
+
140
+ console.log();
141
+ process.exit(1);
142
+ }
package/index.js CHANGED
@@ -1,30 +1,35 @@
1
1
  import exec from "./src/tasks/exec.js";
2
+ import shell from "./src/tasks/shell.js";
2
3
  import fetch from "./src/tasks/fetch.js";
3
4
  import writeToFile from "./src/tasks/writeToFile.js";
4
5
  import copyFile from "./src/tasks/copyFile.js";
6
+ import ai from "./src/tasks/ai.js";
5
7
  import { ask, yell, say } from "./src/io.js";
6
8
  import { task, stopOnFail, ignoreFail } from "./src/task.js";
7
9
 
8
- export { exec, fetch, writeToFile, copyFile, ask, yell, say, task, stopOnFail, ignoreFail };
9
-
10
+ export { exec, shell, fetch, writeToFile, copyFile, ai, ask, yell, say, task, stopOnFail, ignoreFail };
10
11
 
11
12
  export function buildCmd(cmd) {
12
- return function(args) {
13
- return exec`${cmd} ${args}`
14
- }
13
+ return function (args) {
14
+ return exec`${cmd} ${args}`;
15
+ };
15
16
  }
16
17
 
17
18
  global.bunosh = {
18
- ask, yell, say,
19
+ ask,
20
+ yell,
21
+ say,
19
22
  fetch,
20
23
  exec,
24
+ shell,
21
25
  writeToFile,
22
26
  copyFile,
27
+ ai,
23
28
  stopOnFail,
24
29
  ignoreFail,
25
30
  task,
26
31
  buildCmd,
27
32
  $: exec,
28
- }
33
+ };
29
34
 
30
35
  export default global.bunosh;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunosh",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "module": "index.js",
6
6
  "bin": {
@@ -14,15 +14,19 @@
14
14
  },
15
15
  "types": "types.d.ts",
16
16
  "dependencies": {
17
+ "@ai-sdk/anthropic": "^2.0.9",
18
+ "@ai-sdk/groq": "^2.0.16",
19
+ "@ai-sdk/openai": "^2.0.23",
17
20
  "@babel/parser": "^7.27.5",
18
21
  "@babel/traverse": "^7.27.4",
22
+ "ai": "^5.0.29",
19
23
  "chalk": "^5.4.1",
20
24
  "commander": "^14.0.0",
21
25
  "debug": "^4.4.1",
22
26
  "fs-extra": "^11.3.0",
23
27
  "inquirer": "^12.6.3",
24
- "open-editor": "^5.1.0",
25
- "timer-node": "^5.0.9"
28
+ "timer-node": "^5.0.9",
29
+ "zod": "^4.1.5"
26
30
  },
27
31
  "license": "ISC",
28
32
  "files": [
package/src/init.js CHANGED
@@ -3,8 +3,10 @@ import color from "chalk";
3
3
  import fs from 'fs';
4
4
 
5
5
  const template = `
6
- // tasks
7
- const { exec, fetch, writeToFile, task } = global.bunosh;
6
+ // Bunosh CLI required to execute tasks from this file
7
+ // Get it here -> https://github.com/DavertMik/bunosh/releases
8
+
9
+ const { exec, fetch, writeToFile, task, ai } = global.bunosh;
8
10
 
9
11
  // input/output
10
12
  const { say, ask, yell } = global.bunosh;
@@ -22,13 +24,19 @@ export async function helloWorld() {
22
24
  // use fetch() to make HTTP requests
23
25
  // await fetch('https://reqres.in/api/users')
24
26
 
27
+ // use ai() to make AI requests with structured output:
28
+ // REQUIRED env vars: AI_MODEL and any of: OPENAI_API_KEY, ANTHROPIC_API_KEY, or GROQ_API_KEY
29
+ // await ai('Summarize this text: JavaScript is awesome', {
30
+ // summary: 'Brief summary of the text',
31
+ // sentiment: 'Sentiment of the text (positive/negative/neutral)',
32
+ // keyWords: 'Main keywords from the text'
33
+ // });
34
+
25
35
  // add arguments and options to this function if needed
26
36
  // export async function helloWorld(userName, opts = { force: false })
27
37
  //
28
38
  // bunosh hello:world 'bob' --force
29
39
 
30
- // use ignoreFail(true) to prevent the command from stopping on error
31
-
32
40
  yell('Heloo Bunosh!');
33
41
  say('Edit me with bunosh edit');
34
42
  }
package/src/io.js CHANGED
@@ -6,11 +6,65 @@ export function say(...args) {
6
6
  console.log('!', ...args);
7
7
  }
8
8
 
9
- export async function ask(question, opts = {}) {
9
+ export async function ask(question, defaultValueOrOptions = {}, options = {}) {
10
+ // Smart parameter detection
11
+ let opts = {};
12
+
13
+ // If second parameter is not an object, it's a default value
14
+ if (defaultValueOrOptions !== null && typeof defaultValueOrOptions !== 'object') {
15
+ opts.default = defaultValueOrOptions;
16
+ opts = { ...opts, ...options }; // Merge with third parameter options
17
+
18
+ // Auto-detect type based on default value
19
+ if (typeof defaultValueOrOptions === 'boolean') {
20
+ opts.type = 'confirm';
21
+ }
22
+ } else if (Array.isArray(defaultValueOrOptions)) {
23
+ // If it's an array, treat as choices
24
+ opts.choices = defaultValueOrOptions;
25
+ opts = { ...opts, ...options }; // Merge with third parameter options
26
+ } else {
27
+ // Traditional object parameter
28
+ opts = { ...defaultValueOrOptions, ...options };
29
+ }
30
+
31
+ // Route to appropriate handler based on options
32
+ if (opts.editor || opts.multiline) {
33
+ return await askWithEditor(question, opts);
34
+ }
35
+
36
+ if (opts.choices) {
37
+ return await askWithChoices(question, opts);
38
+ }
39
+
10
40
  const answers = await inquirer.prompt({ name: question, message: question, ...opts })
11
41
  return Object.values(answers)[0];
12
42
  }
13
43
 
44
+ async function askWithEditor(question, opts = {}) {
45
+ const answers = await inquirer.prompt({
46
+ name: question,
47
+ message: question,
48
+ type: 'editor',
49
+ ...opts
50
+ });
51
+ return Object.values(answers)[0];
52
+ }
53
+
54
+ async function askWithChoices(question, opts = {}) {
55
+ const promptType = opts.multiple ? 'checkbox' : 'list';
56
+
57
+ const answers = await inquirer.prompt({
58
+ name: question,
59
+ message: question,
60
+ type: promptType,
61
+ choices: opts.choices,
62
+ ...opts
63
+ });
64
+
65
+ return Object.values(answers)[0];
66
+ }
67
+
14
68
  export function yell(text) {
15
69
  console.log();
16
70
 
@@ -0,0 +1,95 @@
1
+ import { spawn } from 'child_process';
2
+ import path from 'path';
3
+
4
+ export default function openEditor(files, options = {}) {
5
+ if (!Array.isArray(files) || files.length === 0) {
6
+ throw new Error('Files array is required and cannot be empty');
7
+ }
8
+
9
+ const editor = options.editor || getDefaultEditor();
10
+ const fileArgs = buildEditorArgs(editor, files);
11
+
12
+ return new Promise((resolve, reject) => {
13
+ const child = spawn(editor, fileArgs, {
14
+ stdio: 'inherit',
15
+ detached: true
16
+ });
17
+
18
+ child.on('error', (err) => {
19
+ reject(new Error(`Failed to open editor '${editor}': ${err.message}`));
20
+ });
21
+
22
+ child.on('spawn', () => {
23
+ resolve();
24
+ });
25
+
26
+ if (child.pid) {
27
+ child.unref();
28
+ }
29
+ });
30
+ }
31
+
32
+ function getDefaultEditor() {
33
+ if (process.env.EDITOR) {
34
+ return process.env.EDITOR;
35
+ }
36
+
37
+ const editors = ['code', 'subl', 'atom', 'vim', 'nvim', 'nano', 'gedit'];
38
+
39
+ for (const editor of editors) {
40
+ if (isCommandAvailable(editor)) {
41
+ return editor;
42
+ }
43
+ }
44
+
45
+ return process.platform === 'win32' ? 'notepad' : 'vi';
46
+ }
47
+
48
+ function isCommandAvailable(command) {
49
+ try {
50
+ const { execSync } = require('child_process');
51
+ execSync(`which ${command}`, { stdio: 'ignore' });
52
+ return true;
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ function buildEditorArgs(editor, files) {
59
+ const editorName = path.basename(editor);
60
+ const args = [];
61
+
62
+ for (const fileInfo of files) {
63
+ const filePath = typeof fileInfo === 'string' ? fileInfo : fileInfo.file;
64
+
65
+ if (!filePath) {
66
+ continue;
67
+ }
68
+
69
+ if (fileInfo.line && typeof fileInfo === 'object') {
70
+ switch (editorName) {
71
+ case 'code':
72
+ case 'code-insiders':
73
+ args.push('--goto', `${filePath}:${fileInfo.line}:${fileInfo.column || 1}`);
74
+ break;
75
+ case 'subl':
76
+ case 'sublime_text':
77
+ args.push(`${filePath}:${fileInfo.line}:${fileInfo.column || 1}`);
78
+ break;
79
+ case 'vim':
80
+ case 'nvim':
81
+ args.push(`+${fileInfo.line}`, filePath);
82
+ break;
83
+ case 'nano':
84
+ args.push(`+${fileInfo.line}`, filePath);
85
+ break;
86
+ default:
87
+ args.push(filePath);
88
+ }
89
+ } else {
90
+ args.push(filePath);
91
+ }
92
+ }
93
+
94
+ return args;
95
+ }
package/src/program.js CHANGED
@@ -4,7 +4,7 @@ import traverseDefault from "@babel/traverse";
4
4
  const traverse = traverseDefault.default || traverseDefault;
5
5
  import color from "chalk";
6
6
  import fs from 'fs';
7
- import openEditor from 'open-editor';
7
+ import openEditor from './open-editor.js';
8
8
  import { yell } from './io.js';
9
9
  import cprint from "./font.js";
10
10
  import { handleCompletion, detectCurrentShell, installCompletion, getCompletionPaths } from './completion.js';
@@ -37,7 +37,10 @@ export default function bunosh(commands, source) {
37
37
  showGlobalOptions: false,
38
38
  visibleGlobalOptions: _opt => [],
39
39
  visibleOptions: _opt => [],
40
- visibleCommands: cmd => cmd.commands.filter(c => !internalCommands.includes(c)),
40
+ visibleCommands: cmd => {
41
+ const commands = cmd.commands.filter(c => !internalCommands.includes(c));
42
+ return commands.filter(c => !c.name().startsWith('npm:'));
43
+ },
41
44
  subcommandTerm: (cmd) => color.white.bold(cmd.name()),
42
45
  subcommandDescription: (cmd) => color.gray(cmd.description()),
43
46
  });
@@ -46,13 +49,20 @@ export default function bunosh(commands, source) {
46
49
  program.showSuggestionAfterError(true);
47
50
  program.addHelpCommand(false);
48
51
 
49
- const completeAst = babelParser.parse(source, {
50
- sourceType: "module",
51
- ranges: true,
52
- tokens: true,
53
- comments: true,
54
- attachComment: true,
55
- });
52
+ let completeAst;
53
+ try {
54
+ completeAst = babelParser.parse(source, {
55
+ sourceType: "module",
56
+ ranges: true,
57
+ tokens: true,
58
+ comments: true,
59
+ attachComment: true,
60
+ });
61
+ } catch (parseError) {
62
+ // Re-throw with more specific error information
63
+ parseError.code = 'BABEL_PARSER_SYNTAX_ERROR';
64
+ throw parseError;
65
+ }
56
66
 
57
67
  const comments = fetchComments();
58
68
 
@@ -241,12 +251,16 @@ export default function bunosh(commands, source) {
241
251
 
242
252
  const editCmd = program.command('edit')
243
253
  .description('Open the bunosh file in your editor. $EDITOR or \'code\' is used.')
244
- .action(() => {
245
- openEditor([{
246
- file: BUNOSHFILE,
247
- }], {
248
- editor: process.env.EDITOR ? null : 'code',
249
- });
254
+ .action(async () => {
255
+ try {
256
+ await openEditor([{
257
+ file: BUNOSHFILE,
258
+ }]);
259
+ } catch (error) {
260
+ console.error(error.message);
261
+ console.error('Set $EDITOR environment variable to use a different editor');
262
+ process.exit(1);
263
+ }
250
264
  });
251
265
 
252
266
  internalCommands.push(editCmd);
@@ -282,11 +296,11 @@ export default function bunosh(commands, source) {
282
296
  try {
283
297
  // Detect current shell or use specified shell
284
298
  const shell = options.shell || detectCurrentShell();
285
-
299
+
286
300
  if (!shell) {
287
301
  console.error('❌ Could not detect your shell. Please specify one:');
288
302
  console.log(' bunosh setup-completion --shell bash');
289
- console.log(' bunosh setup-completion --shell zsh');
303
+ console.log(' bunosh setup-completion --shell zsh');
290
304
  console.log(' bunosh setup-completion --shell fish');
291
305
  process.exit(1);
292
306
  }
@@ -296,7 +310,7 @@ export default function bunosh(commands, source) {
296
310
 
297
311
  // Get paths for this shell
298
312
  const paths = getCompletionPaths(shell);
299
-
313
+
300
314
  // Check if already installed
301
315
  if (!options.force && fs.existsSync(paths.completionFile)) {
302
316
  console.log(`âš ī¸ Completion already installed at: ${paths.completionFile}`);
@@ -311,7 +325,7 @@ export default function bunosh(commands, source) {
311
325
 
312
326
  // Report success
313
327
  console.log(`✅ Completion installed: ${color.green(paths.completionFile)}`);
314
-
328
+
315
329
  if (result.configFile && result.added) {
316
330
  console.log(`📝 Updated shell config: ${color.green(result.configFile)}`);
317
331
  console.log();
@@ -327,10 +341,10 @@ export default function bunosh(commands, source) {
327
341
  console.log(`â„šī¸ Shell config already has completion setup: ${result.configFile}`);
328
342
  console.log(' Restart your terminal if completion isn\'t working.');
329
343
  }
330
-
344
+
331
345
  console.log();
332
346
  console.log('đŸŽ¯ Test completion by typing: ' + color.bold('bunosh <TAB>'));
333
-
347
+
334
348
  } catch (error) {
335
349
  console.error(`❌ Setup failed: ${error.message}`);
336
350
  process.exit(1);
@@ -360,9 +374,9 @@ export default function bunosh(commands, source) {
360
374
  const { getLatestRelease, isNewerVersion } = await import('./upgrade.js');
361
375
  const release = await getLatestRelease();
362
376
  const latestVersion = release.tag_name;
363
-
377
+
364
378
  console.log(`đŸ“Ļ Latest version: ${color.bold(latestVersion)}`);
365
-
379
+
366
380
  if (isNewerVersion(latestVersion, currentVersion)) {
367
381
  console.log(`✨ ${color.green('Update available!')} ${currentVersion} → ${latestVersion}`);
368
382
  console.log('Run ' + color.bold('bunosh upgrade') + ' to update.');
@@ -405,7 +419,7 @@ export default function bunosh(commands, source) {
405
419
 
406
420
  } catch (error) {
407
421
  console.error(`❌ Upgrade failed: ${error.message}`);
408
-
422
+
409
423
  if (error.message.includes('Unsupported platform')) {
410
424
  console.log();
411
425
  console.log('💡 Supported platforms:');
@@ -416,21 +430,36 @@ export default function bunosh(commands, source) {
416
430
  console.log();
417
431
  console.log('💡 Try again later or check your internet connection.');
418
432
  }
419
-
433
+
420
434
  process.exit(1);
421
435
  }
422
436
  });
423
437
 
424
438
  internalCommands.push(upgradeCmd);
425
439
 
440
+ // Add npm scripts help section if npm scripts exist
441
+ const npmScriptNamesForHelp = Object.keys(npmScripts);
442
+ if (npmScriptNamesForHelp.length > 0) {
443
+ const npmCommandsList = npmScriptNamesForHelp.sort().map(scriptName => {
444
+ const commandName = `npm:${scriptName}`;
445
+ const scriptCommand = npmScripts[scriptName];
446
+ return ` ${color.white.bold(commandName.padEnd(18))} ${color.gray(scriptCommand)}`;
447
+ }).join('\n');
448
+
449
+ program.addHelpText('after', `
450
+
451
+ NPM Scripts:
452
+ ${npmCommandsList}
453
+ `);
454
+ }
455
+
426
456
  program.addHelpText('after', `
427
457
 
428
458
  Special Commands:
459
+
429
460
  📝 Edit bunosh file: ${color.bold('bunosh edit')}
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')}
461
+ đŸ“Ĩ Export commands as scripts to package.json: ${color.bold('bunosh export:scripts')}
462
+ đŸĻž Upgrade bunosh: ${color.bold('bunosh upgrade')}
434
463
  `);
435
464
 
436
465
  program.on("command:*", (cmd) => {
package/src/task.js CHANGED
@@ -138,6 +138,14 @@ export class TaskResult {
138
138
  this.output = output;
139
139
  }
140
140
 
141
+ get hasFailed() {
142
+ return this.status === TaskStatus.FAIL;
143
+ }
144
+
145
+ get hasSucceeded() {
146
+ return this.status === TaskStatus.SUCCESS;
147
+ }
148
+
141
149
  static fail(output = null) {
142
150
  return new TaskResult({ status: TaskStatus.FAIL, output });
143
151
  }