bunosh 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +570 -13
- package/bunosh.js +90 -4
- package/index.js +12 -7
- package/package.json +7 -3
- package/src/init.js +12 -4
- package/src/io.js +55 -1
- package/src/open-editor.js +95 -0
- package/src/program.js +58 -29
- package/src/task.js +8 -0
- package/src/tasks/ai.js +143 -0
- package/src/tasks/shell.js +119 -0
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;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { TaskResult, createTaskInfo, finishTaskInfo } from "../task.js";
|
|
2
|
+
import Printer from "../printer.js";
|
|
3
|
+
|
|
4
|
+
const isBun = typeof Bun !== 'undefined' && typeof Bun.spawn === 'function';
|
|
5
|
+
|
|
6
|
+
export default function shell(strings, ...values) {
|
|
7
|
+
const cmd = strings.reduce((accumulator, str, i) => {
|
|
8
|
+
return accumulator + str + (values[i] || "");
|
|
9
|
+
}, "");
|
|
10
|
+
|
|
11
|
+
let envs = null;
|
|
12
|
+
let cwd = null;
|
|
13
|
+
|
|
14
|
+
const cmdPromise = new Promise(async (resolve, reject) => {
|
|
15
|
+
const extraInfo = {};
|
|
16
|
+
if (cwd) extraInfo.cwd = cwd;
|
|
17
|
+
if (envs) extraInfo.env = envs;
|
|
18
|
+
|
|
19
|
+
if (!isBun) {
|
|
20
|
+
const { default: exec } = await import("./exec.js");
|
|
21
|
+
let execPromise = exec(strings, ...values);
|
|
22
|
+
if (envs) execPromise = execPromise.env(envs);
|
|
23
|
+
if (cwd) execPromise = execPromise.cwd(cwd);
|
|
24
|
+
const result = await execPromise;
|
|
25
|
+
resolve(result);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const taskInfo = createTaskInfo(cmd);
|
|
30
|
+
const printer = new Printer("shell", taskInfo.id);
|
|
31
|
+
printer.start(cmd, extraInfo);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const { $ } = await import("bun");
|
|
35
|
+
|
|
36
|
+
let shell = $;
|
|
37
|
+
|
|
38
|
+
if (cwd) {
|
|
39
|
+
shell = shell.cwd(cwd);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (envs) {
|
|
43
|
+
shell = shell.env(envs);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let result;
|
|
47
|
+
try {
|
|
48
|
+
result = await shell(strings, ...values);
|
|
49
|
+
|
|
50
|
+
const output = await result.text();
|
|
51
|
+
|
|
52
|
+
printer.finish(cmd);
|
|
53
|
+
finishTaskInfo(taskInfo, true, null, output.trim());
|
|
54
|
+
resolve(TaskResult.success(output.trim()));
|
|
55
|
+
return;
|
|
56
|
+
|
|
57
|
+
} catch (shellError) {
|
|
58
|
+
const isCommandNotFound = shellError.stderr &&
|
|
59
|
+
(shellError.stderr.includes('command not found') ||
|
|
60
|
+
shellError.stderr.includes('bun: command not found'));
|
|
61
|
+
|
|
62
|
+
if (isCommandNotFound) {
|
|
63
|
+
printer.finish(cmd);
|
|
64
|
+
finishTaskInfo(taskInfo, true, null, "fallback to exec");
|
|
65
|
+
|
|
66
|
+
const { default: exec } = await import("./exec.js");
|
|
67
|
+
let execPromise = exec`${cmd}`;
|
|
68
|
+
if (envs) execPromise = execPromise.env(envs);
|
|
69
|
+
if (cwd) execPromise = execPromise.cwd(cwd);
|
|
70
|
+
const result = await execPromise;
|
|
71
|
+
resolve(result);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (shellError.exitCode !== undefined) {
|
|
76
|
+
const stderr = shellError.stderr ? Buffer.isBuffer(shellError.stderr) ? shellError.stderr.toString() : shellError.stderr : "";
|
|
77
|
+
const stdout = shellError.stdout ? Buffer.isBuffer(shellError.stdout) ? shellError.stdout.toString() : shellError.stdout : "";
|
|
78
|
+
const errorOutput = (stderr + stdout).trim() || `Command failed with exit code ${shellError.exitCode}`;
|
|
79
|
+
|
|
80
|
+
if (errorOutput) {
|
|
81
|
+
const lines = errorOutput.split('\n');
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
if (line.trim()) {
|
|
84
|
+
printer.output(line, true);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const error = new Error(`Exit code: ${shellError.exitCode}`);
|
|
90
|
+
printer.error(cmd, null, { exitCode: shellError.exitCode });
|
|
91
|
+
finishTaskInfo(taskInfo, false, error, errorOutput);
|
|
92
|
+
resolve(TaskResult.fail(errorOutput));
|
|
93
|
+
return;
|
|
94
|
+
} else {
|
|
95
|
+
const errorMessage = shellError.message || shellError.toString();
|
|
96
|
+
printer.error(cmd, shellError);
|
|
97
|
+
finishTaskInfo(taskInfo, false, shellError, errorMessage);
|
|
98
|
+
resolve(TaskResult.fail(errorMessage));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
printer.error(cmd, error);
|
|
103
|
+
finishTaskInfo(taskInfo, false, error, error.message);
|
|
104
|
+
resolve(TaskResult.fail(error.message));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
cmdPromise.env = (newEnvs) => {
|
|
109
|
+
envs = newEnvs;
|
|
110
|
+
return cmdPromise;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
cmdPromise.cwd = (newCwd) => {
|
|
114
|
+
cwd = newCwd;
|
|
115
|
+
return cmdPromise;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return cmdPromise;
|
|
119
|
+
}
|