openbot 0.2.5 → 0.2.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/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 +30 -8
- package/dist/server.js +38 -5
- 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.7");
|
|
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
|
}
|
|
@@ -138,7 +145,7 @@ async function loadToolPluginsFromDir(dir) {
|
|
|
138
145
|
if (!indexPath)
|
|
139
146
|
continue;
|
|
140
147
|
try {
|
|
141
|
-
const module = await import(pathToFileURL(indexPath).href);
|
|
148
|
+
const module = await import(pathToFileURL(indexPath).href + `?update=${Date.now()}`);
|
|
142
149
|
const entryData = module.plugin || module.default || module.entry;
|
|
143
150
|
if (entryData && typeof entryData.factory === "function") {
|
|
144
151
|
plugins.push({
|
|
@@ -212,12 +219,16 @@ export async function discoverPlugins(dir, registry, defaultModel, options) {
|
|
|
212
219
|
if (!indexPath)
|
|
213
220
|
continue;
|
|
214
221
|
try {
|
|
215
|
-
const module = await import(pathToFileURL(indexPath).href);
|
|
222
|
+
const module = await import(pathToFileURL(indexPath).href + `?update=${Date.now()}`);
|
|
216
223
|
const codeAgentDef = module.agent;
|
|
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,
|
|
@@ -261,12 +277,15 @@ export async function discoverPlugins(dir, registry, defaultModel, options) {
|
|
|
261
277
|
const indexPath = await findIndexFile(agentDir);
|
|
262
278
|
if (!indexPath)
|
|
263
279
|
continue;
|
|
264
|
-
const module = await import(pathToFileURL(indexPath).href);
|
|
280
|
+
const module = await import(pathToFileURL(indexPath).href + `?update=${Date.now()}`);
|
|
265
281
|
const definition = module.agent || module.plugin || module.default || module.entry;
|
|
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 })
|
|
@@ -365,7 +387,7 @@ export async function listPlugins(dir) {
|
|
|
365
387
|
continue;
|
|
366
388
|
}
|
|
367
389
|
try {
|
|
368
|
-
const module = await import(pathToFileURL(indexPath).href);
|
|
390
|
+
const module = await import(pathToFileURL(indexPath).href + `?update=${Date.now()}`);
|
|
369
391
|
const codeAgentDef = module.agent;
|
|
370
392
|
const toolEntry = module.plugin || module.default || module.entry;
|
|
371
393
|
if (codeAgentDef && typeof codeAgentDef.factory === "function") {
|
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";
|
|
@@ -21,6 +22,8 @@ import { getMarketplaceRegistry, installMarketplaceAgent, installMarketplacePlug
|
|
|
21
22
|
import { getVersionStatus } from "./version.js";
|
|
22
23
|
export async function startServer(options = {}) {
|
|
23
24
|
const config = loadConfig();
|
|
25
|
+
const baseDir = config.baseDir || DEFAULT_BASE_DIR;
|
|
26
|
+
const resolvedBaseDir = resolvePath(baseDir);
|
|
24
27
|
const PORT = Number(options.port ?? config.port ?? process.env.PORT ?? 4001);
|
|
25
28
|
const app = express();
|
|
26
29
|
const createRuntime = () => createOpenBot({
|
|
@@ -61,12 +64,16 @@ export async function startServer(options = {}) {
|
|
|
61
64
|
void reloadRuntime();
|
|
62
65
|
}, 800);
|
|
63
66
|
};
|
|
64
|
-
const openBotDir =
|
|
67
|
+
const openBotDir = resolvedBaseDir;
|
|
68
|
+
const agentsDir = path.join(openBotDir, "agents");
|
|
69
|
+
const pluginsDir = path.join(openBotDir, "plugins");
|
|
70
|
+
await fs.mkdir(agentsDir, { recursive: true });
|
|
71
|
+
await fs.mkdir(pluginsDir, { recursive: true });
|
|
65
72
|
const watcher = chokidar.watch([
|
|
66
73
|
path.join(openBotDir, "config.json"),
|
|
67
74
|
path.join(openBotDir, "AGENT.md"),
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
agentsDir,
|
|
76
|
+
pluginsDir,
|
|
70
77
|
], {
|
|
71
78
|
ignoreInitial: true,
|
|
72
79
|
awaitWriteFinish: {
|
|
@@ -412,6 +419,7 @@ export async function startServer(options = {}) {
|
|
|
412
419
|
updates.anthropicApiKey = anthropic_api_key.trim();
|
|
413
420
|
if (Object.keys(updates).length > 0) {
|
|
414
421
|
saveConfig(updates);
|
|
422
|
+
scheduleReload();
|
|
415
423
|
}
|
|
416
424
|
res.json({ success: true });
|
|
417
425
|
});
|
|
@@ -523,6 +531,7 @@ export async function startServer(options = {}) {
|
|
|
523
531
|
}
|
|
524
532
|
try {
|
|
525
533
|
const result = await installMarketplaceAgent(id.trim());
|
|
534
|
+
scheduleReload();
|
|
526
535
|
res.json({ success: true, installedName: result.installedName, item: result.agent });
|
|
527
536
|
}
|
|
528
537
|
catch (error) {
|
|
@@ -537,6 +546,7 @@ export async function startServer(options = {}) {
|
|
|
537
546
|
}
|
|
538
547
|
try {
|
|
539
548
|
const result = await installMarketplacePlugin(id.trim());
|
|
549
|
+
scheduleReload();
|
|
540
550
|
res.json({ success: true, installedName: result.installedName, item: result.plugin });
|
|
541
551
|
}
|
|
542
552
|
catch (error) {
|
|
@@ -604,6 +614,7 @@ export async function startServer(options = {}) {
|
|
|
604
614
|
}
|
|
605
615
|
const consolidated = matter.stringify(md, frontmatter);
|
|
606
616
|
await fs.writeFile(mdPath, consolidated, "utf-8");
|
|
617
|
+
scheduleReload();
|
|
607
618
|
res.json({ success: true });
|
|
608
619
|
}
|
|
609
620
|
catch (err) {
|
|
@@ -779,12 +790,32 @@ export async function startServer(options = {}) {
|
|
|
779
790
|
const baseDir = cfg.baseDir || DEFAULT_BASE_DIR;
|
|
780
791
|
const resolvedBaseDir = resolvePath(baseDir);
|
|
781
792
|
const defaultName = cfg.name || "OpenBot";
|
|
793
|
+
// 1. Resolve agent folder
|
|
794
|
+
let agentFolder = null;
|
|
795
|
+
if (name === defaultName || name === "default") {
|
|
796
|
+
agentFolder = resolvedBaseDir;
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
agentFolder = await resolveAgentFolder(name, resolvedBaseDir);
|
|
800
|
+
}
|
|
801
|
+
// 2. Check for remote image in AGENT.md if folder exists
|
|
802
|
+
if (agentFolder) {
|
|
803
|
+
try {
|
|
804
|
+
const { image } = await readAgentConfig(agentFolder);
|
|
805
|
+
if (image && (image.startsWith("http://") || image.startsWith("https://"))) {
|
|
806
|
+
return res.redirect(image);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
catch {
|
|
810
|
+
// ignore
|
|
811
|
+
}
|
|
812
|
+
}
|
|
782
813
|
const extensions = [".png", ".jpg", ".jpeg", ".svg", ".webp", ".gif"];
|
|
783
814
|
const fileNames = ["avatar", "icon", "image", "logo"];
|
|
784
815
|
const searchDirs = [
|
|
785
816
|
(name === defaultName || name === "default")
|
|
786
817
|
? path.join(resolvedBaseDir, "assets")
|
|
787
|
-
: path.join(resolvedBaseDir, "agents", name, "assets"),
|
|
818
|
+
: (agentFolder ? path.join(agentFolder, "assets") : path.join(resolvedBaseDir, "agents", name, "assets")),
|
|
788
819
|
path.join(process.cwd(), "server", "src", "agents", name, "assets"),
|
|
789
820
|
path.join(process.cwd(), "server", "src", "assets", "agents", name),
|
|
790
821
|
path.join(process.cwd(), "server", "src", "agents", "assets"),
|
|
@@ -793,7 +824,8 @@ export async function startServer(options = {}) {
|
|
|
793
824
|
for (const dir of searchDirs) {
|
|
794
825
|
for (const fileName of fileNames) {
|
|
795
826
|
for (const ext of extensions) {
|
|
796
|
-
const
|
|
827
|
+
const isAgentSpecificDir = dir.includes(name) || (agentFolder && dir.includes(agentFolder));
|
|
828
|
+
const baseName = (dir.endsWith("assets") && !isAgentSpecificDir) ? name : fileName;
|
|
797
829
|
const p = path.join(dir, `${baseName}${ext}`);
|
|
798
830
|
try {
|
|
799
831
|
await fs.access(p);
|
|
@@ -868,6 +900,7 @@ export async function startServer(options = {}) {
|
|
|
868
900
|
console.log(`OpenBot server listening at http://localhost:${PORT}`);
|
|
869
901
|
console.log(` - Chat endpoint: POST /api/chat`);
|
|
870
902
|
console.log(` - REST endpoints: /api/config, /api/sessions, /api/agents`);
|
|
903
|
+
console.log(`\n🚀 TIP: Use 'openbot up' to run both the server and web dashboard together.`);
|
|
871
904
|
if (options.openaiApiKey)
|
|
872
905
|
console.log(" - Using OpenAI API Key from CLI");
|
|
873
906
|
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.7",
|
|
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
|
+
}
|