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 +329 -0
- package/bunosh.js +90 -4
- package/index.js +3 -1
- package/package.json +7 -2
- package/src/init.js +12 -4
- package/src/program.js +47 -22
- package/src/task.js +8 -0
- package/src/tasks/ai.js +143 -0
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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.
|
|
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
|
-
|
|
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 =>
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/tasks/ai.js
ADDED
|
@@ -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;
|