bunosh 0.2.2 → 0.3.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/README.md CHANGED
@@ -377,6 +377,9 @@ const { exec, fetch, writeToFile, copyFile, say, ask, yell, task } = global.buno
377
377
  ```
378
378
 
379
379
  ### Shell Execution (`exec`)
380
+
381
+ The `exec` function runs shell commands and returns a `TaskResult` object with the command output and status.
382
+
380
383
  ```javascript
381
384
  // Simple commands
382
385
  await exec`echo "Hello World"`;
@@ -392,6 +395,63 @@ await exec`ls -la`.cwd('/tmp');
392
395
  await exec`find . -name "*.js" | grep -v node_modules | wc -l`;
393
396
  ```
394
397
 
398
+ #### TaskResult Object
399
+
400
+ The `exec` function returns a `TaskResult` object with the following properties and methods:
401
+
402
+ ```javascript
403
+ const result = await exec`ls -la`;
404
+
405
+ // Properties
406
+ result.status // 'success' or 'fail'
407
+ result.output // Combined stdout/stderr as string
408
+
409
+ // Getters (boolean)
410
+ result.hasFailed // true if command failed (non-zero exit code)
411
+ result.hasSucceeded // true if command succeeded (exit code 0)
412
+ ```
413
+
414
+ #### Error Handling Examples
415
+
416
+ ```javascript
417
+ // Check command success
418
+ const result = await exec`npm test`;
419
+ if (result.hasSucceeded) {
420
+ say('✅ Tests passed!');
421
+ } else {
422
+ yell('❌ Tests failed!');
423
+ console.log(result.output); // Show error details
424
+ }
425
+
426
+ // Get command output
427
+ const result = await exec`git rev-parse HEAD`;
428
+ if (result.hasSucceeded) {
429
+ const commitHash = result.output.trim();
430
+ say(`Current commit: ${commitHash}`);
431
+ }
432
+
433
+ // Handle failures gracefully
434
+ const result = await exec`optional-command-that-might-fail`;
435
+ if (result.hasFailed) {
436
+ say('Command failed, but continuing...');
437
+ console.log('Error output:', result.output);
438
+ }
439
+
440
+ // Old vs New style comparison
441
+ // ❌ Old: Commands throw on failure
442
+ try {
443
+ await someOtherTaskRunner('failing-command');
444
+ } catch (error) {
445
+ // Handle error
446
+ }
447
+
448
+ // ✅ New: Explicit success/failure handling
449
+ const result = await exec`failing-command`;
450
+ if (result.hasFailed) {
451
+ // Handle failure explicitly
452
+ }
453
+ ```
454
+
395
455
  ### HTTP Requests (`fetch`)
396
456
  ```javascript
397
457
  // GET request with progress indicator
@@ -428,6 +488,275 @@ await task('Installing dependencies', async () => {
428
488
  });
429
489
  ```
430
490
 
491
+ ## 🤖 AI-Powered Tasks
492
+
493
+ Bunosh now supports AI integration with structured outputs! Connect to popular AI providers and generate content, analyze data, or automate text processing with simple function calls.
494
+
495
+ ### Quick Setup
496
+
497
+ Set your AI provider credentials:
498
+ ```bash
499
+ # Required: Choose your model
500
+ export AI_MODEL=gpt-4o # or claude-3-5-sonnet-20241022, llama-3.3-70b-versatile, etc.
501
+
502
+ # Required: Set API key for your chosen provider
503
+ export OPENAI_API_KEY=your_key_here # for OpenAI models
504
+ # export ANTHROPIC_API_KEY=your_key_here # for Claude models
505
+ # export GROQ_API_KEY=your_key_here # for Groq models
506
+ ```
507
+
508
+ ### Built-in AI Providers
509
+
510
+ - **OpenAI** - GPT-4o, GPT-4o-mini, GPT-3.5-turbo (via `OPENAI_API_KEY`)
511
+ - **Anthropic** - Claude 3.5 Sonnet, Claude 3 Haiku (via `ANTHROPIC_API_KEY`)
512
+ - **Groq** - Llama 3.3, Mixtral, Gemma models (via `GROQ_API_KEY` or `GROQ_KEY`)
513
+
514
+ ### Custom AI Providers
515
+
516
+ For enterprise and custom setups, you can import and register any AI provider manually:
517
+
518
+ ```javascript
519
+ const { ai } = global.bunosh;
520
+
521
+ // Method 1: Direct model configuration (most flexible)
522
+ import { bedrock } from '@ai-sdk/amazon-bedrock';
523
+ const bedrockModel = bedrock('anthropic.claude-3-sonnet-20240229-v1:0', {
524
+ region: 'us-east-1',
525
+ credentials: {
526
+ accessKeyId: 'your-access-key',
527
+ secretAccessKey: 'your-secret-key'
528
+ }
529
+ });
530
+
531
+ ai.configure({ model: bedrockModel });
532
+
533
+ // Method 2: Register custom provider with environment variable
534
+ import { xai } from '@ai-sdk/xai';
535
+ ai.configure({
536
+ registerProvider: {
537
+ envVar: 'XAI_API_KEY',
538
+ provider: {
539
+ createInstance: (modelName) => xai(modelName)
540
+ }
541
+ }
542
+ });
543
+
544
+ // Method 3: Register any custom provider (Azure OpenAI, OpenRouter, etc.)
545
+ import { openrouter } from '@openrouter/ai-sdk-provider';
546
+ ai.configure({
547
+ registerProvider: {
548
+ envVar: 'OPENROUTER_API_KEY',
549
+ provider: {
550
+ createInstance: (modelName) => openrouter(modelName)
551
+ }
552
+ }
553
+ });
554
+
555
+ // Method 4: Register completely custom provider
556
+ ai.configure({
557
+ registerProvider: {
558
+ envVar: 'CUSTOM_AI_API_KEY',
559
+ provider: {
560
+ createInstance: (modelName) => {
561
+ // Your custom provider logic
562
+ return customAIProvider(modelName, {
563
+ apiKey: process.env.CUSTOM_AI_API_KEY,
564
+ endpoint: 'https://custom-ai.company.com/v1'
565
+ });
566
+ }
567
+ }
568
+ }
569
+ });
570
+
571
+ // Reset to environment variable configuration
572
+ ai.reset();
573
+
574
+ // Check current configuration
575
+ const config = ai.getConfig();
576
+ console.log('Current AI config:', config);
577
+ ```
578
+
579
+ ### AI Task Examples
580
+
581
+ ```javascript
582
+ const { ai, writeToFile, say } = global.bunosh;
583
+
584
+ /**
585
+ * Generate project documentation with AI
586
+ */
587
+ export async function generateDocs() {
588
+ const codebase = fs.readFileSync('src/index.js', 'utf8');
589
+
590
+ const result = await ai(
591
+ `Generate documentation for this code: ${codebase}`,
592
+ {
593
+ overview: 'Brief project overview',
594
+ apiReference: 'API documentation',
595
+ examples: 'Usage examples',
596
+ installation: 'Installation instructions'
597
+ }
598
+ );
599
+
600
+ writeToFile('README.md', (line) => {
601
+ line`# ${result.overview}`;
602
+ line``;
603
+ line`## Installation`;
604
+ line`${result.installation}`;
605
+ line``;
606
+ line`## API Reference`;
607
+ line`${result.apiReference}`;
608
+ line``;
609
+ line`## Examples`;
610
+ line`${result.examples}`;
611
+ });
612
+
613
+ say('📚 Documentation generated!');
614
+ }
615
+
616
+ /**
617
+ * Analyze and optimize code with AI suggestions
618
+ */
619
+ export async function codeReview(filename) {
620
+ const code = fs.readFileSync(filename, 'utf8');
621
+
622
+ const analysis = await ai(
623
+ `Review this code for improvements: ${code}`,
624
+ {
625
+ issues: 'List of potential issues',
626
+ suggestions: 'Specific improvement suggestions',
627
+ security: 'Security considerations',
628
+ performance: 'Performance optimization tips',
629
+ rating: 'Overall code quality rating (1-10)'
630
+ }
631
+ );
632
+
633
+ say(`🔍 Code Review for ${filename}:`);
634
+ console.log(`Rating: ${analysis.rating}/10`);
635
+ console.log(`Issues: ${analysis.issues}`);
636
+ console.log(`Suggestions: ${analysis.suggestions}`);
637
+ console.log(`Security: ${analysis.security}`);
638
+ console.log(`Performance: ${analysis.performance}`);
639
+ }
640
+
641
+ /**
642
+ * Generate test cases from code
643
+ */
644
+ export async function generateTests(sourceFile) {
645
+ const code = fs.readFileSync(sourceFile, 'utf8');
646
+
647
+ const tests = await ai(
648
+ `Generate comprehensive unit tests for this code: ${code}`,
649
+ {
650
+ testSuite: 'Complete test suite code',
651
+ edgeCases: 'List of edge cases covered',
652
+ mockSetup: 'Required mocks and setup code'
653
+ }
654
+ );
655
+
656
+ const testFile = sourceFile.replace('.js', '.test.js');
657
+ writeToFile(testFile, (line) => {
658
+ line`${tests.mockSetup}`;
659
+ line``;
660
+ line`${tests.testSuite}`;
661
+ });
662
+
663
+ say(`🧪 Tests generated: ${testFile}`);
664
+ say(`Edge cases: ${tests.edgeCases}`);
665
+ }
666
+
667
+ /**
668
+ * Create commit messages from git diff
669
+ */
670
+ export async function smartCommit() {
671
+ const diff = await exec`git diff --staged`;
672
+
673
+ if (diff.hasFailed || !diff.output.trim()) {
674
+ say('No staged changes found');
675
+ return;
676
+ }
677
+
678
+ const commit = await ai(
679
+ `Generate a commit message for these changes: ${diff.output}`,
680
+ {
681
+ title: 'Concise commit title (50 chars max)',
682
+ body: 'Detailed commit body explaining what and why',
683
+ type: 'Commit type (feat/fix/docs/refactor/test/chore)'
684
+ }
685
+ );
686
+
687
+ const message = `${commit.type}: ${commit.title}\n\n${commit.body}`;
688
+ await exec`git commit -m "${message}"`;
689
+
690
+ say(`✅ Committed with AI-generated message:`);
691
+ console.log(message);
692
+ }
693
+
694
+ /**
695
+ * Enterprise AI setup with custom provider
696
+ */
697
+ export async function setupEnterpriseAI() {
698
+ // Import your enterprise AI provider
699
+ import { bedrock } from '@ai-sdk/amazon-bedrock';
700
+
701
+ // Configure for enterprise use
702
+ const enterpriseModel = bedrock('anthropic.claude-3-sonnet-20240229-v1:0', {
703
+ region: 'us-east-1'
704
+ // Uses AWS credentials from environment/profile
705
+ });
706
+
707
+ ai.configure({ model: enterpriseModel });
708
+
709
+ const analysis = await ai(
710
+ 'Analyze our company performance from this quarterly report: [data]',
711
+ {
712
+ summary: 'Executive summary of performance',
713
+ risks: 'Identified business risks',
714
+ opportunities: 'Growth opportunities',
715
+ recommendations: 'Strategic recommendations'
716
+ }
717
+ );
718
+
719
+ say('📊 Enterprise AI analysis complete');
720
+ console.log(analysis);
721
+ }
722
+ ```
723
+
724
+ ### Simple Text Generation
725
+
726
+ For quick text generation without structured output:
727
+
728
+ ```javascript
729
+ /**
730
+ * Generate marketing copy
731
+ */
732
+ export async function generateCopy(product) {
733
+ const copy = await ai(`Write compelling marketing copy for: ${product}`);
734
+ say('📝 Generated copy:');
735
+ console.log(copy);
736
+ }
737
+
738
+ /**
739
+ * Translate content
740
+ */
741
+ export async function translate(text, language = 'Spanish') {
742
+ const translation = await ai(`Translate to ${language}: ${text}`);
743
+ say(`🌐 Translation to ${language}:`);
744
+ console.log(translation);
745
+ }
746
+ ```
747
+
748
+ ### Progressive Enhancement
749
+
750
+ The AI task features:
751
+ - **🎭 Animated Progress**: Braille spinner animation during generation
752
+ - **📊 Token Tracking**: Shows token usage for cost monitoring
753
+ - **⚡ Fast Inference**: Optimized for speed with Groq and other providers
754
+ - **🔧 Structured Output**: Get JSON responses with defined schemas
755
+ - **🎯 Provider Auto-Detection**: Automatically detects available API keys
756
+ - **💪 Error Handling**: Graceful handling of API errors and rate limits
757
+
758
+ Transform your development workflow with AI-powered automation! Generate documentation, analyze code, create tests, write commit messages, and much more.
759
+
431
760
  ## Command Features
432
761
 
433
762
  ### Automatic CLI Generation
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
@@ -2,10 +2,11 @@ import exec from "./src/tasks/exec.js";
2
2
  import fetch from "./src/tasks/fetch.js";
3
3
  import writeToFile from "./src/tasks/writeToFile.js";
4
4
  import copyFile from "./src/tasks/copyFile.js";
5
+ import ai from "./src/tasks/ai.js";
5
6
  import { ask, yell, say } from "./src/io.js";
6
7
  import { task, stopOnFail, ignoreFail } from "./src/task.js";
7
8
 
8
- export { exec, fetch, writeToFile, copyFile, ask, yell, say, task, stopOnFail, ignoreFail };
9
+ export { exec, fetch, writeToFile, copyFile, ai, ask, yell, say, task, stopOnFail, ignoreFail };
9
10
 
10
11
 
11
12
  export function buildCmd(cmd) {
@@ -20,6 +21,7 @@ global.bunosh = {
20
21
  exec,
21
22
  writeToFile,
22
23
  copyFile,
24
+ ai,
23
25
  stopOnFail,
24
26
  ignoreFail,
25
27
  task,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunosh",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "module": "index.js",
6
6
  "bin": {
@@ -14,15 +14,20 @@
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
28
  "open-editor": "^5.1.0",
25
- "timer-node": "^5.0.9"
29
+ "timer-node": "^5.0.9",
30
+ "zod": "^4.1.5"
26
31
  },
27
32
  "license": "ISC",
28
33
  "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/program.js CHANGED
@@ -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
 
@@ -282,11 +292,11 @@ export default function bunosh(commands, source) {
282
292
  try {
283
293
  // Detect current shell or use specified shell
284
294
  const shell = options.shell || detectCurrentShell();
285
-
295
+
286
296
  if (!shell) {
287
297
  console.error('❌ Could not detect your shell. Please specify one:');
288
298
  console.log(' bunosh setup-completion --shell bash');
289
- console.log(' bunosh setup-completion --shell zsh');
299
+ console.log(' bunosh setup-completion --shell zsh');
290
300
  console.log(' bunosh setup-completion --shell fish');
291
301
  process.exit(1);
292
302
  }
@@ -296,7 +306,7 @@ export default function bunosh(commands, source) {
296
306
 
297
307
  // Get paths for this shell
298
308
  const paths = getCompletionPaths(shell);
299
-
309
+
300
310
  // Check if already installed
301
311
  if (!options.force && fs.existsSync(paths.completionFile)) {
302
312
  console.log(`⚠️ Completion already installed at: ${paths.completionFile}`);
@@ -311,7 +321,7 @@ export default function bunosh(commands, source) {
311
321
 
312
322
  // Report success
313
323
  console.log(`✅ Completion installed: ${color.green(paths.completionFile)}`);
314
-
324
+
315
325
  if (result.configFile && result.added) {
316
326
  console.log(`📝 Updated shell config: ${color.green(result.configFile)}`);
317
327
  console.log();
@@ -327,10 +337,10 @@ export default function bunosh(commands, source) {
327
337
  console.log(`ℹ️ Shell config already has completion setup: ${result.configFile}`);
328
338
  console.log(' Restart your terminal if completion isn\'t working.');
329
339
  }
330
-
340
+
331
341
  console.log();
332
342
  console.log('🎯 Test completion by typing: ' + color.bold('bunosh <TAB>'));
333
-
343
+
334
344
  } catch (error) {
335
345
  console.error(`❌ Setup failed: ${error.message}`);
336
346
  process.exit(1);
@@ -360,9 +370,9 @@ export default function bunosh(commands, source) {
360
370
  const { getLatestRelease, isNewerVersion } = await import('./upgrade.js');
361
371
  const release = await getLatestRelease();
362
372
  const latestVersion = release.tag_name;
363
-
373
+
364
374
  console.log(`📦 Latest version: ${color.bold(latestVersion)}`);
365
-
375
+
366
376
  if (isNewerVersion(latestVersion, currentVersion)) {
367
377
  console.log(`✨ ${color.green('Update available!')} ${currentVersion} → ${latestVersion}`);
368
378
  console.log('Run ' + color.bold('bunosh upgrade') + ' to update.');
@@ -405,7 +415,7 @@ export default function bunosh(commands, source) {
405
415
 
406
416
  } catch (error) {
407
417
  console.error(`❌ Upgrade failed: ${error.message}`);
408
-
418
+
409
419
  if (error.message.includes('Unsupported platform')) {
410
420
  console.log();
411
421
  console.log('💡 Supported platforms:');
@@ -416,21 +426,36 @@ export default function bunosh(commands, source) {
416
426
  console.log();
417
427
  console.log('💡 Try again later or check your internet connection.');
418
428
  }
419
-
429
+
420
430
  process.exit(1);
421
431
  }
422
432
  });
423
433
 
424
434
  internalCommands.push(upgradeCmd);
425
435
 
436
+ // Add npm scripts help section if npm scripts exist
437
+ const npmScriptNamesForHelp = Object.keys(npmScripts);
438
+ if (npmScriptNamesForHelp.length > 0) {
439
+ const npmCommandsList = npmScriptNamesForHelp.sort().map(scriptName => {
440
+ const commandName = `npm:${scriptName}`;
441
+ const scriptCommand = npmScripts[scriptName];
442
+ return ` ${color.white.bold(commandName.padEnd(18))} ${color.gray(scriptCommand)}`;
443
+ }).join('\n');
444
+
445
+ program.addHelpText('after', `
446
+
447
+ NPM Scripts:
448
+ ${npmCommandsList}
449
+ `);
450
+ }
451
+
426
452
  program.addHelpText('after', `
427
453
 
428
454
  Special Commands:
455
+
429
456
  📝 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')}
457
+ 📥 Export commands as scripts to package.json: ${color.bold('bunosh export:scripts')}
458
+ 🦾 Upgrade bunosh: ${color.bold('bunosh upgrade')}
434
459
  `);
435
460
 
436
461
  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
  }
@@ -0,0 +1,143 @@
1
+ import { TaskResult, createTaskInfo, finishTaskInfo } from '../task.js';
2
+ import Printer from '../printer.js';
3
+ import { generateObject, generateText } from 'ai';
4
+ import { openai } from '@ai-sdk/openai';
5
+ import { anthropic } from '@ai-sdk/anthropic';
6
+ import { createGroq } from '@ai-sdk/groq';
7
+ import { z } from 'zod';
8
+
9
+ let customConfiguration = null;
10
+
11
+ const PROVIDER_REGISTRY = {
12
+ OPENAI_API_KEY: (modelName) => openai(modelName),
13
+ ANTHROPIC_API_KEY: (modelName) => anthropic(modelName),
14
+ GROQ_API_KEY: (modelName) => createGroq({ apiKey: process.env.GROQ_API_KEY })(modelName),
15
+ GROQ_KEY: (modelName) => createGroq({ apiKey: process.env.GROQ_KEY })(modelName)
16
+ };
17
+
18
+ const customProviders = new Map();
19
+
20
+ function detectProvider() {
21
+ if (customConfiguration) {
22
+ return customConfiguration.model;
23
+ }
24
+
25
+ if (!process.env.AI_MODEL) {
26
+ throw new Error('AI_MODEL environment variable is required. Set it to specify which model to use (e.g., AI_MODEL=gpt-4o, AI_MODEL=claude-3-5-sonnet-20241022, AI_MODEL=llama-3.3-70b-versatile).');
27
+ }
28
+
29
+ for (const [envVar, provider] of customProviders) {
30
+ if (process.env[envVar]) {
31
+ return provider.createInstance(process.env.AI_MODEL);
32
+ }
33
+ }
34
+
35
+ for (const [envVar, createInstance] of Object.entries(PROVIDER_REGISTRY)) {
36
+ if (process.env[envVar]) {
37
+ return createInstance(process.env.AI_MODEL);
38
+ }
39
+ }
40
+
41
+ const availableProviders = [
42
+ ...Object.keys(PROVIDER_REGISTRY),
43
+ ...Array.from(customProviders.keys())
44
+ ];
45
+
46
+ throw new Error(`No AI provider configured. Set one of these environment variables: ${availableProviders.join(', ')}. Or use ai.configure() for custom setup.`);
47
+ }
48
+
49
+ function configure(config) {
50
+ if (!config || typeof config !== 'object') {
51
+ throw new Error('ai.configure() requires a configuration object');
52
+ }
53
+
54
+ if (config.model && typeof config.model === 'object') {
55
+ customConfiguration = { model: config.model };
56
+ return;
57
+ }
58
+
59
+ if (config.registerProvider) {
60
+ const { envVar, provider } = config.registerProvider;
61
+ if (!envVar || !provider) {
62
+ throw new Error('registerProvider requires { envVar: "ENV_VAR_NAME", provider: { createInstance: (model) => providerInstance } }');
63
+ }
64
+ customProviders.set(envVar, provider);
65
+ return;
66
+ }
67
+
68
+ throw new Error('Invalid configuration. Use ai.configure({ model: providerInstance }) or ai.configure({ registerProvider: { envVar: "CUSTOM_API_KEY", provider: { createInstance: (model) => customProvider(model) } } })');
69
+ }
70
+
71
+ function createSchema(outputFormat) {
72
+ const schemaObject = {};
73
+ for (const [key, description] of Object.entries(outputFormat)) {
74
+ schemaObject[key] = z.string().describe(description);
75
+ }
76
+
77
+ return z.object(schemaObject);
78
+ }
79
+
80
+ function createProgressIndicator() {
81
+ const dots = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
82
+ let index = 0;
83
+
84
+ return setInterval(() => {
85
+ const spinner = dots[index % dots.length];
86
+ process.stdout.write(`\r${spinner} Generating...`);
87
+ index++;
88
+ }, 100);
89
+ }
90
+
91
+ async function ai(prompt, outputFormat = null) {
92
+ const cleanPrompt = prompt.replace(/\s+/g, ' ').trim();
93
+ const taskName = `AI: ${cleanPrompt.substring(0, 50)}${cleanPrompt.length > 50 ? '...' : ''}`;
94
+
95
+ const taskInfo = createTaskInfo(taskName);
96
+ const printer = new Printer('ai', taskInfo.id);
97
+ printer.start(taskName);
98
+
99
+ // Start progress indicator
100
+ const progressInterval = createProgressIndicator();
101
+
102
+ try {
103
+ const model = detectProvider();
104
+ let result, usage;
105
+
106
+ if (outputFormat) {
107
+ const schema = createSchema(outputFormat);
108
+ const response = await generateObject({ model, prompt, schema });
109
+ result = response.object;
110
+ usage = response.usage;
111
+ } else {
112
+ const response = await generateText({ model, prompt });
113
+ result = response.text;
114
+ usage = response.usage;
115
+ }
116
+
117
+ clearInterval(progressInterval);
118
+ process.stdout.write('\r' + ' '.repeat(20) + '\r');
119
+ const tokenInfo = usage ? `${usage.totalTokens} tokens` : '';
120
+ printer.finish(taskName, { tokenInfo });
121
+ finishTaskInfo(taskInfo, true, null, outputFormat ? JSON.stringify(result, null, 2) : result);
122
+ return TaskResult.success(result);
123
+
124
+ } catch (error) {
125
+ clearInterval(progressInterval);
126
+ process.stdout.write('\r' + ' '.repeat(20) + '\r');
127
+ printer.error(taskName, error);
128
+ finishTaskInfo(taskInfo, false, error, error.message);
129
+ return TaskResult.fail(error.message);
130
+ }
131
+ }
132
+
133
+ ai.configure = configure;
134
+
135
+ ai.reset = function() {
136
+ customConfiguration = null;
137
+ };
138
+
139
+ ai.getConfig = function() {
140
+ return customConfiguration;
141
+ };
142
+
143
+ export default ai;