bunosh 0.3.1 β 0.4.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 +503 -829
- package/bunosh.js +195 -46
- package/index.js +8 -2
- package/package.json +1 -1
- package/src/formatters/console.js +1 -0
- package/src/formatters/github-actions.js +4 -0
- package/src/init.js +2 -2
- package/src/io.js +90 -21
- package/src/printer.js +25 -2
- package/src/program.js +321 -21
- package/src/task.js +143 -22
- package/src/tasks/exec.js +14 -2
- package/src/tasks/fetch.js +4 -3
- package/src/tasks/shell.js +37 -5
- package/src/open-editor.js +0 -95
package/README.md
CHANGED
|
@@ -1,27 +1,88 @@
|
|
|
1
1
|
# π² Bunosh
|
|
2
2
|
|
|
3
|
-
> *Named after **banosh**, a traditional Ukrainian dish from cornmeal cooked with various ingredients such as mushrooms, cheese, sour cream*
|
|
4
|
-
|
|
5
3
|
<p align="center">
|
|
6
4
|
<img src="assets/logo.png" alt="Logo" width="150">
|
|
7
5
|
</p>
|
|
8
6
|
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Your exceptional task runner</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
Transform JavaScript functions into CLI commands.
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
9
17
|
## What is Bunosh?
|
|
10
18
|
|
|
11
|
-
Bunosh is a modern task runner that
|
|
19
|
+
Bunosh is a modern task runner that turns your JavaScript functions into CLI commands instantly. No configuration, no boilerplate - just write functions and run them from the terminal.
|
|
20
|
+
|
|
21
|
+
> *Named after **banosh**, a traditional Ukrainian dish from cornmeal cooked with various ingredients*
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
23
|
+
### β¨ Key Features
|
|
24
|
+
|
|
25
|
+
- **π Zero Configuration** - Write functions, get CLI commands automatically
|
|
26
|
+
- **π¨ Pure JavaScript** - write commands as JavaScript functions
|
|
27
|
+
- **π Personal Commands** - Global commands from `~/Bunoshfile.js` available everywhere with `my:` namespace
|
|
28
|
+
- **π¦ Built-in Tasks** - Shell execution, HTTP requests, file operations
|
|
29
|
+
- **π€ AI-Powered** - integrate LLM calls into your daily tasks
|
|
30
|
+
- **π§ Cross-Platform** - Works seamlessly on macOS, Linux, and Windows. Via bun, npm, or as single executable.
|
|
31
|
+
- **π― Smart CLI** - Auto-completion, help generation, and intuitive argument handling
|
|
32
|
+
|
|
33
|
+
## Hello World
|
|
34
|
+
|
|
35
|
+
No nore words, just code:
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
// this is a command in Bunoshfile.js
|
|
39
|
+
// bunosh hello:world
|
|
40
|
+
|
|
41
|
+
export async function helloWorld(name = 'person') {
|
|
42
|
+
name = await ask("What's your name?", name);
|
|
43
|
+
say(`π Hello, ${name}!`);
|
|
44
|
+
const city = await ask('Which city do you live in?')
|
|
45
|
+
const result = await fetch(`https://wttr.in/${city}?format=3`)
|
|
46
|
+
say(`Weather in your city ${result.output}`)
|
|
47
|
+
|
|
48
|
+
const toCleanup = await ask('Do you want me to cleanup tmp for you?', true);
|
|
49
|
+
|
|
50
|
+
if (!toCleanup) {
|
|
51
|
+
say('Bye, then!');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await shell`rm -rf ${require('os').tmpdir()}/*`;
|
|
56
|
+
say('π§Ή Cleaned up! Have a great day!');
|
|
57
|
+
}
|
|
58
|
+
````
|
|
59
|
+
|
|
60
|
+
## Why Choose Bunosh?
|
|
61
|
+
|
|
62
|
+
| Comparison | π Bash Scripts | π¦ npm scripts | π οΈ Task Runners | π² **Bunosh** |
|
|
63
|
+
|------------|-----------------|----------------|------------------------------|----------------|
|
|
64
|
+
| **Syntax** | bash/zsh | Simple commands | Custom DSL | β
JavaScript |
|
|
65
|
+
| **Cross-platform** | No | Yes | Yes | β
Yes |
|
|
66
|
+
| **Ecosystem** | CLI tools | npm packages | Plugin dependent | β
Bash + npm |
|
|
67
|
+
| **Composability** | Commands | Separate scripts | Task dependencies | β
Import any JS code |
|
|
68
|
+
|
|
69
|
+
## TOC
|
|
70
|
+
|
|
71
|
+
- [Installation](#installation)
|
|
72
|
+
- [Quickstart](#quickstart)
|
|
73
|
+
- [Commands](#commands)
|
|
74
|
+
- [Personal Commands (My Namespace)](#personal-commands-my-namespace)
|
|
75
|
+
- [Tasks](#tasks)
|
|
76
|
+
- [Input/Output](#inputoutput)
|
|
77
|
+
- [Task Control](#task-control)
|
|
78
|
+
- [AI Integration](#ai-integration)
|
|
79
|
+
- [Examples](#examples)
|
|
19
80
|
|
|
20
81
|
## Installation
|
|
21
82
|
|
|
22
83
|
### Option 1: Single Executable (Recommended)
|
|
23
84
|
|
|
24
|
-
Download
|
|
85
|
+
Download the standalone executable - no Node.js or Bun required:
|
|
25
86
|
|
|
26
87
|
**macOS:**
|
|
27
88
|
```bash
|
|
@@ -40,553 +101,361 @@ sudo mv bunosh-linux-x64 /usr/local/bin/bunosh
|
|
|
40
101
|
Invoke-WebRequest -Uri "https://github.com/davertmik/bunosh/releases/latest/download/bunosh-windows-x64.exe.zip" -OutFile "bunosh.zip"
|
|
41
102
|
Expand-Archive -Path "bunosh.zip" -DestinationPath .
|
|
42
103
|
Move-Item "bunosh-windows-x64.exe" "bunosh.exe"
|
|
43
|
-
# Add bunosh.exe to your PATH
|
|
44
104
|
```
|
|
45
105
|
|
|
46
|
-
### Option 2:
|
|
106
|
+
### Option 2: Package Managers
|
|
47
107
|
|
|
48
108
|
```bash
|
|
109
|
+
# Using Bun
|
|
49
110
|
bun add -g bunosh
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Option 3: NPM Package
|
|
53
111
|
|
|
54
|
-
|
|
112
|
+
# Using npm
|
|
55
113
|
npm install -g bunosh
|
|
56
114
|
```
|
|
57
115
|
|
|
116
|
+
## Quickstart
|
|
58
117
|
|
|
118
|
+
1. **Initialize your Bunoshfile:**
|
|
59
119
|
```bash
|
|
60
|
-
# Initialize a new Bunoshfile
|
|
61
120
|
bunosh init
|
|
62
|
-
|
|
63
|
-
# This creates Bunoshfile.js with sample tasks
|
|
64
121
|
```
|
|
65
122
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
Create a `Bunoshfile.js`:
|
|
69
|
-
|
|
123
|
+
2. **Write your first command:**
|
|
70
124
|
```javascript
|
|
71
|
-
//
|
|
72
|
-
const { exec,
|
|
125
|
+
// Bunoshfile.js
|
|
126
|
+
const { exec, say } = global.bunosh;
|
|
73
127
|
|
|
74
128
|
/**
|
|
75
|
-
*
|
|
129
|
+
* Builds the project for production
|
|
76
130
|
*/
|
|
77
|
-
export async function
|
|
78
|
-
|
|
79
|
-
|
|
131
|
+
export async function build(env = 'production') {
|
|
132
|
+
say(`π¨ Building for ${env}...`);
|
|
133
|
+
await exec`npm run build`.env({ NODE_ENV: env });
|
|
134
|
+
say('β
Build complete!');
|
|
80
135
|
}
|
|
136
|
+
```
|
|
81
137
|
|
|
82
|
-
|
|
83
|
-
* Starts development server
|
|
84
|
-
*/
|
|
85
|
-
export async function dev() {
|
|
86
|
-
say('π Starting development server...');
|
|
87
|
-
await exec`npm run dev`;
|
|
88
|
-
}
|
|
138
|
+
That's it! Your function is now a CLI command.
|
|
89
139
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
say(`π¨ Building for ${target}...`);
|
|
95
|
-
await exec`npm run build`;
|
|
140
|
+
3. **Run it:**
|
|
141
|
+
```bash
|
|
142
|
+
# build for production
|
|
143
|
+
bunosh build
|
|
96
144
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
yell('BUILD COMPLETE!');
|
|
100
|
-
}
|
|
101
|
-
}
|
|
145
|
+
# build for staging
|
|
146
|
+
bunosh build staging
|
|
102
147
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
export async function deploy(env = 'staging', options = { skipTests: false }) {
|
|
107
|
-
if (!options.skipTests) {
|
|
108
|
-
say('π§ͺ Running tests...');
|
|
109
|
-
await exec`npm test`;
|
|
110
|
-
}
|
|
148
|
+
# build for development
|
|
149
|
+
bunosh build development
|
|
150
|
+
```
|
|
111
151
|
|
|
112
|
-
|
|
113
|
-
await build('production');
|
|
114
|
-
await exec`docker build -t myapp:${env} .`;
|
|
115
|
-
await exec`docker push myapp:${env}`;
|
|
152
|
+
## Commands
|
|
116
153
|
|
|
117
|
-
|
|
118
|
-
}
|
|
154
|
+
### Creating Commands
|
|
119
155
|
|
|
120
|
-
|
|
121
|
-
* Cleans up temporary files
|
|
122
|
-
*/
|
|
123
|
-
export async function clean() {
|
|
124
|
-
await exec`rm -rf dist node_modules/.cache tmp`;
|
|
125
|
-
say('β¨ All clean!');
|
|
126
|
-
}
|
|
156
|
+
Every exported function in `Bunoshfile.js` becomes a CLI command:
|
|
127
157
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const useTypescript = await ask('Use TypeScript? (y/n)') === 'y';
|
|
134
|
-
|
|
135
|
-
say('ποΈ Setting up project...');
|
|
136
|
-
|
|
137
|
-
// Create package.json
|
|
138
|
-
writeToFile('package.json', (line) => {
|
|
139
|
-
line`{`;
|
|
140
|
-
line` "name": "${projectName}",`;
|
|
141
|
-
line` "version": "1.0.0",`;
|
|
142
|
-
line` "type": "module"`;
|
|
143
|
-
if (useTypescript) {
|
|
144
|
-
line`, "devDependencies": {`;
|
|
145
|
-
line` "typescript": "^5.0.0"`;
|
|
146
|
-
line` }`;
|
|
147
|
-
}
|
|
148
|
-
line`}`;
|
|
149
|
-
});
|
|
158
|
+
```javascript
|
|
159
|
+
// Simple command
|
|
160
|
+
export function hello() {
|
|
161
|
+
console.log('Hello, World!');
|
|
162
|
+
}
|
|
150
163
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
164
|
+
// Command with parameters
|
|
165
|
+
export function greet(name = 'friend') {
|
|
166
|
+
console.log(`Hello, ${name}!`);
|
|
167
|
+
}
|
|
154
168
|
|
|
155
|
-
|
|
169
|
+
// Command with options
|
|
170
|
+
export function deploy(env = 'staging', options = { force: false, verbose: false }) {
|
|
171
|
+
if (options.verbose) console.log('Verbose mode enabled');
|
|
172
|
+
console.log(`Deploying to ${env}${options.force ? ' (forced)' : ''}`);
|
|
156
173
|
}
|
|
157
174
|
```
|
|
158
175
|
|
|
159
|
-
|
|
160
|
-
|
|
176
|
+
**CLI Usage:**
|
|
161
177
|
```bash
|
|
162
|
-
|
|
163
|
-
bunosh
|
|
164
|
-
|
|
165
|
-
# Run individual tasks
|
|
166
|
-
bunosh install
|
|
167
|
-
bunosh dev
|
|
168
|
-
bunosh build
|
|
169
|
-
bunosh build staging
|
|
170
|
-
bunosh deploy production --skip-tests
|
|
171
|
-
bunosh clean
|
|
172
|
-
bunosh setup
|
|
178
|
+
bunosh hello
|
|
179
|
+
bunosh greet John
|
|
180
|
+
bunosh deploy production --force --verbose
|
|
173
181
|
```
|
|
174
182
|
|
|
175
|
-
|
|
183
|
+
### Arguments and Options
|
|
176
184
|
|
|
177
|
-
|
|
178
|
-
π² Your exceptional task runner
|
|
185
|
+
Bunosh automatically maps function parameters to CLI arguments:
|
|
179
186
|
|
|
180
|
-
|
|
187
|
+
```javascript
|
|
188
|
+
/**
|
|
189
|
+
* Create a new feature branch
|
|
190
|
+
* @param {string} name - Feature name (required)
|
|
191
|
+
* @param {string} base - Base branch (optional, defaults to 'main')
|
|
192
|
+
* @param {object} options - CLI options
|
|
193
|
+
* @param {boolean} options.push - Push to remote after creation
|
|
194
|
+
*/
|
|
195
|
+
export async function feature(name, base = 'main', options = { push: false }) {
|
|
196
|
+
await exec`git checkout -b feature/${name} ${base}`;
|
|
181
197
|
|
|
182
|
-
|
|
198
|
+
if (options.push) {
|
|
199
|
+
await exec`git push -u origin feature/${name}`;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
183
203
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
bunosh deploy [env] --skip-tests
|
|
190
|
-
dev Starts development server
|
|
191
|
-
install Installs project dependencies
|
|
192
|
-
setup Setup new project environment
|
|
204
|
+
**Generated CLI:**
|
|
205
|
+
```bash
|
|
206
|
+
bunosh feature my-feature # Creates from main
|
|
207
|
+
bunosh feature my-feature develop # Creates from develop
|
|
208
|
+
bunosh feature my-feature --push # Creates and pushes
|
|
193
209
|
```
|
|
194
210
|
|
|
195
|
-
|
|
211
|
+
### Command Naming
|
|
196
212
|
|
|
197
|
-
|
|
198
|
-
const { exec, fetch, writeToFile, say, task } = global.bunosh;
|
|
213
|
+
Functions are automatically converted to kebab-case commands:
|
|
199
214
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
for (const service of services) {
|
|
207
|
-
const url = `https://${service}.${env}.example.com/health`;
|
|
208
|
-
await task(`Checking ${service}`, async () => {
|
|
209
|
-
const response = await fetch(url);
|
|
210
|
-
if (!response.ok) throw new Error(`${service} is down!`);
|
|
211
|
-
});
|
|
212
|
-
}
|
|
215
|
+
| Function Name | CLI Command |
|
|
216
|
+
|--------------|-------------|
|
|
217
|
+
| `build` | `bunosh build` |
|
|
218
|
+
| `gitPush` | `bunosh git:push` |
|
|
219
|
+
| `npmInstall` | `bunosh npm:install` |
|
|
220
|
+
| `buildAndDeploy` | `bunosh build:and-deploy` |
|
|
213
221
|
|
|
214
|
-
|
|
215
|
-
}
|
|
222
|
+
### Personal Commands (My Namespace)
|
|
216
223
|
|
|
217
|
-
|
|
218
|
-
* Backup database with compression
|
|
219
|
-
*/
|
|
220
|
-
export async function backup(database = 'main') {
|
|
221
|
-
const timestamp = new Date().toISOString().split('T')[0];
|
|
222
|
-
const filename = `backup-${database}-${timestamp}.sql.gz`;
|
|
224
|
+
Bunosh automatically loads commands from your home directory (`~/Bunoshfile.js`) and makes them available in any project with the `my:` namespace prefix.
|
|
223
225
|
|
|
224
|
-
|
|
225
|
-
await exec`aws s3 cp ${filename} s3://backups/${filename}`;
|
|
226
|
-
await exec`rm ${filename}`;
|
|
226
|
+
**Create your personal toolkit:**
|
|
227
227
|
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
```javascript
|
|
229
|
+
// ~/Bunoshfile.js - Your global commands available everywhere
|
|
230
230
|
|
|
231
231
|
/**
|
|
232
|
-
*
|
|
232
|
+
* Quick deployment to personal staging server
|
|
233
233
|
*/
|
|
234
|
-
export
|
|
235
|
-
|
|
236
|
-
await exec`
|
|
237
|
-
say('
|
|
234
|
+
export function deploy(app, env = 'staging') {
|
|
235
|
+
say(`π Deploying ${app} to personal ${env} environment...`);
|
|
236
|
+
await exec`ssh deploy@myserver.com "deploy.sh ${app} ${env}"`;
|
|
237
|
+
say('β
Deployment complete!');
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
/**
|
|
241
|
-
*
|
|
241
|
+
* Personal backup script
|
|
242
242
|
*/
|
|
243
|
-
export
|
|
244
|
-
|
|
245
|
-
await exec`
|
|
246
|
-
|
|
247
|
-
say(`π Successfully deployed to ${env}!`);
|
|
243
|
+
export function backup(target = 'documents') {
|
|
244
|
+
say(`πΎ Creating backup of ${target}...`);
|
|
245
|
+
await exec`rsync -av ~/${target}/ ~/Backups/${target}-$(date +%Y%m%d)/`;
|
|
246
|
+
say('π¦ Backup completed!');
|
|
248
247
|
}
|
|
249
248
|
|
|
250
249
|
/**
|
|
251
|
-
*
|
|
250
|
+
* Quick project setup
|
|
252
251
|
*/
|
|
253
|
-
export
|
|
254
|
-
|
|
255
|
-
|
|
252
|
+
export function newProject(name, template = 'basic') {
|
|
253
|
+
say(`ποΈ Creating new project: ${name}`);
|
|
254
|
+
await exec`git clone https://github.com/my-templates/${template}.git ${name}`;
|
|
255
|
+
await exec`cd ${name} && npm install`;
|
|
256
|
+
say(`β
Project ${name} ready!`);
|
|
256
257
|
}
|
|
257
258
|
```
|
|
258
259
|
|
|
259
|
-
**
|
|
260
|
+
**Available in any project:**
|
|
260
261
|
|
|
262
|
+
```bash
|
|
263
|
+
# From any directory, these commands work:
|
|
264
|
+
bunosh my:deploy my-app production
|
|
265
|
+
bunosh my:backup projects
|
|
266
|
+
bunosh my:new-project awesome-app react
|
|
267
|
+
|
|
268
|
+
# List all your personal commands
|
|
269
|
+
bunosh --help # Shows "My Commands" section
|
|
261
270
|
```
|
|
262
|
-
Usage: bunosh <command> <args> [options]
|
|
263
271
|
|
|
264
|
-
|
|
272
|
+
**Key Features:**
|
|
273
|
+
- π **Global availability** - Access your personal commands from any project directory
|
|
274
|
+
- π·οΈ **Namespaced** - `my:` prefix prevents conflicts with project commands
|
|
275
|
+
- π **Same syntax** - Uses identical JavaScript function syntax as project commands
|
|
276
|
+
- π§ **Parameters & options** - Full support for arguments and CLI options
|
|
277
|
+
- π **Help integration** - Shows in help output with descriptions
|
|
278
|
+
- π« **Graceful fallback** - Works seamlessly when no home Bunoshfile exists
|
|
265
279
|
|
|
266
|
-
|
|
267
|
-
backup Backup database with compression
|
|
268
|
-
bunosh backup [database]
|
|
269
|
-
deploy:with-checks Deploys application with health checks
|
|
270
|
-
bunosh deploy:with-checks [env]
|
|
271
|
-
health:check Checks service health across environments
|
|
272
|
-
bunosh health:check [env]
|
|
273
|
-
scale Scales application instances
|
|
274
|
-
bunosh scale [replicas] [service]
|
|
275
|
-
update:certs Updates SSL certificates
|
|
280
|
+
## Tasks
|
|
276
281
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
## Example: Content Management
|
|
282
|
+
Bunosh provides built-in tasks which are available via `global.bunosh`:
|
|
280
283
|
|
|
281
284
|
```javascript
|
|
282
|
-
const { exec, writeToFile,
|
|
285
|
+
const { exec, shell, fetch, writeToFile, copyFile, task } = global.bunosh;
|
|
286
|
+
```
|
|
283
287
|
|
|
284
|
-
|
|
285
|
-
* Creates new blog post template
|
|
286
|
-
*/
|
|
287
|
-
export async function newPost() {
|
|
288
|
-
const title = await ask('Post title:');
|
|
289
|
-
const slug = title.toLowerCase().replace(/\s+/g, '-');
|
|
290
|
-
const date = new Date().toISOString().split('T')[0];
|
|
288
|
+
> We use global variables instead of imports to ensure you can use it with bunosh single-executable on any platform.
|
|
291
289
|
|
|
292
|
-
writeToFile(`posts/${date}-${slug}.md`, (line) => {
|
|
293
|
-
line`---`;
|
|
294
|
-
line`title: "${title}"`;
|
|
295
|
-
line`date: ${date}`;
|
|
296
|
-
line`draft: true`;
|
|
297
|
-
line`---`;
|
|
298
|
-
line``;
|
|
299
|
-
line`# ${title}`;
|
|
300
|
-
line``;
|
|
301
|
-
line`Your content here...`;
|
|
302
|
-
});
|
|
303
290
|
|
|
304
|
-
|
|
305
|
-
|
|
291
|
+
* Async tasks: `exec`, `shell`, `fetch`
|
|
292
|
+
* Sync tasks: `writeToFile`, `copyFile`
|
|
293
|
+
* Task wrapper: `task`
|
|
306
294
|
|
|
307
|
-
|
|
308
|
-
* Optimizes and compresses images
|
|
309
|
-
*/
|
|
310
|
-
export async function optimizeImages() {
|
|
311
|
-
await exec`find ./images -name "*.jpg" -exec jpegoptim --max=80 {} \\;`;
|
|
312
|
-
await exec`find ./images -name "*.png" -exec optipng -o2 {} \\;`;
|
|
313
|
-
say('πΌοΈ Images optimized!');
|
|
314
|
-
}
|
|
295
|
+
Each executed task returns `TaskResult` object which can be analyzed and used in next steps:
|
|
315
296
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
297
|
+
```js
|
|
298
|
+
const result = await shell`echo "Hello"`;
|
|
299
|
+
console.log(result.status); // 'success', 'fail', or 'warning'
|
|
300
|
+
console.log(result.output); // Command output or result data
|
|
301
|
+
console.log(result.hasFailed); // true if status is 'fail'
|
|
302
|
+
console.log(result.hasSucceeded); // true if status is 'success'
|
|
303
|
+
console.log(result.hasWarning); // true if status is 'warning'
|
|
304
|
+
```
|
|
321
305
|
|
|
322
|
-
|
|
323
|
-
line`---`;
|
|
324
|
-
line`title: "${name}"`;
|
|
325
|
-
line`type: "page"`;
|
|
326
|
-
line`---`;
|
|
327
|
-
line``;
|
|
328
|
-
line`# ${name}`;
|
|
329
|
-
line``;
|
|
330
|
-
line`Page content here...`;
|
|
331
|
-
});
|
|
306
|
+
Now let's look into other tasks:
|
|
332
307
|
|
|
333
|
-
|
|
334
|
-
}
|
|
308
|
+
#### `task`
|
|
335
309
|
|
|
336
|
-
|
|
337
|
-
* Generates site and deploys
|
|
338
|
-
*/
|
|
339
|
-
export async function publish() {
|
|
340
|
-
await exec`hugo --minify`;
|
|
341
|
-
await exec`rsync -avz public/ user@server:/var/www/site/`;
|
|
342
|
-
say('π Site published!');
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Builds and serves development site
|
|
347
|
-
*/
|
|
348
|
-
export async function serve(port = 1313) {
|
|
349
|
-
await exec`hugo server --port ${port} --buildDrafts`;
|
|
350
|
-
}
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
**Bunosh displays these as:**
|
|
310
|
+
General method that transforms a function into a task. Adds it to tasks registry and prints task information:
|
|
354
311
|
|
|
312
|
+
```js
|
|
313
|
+
// register operation as a task
|
|
314
|
+
const result = task('Fetch Readme file', () => {
|
|
315
|
+
const content = fs.readFileSync('README.md', 'utf8');
|
|
316
|
+
console.log(content);
|
|
317
|
+
return content;
|
|
318
|
+
});
|
|
355
319
|
```
|
|
356
|
-
Usage: bunosh <command> <args> [options]
|
|
357
320
|
|
|
358
|
-
|
|
321
|
+
If a another task is executed inside a task function, its description will be appended to all child tasks.
|
|
359
322
|
|
|
360
|
-
|
|
361
|
-
new:page Creates new page template
|
|
362
|
-
bunosh new:page <name>
|
|
363
|
-
new:post Creates new blog post template
|
|
364
|
-
optimize:images Optimizes and compresses images
|
|
365
|
-
publish Generates site and deploys
|
|
366
|
-
serve Builds and serves development site
|
|
367
|
-
bunosh serve [port]
|
|
323
|
+
#### `exec`
|
|
368
324
|
|
|
369
|
-
|
|
325
|
+
Run single command using [child process `spawn`](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options)
|
|
370
326
|
|
|
371
|
-
|
|
327
|
+
```javascript
|
|
328
|
+
// Complex commands with pipes and streaming output
|
|
329
|
+
await exec`npm install --verbose`;
|
|
330
|
+
await exec`docker build . | tee build.log`;
|
|
331
|
+
await exec`find . -name "*.js" | grep -v node_modules | wc -l`;
|
|
372
332
|
|
|
373
|
-
|
|
333
|
+
// With environment variables
|
|
334
|
+
await exec`echo $NODE_ENV`.env({ NODE_ENV: 'production' });
|
|
374
335
|
|
|
375
|
-
|
|
376
|
-
|
|
336
|
+
// In specific directory
|
|
337
|
+
await exec`npm install`.cwd('/tmp/project');
|
|
377
338
|
```
|
|
378
339
|
|
|
379
|
-
|
|
340
|
+
By default task prints live line-by-line output from stdout and stderr. To disable output, use `silent` method:
|
|
380
341
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
#### `exec` - Universal Shell Execution
|
|
342
|
+
```javascript
|
|
384
343
|
|
|
385
|
-
|
|
344
|
+
// disable printing output
|
|
345
|
+
await task.silent(() => exec`npm install`);
|
|
386
346
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
await exec`find . -name "*.js" | grep -v node_modules | wc -l`;
|
|
390
|
-
await exec`npm install --verbose`; // Shows progress in real-time
|
|
391
|
-
await exec`docker build . | tee build.log`;
|
|
347
|
+
// disable output for all commands
|
|
348
|
+
await task.silence();
|
|
392
349
|
```
|
|
393
350
|
|
|
394
|
-
|
|
351
|
+
See more [#silent](#silent)
|
|
395
352
|
|
|
396
|
-
|
|
353
|
+
#### `shell` - Fast Native Execution
|
|
354
|
+
|
|
355
|
+
Optimized for simple, fast commands when running under Bun:
|
|
397
356
|
|
|
398
357
|
```javascript
|
|
399
358
|
// Simple, fast commands
|
|
400
359
|
await shell`pwd`;
|
|
401
|
-
await shell`echo "Hello World"`;
|
|
402
360
|
await shell`ls -la`;
|
|
403
361
|
await shell`cat package.json`;
|
|
404
362
|
```
|
|
405
363
|
|
|
406
|
-
|
|
364
|
+
For more details see [bun shell](https://bun.sh/docs/runtime/shell) reference
|
|
407
365
|
|
|
408
|
-
|
|
409
|
-
- β
Running under Bun for optimal performance
|
|
410
|
-
- β
Executing simple commands (`pwd`, `ls`, `echo`, `cat`)
|
|
411
|
-
- β
Want fastest possible execution
|
|
412
|
-
- β
Working with basic file operations
|
|
366
|
+
`shell` vs `exec`
|
|
413
367
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
- β
Running package managers (`npm install`, `docker build`)
|
|
368
|
+
| Command | Best For | Use Cases | Implementation | Compatibility |
|
|
369
|
+
|---------|----------|-----------|----------------|---------------|
|
|
370
|
+
| `exec` | Single command execution | single command | spawn process | NodeJS + Bun but platform dependent |
|
|
371
|
+
| `shell` | Multiple cross-platform shell commands | exec + `pwd`, `ls`, `echo`, `cat`, basic file ops | bun shell | Bun only but Cross-platform |
|
|
419
372
|
|
|
420
|
-
|
|
373
|
+
shell prints output from stdout and stderr. To disable output, [make tasks silent](#silent):
|
|
421
374
|
|
|
422
|
-
|
|
423
|
-
// Both tasks support environment variables
|
|
424
|
-
await shell`echo $NODE_ENV`.env({ NODE_ENV: 'production' });
|
|
425
|
-
await exec`echo $NODE_ENV`.env({ NODE_ENV: 'production' });
|
|
375
|
+
###$ `fetch`
|
|
426
376
|
|
|
427
|
-
|
|
428
|
-
await shell`pwd`.cwd('/tmp');
|
|
429
|
-
await exec`ls -la`.cwd('/tmp');
|
|
377
|
+
`fetch` task wraps fetch:
|
|
430
378
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
379
|
+
```javascript
|
|
380
|
+
/**
|
|
381
|
+
* Check service health
|
|
382
|
+
*/
|
|
383
|
+
export async function healthCheck(url) {
|
|
384
|
+
const response = await fetch(url);
|
|
385
|
+
|
|
386
|
+
if (response.ok) {
|
|
387
|
+
const data = await response.json();
|
|
388
|
+
say(`β
Service healthy: ${data.status}`);
|
|
389
|
+
} else {
|
|
390
|
+
yell(`β Service down: ${response.status}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
434
393
|
```
|
|
435
394
|
|
|
436
|
-
|
|
395
|
+
### File Operations
|
|
437
396
|
|
|
438
|
-
|
|
397
|
+
Template-based file writing and copying:
|
|
439
398
|
|
|
440
399
|
```javascript
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
#### Error Handling Examples
|
|
400
|
+
/**
|
|
401
|
+
* Generate configuration file
|
|
402
|
+
*/
|
|
403
|
+
export function generatePage(name, description = '') {
|
|
404
|
+
writeToFile('index.mdx', (line) => {
|
|
405
|
+
line`name": "${name}",`;
|
|
406
|
+
if (description) {
|
|
407
|
+
line`description: "${description}"`;
|
|
408
|
+
}
|
|
409
|
+
line`---`;
|
|
410
|
+
});
|
|
455
411
|
|
|
456
|
-
|
|
457
|
-
// Check command success
|
|
458
|
-
const result = await exec`npm test`;
|
|
459
|
-
if (result.hasSucceeded) {
|
|
460
|
-
say('β
Tests passed!');
|
|
461
|
-
} else {
|
|
462
|
-
yell('β Tests failed!');
|
|
463
|
-
console.log(result.output); // Show error details
|
|
412
|
+
say('π Page created');
|
|
464
413
|
}
|
|
465
414
|
|
|
466
|
-
//
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const commitHash = result.output.trim();
|
|
470
|
-
say(`Current commit: ${commitHash}`);
|
|
471
|
-
}
|
|
415
|
+
// Copy files
|
|
416
|
+
copyFile('template.env', '.env');
|
|
417
|
+
```
|
|
472
418
|
|
|
473
|
-
// Handle failures gracefully
|
|
474
|
-
const result = await exec`optional-command-that-might-fail`;
|
|
475
|
-
if (result.hasFailed) {
|
|
476
|
-
say('Command failed, but continuing...');
|
|
477
|
-
console.log('Error output:', result.output);
|
|
478
|
-
}
|
|
479
419
|
|
|
480
|
-
|
|
481
|
-
// β Old: Commands throw on failure
|
|
482
|
-
try {
|
|
483
|
-
await someOtherTaskRunner('failing-command');
|
|
484
|
-
} catch (error) {
|
|
485
|
-
// Handle error
|
|
486
|
-
}
|
|
420
|
+
## Input/Output
|
|
487
421
|
|
|
488
|
-
|
|
489
|
-
const result = await exec`failing-command`;
|
|
490
|
-
if (result.hasFailed) {
|
|
491
|
-
// Handle failure explicitly
|
|
492
|
-
}
|
|
493
|
-
```
|
|
422
|
+
### `say` - Normal Output
|
|
494
423
|
|
|
495
|
-
|
|
496
|
-
```javascript
|
|
497
|
-
// GET request with progress indicator
|
|
498
|
-
const response = await fetch('https://api.github.com/repos/user/repo');
|
|
499
|
-
const data = await response.json();
|
|
500
|
-
```
|
|
424
|
+
Standard output with visual indicator:
|
|
501
425
|
|
|
502
|
-
### File Operations
|
|
503
426
|
```javascript
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
line` "name": "myapp",`;
|
|
508
|
-
line` "version": "1.0.0"`;
|
|
509
|
-
line`}`;
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
// Copy files
|
|
513
|
-
copyFile('template.js', 'output.js');
|
|
427
|
+
say('Building project...');
|
|
428
|
+
say('π¦ Dependencies installed');
|
|
429
|
+
say(`Found ${count} files to process`);
|
|
514
430
|
```
|
|
515
431
|
|
|
516
|
-
### User
|
|
432
|
+
### `ask` - User Input
|
|
517
433
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
The `ask()` function provides flexible ways to get user input with smart parameter detection and multiple modes:
|
|
434
|
+
Flexible user input with smart parameter detection:
|
|
521
435
|
|
|
522
436
|
```javascript
|
|
523
|
-
//
|
|
524
|
-
|
|
525
|
-
// Basic text input
|
|
526
|
-
const name = await ask('What is your name?');
|
|
527
|
-
|
|
528
|
-
// Text input with default value
|
|
529
|
-
const projectName = await ask('Project name:', 'my-awesome-app');
|
|
530
|
-
|
|
531
|
-
// Boolean confirmation (auto-detects confirm type)
|
|
532
|
-
const shouldContinue = await ask('Continue with deployment?', true);
|
|
533
|
-
const forceUpdate = await ask('Force update?', false);
|
|
534
|
-
|
|
535
|
-
// Number input with default
|
|
536
|
-
const port = await ask('Enter port number:', 3000);
|
|
437
|
+
// Text input with default
|
|
438
|
+
const name = await ask('Project name:', 'my-app');
|
|
537
439
|
|
|
538
|
-
//
|
|
539
|
-
const
|
|
540
|
-
'React', 'Vue', 'Angular', 'Svelte'
|
|
541
|
-
]);
|
|
542
|
-
|
|
543
|
-
// Multiple choice selection (array + options)
|
|
544
|
-
const features = await ask('Select features to include:', [
|
|
545
|
-
'TypeScript', 'ESLint', 'Prettier', 'Tests', 'CI/CD'
|
|
546
|
-
], { multiple: true });
|
|
440
|
+
// Boolean confirmation (auto-detects)
|
|
441
|
+
const proceed = await ask('Continue?', true);
|
|
547
442
|
|
|
548
|
-
//
|
|
443
|
+
// Single selection (auto-detects from array)
|
|
444
|
+
const env = await ask('Select environment:', ['dev', 'staging', 'prod']);
|
|
549
445
|
|
|
550
|
-
//
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// Editor input with default content
|
|
556
|
-
const config = await ask('Edit configuration:', {
|
|
557
|
-
editor: true,
|
|
558
|
-
default: 'Initial content here...'
|
|
559
|
-
});
|
|
446
|
+
// Multiple selection
|
|
447
|
+
const features = await ask('Select features:',
|
|
448
|
+
['TypeScript', 'ESLint', 'Tests'],
|
|
449
|
+
{ multiple: true }
|
|
450
|
+
);
|
|
560
451
|
|
|
561
|
-
// Password input
|
|
562
|
-
const password = await ask('Enter password:', {
|
|
563
|
-
type: 'password'
|
|
564
|
-
});
|
|
452
|
+
// Password input
|
|
453
|
+
const password = await ask('Enter password:', { type: 'password' });
|
|
565
454
|
|
|
566
|
-
//
|
|
567
|
-
const
|
|
568
|
-
validate: (input) => input.includes('@') || 'Please enter valid email'
|
|
569
|
-
});
|
|
455
|
+
// Multiline editor input
|
|
456
|
+
const description = await ask('Enter description:', { editor: true });
|
|
570
457
|
```
|
|
571
458
|
|
|
572
|
-
#### Ask Function Signatures
|
|
573
|
-
|
|
574
|
-
```javascript
|
|
575
|
-
// Smart detection syntax
|
|
576
|
-
ask(question, defaultValue, options?)
|
|
577
|
-
ask(question, choices[], options?)
|
|
578
|
-
ask(question, options)
|
|
579
|
-
|
|
580
|
-
// Examples:
|
|
581
|
-
ask('Name?', 'John') // String default
|
|
582
|
-
ask('Continue?', true) // Boolean -> confirm type
|
|
583
|
-
ask('Port?', 3000) // Number default
|
|
584
|
-
ask('Color?', ['red', 'blue']) // Array -> choices
|
|
585
|
-
ask('Colors?', ['red', 'blue'], { multiple: true }) // Array + options
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
#### Ask Options Reference
|
|
589
|
-
|
|
590
459
|
| Parameter/Option | Type | Description | Example |
|
|
591
460
|
|------------------|------|-------------|---------|
|
|
592
461
|
| **Smart Detection** | | |
|
|
@@ -601,493 +470,298 @@ ask('Colors?', ['red', 'blue'], { multiple: true }) // Array + options
|
|
|
601
470
|
| `type` | String | Input type: `'input'`, `'confirm'`, `'password'`, `'number'` | `'password'` |
|
|
602
471
|
| `validate` | Function | Custom validation function | `(input) => input.length > 0` |
|
|
603
472
|
|
|
604
|
-
#### Advanced Ask Examples
|
|
605
473
|
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* Interactive project setup with smart syntax
|
|
609
|
-
*/
|
|
610
|
-
export async function setupProject() {
|
|
611
|
-
// Simple syntax with smart detection
|
|
612
|
-
const projectName = await ask('Project name:', 'my-awesome-project');
|
|
613
|
-
|
|
614
|
-
const projectType = await ask('Project type:', [
|
|
615
|
-
'Web App', 'API', 'CLI Tool', 'Library'
|
|
616
|
-
]);
|
|
617
|
-
|
|
618
|
-
const dependencies = await ask('Select dependencies:', [
|
|
619
|
-
'express', 'lodash', 'axios', 'moment', 'uuid'
|
|
620
|
-
], { multiple: true });
|
|
621
|
-
|
|
622
|
-
const useTypescript = await ask('Use TypeScript?', false);
|
|
623
|
-
|
|
624
|
-
// Editor input for complex configuration
|
|
625
|
-
const packageJson = await ask('Customize package.json:', {
|
|
626
|
-
editor: true,
|
|
627
|
-
default: JSON.stringify({
|
|
628
|
-
name: projectName,
|
|
629
|
-
version: '1.0.0',
|
|
630
|
-
description: '',
|
|
631
|
-
dependencies: {}
|
|
632
|
-
}, null, 2)
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
say(`Creating ${projectType}: ${projectName}`);
|
|
636
|
-
say(`Dependencies: ${dependencies.join(', ')}`);
|
|
637
|
-
say(`TypeScript: ${useTypescript ? 'Yes' : 'No'}`);
|
|
638
|
-
|
|
639
|
-
writeToFile('package.json', packageJson);
|
|
640
|
-
}
|
|
474
|
+
### `yell`
|
|
641
475
|
|
|
642
|
-
|
|
643
|
-
* Git commit with editor input
|
|
644
|
-
*/
|
|
645
|
-
export async function interactiveCommit() {
|
|
646
|
-
const message = await ask('Enter commit message:', {
|
|
647
|
-
editor: true,
|
|
648
|
-
default: 'feat: \n\n# Write your commit message above\n# First line: brief summary (50 chars max)\n# Blank line, then detailed explanation'
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
await exec`git commit -m "${message}"`;
|
|
652
|
-
say('β
Committed successfully!');
|
|
653
|
-
}
|
|
476
|
+
Emphasized Output
|
|
654
477
|
|
|
655
|
-
|
|
656
|
-
* Database migration with smart syntax
|
|
657
|
-
*/
|
|
658
|
-
export async function migrate() {
|
|
659
|
-
// Smart array detection for choices
|
|
660
|
-
const action = await ask('Migration action:', [
|
|
661
|
-
'Run pending migrations',
|
|
662
|
-
'Rollback last migration',
|
|
663
|
-
'Reset database',
|
|
664
|
-
'Create new migration'
|
|
665
|
-
]);
|
|
666
|
-
|
|
667
|
-
if (action === 'Reset database') {
|
|
668
|
-
// Smart boolean detection for confirmation
|
|
669
|
-
const confirmed = await ask('β οΈ This will DELETE ALL DATA. Are you sure?', false);
|
|
670
|
-
|
|
671
|
-
if (!confirmed) {
|
|
672
|
-
say('Migration cancelled');
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// Execute migration based on selection...
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* Server configuration with mixed smart syntax
|
|
682
|
-
*/
|
|
683
|
-
export async function configureServer() {
|
|
684
|
-
// Simple defaults
|
|
685
|
-
const serverName = await ask('Server name:', 'my-server');
|
|
686
|
-
const port = await ask('Port number:', 8080);
|
|
687
|
-
const enableHTTPS = await ask('Enable HTTPS?', true);
|
|
688
|
-
|
|
689
|
-
// Array with additional options
|
|
690
|
-
const databases = await ask('Select databases to connect:', [
|
|
691
|
-
'PostgreSQL', 'MongoDB', 'Redis', 'MySQL'
|
|
692
|
-
], { multiple: true });
|
|
693
|
-
|
|
694
|
-
// Mix of default + validation
|
|
695
|
-
const adminEmail = await ask('Admin email:', 'admin@example.com', {
|
|
696
|
-
validate: (email) => email.includes('@') || 'Please enter a valid email'
|
|
697
|
-
});
|
|
698
|
-
|
|
699
|
-
say(`Configuring ${serverName} on port ${port}`);
|
|
700
|
-
say(`HTTPS: ${enableHTTPS ? 'Enabled' : 'Disabled'}`);
|
|
701
|
-
say(`Databases: ${databases.join(', ')}`);
|
|
702
|
-
say(`Admin: ${adminEmail}`);
|
|
703
|
-
}
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
#### Output Functions
|
|
478
|
+
ASCII art output for important messages:
|
|
707
479
|
|
|
708
480
|
```javascript
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
yell('BUILD COMPLETE!'); // Emphasized ASCII art output
|
|
712
|
-
|
|
713
|
-
// Wrap long operations with progress
|
|
714
|
-
await task('Installing dependencies', async () => {
|
|
715
|
-
await exec`npm install`;
|
|
716
|
-
});
|
|
481
|
+
yell('BUILD COMPLETE!');
|
|
482
|
+
yell('DEPLOYMENT SUCCESSFUL!');
|
|
717
483
|
```
|
|
718
484
|
|
|
719
|
-
|
|
485
|
+
### `silent`
|
|
720
486
|
|
|
721
|
-
|
|
487
|
+
Stop printing realtime output
|
|
722
488
|
|
|
723
|
-
|
|
489
|
+
```javascript
|
|
490
|
+
// Silence all task output
|
|
491
|
+
task.silence();
|
|
492
|
+
await shell`npm build`;
|
|
724
493
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
# Required: Choose your model
|
|
728
|
-
export AI_MODEL=gpt-4o # or claude-3-5-sonnet-20241022, llama-3.3-70b-versatile, etc.
|
|
494
|
+
// restore printing output
|
|
495
|
+
task.prints();
|
|
729
496
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
# export ANTHROPIC_API_KEY=your_key_here # for Claude models
|
|
733
|
-
# export GROQ_API_KEY=your_key_here # for Groq models
|
|
497
|
+
// Silent specific task
|
|
498
|
+
const labels = await task.silent(() => shell(`gh api repos/:org/:repo/labels`));
|
|
734
499
|
```
|
|
735
500
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
- **OpenAI** - GPT-4o, GPT-4o-mini, GPT-3.5-turbo (via `OPENAI_API_KEY`)
|
|
739
|
-
- **Anthropic** - Claude 3.5 Sonnet, Claude 3 Haiku (via `ANTHROPIC_API_KEY`)
|
|
740
|
-
- **Groq** - Llama 3.3, Mixtral, Gemma models (via `GROQ_API_KEY` or `GROQ_KEY`)
|
|
501
|
+
## Task Control
|
|
741
502
|
|
|
742
|
-
###
|
|
503
|
+
### Parallel Executions
|
|
743
504
|
|
|
744
|
-
|
|
505
|
+
No magic here. Use `Promise.all()` to run tasks in parallel:
|
|
745
506
|
|
|
746
507
|
```javascript
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
accessKeyId: 'your-access-key',
|
|
755
|
-
secretAccessKey: 'your-secret-key'
|
|
756
|
-
}
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
ai.configure({ model: bedrockModel });
|
|
508
|
+
// Parallel tasks
|
|
509
|
+
const results = await Promise.all([
|
|
510
|
+
exec`npm run build:frontend`,
|
|
511
|
+
exec`npm run build:backend`,
|
|
512
|
+
exec`npm run build:docs`
|
|
513
|
+
]);
|
|
514
|
+
```
|
|
760
515
|
|
|
761
|
-
|
|
762
|
-
import { xai } from '@ai-sdk/xai';
|
|
763
|
-
ai.configure({
|
|
764
|
-
registerProvider: {
|
|
765
|
-
envVar: 'XAI_API_KEY',
|
|
766
|
-
provider: {
|
|
767
|
-
createInstance: (modelName) => xai(modelName)
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
});
|
|
516
|
+
### Custom Tasks
|
|
771
517
|
|
|
772
|
-
|
|
773
|
-
import { openrouter } from '@openrouter/ai-sdk-provider';
|
|
774
|
-
ai.configure({
|
|
775
|
-
registerProvider: {
|
|
776
|
-
envVar: 'OPENROUTER_API_KEY',
|
|
777
|
-
provider: {
|
|
778
|
-
createInstance: (modelName) => openrouter(modelName)
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
});
|
|
518
|
+
Name and group your tasks operations:
|
|
782
519
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
provider: {
|
|
788
|
-
createInstance: (modelName) => {
|
|
789
|
-
// Your custom provider logic
|
|
790
|
-
return customAIProvider(modelName, {
|
|
791
|
-
apiKey: process.env.CUSTOM_AI_API_KEY,
|
|
792
|
-
endpoint: 'https://custom-ai.company.com/v1'
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
}
|
|
520
|
+
```js
|
|
521
|
+
await task('Build', () => {
|
|
522
|
+
await exec`npm run build:frontend`);
|
|
523
|
+
await exec`npm run build:docs`);
|
|
797
524
|
});
|
|
525
|
+
````
|
|
798
526
|
|
|
799
|
-
|
|
800
|
-
ai.reset();
|
|
527
|
+
### Stop on Failure
|
|
801
528
|
|
|
802
|
-
|
|
803
|
-
const config = ai.getConfig();
|
|
804
|
-
console.log('Current AI config:', config);
|
|
805
|
-
```
|
|
529
|
+
By default bunosh executes all tasks event if they fail. To stop execution immediately on failure, use the `task.stopOnFailures()` method.
|
|
806
530
|
|
|
807
|
-
### AI Task Examples
|
|
808
531
|
|
|
809
532
|
```javascript
|
|
810
|
-
const { ai, writeToFile, say } = global.bunosh;
|
|
811
|
-
|
|
812
533
|
/**
|
|
813
|
-
*
|
|
534
|
+
* Strict deployment - stop on any failure
|
|
814
535
|
*/
|
|
815
|
-
export async function
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
apiReference: 'API documentation',
|
|
823
|
-
examples: 'Usage examples',
|
|
824
|
-
installation: 'Installation instructions'
|
|
825
|
-
}
|
|
826
|
-
);
|
|
827
|
-
|
|
828
|
-
writeToFile('README.md', (line) => {
|
|
829
|
-
line`# ${result.overview}`;
|
|
830
|
-
line``;
|
|
831
|
-
line`## Installation`;
|
|
832
|
-
line`${result.installation}`;
|
|
833
|
-
line``;
|
|
834
|
-
line`## API Reference`;
|
|
835
|
-
line`${result.apiReference}`;
|
|
836
|
-
line``;
|
|
837
|
-
line`## Examples`;
|
|
838
|
-
line`${result.examples}`;
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
say('π Documentation generated!');
|
|
536
|
+
export async function deployStrict() {
|
|
537
|
+
task.stopOnFailures(); // Exit immediately on any task failure
|
|
538
|
+
|
|
539
|
+
await exec`npm test`;
|
|
540
|
+
await exec`npm run build`;
|
|
541
|
+
await exec`deploy-script`;
|
|
542
|
+
// If any task fails, script exits immediately
|
|
842
543
|
}
|
|
843
544
|
|
|
844
545
|
/**
|
|
845
|
-
*
|
|
546
|
+
* Cleanup - continue despite failures
|
|
846
547
|
*/
|
|
847
|
-
export async function
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
suggestions: 'Specific improvement suggestions',
|
|
855
|
-
security: 'Security considerations',
|
|
856
|
-
performance: 'Performance optimization tips',
|
|
857
|
-
rating: 'Overall code quality rating (1-10)'
|
|
858
|
-
}
|
|
859
|
-
);
|
|
860
|
-
|
|
861
|
-
say(`π Code Review for ${filename}:`);
|
|
862
|
-
console.log(`Rating: ${analysis.rating}/10`);
|
|
863
|
-
console.log(`Issues: ${analysis.issues}`);
|
|
864
|
-
console.log(`Suggestions: ${analysis.suggestions}`);
|
|
865
|
-
console.log(`Security: ${analysis.security}`);
|
|
866
|
-
console.log(`Performance: ${analysis.performance}`);
|
|
548
|
+
export async function cleanup() {
|
|
549
|
+
task.ignoreFailures(); // Continue even if tasks fail
|
|
550
|
+
|
|
551
|
+
await task('Remove temp files', () => shell`rm -rf tmp/*`);
|
|
552
|
+
await task('Clear logs', () => shell`rm -f logs/*.log`);
|
|
553
|
+
await task('Reset cache', () => shell`rm -rf .cache`);
|
|
554
|
+
// All tasks run regardless of failures
|
|
867
555
|
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### Try Operations
|
|
868
559
|
|
|
560
|
+
Gracefully handle operations that might fail:
|
|
561
|
+
|
|
562
|
+
```javascript
|
|
869
563
|
/**
|
|
870
|
-
*
|
|
564
|
+
* Check service availability
|
|
871
565
|
*/
|
|
872
|
-
export async function
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
);
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
line``;
|
|
888
|
-
line`${tests.testSuite}`;
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
say(`π§ͺ Tests generated: ${testFile}`);
|
|
892
|
-
say(`Edge cases: ${tests.edgeCases}`);
|
|
566
|
+
export async function checkServices() {
|
|
567
|
+
const dbConnected = await task.try(shell`nc -z localhost 5432`);
|
|
568
|
+
|
|
569
|
+
if (dbConnected) {
|
|
570
|
+
say('β
Database connected');
|
|
571
|
+
} else {
|
|
572
|
+
say('β οΈ Database unavailable, using fallback');
|
|
573
|
+
await useFallbackDatabase();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const apiHealthy = await task.try(() => fetch('http://localhost:3000/health'));
|
|
577
|
+
|
|
578
|
+
if (!apiHealthy) {
|
|
579
|
+
yell('API IS DOWN!');
|
|
580
|
+
}
|
|
893
581
|
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
## π« AI Integration
|
|
585
|
+
|
|
586
|
+
Built-in AI support for code generation, documentation, and automation.
|
|
587
|
+
Automatically responds to structured JSON output.
|
|
588
|
+
|
|
589
|
+
AI provider automatically detected, but you need to provide API key and model name.
|
|
590
|
+
Use `.env` file with `AI_MODEL` and `OPENAI_API_KEY` variables.
|
|
591
|
+
In case you use provider other than OpenAI, Anthropic, Groq, you may need to configure it manually in top of Bunoshfile
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
# Choose your AI model
|
|
595
|
+
export AI_MODEL=gpt-5 # or claude-4-sonnet, llama-3.3-70b, etc.
|
|
596
|
+
|
|
597
|
+
# Set API key for your provider
|
|
598
|
+
export OPENAI_API_KEY=your_key_here # For OpenAI
|
|
599
|
+
# export ANTHROPIC_API_KEY=your_key_here # For Claude
|
|
600
|
+
# export GROQ_API_KEY=your_key_here # For Groq
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
Use the `ai` function to interact with the AI.
|
|
605
|
+
|
|
606
|
+
```js
|
|
607
|
+
const resp = await ai(message, { field1: 'what should be there', field2: 'what should be there' })
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### Usage
|
|
611
|
+
|
|
612
|
+
```javascript
|
|
613
|
+
const { ai, writeToFile } = global.bunosh;
|
|
894
614
|
|
|
895
615
|
/**
|
|
896
|
-
*
|
|
616
|
+
* Generate commit message from staged changes
|
|
897
617
|
*/
|
|
898
|
-
export async function
|
|
618
|
+
export async function commit() {
|
|
899
619
|
const diff = await exec`git diff --staged`;
|
|
900
|
-
|
|
901
|
-
if (
|
|
902
|
-
say('No staged changes
|
|
620
|
+
|
|
621
|
+
if (!diff.output.trim()) {
|
|
622
|
+
say('No staged changes');
|
|
903
623
|
return;
|
|
904
624
|
}
|
|
905
|
-
|
|
625
|
+
|
|
906
626
|
const commit = await ai(
|
|
907
|
-
`Generate a commit message for
|
|
627
|
+
`Generate a conventional commit message for: ${diff.output}`,
|
|
908
628
|
{
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
629
|
+
type: 'Commit type (feat/fix/docs/chore)',
|
|
630
|
+
scope: 'Commit scope (optional)',
|
|
631
|
+
subject: 'Brief subject line (50 chars max)',
|
|
632
|
+
body: 'Detailed explanation'
|
|
912
633
|
}
|
|
913
634
|
);
|
|
914
|
-
|
|
915
|
-
const message = `${commit.type}: ${commit.title}\n\n${commit.body}`;
|
|
916
|
-
await exec`git commit -m "${message}"`;
|
|
917
|
-
|
|
918
|
-
say(`β
Committed with AI-generated message:`);
|
|
919
|
-
console.log(message);
|
|
920
|
-
}
|
|
921
635
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
// Configure for enterprise use
|
|
930
|
-
const enterpriseModel = bedrock('anthropic.claude-3-sonnet-20240229-v1:0', {
|
|
931
|
-
region: 'us-east-1'
|
|
932
|
-
// Uses AWS credentials from environment/profile
|
|
933
|
-
});
|
|
934
|
-
|
|
935
|
-
ai.configure({ model: enterpriseModel });
|
|
936
|
-
|
|
937
|
-
const analysis = await ai(
|
|
938
|
-
'Analyze our company performance from this quarterly report: [data]',
|
|
939
|
-
{
|
|
940
|
-
summary: 'Executive summary of performance',
|
|
941
|
-
risks: 'Identified business risks',
|
|
942
|
-
opportunities: 'Growth opportunities',
|
|
943
|
-
recommendations: 'Strategic recommendations'
|
|
944
|
-
}
|
|
945
|
-
);
|
|
946
|
-
|
|
947
|
-
say('π Enterprise AI analysis complete');
|
|
948
|
-
console.log(analysis);
|
|
636
|
+
const message = commit.scope
|
|
637
|
+
? `${commit.type}(${commit.scope}): ${commit.subject}\n\n${commit.body}`
|
|
638
|
+
: `${commit.type}: ${commit.subject}\n\n${commit.body}`;
|
|
639
|
+
|
|
640
|
+
await exec`git commit -m "${message}"`;
|
|
641
|
+
say('β
AI-generated commit created');
|
|
949
642
|
}
|
|
950
643
|
```
|
|
951
644
|
|
|
952
|
-
|
|
645
|
+
See more ai usage examples in [docs/examples.md](docs/examples.md)
|
|
953
646
|
|
|
954
|
-
For quick text generation without structured output:
|
|
955
647
|
|
|
956
|
-
|
|
957
|
-
/**
|
|
958
|
-
* Generate marketing copy
|
|
959
|
-
*/
|
|
960
|
-
export async function generateCopy(product) {
|
|
961
|
-
const copy = await ai(`Write compelling marketing copy for: ${product}`);
|
|
962
|
-
say('π Generated copy:');
|
|
963
|
-
console.log(copy);
|
|
964
|
-
}
|
|
648
|
+
## Execute JavaScript Code
|
|
965
649
|
|
|
966
|
-
|
|
967
|
-
* Translate content
|
|
968
|
-
*/
|
|
969
|
-
export async function translate(text, language = 'Spanish') {
|
|
970
|
-
const translation = await ai(`Translate to ${language}: ${text}`);
|
|
971
|
-
say(`π Translation to ${language}:`);
|
|
972
|
-
console.log(translation);
|
|
973
|
-
}
|
|
974
|
-
```
|
|
650
|
+
Bunosh supports executing JavaScript code directly using the `-e` flag, allowing for powerful one-liners and integration with shell scripts and CI/CD systems.
|
|
975
651
|
|
|
976
|
-
###
|
|
652
|
+
### Basic Usage
|
|
977
653
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
-
|
|
981
|
-
- **β‘ Fast Inference**: Optimized for speed with Groq and other providers
|
|
982
|
-
- **π§ Structured Output**: Get JSON responses with defined schemas
|
|
983
|
-
- **π― Provider Auto-Detection**: Automatically detects available API keys
|
|
984
|
-
- **πͺ Error Handling**: Graceful handling of API errors and rate limits
|
|
654
|
+
```bash
|
|
655
|
+
# Execute inline JavaScript
|
|
656
|
+
bunosh -e "say('Hello')"
|
|
985
657
|
|
|
986
|
-
|
|
658
|
+
# Execute JavaScript from stdin
|
|
659
|
+
echo "say('Hello')" | bunosh -e
|
|
660
|
+
```
|
|
987
661
|
|
|
988
|
-
|
|
662
|
+
### Heredoc Syntax
|
|
989
663
|
|
|
990
|
-
|
|
991
|
-
- `functionName` β `bunosh function:name`
|
|
992
|
-
- Function parameters become command arguments
|
|
993
|
-
- Last object parameter becomes CLI options
|
|
994
|
-
- JSDoc comments become help descriptions
|
|
664
|
+
For multi-line scripts, use heredoc syntax for clean, readable code:
|
|
995
665
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
bunosh deploy production --force --verbose
|
|
666
|
+
```bash
|
|
667
|
+
bunosh -e << 'EOF'
|
|
668
|
+
say('π Starting build process...')
|
|
669
|
+
await task('Install Dependencies', () => shell`npm ci`)
|
|
670
|
+
await task('Build', () => shell`npm run build`)
|
|
671
|
+
await task('Test', () => shell`npm test`)
|
|
672
|
+
say('β
All tasks completed successfully!')
|
|
673
|
+
EOF
|
|
1005
674
|
```
|
|
1006
675
|
|
|
1007
|
-
###
|
|
676
|
+
### With Environment Variables and Control Flow
|
|
677
|
+
|
|
1008
678
|
```bash
|
|
1009
|
-
#
|
|
1010
|
-
bunosh
|
|
679
|
+
# Complex script with conditions
|
|
680
|
+
bunosh -e << 'EOF'
|
|
681
|
+
const env = process.env.NODE_ENV || 'development'
|
|
682
|
+
say(`Building for ${env}...`)
|
|
683
|
+
|
|
684
|
+
if (env === 'production') {
|
|
685
|
+
await shell`npm run build:prod`
|
|
686
|
+
await task('Deploy', () => shell`./deploy.sh`)
|
|
687
|
+
} else {
|
|
688
|
+
await shell`npm run build:dev`
|
|
689
|
+
}
|
|
1011
690
|
|
|
1012
|
-
|
|
1013
|
-
|
|
691
|
+
yell('BUILD COMPLETE!')
|
|
692
|
+
EOF
|
|
1014
693
|
```
|
|
1015
694
|
|
|
1016
|
-
###
|
|
1017
|
-
Enable tab completion for faster command typing:
|
|
695
|
+
### Error Handling
|
|
1018
696
|
|
|
1019
697
|
```bash
|
|
1020
|
-
#
|
|
1021
|
-
bunosh
|
|
1022
|
-
|
|
1023
|
-
# Manual setup if needed
|
|
1024
|
-
bunosh completion bash > ~/.bunosh-completion.bash
|
|
1025
|
-
echo "source ~/.bunosh-completion.bash" >> ~/.bashrc
|
|
1026
|
-
source ~/.bashrc
|
|
698
|
+
# Script with error handling
|
|
699
|
+
bunosh -e << 'EOF'
|
|
700
|
+
task.stopOnFailures()
|
|
1027
701
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
702
|
+
try {
|
|
703
|
+
await shell`npm test`
|
|
704
|
+
await shell`npm run build`
|
|
705
|
+
say('β
Success!')
|
|
706
|
+
} catch (error) {
|
|
707
|
+
yell(`β Build failed: ${error.message}`)
|
|
708
|
+
process.exit(1)
|
|
709
|
+
}
|
|
710
|
+
EOF
|
|
1031
711
|
```
|
|
1032
712
|
|
|
1033
|
-
|
|
713
|
+
### JavaScript Execution in GitHub Actions
|
|
1034
714
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
**Single Executable:**
|
|
1038
|
-
```bash
|
|
1039
|
-
# Check for updates
|
|
1040
|
-
bunosh upgrade --check
|
|
715
|
+
Use JavaScript execution to run Bunosh scripts inside CI/CD workflows without creating separate files:
|
|
1041
716
|
|
|
1042
|
-
|
|
1043
|
-
|
|
717
|
+
```yaml
|
|
718
|
+
- name: Build and Deploy
|
|
719
|
+
run: |
|
|
720
|
+
bunosh -e << 'EOF'
|
|
721
|
+
say('π Starting deployment...')
|
|
1044
722
|
|
|
1045
|
-
|
|
1046
|
-
bunosh upgrade --force
|
|
1047
|
-
```
|
|
723
|
+
if (!process.env.NODE_ENV === 'production') return;
|
|
1048
724
|
|
|
1049
|
-
|
|
1050
|
-
```bash
|
|
1051
|
-
npm update -g bunosh
|
|
1052
|
-
```
|
|
725
|
+
shell`./deploy.sh`
|
|
1053
726
|
|
|
1054
|
-
|
|
727
|
+
const response = await fetch('${{ secrets.DEPLOY_WEBHOOK }}', {
|
|
728
|
+
method: 'POST',
|
|
729
|
+
headers: { 'Authorization': 'Bearer ${{ secrets.API_TOKEN }}' }
|
|
730
|
+
})
|
|
1055
731
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
732
|
+
if (response.ok) {
|
|
733
|
+
yell('π DEPLOYMENT COMPLETE!')
|
|
734
|
+
} else {
|
|
735
|
+
yell('β DEPLOYMENT FAILED!')
|
|
736
|
+
process.exit(1)
|
|
737
|
+
}
|
|
738
|
+
EOF
|
|
739
|
+
env:
|
|
740
|
+
NODE_ENV: production
|
|
1063
741
|
```
|
|
1064
742
|
|
|
1065
|
-
###
|
|
1066
|
-
```javascript
|
|
1067
|
-
export async function deployWithRollback(env) {
|
|
1068
|
-
try {
|
|
1069
|
-
await deploy(env);
|
|
1070
|
-
} catch (error) {
|
|
1071
|
-
say('β Deployment failed, rolling back...');
|
|
1072
|
-
await exec`kubectl rollout undo deployment/myapp`;
|
|
1073
|
-
throw error;
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
```
|
|
743
|
+
### Shell Integration
|
|
1077
744
|
|
|
1078
|
-
### NPM Scripts Integration
|
|
1079
|
-
Bunosh automatically includes your package.json scripts:
|
|
1080
745
|
```bash
|
|
1081
|
-
bunosh
|
|
1082
|
-
|
|
746
|
+
bunosh -e << 'EOF'
|
|
747
|
+
say('Running database migrations...')
|
|
748
|
+
await shell`npm run migrate`
|
|
749
|
+
say('Migrations completed')
|
|
750
|
+
EOF
|
|
1083
751
|
```
|
|
1084
752
|
|
|
1085
|
-
## Contributing
|
|
1086
753
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
754
|
+
## Examples
|
|
755
|
+
|
|
756
|
+
For comprehensive examples of Bunosh in action, see [docs/examples.md](docs/examples.md).
|
|
757
|
+
|
|
758
|
+
This includes:
|
|
759
|
+
- Feature branch workflow with git worktrees
|
|
760
|
+
- AI-powered release note generation
|
|
761
|
+
- Container building and publishing
|
|
762
|
+
- Kubernetes deployment and rollback
|
|
763
|
+
- AWS infrastructure management
|
|
764
|
+
- And more practical examples
|
|
1091
765
|
|
|
1092
766
|
## License
|
|
1093
767
|
|
|
@@ -1095,4 +769,4 @@ MIT License - see LICENSE file for details.
|
|
|
1095
769
|
|
|
1096
770
|
---
|
|
1097
771
|
|
|
1098
|
-
|
|
772
|
+
Cooked with β€οΈ from Ukraine πΊπ¦
|