bunosh 0.4.1 → 0.4.7
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 +202 -61
- package/bunosh.js +246 -82
- package/package.json +7 -5
- package/src/completion.js +15 -2
- package/src/formatters/console.js +10 -5
- package/src/formatters/factory.js +30 -0
- package/src/io.js +5 -0
- package/src/mcp-server.js +575 -0
- package/src/printer.js +1 -1
- package/src/program.js +564 -486
- package/src/task.js +89 -11
- package/src/tasks/ai.js +10 -2
- package/src/tasks/exec.js +31 -9
- package/src/tasks/fetch.js +11 -3
- package/src/tasks/shell.js +18 -4
- package/src/upgrade.js +279 -199
package/src/task.js
CHANGED
|
@@ -28,15 +28,23 @@ export function ignoreFail(enable = true) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// Global failure mode control
|
|
31
|
-
// true = stop on failures (exit with code 1), false = continue on failures
|
|
32
31
|
let stopOnFailuresMode = false;
|
|
32
|
+
let ignoreFailuresMode = false;
|
|
33
33
|
|
|
34
34
|
export function ignoreFailures() {
|
|
35
35
|
stopOnFailuresMode = false;
|
|
36
|
+
ignoreFailuresMode = true;
|
|
37
|
+
globalThis._bunoshIgnoreFailuresMode = true;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export function stopOnFailures() {
|
|
39
41
|
stopOnFailuresMode = true;
|
|
42
|
+
ignoreFailuresMode = false;
|
|
43
|
+
globalThis._bunoshIgnoreFailuresMode = false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getIgnoreFailuresMode() {
|
|
47
|
+
return ignoreFailuresMode;
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
export function silence() {
|
|
@@ -185,7 +193,7 @@ export async function task(name, fn, isSilent = false) {
|
|
|
185
193
|
printer.finish(name);
|
|
186
194
|
runningTasks.delete(taskInfo.id);
|
|
187
195
|
|
|
188
|
-
return result;
|
|
196
|
+
return TaskResult.success(result, { taskType: 'task' });
|
|
189
197
|
} catch (err) {
|
|
190
198
|
const endTime = Date.now();
|
|
191
199
|
const duration = endTime - taskInfo.startTime;
|
|
@@ -198,9 +206,16 @@ export async function task(name, fn, isSilent = false) {
|
|
|
198
206
|
runningTasks.delete(taskInfo.id);
|
|
199
207
|
|
|
200
208
|
// Don't exit during testing
|
|
209
|
+
const commandArgs = process.argv.slice(2);
|
|
201
210
|
const isTestEnvironment = process.env.NODE_ENV === 'test' ||
|
|
202
211
|
typeof Bun?.jest !== 'undefined' ||
|
|
203
|
-
|
|
212
|
+
commandArgs.some(arg => {
|
|
213
|
+
const lowerArg = arg.toLowerCase();
|
|
214
|
+
return lowerArg.includes('vitest') ||
|
|
215
|
+
lowerArg.includes('jest') ||
|
|
216
|
+
lowerArg === '--test' ||
|
|
217
|
+
lowerArg.startsWith('test:');
|
|
218
|
+
});
|
|
204
219
|
|
|
205
220
|
// Exit immediately if stopOnFailures mode is enabled
|
|
206
221
|
if (stopOnFailuresMode && !isTestEnvironment) {
|
|
@@ -212,13 +227,14 @@ export async function task(name, fn, isSilent = false) {
|
|
|
212
227
|
process.exit(1);
|
|
213
228
|
}
|
|
214
229
|
|
|
215
|
-
|
|
230
|
+
return TaskResult.fail(err.message, { taskType: 'task', error: err });
|
|
216
231
|
}
|
|
217
232
|
}
|
|
218
233
|
|
|
219
234
|
// Add try method to task function
|
|
220
235
|
task.try = tryTask;
|
|
221
236
|
|
|
237
|
+
|
|
222
238
|
export class SilentTaskWrapper {
|
|
223
239
|
constructor() {
|
|
224
240
|
this.silent = true;
|
|
@@ -246,9 +262,10 @@ export function silent() {
|
|
|
246
262
|
}
|
|
247
263
|
|
|
248
264
|
export class TaskResult {
|
|
249
|
-
constructor({ status, output }) {
|
|
265
|
+
constructor({ status, output, metadata = {} }) {
|
|
250
266
|
this.status = status;
|
|
251
267
|
this.output = output;
|
|
268
|
+
this._metadata = metadata;
|
|
252
269
|
}
|
|
253
270
|
|
|
254
271
|
get hasFailed() {
|
|
@@ -263,15 +280,76 @@ export class TaskResult {
|
|
|
263
280
|
return this.status === TaskStatus.WARNING;
|
|
264
281
|
}
|
|
265
282
|
|
|
266
|
-
|
|
267
|
-
|
|
283
|
+
async json() {
|
|
284
|
+
const taskType = this._metadata.taskType || 'unknown';
|
|
285
|
+
|
|
286
|
+
switch (taskType) {
|
|
287
|
+
case 'fetch':
|
|
288
|
+
// For fetch tasks, parse the response body as JSON
|
|
289
|
+
if (this._metadata.response) {
|
|
290
|
+
try {
|
|
291
|
+
return await this._metadata.response.json();
|
|
292
|
+
} catch (error) {
|
|
293
|
+
throw new Error(`Failed to parse fetch response as JSON: ${error.message}`);
|
|
294
|
+
}
|
|
295
|
+
} else if (typeof this.output === 'string') {
|
|
296
|
+
try {
|
|
297
|
+
return JSON.parse(this.output);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
throw new Error(`Failed to parse fetch output as JSON: ${error.message}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
throw new Error('No JSON data available from fetch task');
|
|
303
|
+
|
|
304
|
+
case 'exec':
|
|
305
|
+
case 'shell':
|
|
306
|
+
// For exec/shell tasks, return structured output
|
|
307
|
+
const lines = this.output ? this.output.split('\n').filter(line => line.trim()) : [];
|
|
308
|
+
return {
|
|
309
|
+
stdout: this._metadata.stdout || this.output || '',
|
|
310
|
+
stderr: this._metadata.stderr || '',
|
|
311
|
+
exitCode: this._metadata.exitCode || (this.hasFailed ? 1 : 0),
|
|
312
|
+
lines
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
case 'ai':
|
|
316
|
+
// For AI tasks, return the structured output if available
|
|
317
|
+
if (typeof this.output === 'object') {
|
|
318
|
+
return this.output;
|
|
319
|
+
} else if (typeof this.output === 'string') {
|
|
320
|
+
try {
|
|
321
|
+
return JSON.parse(this.output);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
// If it's not JSON, return as text property
|
|
324
|
+
return { text: this.output };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return { text: this.output || '' };
|
|
328
|
+
|
|
329
|
+
default:
|
|
330
|
+
// For unknown task types, try to parse output as JSON or return as text
|
|
331
|
+
if (typeof this.output === 'object') {
|
|
332
|
+
return this.output;
|
|
333
|
+
} else if (typeof this.output === 'string') {
|
|
334
|
+
try {
|
|
335
|
+
return JSON.parse(this.output);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
return { text: this.output };
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return { text: this.output || '' };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
static fail(output = null, metadata = {}) {
|
|
345
|
+
return new TaskResult({ status: TaskStatus.FAIL, output, metadata });
|
|
268
346
|
}
|
|
269
347
|
|
|
270
|
-
static success(output = null) {
|
|
271
|
-
return new TaskResult({ status: TaskStatus.SUCCESS, output });
|
|
348
|
+
static success(output = null, metadata = {}) {
|
|
349
|
+
return new TaskResult({ status: TaskStatus.SUCCESS, output, metadata });
|
|
272
350
|
}
|
|
273
351
|
|
|
274
|
-
static warning(output = null) {
|
|
275
|
-
return new TaskResult({ status: TaskStatus.WARNING, output });
|
|
352
|
+
static warning(output = null, metadata = {}) {
|
|
353
|
+
return new TaskResult({ status: TaskStatus.WARNING, output, metadata });
|
|
276
354
|
}
|
|
277
355
|
}
|
package/src/tasks/ai.js
CHANGED
|
@@ -118,15 +118,23 @@ async function ai(prompt, outputFormat = null) {
|
|
|
118
118
|
process.stdout.write('\r' + ' '.repeat(20) + '\r');
|
|
119
119
|
const tokenInfo = usage ? `${usage.totalTokens} tokens` : '';
|
|
120
120
|
printer.finish(taskName, { tokenInfo });
|
|
121
|
+
|
|
122
|
+
const metadata = {
|
|
123
|
+
taskType: 'ai',
|
|
124
|
+
outputFormat: !!outputFormat,
|
|
125
|
+
usage,
|
|
126
|
+
prompt: cleanPrompt
|
|
127
|
+
};
|
|
128
|
+
|
|
121
129
|
finishTaskInfo(taskInfo, true, null, outputFormat ? JSON.stringify(result, null, 2) : result);
|
|
122
|
-
return TaskResult.success(result);
|
|
130
|
+
return TaskResult.success(result, metadata);
|
|
123
131
|
|
|
124
132
|
} catch (error) {
|
|
125
133
|
clearInterval(progressInterval);
|
|
126
134
|
process.stdout.write('\r' + ' '.repeat(20) + '\r');
|
|
127
135
|
printer.error(taskName, error);
|
|
128
136
|
finishTaskInfo(taskInfo, false, error, error.message);
|
|
129
|
-
return TaskResult.fail(error.message);
|
|
137
|
+
return TaskResult.fail(error.message, { taskType: 'ai' });
|
|
130
138
|
}
|
|
131
139
|
}
|
|
132
140
|
|
package/src/tasks/exec.js
CHANGED
|
@@ -62,6 +62,8 @@ export default function exec(strings, ...values) {
|
|
|
62
62
|
|
|
63
63
|
const decoder = new TextDecoder();
|
|
64
64
|
let output = '';
|
|
65
|
+
let stdout = '';
|
|
66
|
+
let stderr = '';
|
|
65
67
|
let finished = false;
|
|
66
68
|
|
|
67
69
|
// Process stdout
|
|
@@ -84,6 +86,7 @@ export default function exec(strings, ...values) {
|
|
|
84
86
|
if (line.trim()) {
|
|
85
87
|
printer.output(line);
|
|
86
88
|
output += line + '\n';
|
|
89
|
+
stdout += line + '\n';
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
92
|
}
|
|
@@ -91,6 +94,7 @@ export default function exec(strings, ...values) {
|
|
|
91
94
|
if (buffer.trim()) {
|
|
92
95
|
printer.output(buffer);
|
|
93
96
|
output += buffer + '\n';
|
|
97
|
+
stdout += buffer + '\n';
|
|
94
98
|
}
|
|
95
99
|
} finally {
|
|
96
100
|
reader.releaseLock();
|
|
@@ -117,6 +121,7 @@ export default function exec(strings, ...values) {
|
|
|
117
121
|
if (line.trim()) {
|
|
118
122
|
printer.output(line, true);
|
|
119
123
|
output += line + '\n';
|
|
124
|
+
stderr += line + '\n';
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
}
|
|
@@ -124,6 +129,7 @@ export default function exec(strings, ...values) {
|
|
|
124
129
|
if (buffer.trim()) {
|
|
125
130
|
printer.output(buffer, true);
|
|
126
131
|
output += buffer + '\n';
|
|
132
|
+
stderr += buffer + '\n';
|
|
127
133
|
}
|
|
128
134
|
} finally {
|
|
129
135
|
reader.releaseLock();
|
|
@@ -140,20 +146,27 @@ export default function exec(strings, ...values) {
|
|
|
140
146
|
finished = true;
|
|
141
147
|
const exitCode = parseInt(exitResult, 10);
|
|
142
148
|
|
|
149
|
+
const metadata = {
|
|
150
|
+
taskType: 'exec',
|
|
151
|
+
exitCode,
|
|
152
|
+
stdout: stdout.trim(),
|
|
153
|
+
stderr: stderr.trim()
|
|
154
|
+
};
|
|
155
|
+
|
|
143
156
|
if (exitCode === 0) {
|
|
144
157
|
printer.finish(cmd);
|
|
145
158
|
finishTaskInfo(taskInfo, true, null, output.trim());
|
|
146
|
-
resolve(TaskResult.success(output.trim()));
|
|
159
|
+
resolve(TaskResult.success(output.trim(), metadata));
|
|
147
160
|
} else {
|
|
148
161
|
const error = new Error(`Exit code: ${exitCode}`);
|
|
149
162
|
printer.error(cmd, null, { exitCode });
|
|
150
163
|
finishTaskInfo(taskInfo, false, error, output.trim());
|
|
151
|
-
resolve(TaskResult.fail(output.trim()));
|
|
164
|
+
resolve(TaskResult.fail(output.trim(), metadata));
|
|
152
165
|
}
|
|
153
166
|
} catch (error) {
|
|
154
167
|
printer.error(cmd, error);
|
|
155
168
|
finishTaskInfo(taskInfo, false, error, error.message);
|
|
156
|
-
resolve(TaskResult.fail(error.message));
|
|
169
|
+
resolve(TaskResult.fail(error.message, { taskType: 'exec' }));
|
|
157
170
|
}
|
|
158
171
|
});
|
|
159
172
|
|
|
@@ -182,35 +195,44 @@ async function nodeExec(cmd, extraInfo, printer, taskInfo) {
|
|
|
182
195
|
});
|
|
183
196
|
|
|
184
197
|
let output = '';
|
|
185
|
-
let
|
|
198
|
+
let stdout = '';
|
|
199
|
+
let stderr = '';
|
|
186
200
|
|
|
187
201
|
proc.stdout.on('data', (data) => {
|
|
188
202
|
const text = data.toString();
|
|
189
203
|
printer.output(text.trim());
|
|
190
204
|
output += text;
|
|
205
|
+
stdout += text;
|
|
191
206
|
});
|
|
192
207
|
|
|
193
208
|
proc.stderr.on('data', (data) => {
|
|
194
209
|
const text = data.toString();
|
|
195
210
|
printer.output(text.trim(), true);
|
|
196
|
-
|
|
211
|
+
output += text;
|
|
212
|
+
stderr += text;
|
|
197
213
|
});
|
|
198
214
|
|
|
199
215
|
proc.on('close', (code) => {
|
|
200
|
-
const combinedOutput = (output
|
|
216
|
+
const combinedOutput = (output).trim();
|
|
217
|
+
const metadata = {
|
|
218
|
+
taskType: 'exec',
|
|
219
|
+
exitCode: code,
|
|
220
|
+
stdout: stdout.trim(),
|
|
221
|
+
stderr: stderr.trim()
|
|
222
|
+
};
|
|
201
223
|
|
|
202
224
|
if (code === 0) {
|
|
203
225
|
printer.finish(cmd);
|
|
204
|
-
resolve(TaskResult.success(combinedOutput));
|
|
226
|
+
resolve(TaskResult.success(combinedOutput, metadata));
|
|
205
227
|
} else {
|
|
206
228
|
printer.error(cmd, new Error(`Exit code: ${code}`));
|
|
207
|
-
resolve(TaskResult.fail(combinedOutput));
|
|
229
|
+
resolve(TaskResult.fail(combinedOutput, metadata));
|
|
208
230
|
}
|
|
209
231
|
});
|
|
210
232
|
|
|
211
233
|
proc.on('error', (error) => {
|
|
212
234
|
printer.error(cmd, error);
|
|
213
|
-
resolve(TaskResult.fail(error.message));
|
|
235
|
+
resolve(TaskResult.fail(error.message, { taskType: 'exec' }));
|
|
214
236
|
});
|
|
215
237
|
});
|
|
216
238
|
}
|
package/src/tasks/fetch.js
CHANGED
|
@@ -28,21 +28,29 @@ export default async function httpFetch() {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
const metadata = {
|
|
32
|
+
taskType: 'fetch',
|
|
33
|
+
response: response.clone(), // Clone to allow json() to be called later
|
|
34
|
+
status: response.status,
|
|
35
|
+
statusText: response.statusText,
|
|
36
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
37
|
+
};
|
|
38
|
+
|
|
31
39
|
if (response.ok) {
|
|
32
40
|
printer.finish(taskName, { status: `${response.status} ${response.statusText}` });
|
|
33
41
|
finishTaskInfo(taskInfo, true, null, output.trim());
|
|
34
|
-
return TaskResult.success(output.trim());
|
|
42
|
+
return TaskResult.success(output.trim(), metadata);
|
|
35
43
|
} else {
|
|
36
44
|
const errorMsg = `HTTP ${response.status} ${response.statusText}`;
|
|
37
45
|
const error = new Error(errorMsg);
|
|
38
46
|
printer.error(taskName, errorMsg);
|
|
39
47
|
finishTaskInfo(taskInfo, false, error, errorMsg);
|
|
40
|
-
return TaskResult.fail(errorMsg);
|
|
48
|
+
return TaskResult.fail(errorMsg, metadata);
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
} catch (error) {
|
|
44
52
|
printer.error(taskName, error);
|
|
45
53
|
finishTaskInfo(taskInfo, false, error, error.message);
|
|
46
|
-
return TaskResult.fail(error.message);
|
|
54
|
+
return TaskResult.fail(error.message, { taskType: 'fetch' });
|
|
47
55
|
}
|
|
48
56
|
}
|
package/src/tasks/shell.js
CHANGED
|
@@ -81,9 +81,16 @@ export default function shell(strings, ...values) {
|
|
|
81
81
|
|
|
82
82
|
const output = await result.text();
|
|
83
83
|
|
|
84
|
+
const metadata = {
|
|
85
|
+
taskType: 'shell',
|
|
86
|
+
exitCode: 0,
|
|
87
|
+
stdout: output.trim(),
|
|
88
|
+
stderr: ''
|
|
89
|
+
};
|
|
90
|
+
|
|
84
91
|
printer.finish(cmd);
|
|
85
92
|
finishTaskInfo(taskInfo, true, null, output.trim());
|
|
86
|
-
resolve(TaskResult.success(output.trim()));
|
|
93
|
+
resolve(TaskResult.success(output.trim(), metadata));
|
|
87
94
|
return;
|
|
88
95
|
|
|
89
96
|
} catch (shellError) {
|
|
@@ -118,22 +125,29 @@ export default function shell(strings, ...values) {
|
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
127
|
|
|
128
|
+
const metadata = {
|
|
129
|
+
taskType: 'shell',
|
|
130
|
+
exitCode: shellError.exitCode,
|
|
131
|
+
stdout: stdout.trim(),
|
|
132
|
+
stderr: stderr.trim()
|
|
133
|
+
};
|
|
134
|
+
|
|
121
135
|
const error = new Error(`Exit code: ${shellError.exitCode}`);
|
|
122
136
|
printer.error(cmd, null, { exitCode: shellError.exitCode });
|
|
123
137
|
finishTaskInfo(taskInfo, false, error, errorOutput);
|
|
124
|
-
resolve(TaskResult.fail(errorOutput));
|
|
138
|
+
resolve(TaskResult.fail(errorOutput, metadata));
|
|
125
139
|
return;
|
|
126
140
|
} else {
|
|
127
141
|
const errorMessage = shellError.message || shellError.toString();
|
|
128
142
|
printer.error(cmd, shellError);
|
|
129
143
|
finishTaskInfo(taskInfo, false, shellError, errorMessage);
|
|
130
|
-
resolve(TaskResult.fail(errorMessage));
|
|
144
|
+
resolve(TaskResult.fail(errorMessage, { taskType: 'shell' }));
|
|
131
145
|
}
|
|
132
146
|
}
|
|
133
147
|
} catch (error) {
|
|
134
148
|
printer.error(cmd, error);
|
|
135
149
|
finishTaskInfo(taskInfo, false, error, error.message);
|
|
136
|
-
resolve(TaskResult.fail(error.message));
|
|
150
|
+
resolve(TaskResult.fail(error.message, { taskType: 'shell' }));
|
|
137
151
|
}
|
|
138
152
|
});
|
|
139
153
|
|