openbot 0.1.25 ā 0.1.26
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/dist/cli.js +194 -65
- package/dist/handlers/settings.js +18 -0
- package/dist/open-bot.js +3 -3
- package/dist/registry/index.js +1 -1
- package/dist/registry/plugin-loader.js +22 -0
- package/dist/registry/yaml-agent-loader.js +45 -2
- package/dist/ui/settings.js +91 -60
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,12 +7,167 @@ import { execSync } from "node:child_process";
|
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { saveConfig, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
|
|
9
9
|
import { startServer } from "./server.js";
|
|
10
|
-
import { ensurePluginReady } from "./registry/plugin-loader.js";
|
|
10
|
+
import { ensurePluginReady, getPluginMetadata } from "./registry/plugin-loader.js";
|
|
11
|
+
import { readAgentConfig } from "./registry/yaml-agent-loader.js";
|
|
11
12
|
const program = new Command();
|
|
12
13
|
program
|
|
13
14
|
.name("openbot")
|
|
14
15
|
.description("OpenBot CLI - Secure and easy configuration")
|
|
15
|
-
.version("0.1.
|
|
16
|
+
.version("0.1.26");
|
|
17
|
+
/**
|
|
18
|
+
* Check if a GitHub repository exists.
|
|
19
|
+
*/
|
|
20
|
+
function checkGitHubRepo(repo) {
|
|
21
|
+
try {
|
|
22
|
+
execSync(`git ls-remote https://github.com/${repo}.git`, { stdio: "ignore" });
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if an NPM package exists.
|
|
31
|
+
*/
|
|
32
|
+
function checkNpmPackage(pkg) {
|
|
33
|
+
try {
|
|
34
|
+
execSync(`npm show ${pkg} version`, { stdio: "ignore" });
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Install a plugin from a source (GitHub, local, or NPM).
|
|
43
|
+
*/
|
|
44
|
+
async function installPlugin(source, quiet = false) {
|
|
45
|
+
const isGitHub = (source.includes("/") || source.startsWith("github:")) && !source.startsWith("/") && !source.startsWith(".");
|
|
46
|
+
const isNpm = source.startsWith("@") || source.startsWith("npm:");
|
|
47
|
+
const repoUrl = isGitHub
|
|
48
|
+
? (source.startsWith("github:") ? `https://github.com/${source.slice(7)}.git` : `https://github.com/${source}.git`)
|
|
49
|
+
: source;
|
|
50
|
+
const tempDir = path.join(tmpdir(), `openbot-plugin-install-${Date.now()}`);
|
|
51
|
+
try {
|
|
52
|
+
if (!quiet)
|
|
53
|
+
console.log(`š¦ Installing plugin from: ${isNpm ? source : repoUrl}`);
|
|
54
|
+
// 1. Fetch to temp directory
|
|
55
|
+
if (isGitHub) {
|
|
56
|
+
execSync(`git clone --depth 1 ${repoUrl} ${tempDir}`, { stdio: quiet ? "ignore" : "inherit" });
|
|
57
|
+
}
|
|
58
|
+
else if (isNpm) {
|
|
59
|
+
const pkgName = source.startsWith("npm:") ? source.slice(4) : source;
|
|
60
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
61
|
+
execSync(`npm install ${pkgName} --prefix ${tempDir}`, { stdio: quiet ? "ignore" : "inherit" });
|
|
62
|
+
// Move from node_modules to tempDir root for consistency
|
|
63
|
+
const pkgFolder = path.join(tempDir, "node_modules", pkgName);
|
|
64
|
+
const moveTemp = path.join(tmpdir(), `openbot-npm-move-${Date.now()}`);
|
|
65
|
+
await fs.rename(pkgFolder, moveTemp);
|
|
66
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
67
|
+
await fs.rename(moveTemp, tempDir);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const absoluteSource = path.resolve(source);
|
|
71
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
72
|
+
execSync(`cp -R ${absoluteSource}/. ${tempDir}`, { stdio: quiet ? "ignore" : "inherit" });
|
|
73
|
+
}
|
|
74
|
+
// 2. Identify name from metadata
|
|
75
|
+
const { name } = await getPluginMetadata(tempDir);
|
|
76
|
+
const baseDir = resolvePath(DEFAULT_BASE_DIR);
|
|
77
|
+
const targetDir = path.join(baseDir, "plugins", name);
|
|
78
|
+
// 3. Move to target directory
|
|
79
|
+
await fs.mkdir(path.dirname(targetDir), { recursive: true });
|
|
80
|
+
if (await fs.access(targetDir).then(() => true).catch(() => false)) {
|
|
81
|
+
if (!quiet)
|
|
82
|
+
console.log(`ā ļø Plugin "${name}" already exists. Overwriting...`);
|
|
83
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
await fs.rename(tempDir, targetDir);
|
|
86
|
+
if (!quiet)
|
|
87
|
+
console.log(`ā
Moved to: ${targetDir}`);
|
|
88
|
+
// 4. Prepare
|
|
89
|
+
if (!quiet)
|
|
90
|
+
console.log(`āļø Preparing plugin "${name}"...`);
|
|
91
|
+
await ensurePluginReady(targetDir);
|
|
92
|
+
if (!quiet)
|
|
93
|
+
console.log(`\nš Successfully installed plugin: ${name}`);
|
|
94
|
+
return name;
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
if (!quiet)
|
|
98
|
+
console.error("\nā Plugin installation failed:", err instanceof Error ? err.message : String(err));
|
|
99
|
+
try {
|
|
100
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
catch { /* ignore */ }
|
|
103
|
+
if (!quiet)
|
|
104
|
+
process.exit(1);
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Install an agent from a source (GitHub).
|
|
110
|
+
*/
|
|
111
|
+
async function installAgent(source) {
|
|
112
|
+
const repoUrl = source.startsWith("github:") ? `https://github.com/${source.slice(7)}.git` : `https://github.com/${source}.git`;
|
|
113
|
+
const tempDir = path.join(tmpdir(), `openbot-agent-install-${Date.now()}`);
|
|
114
|
+
try {
|
|
115
|
+
console.log(`š¤ Installing agent from: ${repoUrl}`);
|
|
116
|
+
// 1. Clone to temp directory
|
|
117
|
+
execSync(`git clone --depth 1 ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
|
118
|
+
// 2. Identify name from agent.yaml
|
|
119
|
+
const config = await readAgentConfig(tempDir);
|
|
120
|
+
const name = config.name || path.basename(source.replace(".git", ""));
|
|
121
|
+
const baseDir = resolvePath(DEFAULT_BASE_DIR);
|
|
122
|
+
const targetDir = path.join(baseDir, "agents", name);
|
|
123
|
+
// 3. Move to target directory
|
|
124
|
+
await fs.mkdir(path.dirname(targetDir), { recursive: true });
|
|
125
|
+
if (await fs.access(targetDir).then(() => true).catch(() => false)) {
|
|
126
|
+
console.log(`ā ļø Agent "${name}" already exists. Overwriting...`);
|
|
127
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
await fs.rename(tempDir, targetDir);
|
|
130
|
+
console.log(`ā
Moved to: ${targetDir}`);
|
|
131
|
+
// 4. Recursive plugin installation
|
|
132
|
+
if (config.plugins && Array.isArray(config.plugins)) {
|
|
133
|
+
for (const pluginItem of config.plugins) {
|
|
134
|
+
const pluginName = typeof pluginItem === "string" ? pluginItem : pluginItem.name;
|
|
135
|
+
// Skip built-in plugins
|
|
136
|
+
if (["shell", "file-system"].includes(pluginName))
|
|
137
|
+
continue;
|
|
138
|
+
// Check if plugin exists locally
|
|
139
|
+
const pluginPath = path.join(baseDir, "plugins", pluginName);
|
|
140
|
+
const existsLocally = await fs.access(pluginPath).then(() => true).catch(() => false);
|
|
141
|
+
if (!existsLocally) {
|
|
142
|
+
console.log(`š Agent needs plugin "${pluginName}". Searching...`);
|
|
143
|
+
// Try GitHub
|
|
144
|
+
const ghRepo = `meetopenbot/plugin-${pluginName}`;
|
|
145
|
+
if (checkGitHubRepo(ghRepo)) {
|
|
146
|
+
await installPlugin(ghRepo);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
// Try NPM
|
|
150
|
+
const npmPkg = `@melony/plugin-${pluginName}`;
|
|
151
|
+
if (checkNpmPackage(npmPkg)) {
|
|
152
|
+
await installPlugin(npmPkg);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
console.warn(`ā ļø Could not find plugin "${pluginName}" for agent "${name}". You may need to install it manually.`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
console.log(`\nš Successfully installed agent: ${name}`);
|
|
160
|
+
return name;
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
console.error("\nā Agent installation failed:", err instanceof Error ? err.message : String(err));
|
|
164
|
+
try {
|
|
165
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
166
|
+
}
|
|
167
|
+
catch { /* ignore */ }
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
16
171
|
program
|
|
17
172
|
.command("configure")
|
|
18
173
|
.description("Configure OpenBot model and settings")
|
|
@@ -72,62 +227,45 @@ program
|
|
|
72
227
|
.action(async (options) => {
|
|
73
228
|
await startServer(options);
|
|
74
229
|
});
|
|
230
|
+
program
|
|
231
|
+
.command("add <name>")
|
|
232
|
+
.description("Add an agent or plugin by name (auto-resolves to GitHub/NPM)")
|
|
233
|
+
.action(async (name) => {
|
|
234
|
+
// 1. Try as Agent
|
|
235
|
+
const agentRepo = `meetopenbot/agent-${name}`;
|
|
236
|
+
if (checkGitHubRepo(agentRepo)) {
|
|
237
|
+
await installAgent(agentRepo);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// 2. Try as Plugin
|
|
241
|
+
const baseDir = resolvePath(DEFAULT_BASE_DIR);
|
|
242
|
+
const pluginPath = path.join(baseDir, "plugins", name);
|
|
243
|
+
const existsLocally = await fs.access(pluginPath).then(() => true).catch(() => false);
|
|
244
|
+
if (existsLocally) {
|
|
245
|
+
console.log(`ā
Plugin "${name}" is already installed locally.`);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
// Check GitHub Plugin
|
|
249
|
+
const pluginGhRepo = `meetopenbot/plugin-${name}`;
|
|
250
|
+
if (checkGitHubRepo(pluginGhRepo)) {
|
|
251
|
+
await installPlugin(pluginGhRepo);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Check NPM Plugin
|
|
255
|
+
const pluginNpmPkg = `@melony/plugin-${name}`;
|
|
256
|
+
if (checkNpmPackage(pluginNpmPkg)) {
|
|
257
|
+
await installPlugin(pluginNpmPkg);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
console.error(`ā Could not find agent or plugin named "${name}" in official repositories.`);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
});
|
|
75
263
|
const plugin = program.command("plugin").description("Manage OpenBot plugins");
|
|
76
264
|
plugin
|
|
77
265
|
.command("install <source>")
|
|
78
266
|
.description("Install a shared plugin from GitHub (user/repo) or a local path")
|
|
79
267
|
.action(async (source) => {
|
|
80
|
-
|
|
81
|
-
const repoUrl = isGitHub ? `https://github.com/${source}.git` : source;
|
|
82
|
-
const tempDir = path.join(tmpdir(), `openbot-plugin-install-${Date.now()}`);
|
|
83
|
-
try {
|
|
84
|
-
console.log(`š¦ Installing plugin from: ${repoUrl}`);
|
|
85
|
-
// 1. Clone or copy to temp directory
|
|
86
|
-
if (isGitHub) {
|
|
87
|
-
execSync(`git clone --depth 1 ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
const absoluteSource = path.resolve(source);
|
|
91
|
-
await fs.mkdir(tempDir, { recursive: true });
|
|
92
|
-
execSync(`cp -R ${absoluteSource}/. ${tempDir}`, { stdio: "inherit" });
|
|
93
|
-
}
|
|
94
|
-
// 2. Identify name from package.json
|
|
95
|
-
let name = path.basename(source.replace(".git", ""));
|
|
96
|
-
const pkgPath = path.join(tempDir, "package.json");
|
|
97
|
-
if (await fs.access(pkgPath).then(() => true).catch(() => false)) {
|
|
98
|
-
try {
|
|
99
|
-
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
|
|
100
|
-
if (pkg.name)
|
|
101
|
-
name = pkg.name.split("/").pop(); // Use last part of scoped names
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
// Fallback to source basename
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
const baseDir = resolvePath(DEFAULT_BASE_DIR);
|
|
108
|
-
const targetDir = path.join(baseDir, "plugins", name);
|
|
109
|
-
// 3. Move to target directory
|
|
110
|
-
await fs.mkdir(path.dirname(targetDir), { recursive: true });
|
|
111
|
-
if (await fs.access(targetDir).then(() => true).catch(() => false)) {
|
|
112
|
-
console.log(`ā ļø Plugin "${name}" already exists. Overwriting...`);
|
|
113
|
-
await fs.rm(targetDir, { recursive: true, force: true });
|
|
114
|
-
}
|
|
115
|
-
await fs.rename(tempDir, targetDir);
|
|
116
|
-
console.log(`ā
Moved to: ${targetDir}`);
|
|
117
|
-
// 4. Prepare
|
|
118
|
-
console.log(`āļø Preparing plugin "${name}"...`);
|
|
119
|
-
await ensurePluginReady(targetDir);
|
|
120
|
-
console.log(`\nš Successfully installed plugin: ${name}`);
|
|
121
|
-
console.log(`This plugin is now available to all agents.`);
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
console.error("\nā Plugin installation failed:", err instanceof Error ? err.message : String(err));
|
|
125
|
-
try {
|
|
126
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
127
|
-
}
|
|
128
|
-
catch { /* ignore */ }
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
268
|
+
await installPlugin(source);
|
|
131
269
|
});
|
|
132
270
|
plugin
|
|
133
271
|
.command("list")
|
|
@@ -149,18 +287,9 @@ plugin
|
|
|
149
287
|
continue;
|
|
150
288
|
if (entry.name.startsWith(".") || entry.name.startsWith("_"))
|
|
151
289
|
continue;
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
|
|
157
|
-
description = pkg.description || description;
|
|
158
|
-
version = pkg.version || version;
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
// Use defaults
|
|
162
|
-
}
|
|
163
|
-
plugins.push({ name: entry.name, version, description });
|
|
290
|
+
const pluginDir = path.join(pluginsDir, entry.name);
|
|
291
|
+
const { name, version, description } = await getPluginMetadata(pluginDir);
|
|
292
|
+
plugins.push({ name, version, description });
|
|
164
293
|
}
|
|
165
294
|
if (plugins.length === 0) {
|
|
166
295
|
console.log("No shared plugins found.");
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { saveConfig } from "../config.js";
|
|
2
2
|
import { tabOnlyUI } from "../ui/layout.js";
|
|
3
|
+
import { exec } from "node:child_process";
|
|
4
|
+
import os from "node:os";
|
|
3
5
|
/**
|
|
4
6
|
* Handle settings updates (like API keys)
|
|
5
7
|
*/
|
|
@@ -27,3 +29,19 @@ export async function* updateSettingsHandler(event, { state }) {
|
|
|
27
29
|
};
|
|
28
30
|
}
|
|
29
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Handle opening an agent's folder in the native file explorer
|
|
34
|
+
*/
|
|
35
|
+
export async function* openAgentFolderHandler(event) {
|
|
36
|
+
const { folder } = event.data;
|
|
37
|
+
if (folder) {
|
|
38
|
+
const command = os.platform() === "win32" ? `explorer "${folder}"` :
|
|
39
|
+
os.platform() === "darwin" ? `open "${folder}"` :
|
|
40
|
+
`xdg-open "${folder}"`;
|
|
41
|
+
exec(command, (error) => {
|
|
42
|
+
if (error) {
|
|
43
|
+
console.error(`Failed to open folder: ${error.message}`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
package/dist/open-bot.js
CHANGED
|
@@ -6,7 +6,7 @@ import { llmPlugin } from "./plugins/llm/index.js";
|
|
|
6
6
|
import { initHandler } from "./handlers/init.js";
|
|
7
7
|
import { sessionChangeHandler } from "./handlers/session-change.js";
|
|
8
8
|
import { tabChangeHandler } from "./handlers/tab-change.js";
|
|
9
|
-
import { updateSettingsHandler } from "./handlers/settings.js";
|
|
9
|
+
import { updateSettingsHandler, openAgentFolderHandler } from "./handlers/settings.js";
|
|
10
10
|
import { loadConfig, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
|
|
11
11
|
import { createModel } from "./models.js";
|
|
12
12
|
import path from "node:path";
|
|
@@ -30,7 +30,6 @@ export async function createOpenBot(options) {
|
|
|
30
30
|
const baseDir = config.baseDir || DEFAULT_BASE_DIR;
|
|
31
31
|
const resolvedBaseDir = resolvePath(baseDir);
|
|
32
32
|
const model = createModel(options);
|
|
33
|
-
const userDataDir = path.join(resolvedBaseDir, "browser-data");
|
|
34
33
|
// āāā Plugin Registry āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
35
34
|
// Register built-in plugins so YAML agents can reference them by name.
|
|
36
35
|
const pluginRegistry = new PluginRegistry();
|
|
@@ -195,6 +194,7 @@ Example: If the user asks to "check the weather", and you see a 'browser' agent,
|
|
|
195
194
|
.on("init", initHandler)
|
|
196
195
|
.on("sessionChange", sessionChangeHandler)
|
|
197
196
|
.on("tabChange", tabChangeHandler)
|
|
198
|
-
.on("action:updateSettings", updateSettingsHandler)
|
|
197
|
+
.on("action:updateSettings", updateSettingsHandler)
|
|
198
|
+
.on("action:openAgentFolder", openAgentFolderHandler);
|
|
199
199
|
return app;
|
|
200
200
|
}
|
package/dist/registry/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { PluginRegistry } from "./plugin-registry.js";
|
|
2
2
|
export { AgentRegistry } from "./agent-registry.js";
|
|
3
|
-
export { discoverYamlAgents } from "./yaml-agent-loader.js";
|
|
3
|
+
export { discoverYamlAgents, listYamlAgents } from "./yaml-agent-loader.js";
|
|
4
4
|
export { loadPluginsFromDir } from "./plugin-loader.js";
|
|
@@ -2,6 +2,28 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
5
|
+
/**
|
|
6
|
+
* Get metadata (name, description, version) from a plugin directory.
|
|
7
|
+
*/
|
|
8
|
+
export async function getPluginMetadata(pluginDir) {
|
|
9
|
+
const pkgPath = path.join(pluginDir, "package.json");
|
|
10
|
+
const hasPackageJson = await fs.access(pkgPath).then(() => true).catch(() => false);
|
|
11
|
+
let name = path.basename(pluginDir);
|
|
12
|
+
let description = "No description";
|
|
13
|
+
let version = "0.0.0";
|
|
14
|
+
if (hasPackageJson) {
|
|
15
|
+
try {
|
|
16
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
|
|
17
|
+
name = (pkg.name?.split("/").pop()) || name;
|
|
18
|
+
description = pkg.description || description;
|
|
19
|
+
version = pkg.version || version;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Fallback to defaults
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return { name, description, version };
|
|
26
|
+
}
|
|
5
27
|
/**
|
|
6
28
|
* Ensure a plugin is ready to run by installing dependencies and building if necessary.
|
|
7
29
|
*/
|
|
@@ -25,6 +25,50 @@ function resolveConfigPaths(config) {
|
|
|
25
25
|
}
|
|
26
26
|
return config;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Read and parse an agent.yaml file from a directory.
|
|
30
|
+
*/
|
|
31
|
+
export async function readAgentConfig(agentDir) {
|
|
32
|
+
const yamlPath = path.join(agentDir, "agent.yaml");
|
|
33
|
+
const content = await fs.readFile(yamlPath, "utf-8");
|
|
34
|
+
return yaml.load(content);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Discover YAML-defined agents from a directory without loading plugins.
|
|
38
|
+
*
|
|
39
|
+
* @param agentsDir Absolute path to the agents directory (e.g. ~/.openbot/agents)
|
|
40
|
+
* @returns Array of agent metadata
|
|
41
|
+
*/
|
|
42
|
+
export async function listYamlAgents(agentsDir) {
|
|
43
|
+
const agents = [];
|
|
44
|
+
try {
|
|
45
|
+
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
if (!entry.isDirectory())
|
|
48
|
+
continue;
|
|
49
|
+
if (entry.name.startsWith(".") || entry.name.startsWith("_"))
|
|
50
|
+
continue;
|
|
51
|
+
const agentDir = path.join(agentsDir, entry.name);
|
|
52
|
+
try {
|
|
53
|
+
const config = await readAgentConfig(agentDir);
|
|
54
|
+
if (config.name && config.description) {
|
|
55
|
+
agents.push({
|
|
56
|
+
name: config.name,
|
|
57
|
+
description: config.description,
|
|
58
|
+
folder: agentDir,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Skip invalid agents
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Agents directory doesn't exist
|
|
69
|
+
}
|
|
70
|
+
return agents;
|
|
71
|
+
}
|
|
28
72
|
/**
|
|
29
73
|
* Discover and load YAML-defined agents from a directory.
|
|
30
74
|
*
|
|
@@ -56,8 +100,7 @@ export async function discoverYamlAgents(agentsDir, pluginRegistry, defaultModel
|
|
|
56
100
|
const yamlPath = path.join(agentsDir, entry.name, "agent.yaml");
|
|
57
101
|
const agentDir = path.join(agentsDir, entry.name);
|
|
58
102
|
try {
|
|
59
|
-
const
|
|
60
|
-
const config = yaml.load(content);
|
|
103
|
+
const config = await readAgentConfig(agentDir);
|
|
61
104
|
// Validate required fields
|
|
62
105
|
if (!config.name || !config.description || !config.plugins?.length || !config.systemPrompt) {
|
|
63
106
|
console.warn(`[agents] "${entry.name}/agent.yaml": missing required fields (name, description, plugins, systemPrompt) ā skipping`);
|
package/dist/ui/settings.js
CHANGED
|
@@ -1,75 +1,106 @@
|
|
|
1
1
|
import { ui } from "@melony/ui-kit";
|
|
2
|
-
import { loadConfig } from "../config.js";
|
|
2
|
+
import { loadConfig, resolvePath, DEFAULT_BASE_DIR } from "../config.js";
|
|
3
|
+
import { listYamlAgents } from "../registry/index.js";
|
|
4
|
+
import path from "node:path";
|
|
3
5
|
export const settingsUI = async () => {
|
|
4
6
|
const config = loadConfig();
|
|
7
|
+
const baseDir = config.baseDir || DEFAULT_BASE_DIR;
|
|
8
|
+
const resolvedBaseDir = resolvePath(baseDir);
|
|
9
|
+
const agentsDir = path.join(resolvedBaseDir, "agents");
|
|
5
10
|
const hasOpenAIKey = !!config.openaiApiKey;
|
|
6
11
|
const hasAnthropicKey = !!config.anthropicApiKey;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
const agents = await listYamlAgents(agentsDir);
|
|
13
|
+
return ui.box({
|
|
14
|
+
width: "full",
|
|
15
|
+
height: "full",
|
|
16
|
+
overflow: "auto",
|
|
12
17
|
}, [
|
|
13
|
-
ui.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
ui.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
ui.col({ gap: "
|
|
26
|
-
ui.
|
|
27
|
-
|
|
28
|
-
ui.input("model", undefined, {
|
|
29
|
-
placeholder: "e.g. gpt-4o-mini",
|
|
30
|
-
width: "full",
|
|
31
|
-
defaultValue: config.model || "gpt-4o-mini"
|
|
32
|
-
}),
|
|
33
|
-
]),
|
|
34
|
-
]),
|
|
35
|
-
ui.col({ gap: "md" }, [
|
|
36
|
-
ui.heading("API Keys", 4),
|
|
37
|
-
ui.col({ gap: "lg" }, [
|
|
38
|
-
// OpenAI Key
|
|
18
|
+
ui.col({ padding: "xl", gap: "xl", width: "full", flex: 1 }, [
|
|
19
|
+
ui.col({ gap: "xs" }, [
|
|
20
|
+
ui.heading("Settings", 2),
|
|
21
|
+
ui.text("Manage your OpenBot configuration", { color: "mutedForeground" }),
|
|
22
|
+
]),
|
|
23
|
+
ui.divider(),
|
|
24
|
+
ui.form({
|
|
25
|
+
onSubmitAction: {
|
|
26
|
+
type: "action:updateSettings",
|
|
27
|
+
data: {}
|
|
28
|
+
}
|
|
29
|
+
}, [
|
|
30
|
+
ui.col({ gap: "xl" }, [
|
|
31
|
+
ui.col({ gap: "md" }, [
|
|
32
|
+
ui.heading("Model Configuration", 4),
|
|
39
33
|
ui.col({ gap: "sm" }, [
|
|
40
|
-
ui.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
defaultValue: hasOpenAIKey ? "ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢" : "",
|
|
45
|
-
width: "full"
|
|
34
|
+
ui.input("model", undefined, {
|
|
35
|
+
placeholder: "e.g. gpt-4o-mini",
|
|
36
|
+
width: "full",
|
|
37
|
+
defaultValue: config.model || "gpt-4o-mini"
|
|
46
38
|
}),
|
|
47
39
|
]),
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
]),
|
|
41
|
+
ui.col({ gap: "md" }, [
|
|
42
|
+
ui.heading("API Keys", 4),
|
|
43
|
+
ui.col({ gap: "lg" }, [
|
|
44
|
+
// OpenAI Key
|
|
45
|
+
ui.col({ gap: "sm" }, [
|
|
46
|
+
ui.node("label", { value: "OpenAI API Key" }),
|
|
47
|
+
ui.input("openai_api_key", undefined, {
|
|
48
|
+
placeholder: "sk-...",
|
|
49
|
+
inputType: "password",
|
|
50
|
+
defaultValue: hasOpenAIKey ? "ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢" : "",
|
|
51
|
+
width: "full"
|
|
52
|
+
}),
|
|
53
|
+
]),
|
|
54
|
+
// Anthropic Key
|
|
55
|
+
ui.col({ gap: "sm" }, [
|
|
56
|
+
ui.node("label", { value: "Anthropic API Key" }),
|
|
57
|
+
ui.input("anthropic_api_key", undefined, {
|
|
58
|
+
placeholder: "sk-ant-...",
|
|
59
|
+
inputType: "password",
|
|
60
|
+
defaultValue: hasAnthropicKey ? "ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢ā¢" : "",
|
|
61
|
+
width: "full"
|
|
62
|
+
}),
|
|
63
|
+
])
|
|
57
64
|
])
|
|
65
|
+
]),
|
|
66
|
+
ui.col({ gap: "md" }, [
|
|
67
|
+
ui.heading("Theme", 4),
|
|
68
|
+
ui.themeToggle(),
|
|
69
|
+
]),
|
|
70
|
+
ui.row({ justify: "end" }, [
|
|
71
|
+
ui.button({
|
|
72
|
+
label: "Save Settings",
|
|
73
|
+
type: "submit",
|
|
74
|
+
variant: "primary"
|
|
75
|
+
})
|
|
58
76
|
])
|
|
59
|
-
]),
|
|
60
|
-
ui.col({ gap: "md" }, [
|
|
61
|
-
ui.heading("Theme", 4),
|
|
62
|
-
ui.themeToggle(),
|
|
63
|
-
]),
|
|
64
|
-
ui.divider(),
|
|
65
|
-
ui.row({ justify: "end" }, [
|
|
66
|
-
ui.button({
|
|
67
|
-
label: "Save Settings",
|
|
68
|
-
type: "submit",
|
|
69
|
-
variant: "primary"
|
|
70
|
-
})
|
|
71
77
|
])
|
|
78
|
+
]),
|
|
79
|
+
ui.divider(),
|
|
80
|
+
ui.col({ gap: "md" }, [
|
|
81
|
+
ui.heading("Installed Agents", 4),
|
|
82
|
+
ui.text("Agents installed in ~/.openbot/agents", { color: "mutedForeground", size: "sm" }),
|
|
83
|
+
ui.col({ gap: "sm" }, agents.map(agent => (ui.box({
|
|
84
|
+
padding: "md",
|
|
85
|
+
background: "muted",
|
|
86
|
+
}, [
|
|
87
|
+
ui.row({ justify: "between", align: "center" }, [
|
|
88
|
+
ui.col({ gap: "xs" }, [
|
|
89
|
+
ui.text(agent.name, { weight: "bold" }),
|
|
90
|
+
ui.text(agent.description, { size: "sm", color: "mutedForeground" }),
|
|
91
|
+
]),
|
|
92
|
+
ui.button({
|
|
93
|
+
label: "Open Folder",
|
|
94
|
+
variant: "secondary",
|
|
95
|
+
size: "sm",
|
|
96
|
+
onClickAction: {
|
|
97
|
+
type: "action:openAgentFolder",
|
|
98
|
+
data: { folder: agent.folder }
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
])
|
|
102
|
+
]))))
|
|
72
103
|
])
|
|
73
|
-
])
|
|
104
|
+
]),
|
|
74
105
|
]);
|
|
75
106
|
};
|