openbot 0.2.5 → 0.2.6
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 +65 -8
- package/dist/core/plugins.js +1 -1
- package/dist/installers.js +45 -59
- package/dist/marketplace.js +2 -2
- package/dist/registry/plugin-loader.js +26 -4
- package/dist/server.js +25 -2
- package/package.json +9 -6
package/dist/cli.js
CHANGED
|
@@ -3,19 +3,34 @@ import { Command } from "commander";
|
|
|
3
3
|
import * as readline from "node:readline/promises";
|
|
4
4
|
import * as fs from "node:fs/promises";
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
6
7
|
import { saveConfig, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
|
|
7
8
|
import { startServer } from "./server.js";
|
|
8
9
|
import { getPluginMetadata } from "./registry/plugin-loader.js";
|
|
9
10
|
import { checkGitHubRepo, checkNpmPackage, parsePluginInstallSource, parseAgentInstallSource, installPluginFromSource, installAgentFromSource, } from "./installers.js";
|
|
10
11
|
const program = new Command();
|
|
12
|
+
const REQUIRED_NODE_VERSION = "20.12.0";
|
|
13
|
+
function checkNodeVersion() {
|
|
14
|
+
const [major, minor, patch] = process.versions.node.split(".").map(Number);
|
|
15
|
+
const [reqMajor, reqMinor, reqPatch] = REQUIRED_NODE_VERSION.split(".").map(Number);
|
|
16
|
+
const isOld = major < reqMajor ||
|
|
17
|
+
(major === reqMajor && minor < reqMinor) ||
|
|
18
|
+
(major === reqMajor && minor === reqMinor && patch < reqPatch);
|
|
19
|
+
if (isOld) {
|
|
20
|
+
console.warn(`\n⚠️ WARNING: You are using Node.js ${process.version}.`);
|
|
21
|
+
console.warn(` OpenBot works best with Node.js >=${REQUIRED_NODE_VERSION}.`);
|
|
22
|
+
console.warn(` You may encounter "ERR_REQUIRE_ESM" or other compatibility issues on older versions.\n`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
checkNodeVersion();
|
|
11
26
|
program
|
|
12
27
|
.name("openbot")
|
|
13
28
|
.description("OpenBot CLI - Secure and easy configuration")
|
|
14
|
-
.version("0.2.
|
|
15
|
-
async function installPlugin(source, quiet = false) {
|
|
29
|
+
.version("0.2.6");
|
|
30
|
+
async function installPlugin(source, id, quiet = false) {
|
|
16
31
|
try {
|
|
17
32
|
const parsed = parsePluginInstallSource(source);
|
|
18
|
-
const name = await installPluginFromSource(parsed, { quiet });
|
|
33
|
+
const name = await installPluginFromSource(parsed, { quiet, id });
|
|
19
34
|
return name;
|
|
20
35
|
}
|
|
21
36
|
catch (err) {
|
|
@@ -26,10 +41,10 @@ async function installPlugin(source, quiet = false) {
|
|
|
26
41
|
throw err;
|
|
27
42
|
}
|
|
28
43
|
}
|
|
29
|
-
async function installAgent(source) {
|
|
44
|
+
async function installAgent(source, id) {
|
|
30
45
|
try {
|
|
31
46
|
const parsed = parseAgentInstallSource(source);
|
|
32
|
-
const name = await installAgentFromSource(parsed);
|
|
47
|
+
const name = await installAgentFromSource(parsed, { id });
|
|
33
48
|
return name;
|
|
34
49
|
}
|
|
35
50
|
catch (err) {
|
|
@@ -37,6 +52,9 @@ async function installAgent(source) {
|
|
|
37
52
|
process.exit(1);
|
|
38
53
|
}
|
|
39
54
|
}
|
|
55
|
+
function shellEscape(arg) {
|
|
56
|
+
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
57
|
+
}
|
|
40
58
|
program
|
|
41
59
|
.command("configure")
|
|
42
60
|
.description("Configure OpenBot model and settings")
|
|
@@ -85,6 +103,8 @@ program
|
|
|
85
103
|
console.log("Alternatively, you can set the environment variable:");
|
|
86
104
|
console.log(provider === "openai" ? " export OPENAI_API_KEY=your-key" : " export ANTHROPIC_API_KEY=your-key");
|
|
87
105
|
console.log("------------------------------------------");
|
|
106
|
+
console.log("\n🚀 TIP: Use 'openbot up' to start the server and web UI together.");
|
|
107
|
+
console.log("------------------------------------------\n");
|
|
88
108
|
rl.close();
|
|
89
109
|
});
|
|
90
110
|
program
|
|
@@ -96,6 +116,43 @@ program
|
|
|
96
116
|
.action(async (options) => {
|
|
97
117
|
await startServer(options);
|
|
98
118
|
});
|
|
119
|
+
program
|
|
120
|
+
.command("up")
|
|
121
|
+
.description("Start OpenBot server and web dashboard together")
|
|
122
|
+
.option("-p, --port <number>", "Port to listen on")
|
|
123
|
+
.option("--openai-api-key <key>", "OpenAI API Key")
|
|
124
|
+
.option("--anthropic-api-key <key>", "Anthropic API Key")
|
|
125
|
+
.action(async (options) => {
|
|
126
|
+
const serverArgs = ["openbot", "server"];
|
|
127
|
+
if (options.port)
|
|
128
|
+
serverArgs.push("--port", String(options.port));
|
|
129
|
+
if (options.openaiApiKey)
|
|
130
|
+
serverArgs.push("--openai-api-key", options.openaiApiKey);
|
|
131
|
+
if (options.anthropicApiKey)
|
|
132
|
+
serverArgs.push("--anthropic-api-key", options.anthropicApiKey);
|
|
133
|
+
const serverCommand = serverArgs.map(shellEscape).join(" ");
|
|
134
|
+
await new Promise((resolve, reject) => {
|
|
135
|
+
const child = spawn("npx", [
|
|
136
|
+
"-y",
|
|
137
|
+
"concurrently",
|
|
138
|
+
"--kill-others",
|
|
139
|
+
"--names",
|
|
140
|
+
"SERVER,WEBUI",
|
|
141
|
+
"--prefix",
|
|
142
|
+
"{name}",
|
|
143
|
+
"--prefix-colors",
|
|
144
|
+
"blue.bold,green.bold",
|
|
145
|
+
serverCommand,
|
|
146
|
+
"openbot-web",
|
|
147
|
+
], { stdio: "inherit" });
|
|
148
|
+
child.on("error", reject);
|
|
149
|
+
child.on("exit", (code) => {
|
|
150
|
+
if (typeof code === "number")
|
|
151
|
+
process.exitCode = code;
|
|
152
|
+
resolve();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
99
156
|
program
|
|
100
157
|
.command("add <name>")
|
|
101
158
|
.description("Add an agent or plugin by name (auto-resolves to GitHub/NPM)")
|
|
@@ -103,7 +160,7 @@ program
|
|
|
103
160
|
// 1. Try as Agent
|
|
104
161
|
const agentRepo = `meetopenbot/agent-${name}`;
|
|
105
162
|
if (checkGitHubRepo(agentRepo)) {
|
|
106
|
-
await installAgent(agentRepo);
|
|
163
|
+
await installAgent(agentRepo, `agent-${name}`);
|
|
107
164
|
return;
|
|
108
165
|
}
|
|
109
166
|
// 2. Try as Plugin
|
|
@@ -119,13 +176,13 @@ program
|
|
|
119
176
|
// Check GitHub Plugin
|
|
120
177
|
const pluginGhRepo = `meetopenbot/plugin-${name}`;
|
|
121
178
|
if (checkGitHubRepo(pluginGhRepo)) {
|
|
122
|
-
await installPlugin(pluginGhRepo);
|
|
179
|
+
await installPlugin(pluginGhRepo, `plugin-${name}`);
|
|
123
180
|
return;
|
|
124
181
|
}
|
|
125
182
|
// Check NPM Plugin
|
|
126
183
|
const pluginNpmPkg = `@melony/plugin-${name}`;
|
|
127
184
|
if (checkNpmPackage(pluginNpmPkg)) {
|
|
128
|
-
await installPlugin(pluginNpmPkg);
|
|
185
|
+
await installPlugin(pluginNpmPkg, `plugin-${name}`);
|
|
129
186
|
return;
|
|
130
187
|
}
|
|
131
188
|
console.error(`❌ Could not find agent or plugin named "${name}" in official repositories.`);
|
package/dist/core/plugins.js
CHANGED
|
@@ -71,7 +71,7 @@ export async function setupPluginRegistry(resolvedBaseDir, model, options) {
|
|
|
71
71
|
// ── Custom agents and plugins ────────────────────────────────────
|
|
72
72
|
const agentsDir = path.join(resolvedBaseDir, "agents");
|
|
73
73
|
const pluginsDir = path.join(resolvedBaseDir, "plugins");
|
|
74
|
-
await discoverPlugins(agentsDir, registry, model, options);
|
|
75
74
|
await discoverPlugins(pluginsDir, registry, model, options);
|
|
75
|
+
await discoverPlugins(agentsDir, registry, model, options);
|
|
76
76
|
return registry;
|
|
77
77
|
}
|
package/dist/installers.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { resolvePath, DEFAULT_BASE_DIR, loadConfig } from "./config.js";
|
|
6
|
-
import {
|
|
6
|
+
import { readAgentConfig, ensurePluginReady } from "./registry/plugin-loader.js";
|
|
7
7
|
const BUILT_IN_PLUGIN_NAMES = new Set(["shell", "file-system", "approval"]);
|
|
8
8
|
function run(command, args, options) {
|
|
9
9
|
execFileSync(command, args, {
|
|
@@ -15,9 +15,6 @@ function log(message, quiet) {
|
|
|
15
15
|
if (!quiet)
|
|
16
16
|
console.log(message);
|
|
17
17
|
}
|
|
18
|
-
function githubRepoToCloneUrl(repo) {
|
|
19
|
-
return `https://github.com/${repo}.git`;
|
|
20
|
-
}
|
|
21
18
|
function getBaseDir() {
|
|
22
19
|
const cfg = loadConfig();
|
|
23
20
|
const baseDir = cfg.baseDir || DEFAULT_BASE_DIR;
|
|
@@ -28,7 +25,8 @@ async function directoryExists(targetPath) {
|
|
|
28
25
|
}
|
|
29
26
|
export function checkGitHubRepo(repo) {
|
|
30
27
|
try {
|
|
31
|
-
|
|
28
|
+
const url = `https://github.com/${repo}.git`;
|
|
29
|
+
run("git", ["ls-remote", url], { quiet: true });
|
|
32
30
|
return true;
|
|
33
31
|
}
|
|
34
32
|
catch {
|
|
@@ -44,7 +42,7 @@ export function checkNpmPackage(pkg) {
|
|
|
44
42
|
return false;
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
|
-
export function
|
|
45
|
+
export function parseSource(source) {
|
|
48
46
|
const normalized = source.trim();
|
|
49
47
|
const isGithub = (normalized.includes("/") || normalized.startsWith("github:"))
|
|
50
48
|
&& !normalized.startsWith("/")
|
|
@@ -64,26 +62,33 @@ export function parsePluginInstallSource(source) {
|
|
|
64
62
|
}
|
|
65
63
|
return { type: "local", value: path.resolve(normalized) };
|
|
66
64
|
}
|
|
67
|
-
export function
|
|
68
|
-
const normalized = source.trim();
|
|
69
|
-
if (normalized.startsWith("github:")) {
|
|
70
|
-
return { type: "github", value: normalized.slice(7) };
|
|
71
|
-
}
|
|
72
|
-
return { type: "github", value: normalized };
|
|
73
|
-
}
|
|
74
|
-
export async function installPluginFromSource(source, options = {}) {
|
|
65
|
+
export async function installExtension(type, source, options = {}) {
|
|
75
66
|
const quiet = !!options.quiet;
|
|
76
|
-
const tempDir = path.join(tmpdir(), `openbot-plugin-install-${Date.now()}`);
|
|
77
67
|
const baseDir = getBaseDir();
|
|
78
|
-
const
|
|
79
|
-
await fs.mkdir(
|
|
68
|
+
const targetRoot = path.join(baseDir, type === "agent" ? "agents" : "plugins");
|
|
69
|
+
await fs.mkdir(targetRoot, { recursive: true });
|
|
70
|
+
// 1. Determine the folder name (the "id") - ALWAYS based on source or explicit id
|
|
71
|
+
let id = options.id;
|
|
72
|
+
if (!id) {
|
|
73
|
+
if (source.type === "github") {
|
|
74
|
+
id = path.basename(source.value); // e.g. "agent-browser"
|
|
75
|
+
}
|
|
76
|
+
else if (source.type === "npm") {
|
|
77
|
+
id = source.value.split("/").pop(); // e.g. "@melony/plugin-test" -> "plugin-test"
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
id = path.basename(source.value);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const targetDir = path.join(targetRoot, id);
|
|
84
|
+
const tempDir = path.join(tmpdir(), `openbot-install-${Date.now()}-${id}`);
|
|
80
85
|
try {
|
|
86
|
+
log(`📦 Installing ${type} "${id}" from ${source.type}...`, quiet);
|
|
81
87
|
if (source.type === "github") {
|
|
82
|
-
|
|
83
|
-
run("git", ["clone", "--depth", "1",
|
|
88
|
+
const url = `https://github.com/${source.value}.git`;
|
|
89
|
+
run("git", ["clone", "--depth", "1", url, tempDir], { quiet });
|
|
84
90
|
}
|
|
85
91
|
else if (source.type === "npm") {
|
|
86
|
-
log(`📦 Installing plugin from: ${source.value}`, quiet);
|
|
87
92
|
await fs.mkdir(tempDir, { recursive: true });
|
|
88
93
|
run("npm", ["install", source.value, "--prefix", tempDir], { quiet });
|
|
89
94
|
const pkgFolder = path.join(tempDir, "node_modules", source.value);
|
|
@@ -93,55 +98,34 @@ export async function installPluginFromSource(source, options = {}) {
|
|
|
93
98
|
await fs.rename(moveTemp, tempDir);
|
|
94
99
|
}
|
|
95
100
|
else {
|
|
96
|
-
log(`📦 Installing plugin from: ${source.value}`, quiet);
|
|
97
101
|
await fs.mkdir(tempDir, { recursive: true });
|
|
98
102
|
await fs.cp(source.value, tempDir, { recursive: true });
|
|
99
103
|
}
|
|
100
|
-
const { name } = await getPluginMetadata(tempDir);
|
|
101
|
-
const targetDir = path.join(pluginRoot, name);
|
|
102
104
|
if (await directoryExists(targetDir)) {
|
|
103
|
-
log(`⚠️
|
|
105
|
+
log(`⚠️ Removing existing folder: ${targetDir}`, quiet);
|
|
104
106
|
await fs.rm(targetDir, { recursive: true, force: true });
|
|
105
107
|
}
|
|
106
108
|
await fs.rename(tempDir, targetDir);
|
|
107
|
-
log(`✅
|
|
108
|
-
|
|
109
|
+
log(`✅ Installed to: ${targetDir}`, quiet);
|
|
110
|
+
// Prepare dependencies and build
|
|
109
111
|
await ensurePluginReady(targetDir);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
catch (error) {
|
|
114
|
-
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
|
|
115
|
-
throw error;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
export async function installAgentFromSource(source, options = {}) {
|
|
119
|
-
const quiet = !!options.quiet;
|
|
120
|
-
const tempDir = path.join(tmpdir(), `openbot-agent-install-${Date.now()}`);
|
|
121
|
-
const baseDir = getBaseDir();
|
|
122
|
-
const agentRoot = path.join(baseDir, "agents");
|
|
123
|
-
await fs.mkdir(agentRoot, { recursive: true });
|
|
124
|
-
try {
|
|
125
|
-
log(`🤖 Installing agent from: ${githubRepoToCloneUrl(source.value)}`, quiet);
|
|
126
|
-
run("git", ["clone", "--depth", "1", githubRepoToCloneUrl(source.value), tempDir], { quiet });
|
|
127
|
-
const config = await readAgentConfig(tempDir);
|
|
128
|
-
const name = config.name || path.basename(source.value).replace(/^agent-/, "");
|
|
129
|
-
const targetDir = path.join(agentRoot, name);
|
|
130
|
-
if (await directoryExists(targetDir)) {
|
|
131
|
-
log(`⚠️ Agent "${name}" already exists. Overwriting...`, quiet);
|
|
132
|
-
await fs.rm(targetDir, { recursive: true, force: true });
|
|
112
|
+
// If it's an agent, check for missing plugins
|
|
113
|
+
if (type === "agent") {
|
|
114
|
+
await installMissingPluginsFromAgent(targetDir, { quiet });
|
|
133
115
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
await installMissingPluginsFromAgent(targetDir, { quiet });
|
|
137
|
-
log(`\n🎉 Successfully installed agent: ${name}`, quiet);
|
|
138
|
-
return name;
|
|
116
|
+
log(`🎉 Successfully installed ${type}: ${id}`, quiet);
|
|
117
|
+
return id;
|
|
139
118
|
}
|
|
140
119
|
catch (error) {
|
|
141
120
|
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
|
|
142
121
|
throw error;
|
|
143
122
|
}
|
|
144
123
|
}
|
|
124
|
+
// Backward compatibility aliases
|
|
125
|
+
export const installPluginFromSource = (source, opts) => installExtension("plugin", source, opts);
|
|
126
|
+
export const installAgentFromSource = (source, opts) => installExtension("agent", source, opts);
|
|
127
|
+
export const parsePluginInstallSource = parseSource;
|
|
128
|
+
export const parseAgentInstallSource = parseSource;
|
|
145
129
|
export async function installMissingPluginsFromAgent(agentFolder, options = {}) {
|
|
146
130
|
const quiet = !!options.quiet;
|
|
147
131
|
const config = await readAgentConfig(agentFolder);
|
|
@@ -150,21 +134,23 @@ export async function installMissingPluginsFromAgent(agentFolder, options = {})
|
|
|
150
134
|
const pluginName = typeof pluginItem === "string" ? pluginItem : pluginItem.name;
|
|
151
135
|
if (!pluginName || BUILT_IN_PLUGIN_NAMES.has(pluginName))
|
|
152
136
|
continue;
|
|
137
|
+
// Check if it already exists as a folder in plugins/
|
|
153
138
|
const pluginPath = path.join(baseDir, "plugins", pluginName);
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
139
|
+
const prefixedPluginPath = path.join(baseDir, "plugins", `plugin-${pluginName}`);
|
|
140
|
+
if (await (directoryExists(pluginPath)) || await (directoryExists(prefixedPluginPath))) {
|
|
156
141
|
continue;
|
|
142
|
+
}
|
|
157
143
|
log(`🔍 Agent needs plugin "${pluginName}". Searching...`, quiet);
|
|
158
144
|
const ghRepo = `meetopenbot/plugin-${pluginName}`;
|
|
159
145
|
if (checkGitHubRepo(ghRepo)) {
|
|
160
|
-
await
|
|
146
|
+
await installExtension("plugin", { type: "github", value: ghRepo }, { quiet });
|
|
161
147
|
continue;
|
|
162
148
|
}
|
|
163
149
|
const npmPkg = `@melony/plugin-${pluginName}`;
|
|
164
150
|
if (checkNpmPackage(npmPkg)) {
|
|
165
|
-
await
|
|
151
|
+
await installExtension("plugin", { type: "npm", value: npmPkg }, { quiet });
|
|
166
152
|
continue;
|
|
167
153
|
}
|
|
168
|
-
log(`⚠️ Could not find plugin "${pluginName}" for this agent
|
|
154
|
+
log(`⚠️ Could not find plugin "${pluginName}" for this agent.`, quiet);
|
|
169
155
|
}
|
|
170
156
|
}
|
package/dist/marketplace.js
CHANGED
|
@@ -67,7 +67,7 @@ export async function installMarketplaceAgent(agentId) {
|
|
|
67
67
|
if (agent.source.type !== "github") {
|
|
68
68
|
throw new Error(`Marketplace agent "${agentId}" has unsupported source type "${agent.source.type}"`);
|
|
69
69
|
}
|
|
70
|
-
const installedName = await installAgentFromSource({ type: "github", value: agent.source.value });
|
|
70
|
+
const installedName = await installAgentFromSource({ type: "github", value: agent.source.value }, { id: agent.id });
|
|
71
71
|
return { installedName, agent };
|
|
72
72
|
}
|
|
73
73
|
export async function installMarketplacePlugin(pluginId) {
|
|
@@ -75,6 +75,6 @@ export async function installMarketplacePlugin(pluginId) {
|
|
|
75
75
|
const plugin = registry.plugins.find((entry) => entry.id === pluginId);
|
|
76
76
|
if (!plugin)
|
|
77
77
|
throw new Error(`Plugin "${pluginId}" was not found in marketplace`);
|
|
78
|
-
const installedName = await installPluginFromSource(plugin.source);
|
|
78
|
+
const installedName = await installPluginFromSource(plugin.source, { id: plugin.id });
|
|
79
79
|
return { installedName, plugin };
|
|
80
80
|
}
|
|
@@ -8,6 +8,13 @@ import { llmPlugin } from "../plugins/llm/index.js";
|
|
|
8
8
|
import { createModel } from "../models.js";
|
|
9
9
|
import { resolvePath, DEFAULT_AGENT_MD } from "../config.js";
|
|
10
10
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
11
|
+
function toTitleCaseFromSlug(value) {
|
|
12
|
+
return value
|
|
13
|
+
.split(/[-_]+/)
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
16
|
+
.join(" ") || "Agent";
|
|
17
|
+
}
|
|
11
18
|
async function fileExists(filePath) {
|
|
12
19
|
return fs.access(filePath).then(() => true).catch(() => false);
|
|
13
20
|
}
|
|
@@ -217,7 +224,11 @@ export async function discoverPlugins(dir, registry, defaultModel, options) {
|
|
|
217
224
|
const entryData = module.plugin || module.default || module.entry;
|
|
218
225
|
if (codeAgentDef && typeof codeAgentDef.factory === "function") {
|
|
219
226
|
const meta = await getPluginMetadata(pluginDir);
|
|
220
|
-
const
|
|
227
|
+
const folderName = path.basename(pluginDir);
|
|
228
|
+
let name = codeAgentDef.name || meta.name;
|
|
229
|
+
if (!name || /^Unnamed\s+(Plugin|Tool|Agent)$/i.test(name)) {
|
|
230
|
+
name = toTitleCaseFromSlug(folderName);
|
|
231
|
+
}
|
|
221
232
|
const description = codeAgentDef.description || meta.description || "Code Agent";
|
|
222
233
|
registry.register({
|
|
223
234
|
name,
|
|
@@ -232,8 +243,13 @@ export async function discoverPlugins(dir, registry, defaultModel, options) {
|
|
|
232
243
|
}
|
|
233
244
|
else if (entryData && typeof entryData.factory === "function") {
|
|
234
245
|
const meta = await getPluginMetadata(pluginDir);
|
|
246
|
+
const folderName = path.basename(pluginDir);
|
|
247
|
+
let name = entryData.name || meta.name;
|
|
248
|
+
if (!name || /^Unnamed\s+(Plugin|Tool|Agent)$/i.test(name)) {
|
|
249
|
+
name = toTitleCaseFromSlug(folderName);
|
|
250
|
+
}
|
|
235
251
|
const pluginEntry = {
|
|
236
|
-
name
|
|
252
|
+
name,
|
|
237
253
|
description: entryData.description || meta.description || "Tool plugin",
|
|
238
254
|
type: "tool",
|
|
239
255
|
plugin: entryData.factory,
|
|
@@ -266,7 +282,10 @@ export async function discoverPlugins(dir, registry, defaultModel, options) {
|
|
|
266
282
|
if (definition && typeof definition.factory === "function") {
|
|
267
283
|
const config = await readAgentConfig(agentDir);
|
|
268
284
|
const meta = await getPluginMetadata(agentDir);
|
|
269
|
-
|
|
285
|
+
let name = config.name || definition.name || meta.name;
|
|
286
|
+
if (!name || /^Unnamed\s+(Plugin|Tool|Agent)$/i.test(name)) {
|
|
287
|
+
name = toTitleCaseFromSlug(folderName);
|
|
288
|
+
}
|
|
270
289
|
const description = definition.description || config.description || "TS Agent";
|
|
271
290
|
registry.register({
|
|
272
291
|
name,
|
|
@@ -284,7 +303,10 @@ export async function discoverPlugins(dir, registry, defaultModel, options) {
|
|
|
284
303
|
// Declarative Agent — AGENT.md only, auto-wrapped with llmPlugin.
|
|
285
304
|
const config = await readAgentConfig(agentDir);
|
|
286
305
|
const meta = await getPluginMetadata(agentDir);
|
|
287
|
-
|
|
306
|
+
let resolvedName = config.name || meta.name;
|
|
307
|
+
if (!resolvedName || /^Unnamed\s+(Plugin|Tool|Agent)$/i.test(resolvedName)) {
|
|
308
|
+
resolvedName = toTitleCaseFromSlug(folderName);
|
|
309
|
+
}
|
|
288
310
|
const resolvedDescription = config.description || meta.description || "No description";
|
|
289
311
|
const agentModel = config.model
|
|
290
312
|
? createModel({ ...options, model: config.model })
|
package/dist/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createOpenBot } from "./open-bot.js";
|
|
|
7
7
|
import { loadConfig, saveConfig, isConfigured, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
|
|
8
8
|
import { loadSession, saveSession, logEvent, loadEvents, listSessions } from "./session.js";
|
|
9
9
|
import { listPlugins } from "./registry/index.js";
|
|
10
|
+
import { readAgentConfig } from "./registry/plugin-loader.js";
|
|
10
11
|
import { exec } from "node:child_process";
|
|
11
12
|
import os from "node:os";
|
|
12
13
|
import path from "node:path";
|
|
@@ -779,12 +780,32 @@ export async function startServer(options = {}) {
|
|
|
779
780
|
const baseDir = cfg.baseDir || DEFAULT_BASE_DIR;
|
|
780
781
|
const resolvedBaseDir = resolvePath(baseDir);
|
|
781
782
|
const defaultName = cfg.name || "OpenBot";
|
|
783
|
+
// 1. Resolve agent folder
|
|
784
|
+
let agentFolder = null;
|
|
785
|
+
if (name === defaultName || name === "default") {
|
|
786
|
+
agentFolder = resolvedBaseDir;
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
agentFolder = await resolveAgentFolder(name, resolvedBaseDir);
|
|
790
|
+
}
|
|
791
|
+
// 2. Check for remote image in AGENT.md if folder exists
|
|
792
|
+
if (agentFolder) {
|
|
793
|
+
try {
|
|
794
|
+
const { image } = await readAgentConfig(agentFolder);
|
|
795
|
+
if (image && (image.startsWith("http://") || image.startsWith("https://"))) {
|
|
796
|
+
return res.redirect(image);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
catch {
|
|
800
|
+
// ignore
|
|
801
|
+
}
|
|
802
|
+
}
|
|
782
803
|
const extensions = [".png", ".jpg", ".jpeg", ".svg", ".webp", ".gif"];
|
|
783
804
|
const fileNames = ["avatar", "icon", "image", "logo"];
|
|
784
805
|
const searchDirs = [
|
|
785
806
|
(name === defaultName || name === "default")
|
|
786
807
|
? path.join(resolvedBaseDir, "assets")
|
|
787
|
-
: path.join(resolvedBaseDir, "agents", name, "assets"),
|
|
808
|
+
: (agentFolder ? path.join(agentFolder, "assets") : path.join(resolvedBaseDir, "agents", name, "assets")),
|
|
788
809
|
path.join(process.cwd(), "server", "src", "agents", name, "assets"),
|
|
789
810
|
path.join(process.cwd(), "server", "src", "assets", "agents", name),
|
|
790
811
|
path.join(process.cwd(), "server", "src", "agents", "assets"),
|
|
@@ -793,7 +814,8 @@ export async function startServer(options = {}) {
|
|
|
793
814
|
for (const dir of searchDirs) {
|
|
794
815
|
for (const fileName of fileNames) {
|
|
795
816
|
for (const ext of extensions) {
|
|
796
|
-
const
|
|
817
|
+
const isAgentSpecificDir = dir.includes(name) || (agentFolder && dir.includes(agentFolder));
|
|
818
|
+
const baseName = (dir.endsWith("assets") && !isAgentSpecificDir) ? name : fileName;
|
|
797
819
|
const p = path.join(dir, `${baseName}${ext}`);
|
|
798
820
|
try {
|
|
799
821
|
await fs.access(p);
|
|
@@ -868,6 +890,7 @@ export async function startServer(options = {}) {
|
|
|
868
890
|
console.log(`OpenBot server listening at http://localhost:${PORT}`);
|
|
869
891
|
console.log(` - Chat endpoint: POST /api/chat`);
|
|
870
892
|
console.log(` - REST endpoints: /api/config, /api/sessions, /api/agents`);
|
|
893
|
+
console.log(`\n🚀 TIP: Use 'openbot up' to run both the server and web dashboard together.`);
|
|
871
894
|
if (options.openaiApiKey)
|
|
872
895
|
console.log(" - Using OpenAI API Key from CLI");
|
|
873
896
|
if (options.anthropicApiKey)
|
package/package.json
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbot",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"start": "node dist/cli.js server"
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20.12.0"
|
|
10
8
|
},
|
|
11
9
|
"bin": {
|
|
12
10
|
"openbot": "./dist/cli.js"
|
|
@@ -33,5 +31,10 @@
|
|
|
33
31
|
"@types/node": "^20.10.1",
|
|
34
32
|
"tsx": "^4.21.0",
|
|
35
33
|
"typescript": "^5.9.3"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"dev": "tsx watch src/cli.ts server",
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"start": "node dist/cli.js server"
|
|
36
39
|
}
|
|
37
|
-
}
|
|
40
|
+
}
|